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.
- node_to_json/__init__.py +53 -0
- node_to_json/asset_funcs.py +74 -0
- node_to_json/func_util.py +90 -0
- node_to_json/json_io.py +31 -0
- node_to_json/node_getters.py +471 -0
- node_to_json/node_registry.py +56 -0
- node_to_json/node_setters.py +433 -0
- node_to_json/py.typed +0 -0
- node_to_json/setter_funcs.py +452 -0
- node_to_json/type_util.py +10 -0
- node_to_json-0.1.1.dist-info/METADATA +13 -0
- node_to_json-0.1.1.dist-info/RECORD +13 -0
- node_to_json-0.1.1.dist-info/WHEEL +4 -0
|
@@ -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
|
+
|