procfunc 0.30.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.
Files changed (76) hide show
  1. procfunc/__init__.py +87 -0
  2. procfunc/color.py +57 -0
  3. procfunc/compute_graph/__init__.py +28 -0
  4. procfunc/compute_graph/compute_graph.py +115 -0
  5. procfunc/compute_graph/node.py +200 -0
  6. procfunc/compute_graph/operators_info.py +92 -0
  7. procfunc/compute_graph/proxy.py +173 -0
  8. procfunc/compute_graph/util.py +282 -0
  9. procfunc/context.py +115 -0
  10. procfunc/control.py +174 -0
  11. procfunc/nodes/__init__.py +66 -0
  12. procfunc/nodes/bindings_util.py +196 -0
  13. procfunc/nodes/bpy_node_info.py +280 -0
  14. procfunc/nodes/compositor.py +2242 -0
  15. procfunc/nodes/execute/construct_nodes.py +571 -0
  16. procfunc/nodes/execute/construct_special_cases.py +246 -0
  17. procfunc/nodes/execute/execute.py +548 -0
  18. procfunc/nodes/execute/infer_runtime_data_type.py +195 -0
  19. procfunc/nodes/execute/util.py +247 -0
  20. procfunc/nodes/func.py +1417 -0
  21. procfunc/nodes/geo.py +4240 -0
  22. procfunc/nodes/manifest.json +8769 -0
  23. procfunc/nodes/math.py +644 -0
  24. procfunc/nodes/node_function.py +160 -0
  25. procfunc/nodes/shader.py +2359 -0
  26. procfunc/nodes/types.py +347 -0
  27. procfunc/ops/__init__.py +35 -0
  28. procfunc/ops/_util.py +275 -0
  29. procfunc/ops/addons.py +59 -0
  30. procfunc/ops/attr.py +426 -0
  31. procfunc/ops/collection.py +90 -0
  32. procfunc/ops/curve.py +18 -0
  33. procfunc/ops/file.py +126 -0
  34. procfunc/ops/manifest.json +39149 -0
  35. procfunc/ops/mesh.py +1510 -0
  36. procfunc/ops/modifier.py +603 -0
  37. procfunc/ops/object.py +258 -0
  38. procfunc/ops/primitives/__init__.py +31 -0
  39. procfunc/ops/primitives/camera.py +45 -0
  40. procfunc/ops/primitives/curve.py +71 -0
  41. procfunc/ops/primitives/light.py +114 -0
  42. procfunc/ops/primitives/mesh.py +358 -0
  43. procfunc/ops/uv.py +271 -0
  44. procfunc/random.py +247 -0
  45. procfunc/tracer/__init__.py +43 -0
  46. procfunc/tracer/decorator.py +121 -0
  47. procfunc/tracer/patch.py +494 -0
  48. procfunc/tracer/proxy.py +127 -0
  49. procfunc/tracer/trace.py +222 -0
  50. procfunc/transforms/__init__.py +49 -0
  51. procfunc/transforms/cleanup.py +214 -0
  52. procfunc/transforms/convert.py +20 -0
  53. procfunc/transforms/distribution.py +191 -0
  54. procfunc/transforms/extract_materials.py +116 -0
  55. procfunc/transforms/infer_distribution.py +326 -0
  56. procfunc/transforms/parameters.py +15 -0
  57. procfunc/transforms/util.py +35 -0
  58. procfunc/transpiler/__init__.py +24 -0
  59. procfunc/transpiler/bpy_to_computegraph.py +1348 -0
  60. procfunc/transpiler/codegen.py +919 -0
  61. procfunc/transpiler/identifiers.py +595 -0
  62. procfunc/transpiler/main.py +299 -0
  63. procfunc/types.py +380 -0
  64. procfunc/util/__init__.py +0 -0
  65. procfunc/util/bpy_info.py +145 -0
  66. procfunc/util/camera.py +0 -0
  67. procfunc/util/keyframe.py +70 -0
  68. procfunc/util/log.py +96 -0
  69. procfunc/util/manifest.py +121 -0
  70. procfunc/util/pytree.py +343 -0
  71. procfunc/util/teardown.py +37 -0
  72. procfunc-0.30.0.dist-info/METADATA +120 -0
  73. procfunc-0.30.0.dist-info/RECORD +76 -0
  74. procfunc-0.30.0.dist-info/WHEEL +5 -0
  75. procfunc-0.30.0.dist-info/licenses/LICENSE.md +11 -0
  76. procfunc-0.30.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,571 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import bpy
5
+
6
+ from procfunc import compute_graph as cg
7
+ from procfunc import types as pt
8
+ from procfunc.compute_graph.operators_info import OPERATORS_TO_FUNCTIONS
9
+ from procfunc.nodes import bpy_node_info as bni
10
+ from procfunc.nodes import types as nt
11
+ from procfunc.nodes.bindings_util import (
12
+ ContextualNode,
13
+ RuntimeResolveDataType,
14
+ resolve_contextual_node,
15
+ )
16
+ from procfunc.util import pytree
17
+ from procfunc.util.bpy_info import bpy_nocollide_data_name
18
+ from procfunc.util.log import add_exception_context_msg
19
+
20
+ from .construct_special_cases import NODE_SPECIAL_CASES
21
+ from .infer_runtime_data_type import (
22
+ infer_operation_type,
23
+ map_data_type_for_differing_node_interface,
24
+ resolve_operation_data_type,
25
+ )
26
+ from .util import (
27
+ NODE_OPERATOR_TABLE,
28
+ NodeOperatorResolution,
29
+ assign_default_value,
30
+ get_active_sockets,
31
+ get_input_socket_to_connect_to,
32
+ get_nth_socket,
33
+ normalize_socket_type,
34
+ )
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+
39
+ def connect_single_input(
40
+ node_tree: bpy.types.NodeGroup,
41
+ to_socket: bpy.types.NodeSocket,
42
+ input_val: bpy.types.NodeSocket | Any | None,
43
+ ):
44
+ if isinstance(input_val, nt.ProcNode):
45
+ raise ValueError(
46
+ f"ProcNode {input_val} is not allowed as input to {to_socket.name}"
47
+ )
48
+
49
+ match input_val:
50
+ case None:
51
+ pass
52
+ # case nt.Keyframes():
53
+ # apply_keyframes(to_input_socket, input_val)
54
+ case bpy.types.NodeSocket() as from_socket:
55
+ _connect_socket(node_tree, from_socket, to_socket, input_val.node.name)
56
+ case bpy.types.NodeInternal():
57
+ raise ValueError(
58
+ f"{input_val=} is a bpy.types.NodeInternal, but this should have been "
59
+ f"resolved to a specific socket at an earlier stage"
60
+ )
61
+ case _ if hasattr(to_socket, "default_value"):
62
+ assign_default_value(to_socket, input_val)
63
+ case _:
64
+ raise ValueError(
65
+ f"Could not handle {input_val=} as input to {to_socket.name=}"
66
+ )
67
+
68
+
69
+ def connect_multisocket_input(
70
+ node_tree: bpy.types.NodeGroup,
71
+ to_socket: bpy.types.NodeSocket,
72
+ input_result: list[bpy.types.NodeSocket | Any | None],
73
+ from_py_input: list[cg.Node],
74
+ ):
75
+ if not to_socket.is_multi_input:
76
+ raise ValueError(
77
+ f"list of sockets {input_result} is not valid to connect to {to_socket} as it is not a "
78
+ f"valid multi-input socket"
79
+ )
80
+
81
+ assert isinstance(input_result, list)
82
+ assert isinstance(from_py_input, list)
83
+
84
+ for input_val, py_input in zip(input_result, from_py_input):
85
+ if input_val is None:
86
+ continue
87
+ connect_single_input(node_tree, to_socket, input_val)
88
+
89
+
90
+ def _connect_socket(
91
+ node_tree: bpy.types.NodeGroup,
92
+ source_socket: bpy.types.NodeSocket,
93
+ target_socket: bpy.types.NodeSocket,
94
+ source_node_name: str = "unknown",
95
+ ):
96
+ """Helper function to connect sockets with type compatibility checking."""
97
+ output_type = getattr(source_socket, "bl_idname", source_socket.type)
98
+ input_type = getattr(target_socket, "bl_idname", target_socket.type)
99
+
100
+ normalized_output = normalize_socket_type(output_type)
101
+ normalized_input = normalize_socket_type(input_type)
102
+
103
+ if not bni.are_socket_types_compatible(normalized_output, normalized_input):
104
+ raise ValueError(
105
+ f"Incompatible socket types: cannot connect {output_type} output to {input_type} input. "
106
+ f"Source: {source_node_name}.{source_socket.name} -> "
107
+ f"Target: {getattr(target_socket.node, 'name', 'unknown')}.{target_socket.name}"
108
+ )
109
+
110
+ node_tree.links.new(source_socket, target_socket)
111
+
112
+
113
+ def _get_primary_output_socket(
114
+ node_spec: cg.Node,
115
+ bpy_node: bpy.types.Node,
116
+ ) -> bpy.types.NodeSocket:
117
+ """
118
+ Sometimes a node will have multiple output sockets, but the user didnt say which one they want.
119
+ """
120
+
121
+ enabled = list(get_active_sockets(bpy_node.outputs))
122
+ if len(enabled) == 0:
123
+ raise ValueError(
124
+ f"Got {len(enabled)=} enabled sockets for {node_spec=} {bpy_node.name=}"
125
+ )
126
+ if len(enabled) == 1:
127
+ assert isinstance(enabled[0], bpy.types.NodeSocket), enabled[0]
128
+ return enabled[0]
129
+ names = [socket.name for socket in enabled]
130
+
131
+ raise ValueError(
132
+ f"{node_spec=} should have a single anonymous output, "
133
+ f"but there was actually >1 {enabled=} {names=} for {bpy_node.bl_idname=}"
134
+ )
135
+
136
+
137
+ def _find_operator_row(
138
+ func: Any, data_type: bni.NodeDataType
139
+ ) -> NodeOperatorResolution:
140
+ search = (
141
+ row
142
+ for row in NODE_OPERATOR_TABLE
143
+ if (
144
+ row.value_type == data_type
145
+ and OPERATORS_TO_FUNCTIONS[row.operator_type] is func
146
+ )
147
+ )
148
+ op_row = next(search, None)
149
+ if op_row is None:
150
+ raise ValueError(
151
+ f"User called inline binary operator to invoke {func=} on {data_type=} "
152
+ f"but this data type does not support that operator. Consider explicitly casting to another type with val.astype()"
153
+ )
154
+ return op_row
155
+
156
+
157
+ def _bind_positional_to_tuple_kwargs(
158
+ node: cg.Node, kwarg_keys: list, input_results: dict[str | int, Any]
159
+ ) -> dict[str | int, Any]:
160
+ """
161
+ operator call recieved input_results with anonymous positional args, which have keys 0, 1, 2
162
+ we need to match these up against the expected node input keys in kwarg_keys e.g. ("Value", 0) uses the 0th positional arg
163
+ """
164
+
165
+ inputs_bound = {}
166
+ for k, v in node.kwargs.items():
167
+ assert isinstance(k, tuple)
168
+ assert isinstance(k[1], int)
169
+ if v is None:
170
+ inputs_bound[k] = None
171
+ else:
172
+ inputs_bound[k] = input_results[k[1]]
173
+
174
+ if False and logger.isEnabledFor(logging.DEBUG):
175
+ logger.debug(
176
+ f"{_bind_positional_to_tuple_kwargs.__name__} for {node=} with "
177
+ f"{kwarg_keys=} {input_results=} {inputs_bound=}"
178
+ )
179
+
180
+ return inputs_bound
181
+
182
+
183
+ def _construct_operator_call(
184
+ node: cg.FunctionCallNode,
185
+ bl_node_tree: bpy.types.NodeTree,
186
+ input_results: dict[str | int, Any],
187
+ ) -> bpy.types.Node | bpy.types.NodeSocket:
188
+ assert len(node.kwargs) == 0, node.kwargs
189
+ inputs = [input_results[i] for i in range(len(node.args))]
190
+
191
+ do_coerce_integers = (
192
+ bni.NodeGroupType(bl_node_tree.bl_idname) == bni.NodeGroupType.SHADER
193
+ )
194
+ data_type = infer_operation_type(node, inputs, do_coerce_integers)
195
+ op_res = _find_operator_row(node.func, data_type)
196
+
197
+ node: cg.Node = op_res.pf_func(*inputs).item()
198
+
199
+ inputs_bound = _bind_positional_to_tuple_kwargs(
200
+ node, node.kwargs.keys(), input_results
201
+ )
202
+ return _construct_procnode_standard(node, bl_node_tree, inputs_bound)
203
+
204
+
205
+ def _set_node_attribute(bl_node: bpy.types.Node, k: str, v: Any):
206
+ if not hasattr(bl_node, k):
207
+ available = [
208
+ a for a in dir(bl_node) if not a.startswith("_") and not a.startswith("bl_")
209
+ ]
210
+ raise ValueError(
211
+ f"Node {bl_node.type} has no attribute {k!r}, available attributes: {available}"
212
+ )
213
+ try:
214
+ setattr(bl_node, k, v)
215
+ except Exception as e:
216
+ options = (
217
+ bl_node.bl_rna.properties[k].enum_items.keys()
218
+ if k in bl_node.bl_rna.properties
219
+ and hasattr(bl_node.bl_rna.properties[k], "enum_items")
220
+ else "unknown"
221
+ )
222
+ raise ValueError(
223
+ f"Could not set attribute {k!r} of {bl_node.name} to {v}, {options=}"
224
+ ) from e
225
+
226
+
227
+ def _map_keys(d: dict, map: dict) -> dict:
228
+ return {map.get(k, k): v for k, v in d.items()}
229
+
230
+
231
+ def _resolve_data_type(
232
+ data_type: bni.NodeDataType | RuntimeResolveDataType | None,
233
+ attr_key: str,
234
+ node: cg.Node,
235
+ bl_node: bpy.types.Node,
236
+ node_tree: bpy.types.NodeTree,
237
+ input_results: dict[str | int, Any],
238
+ ) -> Any:
239
+ is_shader = node_tree.bl_idname == bni.NodeGroupType.SHADER.value
240
+ match data_type:
241
+ case RuntimeResolveDataType() as runtime:
242
+ result: bni.NodeDataType = resolve_operation_data_type(
243
+ node,
244
+ input_results,
245
+ runtime,
246
+ coerce_integers=is_shader,
247
+ )
248
+ mapped = map_data_type_for_differing_node_interface(
249
+ result,
250
+ bl_node,
251
+ attr_key,
252
+ )
253
+ logger.debug(
254
+ f"Inferred runtime dtype for {node=}, got {result=} and mapped to {mapped=}"
255
+ )
256
+ return mapped
257
+ case bni.NodeDataType() as user_provided:
258
+ return map_data_type_for_differing_node_interface(
259
+ user_provided,
260
+ bl_node,
261
+ attr_key,
262
+ )
263
+ case str() as s:
264
+ return map_data_type_for_differing_node_interface(
265
+ bni.NodeDataType(s),
266
+ bl_node,
267
+ attr_key,
268
+ )
269
+ case unknown:
270
+ raise ValueError(
271
+ f"Got misconfigured data_type {unknown=} {type(unknown)=} for {node=}"
272
+ )
273
+
274
+
275
+ def _construct_procnode_standard(
276
+ node: cg.ProceduralNode,
277
+ bl_node_tree: bpy.types.NodeTree,
278
+ input_results: dict[str | int, Any],
279
+ ) -> bpy.types.Node:
280
+ """
281
+ Construct a procnode which is "standard", meaning kind=PROCEDURAL NODE (is not NOT an operator or getattr)
282
+
283
+ Returns:
284
+ bpy.types.Node: A blender geometry/shader/compositor node which corresponds to the given procfunc.nodes definition
285
+ """
286
+
287
+ assert isinstance(node, cg.ProceduralNode), node
288
+
289
+ node_type = node.node_type
290
+ kwargs = node.kwargs.copy()
291
+ attrs = node.attrs.copy()
292
+
293
+ # logger.debug(
294
+ # f"{_construct_procnode_standard.__name__} for {node=} with {kwargs=} {attrs=} {input_results=}"
295
+ # )
296
+
297
+ # resolve cases when the `node_type` is not known precisely in advance
298
+ # difference happens when we are using a given operation for shader vs geometry vs compositor - the same op has a different name via the bl4.2 api
299
+ if nt := ContextualNode.parse_name(node_type):
300
+ group_type = bni.NodeGroupType(bl_node_tree.bl_idname)
301
+ node_type, keymap = resolve_contextual_node(nt, group_type)
302
+ if keymap:
303
+ logger.debug(
304
+ f"Applying keymap {keymap} to {input_results.keys()=} for context {node_type=} {group_type=}"
305
+ )
306
+ kwargs = _map_keys(kwargs, keymap)
307
+ input_results = _map_keys(input_results, keymap)
308
+
309
+ bl_node = bl_node_tree.nodes.new(node_type)
310
+
311
+ for attr_key in ["data_type", "input_type"]: # Switch node uses input_type
312
+ if attr_key not in attrs:
313
+ continue
314
+ attrs[attr_key] = _resolve_data_type(
315
+ attrs[attr_key], attr_key, node, bl_node, bl_node_tree, input_results
316
+ )
317
+
318
+ # some nodes need to pop attrs and apply special handling instead of just setattr. we do it here so that they get a chance to
319
+ # remove the attrs / inputs that are problematic before they get applied by code below
320
+ if specialcase := NODE_SPECIAL_CASES.get(node_type):
321
+ specialcase_kwargs = dict(
322
+ node_tree=bl_node_tree,
323
+ bl_node=bl_node,
324
+ attrs=attrs,
325
+ kwargs=kwargs,
326
+ inputs=input_results,
327
+ )
328
+ specialcase(**specialcase_kwargs)
329
+
330
+ for k, v in attrs.items():
331
+ _set_node_attribute(bl_node, k, v)
332
+
333
+ for input_name, input_py in kwargs.items():
334
+ input_result = input_results[input_name]
335
+ if input_result is None:
336
+ continue
337
+ to_socket = get_input_socket_to_connect_to(
338
+ bl_node_tree, bl_node, input_name, input_result
339
+ )
340
+ if to_socket.is_multi_input and isinstance(input_py, list):
341
+ assert isinstance(input_result, list), (input_result, to_socket.node.name)
342
+ connect_multisocket_input(bl_node_tree, to_socket, input_result, input_py)
343
+ else:
344
+ assert not isinstance(input_result, list), (
345
+ input_result,
346
+ to_socket.node.name,
347
+ )
348
+ connect_single_input(bl_node_tree, to_socket, input_result)
349
+
350
+ return bl_node
351
+
352
+
353
+ def instantiate_nodegroup(
354
+ node_tree: bpy.types.NodeTree,
355
+ nodegroup: bpy.types.NodeGroup,
356
+ ) -> bpy.types.Node:
357
+ nodegroup_instance_type = bni.NODEGROUPTYPE_TO_INSTANCE_NODE[node_tree.bl_idname]
358
+ bpy_nodegroup_call = node_tree.nodes.new(nodegroup_instance_type)
359
+ bpy_nodegroup_call.node_tree = nodegroup
360
+ return bpy_nodegroup_call
361
+
362
+
363
+ def _construct_subgraph_call(
364
+ node: cg.SubgraphCallNode,
365
+ bl_node_tree: bpy.types.NodeTree,
366
+ input_results: dict[str | int, Any],
367
+ ) -> bpy.types.Node | bpy.types.NodeSocket | bpy.types.Material:
368
+ node_group_type = bni.NodeGroupType(bl_node_tree.bl_idname)
369
+ nodegroup = as_nodegroup(node.subgraph, node_group_type)
370
+ bpy_nodegroup_call = instantiate_nodegroup(bl_node_tree, nodegroup)
371
+
372
+ for input_name, input_py in node.kwargs.items():
373
+ input_val = input_results[input_name]
374
+ if input_val is None:
375
+ continue
376
+ to_socket = bpy_nodegroup_call.inputs[input_name]
377
+ connect_single_input(
378
+ node_tree=bl_node_tree,
379
+ to_socket=to_socket,
380
+ input_val=input_val,
381
+ )
382
+
383
+ return bpy_nodegroup_call
384
+
385
+
386
+ def _dispatch_construct_procnode_by_type(
387
+ node: cg.Node,
388
+ bl_node_tree: bpy.types.NodeTree,
389
+ cache: dict[int, bpy.types.Node],
390
+ input_results: dict[str | int, Any],
391
+ ):
392
+ if isinstance(node, cg.GetAttributeNode):
393
+ base_node = construct_procnode_to_bpy(node.source, bl_node_tree, cache)
394
+ return get_nth_socket(base_node.outputs, node.attribute_name, 0, base_node.type)
395
+
396
+ match node:
397
+ case cg.GetAttributeNode(args=(source,), attribute_name=socket_name):
398
+ base_node = construct_procnode_to_bpy(source, bl_node_tree, cache)
399
+ return get_nth_socket(base_node.outputs, socket_name, 0, base_node.type)
400
+ case cg.ProceduralNode():
401
+ return _construct_procnode_standard(node, bl_node_tree, input_results)
402
+ case cg.FunctionCallNode():
403
+ return _construct_operator_call(node, bl_node_tree, input_results)
404
+ case cg.SubgraphCallNode():
405
+ return _construct_subgraph_call(node, bl_node_tree, input_results)
406
+ case cg.InputPlaceholderNode():
407
+ input_node = next(
408
+ n for n in bl_node_tree.nodes if n.bl_idname == "NodeGroupInput"
409
+ )
410
+ assert input_node is not None, bl_node_tree.nodes.keys()
411
+ return input_node.outputs[node.input_name]
412
+ case _:
413
+ raise ValueError(f"Got misconfigured {node=}")
414
+
415
+
416
+ def _construct_inputs(
417
+ node: cg.Node,
418
+ bl_node_tree: bpy.types.NodeTree,
419
+ cache: dict[int, bpy.types.Node] | None = None,
420
+ ) -> dict[int | str, bpy.types.NodeSocket | bpy.types.NodeInternal]:
421
+ assert isinstance(node, cg.Node), node
422
+
423
+ input_results: dict[str | int, Any] = {}
424
+ for k, input_val in node.kwargs.items():
425
+ input_results[k] = construct_input(input_val, bl_node_tree, cache) # noqa: F821
426
+ for i, input_val in enumerate(node.args):
427
+ input_results[i] = construct_input(input_val, bl_node_tree, cache) # noqa: F821
428
+
429
+ # any raw Node types indicate the user declined to specify a specific socket,
430
+ # so we will use the primary/first socket on that node
431
+
432
+ return input_results
433
+
434
+
435
+ def construct_procnode_to_bpy(
436
+ node: cg.Node,
437
+ bl_node_tree: bpy.types.NodeTree,
438
+ cache: dict[int, bpy.types.Node] | None = None,
439
+ ) -> bpy.types.NodeSocket | bpy.types.NodeInternal:
440
+ assert node is not None
441
+ assert isinstance(bl_node_tree, bpy.types.NodeTree), bl_node_tree
442
+
443
+ if cache is None:
444
+ cache = {}
445
+ elif cached := cache.get(id(node), None):
446
+ return cached
447
+
448
+ if isinstance(node, cg.GetAttributeNode):
449
+ assert len(node.args) == 1, node.args
450
+ assert len(node.kwargs) == 0, node.kwargs
451
+ base_node = construct_procnode_to_bpy(node.args[0], bl_node_tree, cache)
452
+ assert isinstance(base_node, bpy.types.Node), base_node
453
+ return get_nth_socket(base_node.outputs, node.attribute_name, 0, base_node.type)
454
+
455
+ def _construct_leaf(input_val: cg.Node | Any | None):
456
+ if not isinstance(input_val, cg.Node):
457
+ return input_val
458
+ bpy_res = construct_procnode_to_bpy(input_val, bl_node_tree, cache)
459
+ if isinstance(bpy_res, bpy.types.NodeInternal):
460
+ # nodes that need non-socket input (GetAttribute) wont reach here, so we can return the primary socket
461
+ return _get_primary_output_socket(input_val, bpy_res)
462
+ return bpy_res
463
+
464
+ # resolve pt.Material before PyTree traversal, since Material is a registered
465
+ # PyTree container and would be destructured into shader ProcNodes otherwise
466
+ kwargs_for_tree = {
467
+ k: v.item() if isinstance(v, pt.Material) else v for k, v in node.kwargs.items()
468
+ }
469
+
470
+ # construct any inputs inside lists/dicts e.g. for join_geometry
471
+ input_sockets: dict = pytree.PyTree(kwargs_for_tree).map(_construct_leaf).obj()
472
+ input_sockets.update(
473
+ {
474
+ i: v
475
+ for i, v in enumerate(pytree.PyTree(node.args).map(_construct_leaf).obj())
476
+ }
477
+ )
478
+
479
+ with add_exception_context_msg(
480
+ f"While instantiating {nt.node_definition_context_message(node)}{node=}:\n"
481
+ ):
482
+ result = _dispatch_construct_procnode_by_type(
483
+ node, bl_node_tree, cache, input_sockets
484
+ )
485
+
486
+ assert isinstance(result, (bpy.types.Node, bpy.types.NodeSocket)), result
487
+
488
+ logger.debug(
489
+ f"{construct_procnode_to_bpy.__name__} for {node=} produced {type(result)=}"
490
+ )
491
+ cache[id(node)] = result
492
+ return result
493
+
494
+
495
+ def _construct_nodegroup(
496
+ graph: cg.ComputeGraph,
497
+ node_tree_type: bni.NodeGroupType,
498
+ ) -> bpy.types.NodeGroup:
499
+ name = (
500
+ graph.name
501
+ if graph.name is not None
502
+ else bpy_nocollide_data_name(graph, bpy.data.node_groups)
503
+ )
504
+
505
+ nodegroup = bpy.data.node_groups.new(name, node_tree_type.value)
506
+
507
+ for k in nodegroup.interface.items_tree.keys():
508
+ nodegroup.interface.items_tree.remove(k)
509
+ for k in nodegroup.nodes:
510
+ nodegroup.nodes.remove(k)
511
+
512
+ input_node = nodegroup.nodes.new("NodeGroupInput")
513
+ output_node = nodegroup.nodes.new("NodeGroupOutput")
514
+
515
+ cache = {}
516
+
517
+ for k, v in graph.inputs.items():
518
+ v_type = v.metadata.get("known_value_type", None)
519
+ if v_type is None:
520
+ raise ValueError(f"Got {v=} with no known value type")
521
+ socket_type = bni.PYTHON_TYPE_TO_SOCKET_TYPE[v_type]
522
+ nodegroup.interface.new_socket(
523
+ name=k,
524
+ in_out="INPUT",
525
+ socket_type=socket_type.value,
526
+ )
527
+ cache[id(v)] = input_node.outputs[k]
528
+
529
+ for k, v in graph.outputs.items(nocontainer_name="result"):
530
+ if v is None:
531
+ continue
532
+
533
+ res = construct_procnode_to_bpy(v, nodegroup, cache)
534
+
535
+ if isinstance(res, bpy.types.Node):
536
+ res = _get_primary_output_socket(v, res)
537
+ assert isinstance(res, bpy.types.NodeSocket), res
538
+
539
+ nodegroup.interface.new_socket(
540
+ name=k,
541
+ in_out="OUTPUT",
542
+ socket_type=res.bl_idname,
543
+ )
544
+
545
+ to_socket = output_node.inputs[k]
546
+ connect_single_input(nodegroup, to_socket, res)
547
+
548
+ return nodegroup
549
+
550
+
551
+ def as_nodegroup(
552
+ graph: cg.ComputeGraph,
553
+ node_tree_type: bni.NodeGroupType,
554
+ ) -> bpy.types.NodeGroup:
555
+ ops = graph.metadata.get("operations", [])
556
+ use_cache = len(ops) > 0 and ops[0][0].__name__ == "node_function"
557
+
558
+ if use_cache:
559
+ cached = graph.metadata["bpy_cached_impls"].get(node_tree_type, None)
560
+ if cached is not None:
561
+ return cached
562
+
563
+ with add_exception_context_msg(
564
+ prefix=f"While constructing nodegroup for {graph=} {node_tree_type=}:\n",
565
+ ):
566
+ nodegroup = _construct_nodegroup(graph, node_tree_type)
567
+
568
+ if use_cache:
569
+ graph.metadata["bpy_cached_impls"][node_tree_type] = nodegroup
570
+
571
+ return nodegroup