node_to_json 0.1.1__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,471 @@
1
+ import bpy
2
+ from typing import Any, Generator
3
+ from .type_util import BNode, BGroup, PYDict
4
+ from .asset_funcs import get_asset_nodes
5
+ from .func_util import convert_attr, nested_dict
6
+ from .node_registry import register_ng_getter
7
+
8
+
9
+ node_attr_exclude = ['rna_type', 'bl_rna', 'inputs', 'internal_links', 'outputs', 'enum_definition', 'interface_items', 'panel_states',
10
+ 'item', 'bl_static_type', 'dimensions', 'color_tag']
11
+
12
+ node_type_exclude = [
13
+ 'bpy_prop_collection',
14
+ 'Object',
15
+ 'Material',
16
+ 'Image',
17
+ 'ImageTexture',
18
+ 'Texture',
19
+ 'Collection',
20
+ 'Scene',
21
+ 'Sound',
22
+ 'Node',
23
+ 'GeometryNodeTree',
24
+ 'ShaderNodeTree',
25
+ 'CompositorNodeTree',
26
+ ]
27
+
28
+ preset_attr_exclude = [
29
+ 'bl_description',
30
+ 'bl_height_default',
31
+ 'bl_height_max',
32
+ 'bl_height_min',
33
+ 'bl_icon',
34
+ 'bl_idname',
35
+ 'bl_label',
36
+ 'bl_static_type',
37
+ 'bl_width_default',
38
+ 'bl_width_max',
39
+ 'bl_width_min',
40
+ 'color',
41
+ 'color_tag',
42
+ 'dimensions',
43
+ 'height',
44
+ 'hide',
45
+ 'label',
46
+ 'location',
47
+ 'location_absolute',
48
+ 'mute',
49
+ 'name',
50
+ 'node_tree',
51
+ 'panel_states',
52
+ 'parent',
53
+ 'select',
54
+ 'show_options',
55
+ 'show_preview',
56
+ 'show_texture',
57
+ 'type',
58
+ 'use_custom_color',
59
+ 'warning_propagation',
60
+ 'width',
61
+ ]
62
+
63
+ socket_attr = [
64
+ 'description',
65
+ 'enabled',
66
+ 'hide',
67
+ 'hide_value',
68
+ 'name',
69
+ 'pin_gizmo',
70
+ 'show_expanded',
71
+ 'type',
72
+ 'is_linked',
73
+ 'default_value',
74
+ ]
75
+
76
+ loop_outputs = [
77
+ 'GeometryNodeRepeatOutput',
78
+ 'GeometryNodeForeachGeometryElementOutput',
79
+ 'GeometryNodeSimulationOutput',
80
+ 'NodeClosureOutput',
81
+ ]
82
+
83
+
84
+ ### TREE NODES ###
85
+
86
+ node_tree_attr = [
87
+ 'bl_description',
88
+ 'bl_icon',
89
+ 'bl_idname',
90
+ 'bl_label',
91
+ 'bl_use_group_interface',
92
+ 'color_tag',
93
+ 'default_group_node_width',
94
+ 'description',
95
+ 'node_tree',
96
+ ]
97
+
98
+ geo_tree_attr = [
99
+ 'is_mode_edit',
100
+ 'is_mode_object',
101
+ 'is_mode_paint',
102
+ 'is_mode_sculpt',
103
+ 'is_modifier',
104
+ 'is_tool',
105
+ 'is_type_curve',
106
+ 'is_type_grease_pencil',
107
+ 'is_type_mesh',
108
+ 'is_type_pointcloud',
109
+ 'node_tool_idname',
110
+ 'show_modifier_manage_panel',
111
+ 'use_wait_for_click',
112
+ ]
113
+
114
+ tree_inter_base = [
115
+ 'item_type',
116
+ 'parent',
117
+ 'position',
118
+ 'description',
119
+ 'name',
120
+ 'index',
121
+ ]
122
+
123
+ tree_inter_sock = [
124
+ 'attribute_domain',
125
+ 'bl_socket_idname',
126
+ 'default_attribute_name',
127
+ 'default_input',
128
+ 'hide_in_modifier',
129
+ 'hide_value',
130
+ 'in_out',
131
+ 'is_inspect_output',
132
+ 'is_panel_toggle',
133
+ 'layer_selection_field',
134
+ 'menu_expanded',
135
+ 'optional_label',
136
+ 'socket_type',
137
+ 'structure_type',
138
+ ]
139
+
140
+ tree_inter_panel = [
141
+ 'default_closed',
142
+ 'persistent_uid',
143
+ ]
144
+
145
+ items_tree_attr = [
146
+ 'default_value',
147
+ 'dimensions',
148
+ 'min_value',
149
+ 'max_value',
150
+ 'subtype',
151
+ ]
152
+
153
+
154
+ ### MATERIAL NODES ###
155
+
156
+ mat_attr = [
157
+ 'alpha_threshold',
158
+ 'blend_method',
159
+ 'diffuse_color',
160
+ 'displacement_method',
161
+ 'line_color',
162
+ 'line_priority',
163
+ 'max_vertex_displacement',
164
+ 'metallic',
165
+ 'paint_active_slot',
166
+ 'paint_clone_slot',
167
+ 'pass_index',
168
+ 'preview_render_type',
169
+ 'refraction_depth',
170
+ 'roughness',
171
+ # 'show_transparent_back', #(may introduce transparency sorting problems) (Deprecated: use ‘use_tranparency_overlap’)
172
+ 'specular_color',
173
+ 'specular_intensity',
174
+ 'surface_render_method',
175
+ 'thickness_mode',
176
+ 'use_backface_culling',
177
+ 'use_backface_culling_lightprobe_volume',
178
+ 'use_backface_culling_shadow',
179
+ # 'use_nodes', #Depricated
180
+ 'use_preview_world',
181
+ 'use_raytrace_refraction',
182
+ 'use_screen_refraction',
183
+ 'use_sss_translucency',
184
+ 'use_thickness_from_shadow',
185
+ 'use_transparency_overlap',
186
+ 'use_transparent_shadow',
187
+ 'volume_intersection_method',
188
+ ]
189
+
190
+
191
+ ########################## NODE ###########################
192
+
193
+
194
+ def get_node_attr(ob: BNode, attr: list[str]) -> PYDict:
195
+ func = lambda a: [a, convert_attr(getattr(ob, a, None))]
196
+ mfunc = lambda a: hasattr(ob, a) and not isinstance(getattr(ob, a, None), type(None))
197
+ data = map(func, filter(mfunc, attr))
198
+ return dict(data)
199
+
200
+
201
+ def get_node_data(node: BNode) -> PYDict:
202
+ global node_attr_exclude
203
+ a_filter = lambda a: (not a.startswith("__") and not a in node_attr_exclude) and (not getattr(node, a, None) is None and not type(getattr(node, a, None)).__name__ in ['bpy_func', 'method-wrapper', 'builtin_function_or_method', 'EnumProperty'])
204
+ attributes = filter(a_filter, dir(node))
205
+ def _get_attr(attr):
206
+ val = getattr(node, attr, None)
207
+ val_ = convert_attr(val)
208
+ if type(val).__name__ not in node_type_exclude and attr not in ['parent']:
209
+ if type(val).__name__ in dir(bpy.types):
210
+ return [attr, get_node_data(val)]
211
+ else:
212
+ return [attr, val_]
213
+ else:
214
+ if type(val).__name__ in ['Node', 'GeometryNodeTree', 'ShaderNodeTree', 'CompositorNodeTree'] or attr in ['parent']:
215
+ return [attr, val_]
216
+ elif type(val).__name__ in ['bpy_prop_collection']:
217
+ return [attr, [get_node_data(v) for v in val[:]]]
218
+ else:
219
+ pass
220
+ return {k: v for k, v in map(_get_attr, attributes)}
221
+
222
+
223
+ def get_socket_attr(node: BNode) -> dict[str, list[PYDict]]:
224
+ global socket_attr
225
+ i = []
226
+ o = []
227
+ if node.bl_idname not in loop_outputs:
228
+ if hasattr(node, 'inputs'):
229
+ inputs = getattr(node, 'inputs', None)
230
+ if inputs:
231
+ i = [get_node_attr(i_, socket_attr) for i_ in inputs]
232
+ if hasattr(node, 'outputs'):
233
+ outputs = getattr(node, 'outputs', None)
234
+ if outputs:
235
+ o = [get_node_attr(o_, socket_attr) for o_ in outputs]
236
+ return {'inputs': i, 'outputs': o}
237
+
238
+
239
+ def get_node_build_data(node: BNode) -> dict[str, PYDict]:
240
+ """Serialize data to rebuild a node.
241
+
242
+ :param node: Node to get data from.
243
+ :type node: Node
244
+ :return: Dict containing data to rebuild a node and input / output sockets.
245
+ :rtype: dict[str, Any]"""
246
+
247
+ return {**get_node_data(node), **get_socket_attr(node)}
248
+
249
+
250
+ ####################### NODE GROUP ########################
251
+
252
+
253
+ def _get_node_group_names(nodes: BNode) -> Generator[str, None, None]:
254
+ asset_nodes = set(get_asset_nodes())
255
+ for node in nodes:
256
+ if node.type == 'GROUP':
257
+ yield node.node_tree.name
258
+ if node.node_tree.name.split(".")[0] not in asset_nodes:
259
+ yield from _get_node_group_names(node.node_tree.nodes)
260
+
261
+
262
+ def get_node_group_names(node_group: BGroup) -> PYDict:
263
+ """Retrieve all of the node groups inside a node group.
264
+
265
+ :param node_group: Node group to search internal node groups inside of.
266
+ :type node_group: Node Group
267
+ :return: Dict containing the node group and all internally nested node groups.
268
+ :rtype: dict[str, dict[None]]"""
269
+
270
+ return {**{node_group.name: {}}, **{group: {} for group in _get_node_group_names(node_group.nodes)}}
271
+
272
+
273
+ def get_node_group_attr_list(node_tree: BGroup) -> PYDict:
274
+ global node_tree_attr, geo_tree_attr
275
+ attributes = node_tree_attr + geo_tree_attr if node_tree.bl_idname == 'GeometryNodeTree' else node_tree_attr
276
+ return {a: convert_attr(getattr(node_tree, a, None)) for a in attributes}
277
+
278
+
279
+ def get_items_tree(item: bpy.types.NodeTreeInterfaceItem) -> PYDict:
280
+ global tree_inter_base, tree_inter_sock, tree_inter_panel
281
+ itype = item.item_type
282
+ data = tree_inter_base
283
+ data = data + (tree_inter_panel if itype == 'PANEL' else tree_inter_sock)
284
+ if itype != 'PANEL':
285
+ data = data + items_tree_attr
286
+ return {a: convert_attr(getattr(item, a, None)) for a in data if hasattr(item, a)}
287
+ else:
288
+ return {a: convert_attr(getattr(item, a, None)) for a in data if hasattr(item, a)}
289
+
290
+
291
+ def get_node_group_interface(node_tree: BGroup) -> Generator[PYDict, None, None]:
292
+ """Retrieve data for node group sockets and panels.
293
+
294
+ :param node_tree: Node group to get data from.
295
+ :type node_tree: Node Group
296
+ :return: Generator containing dicts of socket and panel data.
297
+ :rtype: Generator[dict[str, Any], None, None]"""
298
+
299
+ return map(get_items_tree, node_tree.interface.items_tree)
300
+
301
+
302
+ def get_socket_data(node: BNode) -> PYDict:
303
+ global node_attr_exclude
304
+ data = nested_dict()
305
+ attributes = (a for a in dir(node) if not a.startswith("__") and not a in node_attr_exclude)
306
+ for attr in attributes:
307
+ val = getattr(node, attr, None)
308
+ if val is None:
309
+ data[attr] = None
310
+ else:
311
+ if not type(val).__name__ in ['bpy_func', 'method-wrapper', 'builtin_function_or_method', 'EnumProperty']:
312
+ val_ = convert_attr(val)
313
+ data[attr] = val_
314
+ return data
315
+
316
+
317
+ def get_node_tree_nodes_data(node_tree: BGroup) -> PYDict | None:
318
+ nodes = getattr(node_tree, 'nodes', None)
319
+ if nodes:
320
+ return {node.name: get_node_build_data(node) for node in nodes}
321
+ return
322
+
323
+
324
+ def get_node_tree_socket_data(node_tree: BGroup) -> Generator[PYDict, None, None] | None:
325
+ interface = getattr(node_tree, 'interface', None)
326
+ if interface:
327
+ items_tree = getattr(interface, 'items_tree', None)
328
+ if items_tree:
329
+ return (get_socket_data(socket) for socket in items_tree)
330
+ return
331
+
332
+
333
+ def get_links(node_tree: BGroup) -> Generator[PYDict, None, None]:
334
+ link_attr = [
335
+ 'name',
336
+ 'type',
337
+ ]
338
+ links = getattr(node_tree, 'links', None)
339
+ if links:
340
+ for s in ([i.from_socket, i.to_socket] for i in links):
341
+ o_node = s[0].node.name
342
+ o_sock = next(idx for idx, o in enumerate(s[0].node.outputs) if o.identifier == s[0].identifier)
343
+ i_node = s[1].node.name
344
+ i_sock = next(idx for idx, i in enumerate(s[1].node.inputs) if i.identifier == s[1].identifier)
345
+ yield [{**{'node': o_node, 'socket': o_sock}, **{l: getattr(s[0], l, None) for l in link_attr}}, {**{'node': i_node, 'socket': i_sock}, **{l: getattr(s[1], l, None) for l in link_attr}}]
346
+
347
+
348
+ @register_ng_getter('GeometryNodeTree', 'TextureNodeTree')
349
+ def serialize_node_group(node_group: BGroup) -> PYDict:
350
+ """Serialize data to rebuild a node group.
351
+
352
+ :param node_group: Node group to get data from.
353
+ :type node_group: Node Group
354
+ :return: Dict of data to rebuild a node group.
355
+ :rtype: dict[str, Any]"""
356
+
357
+ asset_nodes = set(get_asset_nodes())
358
+ data = get_node_group_names(node_group)
359
+ gnames = [g.name for g in bpy.data.node_groups]
360
+ for group in data:
361
+ if group in gnames:
362
+ is_asset = group.split(".")[0] in asset_nodes
363
+ node_tree = bpy.data.node_groups[group]
364
+ data[group] = {**get_node_group_attr_list(node_tree), **{'interface': (None if is_asset else list(get_node_group_interface(node_tree)))}, **{'links': (None if is_asset else list(get_links(node_tree)))}, **{'nodes': (None if is_asset else get_node_tree_nodes_data(node_tree))}}
365
+ return {d: data[d] for d in data if len(data[d]) > 0}
366
+
367
+
368
+ ######################## MATERIAL #########################
369
+
370
+
371
+ def get_mat_attr(material: bpy.types.Material) -> PYDict:
372
+ global mat_attr
373
+ return {a: convert_attr(getattr(material, a, None)) for a in mat_attr if hasattr(material, a)}
374
+
375
+
376
+ @register_ng_getter('ShaderNodeTree')
377
+ def serialize_mat_group(node_group: BGroup) -> PYDict:
378
+ """Serialize data to rebuild a material node group.
379
+
380
+ :param node_group: Node group to get data from.
381
+ :type node_group: Node Group
382
+ :return: Dict of data to rebuild a material node group.
383
+ :rtype: dict[str, Any]"""
384
+
385
+ asset_nodes = set(get_asset_nodes())
386
+ data = get_node_group_names(node_group)
387
+ gnames = [g.name for g in bpy.data.node_groups]
388
+ for group in data:
389
+ if group in gnames:
390
+ is_asset = group.split(".")[0] in asset_nodes
391
+ node_tree = bpy.data.node_groups[group]
392
+ data[group] = {**get_node_group_attr_list(node_tree), **{'interface': (None if is_asset else list(get_node_group_interface(node_tree)))}, **{'links': (None if is_asset else list(get_links(node_tree)))}, **{'nodes': (None if is_asset else get_node_tree_nodes_data(node_tree))}}
393
+ return {d: data[d] for d in data if len(data[d]) > 0}
394
+
395
+
396
+ def serialize_material(material: bpy.types.Material) -> PYDict:
397
+ """Serialize data to rebuild a material.
398
+
399
+ :param material: Material to get data from.
400
+ :type material: Material
401
+ :return: Dict of data to rebuild a material and material node group.
402
+ :rtype: dict[str, Any]"""
403
+
404
+ return {material.name.split(".")[0]: {**get_mat_attr(material), **{'node_tree': {**{'links': list(get_links(material.node_tree))}, **{'nodes': get_node_tree_nodes_data(material.node_tree)}, **{'node_groups': serialize_mat_group(material.node_tree)}}}}}
405
+
406
+
407
+ ####################### COMPOSITOR ########################
408
+
409
+
410
+ def get_comp_node_data(node: BNode) -> PYDict:
411
+ global node_attr_exclude
412
+ a_filter = lambda a: (not a.startswith("__") and not a in node_attr_exclude) and (not getattr(node, a, None) is None and not type(getattr(node, a, None)).__name__ in ['bpy_func', 'method-wrapper', 'builtin_function_or_method', 'EnumProperty'])
413
+ attributes = filter(a_filter, dir(node))
414
+ def _get_attr(attr):
415
+ val = getattr(node, attr, None)
416
+ val_ = convert_attr(val)
417
+ if type(val).__name__ not in node_type_exclude and attr not in ['parent']:
418
+ if type(val).__name__ in dir(bpy.types):
419
+ return [attr, get_node_data(val)]
420
+ else:
421
+ return [attr, val_]
422
+ else:
423
+ if type(val).__name__ in ['Node', 'GeometryNodeTree', 'ShaderNodeTree', 'CompositorNodeTree'] or attr in ['parent']:
424
+ return [attr, val_]
425
+ elif type(val).__name__ in ['bpy_prop_collection']:
426
+ return [attr, [get_node_data(v) for v in val[:]]]
427
+ else:
428
+ return [attr, val_]
429
+ return dict(map(_get_attr, attributes))
430
+
431
+
432
+ @register_ng_getter('CompositorNodeTree')
433
+ def serialize_comp_group(compositor: bpy.types.CompositorNodeTree) -> PYDict:
434
+ """Serialize data to rebuild a compositor node group.
435
+
436
+ :param compositor: Node group to get data from.
437
+ :type compositor: NodeGroup
438
+ :return: Dict of data to rebuild a compositor node group.
439
+ :rtype: dict[str, Any]"""
440
+
441
+ asset_nodes = set(get_asset_nodes())
442
+ data = get_node_group_names(compositor)
443
+ gnames = [g.name for g in bpy.data.node_groups]
444
+ _nodes = lambda node_tree: {node.name: {**get_comp_node_data(node), **get_socket_attr(node)} for node in node_tree.nodes}
445
+ for group in data:
446
+ if group in gnames:
447
+ is_asset = group.split(".")[0] in asset_nodes
448
+ node_tree = bpy.data.node_groups[group]
449
+ data[group] = {**get_node_group_attr_list(node_tree), **{'interface': (None if is_asset else list(get_node_group_interface(node_tree)))}, **{'links': (None if is_asset else list(get_links(node_tree)))}, **{'nodes': (None if is_asset else _nodes(node_tree))}}
450
+ return {d: data[d] for d in data if len(data[d]) > 0}
451
+
452
+
453
+ def serialize_compositor(compositor: bpy.types.CompositorNodeTree) -> PYDict:
454
+ """Serialize data to rebuild a compositor node group.
455
+
456
+ :param compositor: Node group to get data from.
457
+ :type compositor: Node Group
458
+ :return: Dict of data to rebuild a compositor node group.
459
+ :rtype: dict[str, Any]"""
460
+
461
+ compositor_exclude = ['bl_rna', 'id_type', 'interface', 'rna_type', 'nodes', 'links']
462
+ a_filter = lambda a: (not a.startswith("__") and not a in compositor_exclude) and (not getattr(compositor, a, None) is None and not type(getattr(compositor, a, None)).__name__ in ['bpy_func', 'method-wrapper', 'builtin_function_or_method', 'EnumProperty'])
463
+ attributes = filter(a_filter, dir(compositor))
464
+ def _get_attr(attr):
465
+ val = getattr(compositor, attr, None)
466
+ val_ = convert_attr(val)
467
+ return attr, val_
468
+ data = map(_get_attr, attributes)
469
+ data = dict(data) | {'node_groups': serialize_comp_group(compositor)}
470
+ return data
471
+
@@ -0,0 +1,56 @@
1
+ import bpy
2
+ from typing import Callable
3
+ from .type_util import PYDict
4
+ from functools import wraps
5
+
6
+
7
+ type AttrData = PYDict
8
+ type BtypeFn = Callable[[str, list[str]], AttrData]
9
+
10
+
11
+ bpy_types_funcs: dict[str, BtypeFn] = {}
12
+ ng_getter_funcs: dict[str, BtypeFn] = {}
13
+ ng_setter_funcs: dict[str, BtypeFn] = {}
14
+
15
+
16
+ def register_node_setter(*bpy_type: str) -> Callable[[bpy.types.Node, list[str]], AttrData]:
17
+ def decorator(fn: BtypeFn) -> dict[str, BtypeFn]:
18
+ @wraps(fn)
19
+ def wrapper(node: bpy.types.Node, attr: list[str]) -> BtypeFn:
20
+ return fn(node, attr)
21
+ # Assign types to function
22
+ for b in bpy_type:
23
+ bpy_types_funcs[b] = wrapper
24
+ # Return function
25
+ return wrapper
26
+ # Return the decorator
27
+ return decorator
28
+
29
+
30
+ def register_ng_getter(*bpy_type: str) -> Callable[[bpy.types.Node, list[str]], AttrData]:
31
+ def decorator(fn: BtypeFn) -> dict[str, BtypeFn]:
32
+ @wraps(fn)
33
+ def wrapper(*args, **kwargs) -> BtypeFn:
34
+ return fn(*args, **kwargs)
35
+ # Assign types to function
36
+ for b in bpy_type:
37
+ ng_getter_funcs[b] = wrapper
38
+ # Return function
39
+ return wrapper
40
+ # Return the decorator
41
+ return decorator
42
+
43
+
44
+ def register_ng_setter(*bpy_type: str) -> Callable[[bpy.types.Node, list[str]], AttrData]:
45
+ def decorator(fn: BtypeFn) -> dict[str, BtypeFn]:
46
+ @wraps(fn)
47
+ def wrapper(*args, **kwargs) -> BtypeFn:
48
+ return fn(*args, **kwargs)
49
+ # Assign types to function
50
+ for b in bpy_type:
51
+ ng_setter_funcs[b] = wrapper
52
+ # Return function
53
+ return wrapper
54
+ # Return the decorator
55
+ return decorator
56
+