node-to-json 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,413 @@
1
+ import bpy
2
+ from .asset_funcs import asset_nodes, asset_node_group
3
+ from .setter_funcs import set_node_from_data
4
+ from .type_util import BNode, BGroup, PYObject, PYDict
5
+ from typing import Generator
6
+ from collections import defaultdict
7
+ from itertools import tee, repeat
8
+
9
+
10
+ socket_attr = [
11
+ 'description',
12
+ 'enabled',
13
+ 'hide',
14
+ 'hide_value',
15
+ 'name',
16
+ 'pin_gizmo',
17
+ 'show_expanded',
18
+ 'type',
19
+ 'is_linked',
20
+ 'default_value',
21
+ ]
22
+
23
+ tree_exclude = [
24
+ 'name',
25
+ 'description',
26
+ 'in_out',
27
+ 'socket_type',
28
+ 'default_closed',
29
+ 'index',
30
+ 'position',
31
+ 'parent',
32
+ 'identifier',
33
+ 'is_multi_input',
34
+ 'item_type',
35
+ 'persistent_uid',
36
+ ]
37
+
38
+
39
+ ###################### HELPER FUNCS #######################
40
+
41
+
42
+ def nested_dict() -> defaultdict:
43
+ """Create a nested dict of dicts.
44
+
45
+ :return: dict[str, dict]
46
+ :rtype: defaultdict"""
47
+ return defaultdict(nested_dict)
48
+
49
+
50
+ def convert_default_value(_io: BGroup | BNode, val: PYObject) -> PYObject:
51
+ """Convert a value to a suitable value.
52
+
53
+ :param _io: An object. typically an input / output socket.
54
+ :type _io: NodeSocket
55
+ :param val: Default value to set socket to.
56
+ :type val: Any
57
+ :return: The modified value if it meets the condition, else it returns the original value.
58
+ :rtype: Any"""
59
+ if getattr(_io, 'type', None) == 'INT':
60
+ val = int(val)
61
+ if getattr(_io, 'type', None) == 'BOOLEAN':
62
+ val = bool(val)
63
+ return val
64
+
65
+
66
+ ########################## NODE ###########################
67
+
68
+
69
+ def set_socket_attr(node: BNode, attr: PYDict, set_enum: bool=False) -> None:
70
+ def _set_sock(fil):
71
+ for sock, a in fil:
72
+ try:
73
+ setattr(sock, 'default_value', convert_default_value(sock, a['default_value']))
74
+ except Exception as e:
75
+ print(f"[_set_sock]:", a, e)
76
+ continue
77
+ condition = lambda i: i[1]['type'] not in ['CUSTOM'] and (True if set_enum else i[0].type not in ['MENU']) and not i[1]['is_linked'] and 'default_value' in i[1].keys()
78
+ inputs = getattr(node, 'inputs', None)
79
+ if inputs:
80
+ ifil = filter(condition, zip(inputs[:], attr['inputs']))
81
+ _set_sock(ifil)
82
+
83
+
84
+ def set_parent(node: BNode, attr: PYDict) -> None:
85
+ if hasattr(node, 'parent'):
86
+ if 'parent' in attr.keys():
87
+ if attr['parent'] not in [None, '']:
88
+ try:
89
+ setattr(node, 'parent', node.id_data.nodes[attr['parent']])
90
+ except Exception as e:
91
+ print("[set_parent]:", node.name, e)
92
+
93
+
94
+ def set_hide(node: BNode, attr: PYDict) -> None:
95
+ if 'inputs' in attr.keys():
96
+ if attr['inputs'] not in [None, []]:
97
+ try:
98
+ node.inputs.foreach_set('hide', list(i['hide'] for i in attr['inputs']))
99
+ except Exception as e:
100
+ print("[set_hide][inputs]:", node.name, e)
101
+ if 'outputs' in attr.keys():
102
+ if attr['outputs'] not in [None, []]:
103
+ try:
104
+ node.outputs.foreach_set('hide', list(i['hide'] for i in attr['outputs']))
105
+ except Exception as e:
106
+ print("[set_hide][outputs]:", node.name, e)
107
+
108
+
109
+ def process_node(node: BNode, data: PYDict, group_dict: dict[str, BGroup] | dict[None], set_enum: bool=False) -> None:
110
+ """Set node attributes from data.
111
+
112
+ :param node: Node to set the data to.
113
+ :type node: Node
114
+ :param data: Data used to set the node attributes.
115
+ :type data: dict[str, Any]
116
+ :param group_dict: Dict containing the node group objects.
117
+ :type group_dict: dict[str, Node Group]
118
+ :param set_enum: Dict containing the node group enum sockets.
119
+ :type set_enum: bool"""
120
+ try:
121
+ set_node_from_data(node, data, group_dict)
122
+ set_socket_attr(node, data, set_enum=set_enum)
123
+ if node.bl_idname not in ['NodeReroute']:
124
+ set_hide(node, data)
125
+ set_parent(node, data)
126
+ except Exception as e:
127
+ print('[process_node]', node.name, e)
128
+
129
+
130
+ def set_nodes_from_data(data_dict: PYDict, node_dict: dict[str, BNode], group_dict: dict[str, BGroup]) -> None:
131
+ for group, nodes in node_dict.items():
132
+ for _node, node in nodes.items():
133
+ process_node(node, data_dict[group]['nodes'][_node], group_dict)
134
+
135
+
136
+ ####################### NODE GROUP ########################
137
+
138
+
139
+ def create_node_groups_from_data(data: PYDict, group_type: str='GeometryNodeTree') -> Generator[tuple[str, BGroup], None, None]:
140
+ global asset_nodes
141
+ return ((group, asset_node_group(group.split(".")[0]) if group.split(".")[0] in asset_nodes else bpy.data.node_groups.new(group, group_type)) for group in reversed(data.keys()))
142
+
143
+
144
+ def create_node_tree_nodes(node_tree: BGroup, data_nodes: PYDict) -> Generator[tuple[str, BNode], None, None]:
145
+ for node in data_nodes:
146
+ _node = node_tree.nodes.new(data_nodes[node]['bl_idname'])
147
+ _node.name = data_nodes[node]['name']
148
+ yield node, _node
149
+
150
+
151
+ def node_map(data: PYDict, groups: dict[str, BNode]) -> Generator[tuple[str, dict[str, BNode]], None, None]:
152
+ for group in groups:
153
+ if group[1] not in [None, ''] and data[group[0]]['nodes'] not in [None, '']:
154
+ yield group[0], dict(create_node_tree_nodes(group[1], data[group[0]]['nodes']))
155
+
156
+
157
+ def node_group_map(data: PYDict, group_type: str='GeometryNodeTree') -> tuple[str, Generator[tuple[str, dict[str, BNode]], None, None]]:
158
+ groups, _groups = tee(create_node_groups_from_data(data, group_type=group_type), 2)
159
+ return groups, node_map(data, _groups)
160
+
161
+
162
+ def set_interface(data: PYDict, node_tree: BGroup) -> None:
163
+ """Set sockets of node group from data.
164
+
165
+ :param data: Data to set the sockets.
166
+ :type data: dict[str, Any]
167
+ :param node_tree: Node group to set data to.
168
+ :type node_tree: Node Group"""
169
+ global tree_exclude
170
+ if 'interface' in data.keys() and data['interface'] != None and hasattr(node_tree, 'interface'):
171
+ for item in data['interface']:
172
+ for attr in item.keys():
173
+ if attr not in tree_exclude:
174
+ it = node_tree.interface.items_tree[data['interface']['index']]
175
+ if hasattr(it, attr) and item[attr] not in [None, '', []]:
176
+ try:
177
+ setattr(it, attr, item[attr])
178
+ except Exception as e:
179
+ try:
180
+ gi = getattr(it, attr, None)
181
+ if isinstance(gi, bpy.types.bpy_prop_array):
182
+ ct = len(gi)
183
+ _ct = len(item[attr])
184
+ count = ct if _ct > ct else _ct
185
+ val = list(repeat(0.0, ct))
186
+ for i in range(count):
187
+ val[i] = item[attr][i]
188
+ setattr(it, attr, val)
189
+ except Exception as se:
190
+ print('[set_interface]:', node_tree.name, e, se)
191
+ continue
192
+ return
193
+
194
+
195
+ def set_node_groups_from_data(data: PYDict, group_type: str='GeometryNodeTree') -> tuple[dict[str, BGroup], PYDict, PYDict, dict[str, BNode]]:
196
+ """Set node tree attributes from build data.
197
+
198
+ :param data: Node group build data.
199
+ :type data: dict[str, Any]
200
+ :param group_type: String indicating the bpy type of the node group.
201
+ :type group_type: str
202
+ :return: Tuple containing dictionaries for node group data, enum socket data, data to build node group, and node data.
203
+ :rtype: tuple[dict[str, node tree], dict[node_group.interface.items_tree, item], dict[str, Any], dict[str, node]]"""
204
+ global tree_exclude
205
+ group_dict, node_dict = node_group_map(data, group_type=group_type)
206
+ group_dict, group_dict_, _group_dict = tee(group_dict, 3)
207
+ [[setattr(group[1], a, data[group[0]][a]) for a in data[group[0]] if hasattr(group[1], a) and a not in ['interface', 'links', 'nodes', 'node_tree', 'bl_idname']] for group in group_dict]
208
+ def _set_interface(_group):
209
+ group, node_tree = _group
210
+ enums = []
211
+ if 'interface' in data[group].keys() and data[group]['interface'] != None and hasattr(node_tree, 'interface'):
212
+ for idx, item in enumerate(data[group]['interface']):
213
+ if item['item_type'] == 'SOCKET':
214
+ it = node_tree.interface.new_socket(item['name'], description=item['description'], in_out=item['in_out'], socket_type=item['socket_type'])
215
+ if item['socket_type'] == 'NodeSocketMenu' and (hasattr(it, 'default_value') and item['default_value'] not in [None, '']):
216
+ enums.append([it, idx])
217
+ elif item['item_type'] == 'PANEL':
218
+ it = node_tree.interface.new_panel(item['name'], description=item['description'], default_closed=item['default_closed'])
219
+ else:
220
+ it = None
221
+ for attr in item.keys():
222
+ if attr not in tree_exclude:
223
+ if it != None:
224
+ try:
225
+ if hasattr(it, attr) and item[attr] not in [None, '', []]:
226
+ if attr == 'default_value' and data[group]['interface'][idx]['socket_type'] == 'NodeSocketMenu':
227
+ continue
228
+ else:
229
+ setattr(it, attr, item[attr])
230
+ except Exception as e:
231
+ try:
232
+ gi = getattr(it, attr, None)
233
+ if isinstance(gi, bpy.types.bpy_prop_array):
234
+ ct = len(gi)
235
+ _ct = len(item[attr])
236
+ count = ct if _ct > ct else _ct
237
+ val = list(repeat(0.0, ct))
238
+ for i in range(count):
239
+ val[i] = item[attr][i]
240
+ setattr(it, attr, val)
241
+ except Exception as se:
242
+ print('[_set_interface]:', group, it, attr, item[attr], e, se)
243
+ continue
244
+ data[group]['interface'][idx]['item'] = it
245
+ return group, enums
246
+ enum_dict = dict(_set_interface(group) for group in group_dict_)
247
+ return dict(_group_dict), enum_dict, data, dict(node_dict)
248
+
249
+
250
+ def _new_link(link: bpy.types.NodeLink, node_tree: BGroup) -> None:
251
+ from_node = link[0]['node']
252
+ to_node = link[1]['node']
253
+ from_socket = link[0]['socket']
254
+ to_socket = link[1]['socket']
255
+ output = node_tree.nodes[from_node]
256
+ input = node_tree.nodes[to_node]
257
+ node_tree.links.new(output.outputs[from_socket], input.inputs[to_socket], verify_limits=True, handle_dynamic_sockets=False)
258
+
259
+
260
+ def set_node_group_links(data: PYDict, group_dict: dict[str, BGroup]) -> None:
261
+ for tree in reversed(data.keys()):
262
+ node_tree = group_dict[tree]
263
+ if not isinstance(node_tree, type(None)) and data[tree]['links'] != None:
264
+ for link in data[tree]['links']:
265
+ _new_link(link, node_tree)
266
+
267
+
268
+ def set_node_groups_enums(data: PYDict, enum_dict: PYDict) -> None:
269
+ for group in enum_dict:
270
+ for item in enum_dict[group]:
271
+ try:
272
+ item[0].default_value = data[group]['interface'][item[1]]['default_value']
273
+ except Exception as e:
274
+ print('Enums', group, item[1], data[group]['interface'][item[1]]['default_value'], e)
275
+ continue
276
+
277
+
278
+ def set_node_groups_interface_parents(data: PYDict, group_dict: dict[str, BGroup]) -> None:
279
+ global asset_nodes
280
+ for group in reversed(data.keys()):
281
+ if group.split(".")[0] not in asset_nodes:
282
+ node_tree = group_dict[group]
283
+ for i in range(len(data[group]['interface'])):
284
+ try:
285
+ parent, pid = data[group]['interface'][i]['parent']
286
+ position = data[group]['interface'][i]['index']
287
+ item = data[group]['interface'][i]['item']
288
+ if parent != "":
289
+ parent_ = next((p['item'] for p in (_ for _ in data[group]['interface'] if _['item_type'] == 'PANEL') if [parent, pid] == [p['name'], p['persistent_uid']]))
290
+ node_tree.interface.move_to_parent(item, parent_, position)
291
+ except Exception as e:
292
+ print('Parent ', group, i, item.name, parent, pid, position, e)
293
+ continue
294
+
295
+
296
+ def create_node_group_from_data(name: str, data: PYDict, group_type: str='GeometryNodeTree') -> BGroup:
297
+ """Create a node group from build data.
298
+
299
+ :param name: Name for node group.
300
+ :type name: str
301
+ :param data: Dictionary containing data to build node grouo.
302
+ :type data: dict[str, Any]
303
+ :param group_type: String indicating the bpy type of the node group.
304
+ :type group_type: str
305
+ :return: Node group.
306
+ :rtype: Node Group"""
307
+ group_dict, enum_dict, data, node_dict = set_node_groups_from_data(data, group_type=group_type)
308
+ node_group = group_dict[name]
309
+ set_nodes_from_data(data, node_dict, group_dict)
310
+ set_node_group_links(data, group_dict)
311
+ set_node_groups_enums(data, enum_dict)
312
+ set_node_groups_interface_parents(data, group_dict)
313
+ node_group['name_dict'] = {k: v.name for k, v in group_dict.items()}
314
+ return node_group
315
+
316
+
317
+ def add_node_group(ob: bpy.types.Object, name: str, data: PYDict) -> bpy.types.Modifier:
318
+ """Create Geometry Node modifier and node group.
319
+
320
+ :param ob: Object to add modifier to.
321
+ :type ob: Object
322
+ :param name: Modifier name.
323
+ :type name: str
324
+ :param data: Data to build node group to assign to the modifier.
325
+ :type data: dict[str, Any]
326
+ :return: Object modifier.
327
+ :rtype: Modifier"""
328
+ modifier = ob.modifiers.new(name, 'NODES')
329
+ node_group = create_node_group_from_data(name, data)
330
+ modifier.node_group = node_group
331
+ return modifier
332
+
333
+
334
+ ######################## MATERIAL #########################
335
+
336
+
337
+ def create_shader_group_from_data(data: PYDict, group_type: str='ShaderNodeTree') -> dict[str, BGroup]:
338
+ group_dict, enum_dict, data, node_dict = set_node_groups_from_data(data, group_type=group_type)
339
+ set_nodes_from_data(data, node_dict, group_dict)
340
+ set_node_group_links(data, group_dict)
341
+ set_node_groups_enums(data, enum_dict)
342
+ set_node_groups_interface_parents(data, group_dict)
343
+ return group_dict
344
+
345
+
346
+ def set_shader_from_data(data_dict: PYDict, node_dict: dict[str, BNode], group_dict: dict[str, BGroup]) -> None:
347
+ for _node, node in node_dict.items():
348
+ process_node(node, data_dict[_node], group_dict, set_enum=True)
349
+
350
+
351
+ def create_material(data: PYDict) -> bpy.types.Material:
352
+ """Create material from build data.
353
+
354
+ :param data: Data dict to build material.
355
+ :type data: dict[str, Any]
356
+ :return: Material.
357
+ :rtype: Material"""
358
+ global tree_exclude, asset_nodes
359
+ mat_name = list(data.keys())[0]
360
+ material = bpy.data.materials.new(mat_name)
361
+ for a in data[mat_name]:
362
+ if a not in ['node_tree'] and hasattr(material, a):
363
+ setattr(material, a, data[mat_name][a])
364
+ node_tree = getattr(material, 'node_tree', None)
365
+ if node_tree:
366
+ node_tree.nodes.clear()
367
+ mat_data = data[mat_name]['node_tree']
368
+ group_data = mat_data['node_groups']
369
+ nodes_data = mat_data['nodes']
370
+ link_data = mat_data['links']
371
+ group_dict = create_shader_group_from_data(group_data, group_type='ShaderNodeTree')
372
+ nodes_dict = dict(create_node_tree_nodes(node_tree, nodes_data))
373
+ set_shader_from_data(nodes_data, nodes_dict, group_dict)
374
+ for link in link_data:
375
+ _new_link(link, node_tree)
376
+ material['name_dict'] = {k: v.name for k, v in group_dict.items()}
377
+ return material
378
+
379
+
380
+ def add_material_to_ob(ob: bpy.types.Object, data: PYDict) -> bpy.types.Material:
381
+ """Create material from data and add to object material slot.
382
+
383
+ :param ob: Object to add material to.
384
+ :type ob: Object
385
+ :param data: Data dict to build material.
386
+ :type data: dict[str, Any]
387
+ :return: Material.
388
+ :rtype: Material"""
389
+ materials = ob.data.materials
390
+ material = create_material(data)
391
+ materials.append(material)
392
+ return material
393
+
394
+
395
+ ####################### COMPOSITOR ########################
396
+
397
+
398
+ def add_comp_group(scene: bpy.types.Scene, name: str, data: PYDict) -> BGroup:
399
+ """Create compositor node group and add to scene.
400
+
401
+ :param scene: Scene to add compositor node group to.
402
+ :type scene: Scene
403
+ :param name: Name of the node group.
404
+ :type name: str
405
+ :param data: Data dict to build compositor node group.
406
+ :type data: dict[str, Any]
407
+ :return: Compositor node group.
408
+ :rtype: Node Group"""
409
+ scene.compositing_node_group = None
410
+ node_group = create_node_group_from_data(name, data['node_groups'], group_type='CompositorNodeTree')
411
+ scene.compositing_node_group = node_group
412
+ return node_group
413
+