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.
- procfunc/__init__.py +87 -0
- procfunc/color.py +57 -0
- procfunc/compute_graph/__init__.py +28 -0
- procfunc/compute_graph/compute_graph.py +115 -0
- procfunc/compute_graph/node.py +200 -0
- procfunc/compute_graph/operators_info.py +92 -0
- procfunc/compute_graph/proxy.py +173 -0
- procfunc/compute_graph/util.py +282 -0
- procfunc/context.py +115 -0
- procfunc/control.py +174 -0
- procfunc/nodes/__init__.py +66 -0
- procfunc/nodes/bindings_util.py +196 -0
- procfunc/nodes/bpy_node_info.py +280 -0
- procfunc/nodes/compositor.py +2242 -0
- procfunc/nodes/execute/construct_nodes.py +571 -0
- procfunc/nodes/execute/construct_special_cases.py +246 -0
- procfunc/nodes/execute/execute.py +548 -0
- procfunc/nodes/execute/infer_runtime_data_type.py +195 -0
- procfunc/nodes/execute/util.py +247 -0
- procfunc/nodes/func.py +1417 -0
- procfunc/nodes/geo.py +4240 -0
- procfunc/nodes/manifest.json +8769 -0
- procfunc/nodes/math.py +644 -0
- procfunc/nodes/node_function.py +160 -0
- procfunc/nodes/shader.py +2359 -0
- procfunc/nodes/types.py +347 -0
- procfunc/ops/__init__.py +35 -0
- procfunc/ops/_util.py +275 -0
- procfunc/ops/addons.py +59 -0
- procfunc/ops/attr.py +426 -0
- procfunc/ops/collection.py +90 -0
- procfunc/ops/curve.py +18 -0
- procfunc/ops/file.py +126 -0
- procfunc/ops/manifest.json +39149 -0
- procfunc/ops/mesh.py +1510 -0
- procfunc/ops/modifier.py +603 -0
- procfunc/ops/object.py +258 -0
- procfunc/ops/primitives/__init__.py +31 -0
- procfunc/ops/primitives/camera.py +45 -0
- procfunc/ops/primitives/curve.py +71 -0
- procfunc/ops/primitives/light.py +114 -0
- procfunc/ops/primitives/mesh.py +358 -0
- procfunc/ops/uv.py +271 -0
- procfunc/random.py +247 -0
- procfunc/tracer/__init__.py +43 -0
- procfunc/tracer/decorator.py +121 -0
- procfunc/tracer/patch.py +494 -0
- procfunc/tracer/proxy.py +127 -0
- procfunc/tracer/trace.py +222 -0
- procfunc/transforms/__init__.py +49 -0
- procfunc/transforms/cleanup.py +214 -0
- procfunc/transforms/convert.py +20 -0
- procfunc/transforms/distribution.py +191 -0
- procfunc/transforms/extract_materials.py +116 -0
- procfunc/transforms/infer_distribution.py +326 -0
- procfunc/transforms/parameters.py +15 -0
- procfunc/transforms/util.py +35 -0
- procfunc/transpiler/__init__.py +24 -0
- procfunc/transpiler/bpy_to_computegraph.py +1348 -0
- procfunc/transpiler/codegen.py +919 -0
- procfunc/transpiler/identifiers.py +595 -0
- procfunc/transpiler/main.py +299 -0
- procfunc/types.py +380 -0
- procfunc/util/__init__.py +0 -0
- procfunc/util/bpy_info.py +145 -0
- procfunc/util/camera.py +0 -0
- procfunc/util/keyframe.py +70 -0
- procfunc/util/log.py +96 -0
- procfunc/util/manifest.py +121 -0
- procfunc/util/pytree.py +343 -0
- procfunc/util/teardown.py +37 -0
- procfunc-0.30.0.dist-info/METADATA +120 -0
- procfunc-0.30.0.dist-info/RECORD +76 -0
- procfunc-0.30.0.dist-info/WHEEL +5 -0
- procfunc-0.30.0.dist-info/licenses/LICENSE.md +11 -0
- 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
|