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,548 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections import OrderedDict
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import bpy
|
|
6
|
+
|
|
7
|
+
from procfunc import compute_graph as cg
|
|
8
|
+
from procfunc import context
|
|
9
|
+
from procfunc import types as pt
|
|
10
|
+
from procfunc.nodes import geo
|
|
11
|
+
from procfunc.nodes import types as nt
|
|
12
|
+
from procfunc.nodes.bpy_node_info import (
|
|
13
|
+
NodeGroupType,
|
|
14
|
+
SocketType,
|
|
15
|
+
)
|
|
16
|
+
from procfunc.nodes.geo import store_named_attribute
|
|
17
|
+
from procfunc.ops._util import modify
|
|
18
|
+
from procfunc.ops.object import mesh_to_curve as ops_mesh_to_curve
|
|
19
|
+
from procfunc.ops.primitives.mesh import mesh_single_vertex
|
|
20
|
+
from procfunc.tracer import primitive as tracer_primitive
|
|
21
|
+
from procfunc.util import pytree
|
|
22
|
+
from procfunc.util.bpy_info import bpy_nocollide_data_name
|
|
23
|
+
|
|
24
|
+
from .construct_nodes import (
|
|
25
|
+
as_nodegroup,
|
|
26
|
+
construct_procnode_to_bpy,
|
|
27
|
+
instantiate_nodegroup,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _unwrap_and_drop(x: dict[str, nt.ProcNode | None]):
|
|
34
|
+
return {
|
|
35
|
+
k: v.item() if isinstance(v, nt.ProcNode) else v
|
|
36
|
+
for k, v in x.items()
|
|
37
|
+
if v is not None
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_interface_by_name(
|
|
42
|
+
nodegroup: pt.NodeGroup, socket_name: str, in_out: str
|
|
43
|
+
) -> bpy.types.NodeSocket:
|
|
44
|
+
return next(
|
|
45
|
+
(
|
|
46
|
+
item
|
|
47
|
+
for item in nodegroup.interface.items_tree.values()
|
|
48
|
+
if item.name == socket_name and item.in_out == in_out
|
|
49
|
+
),
|
|
50
|
+
None,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_interface_by_type(
|
|
55
|
+
nodegroup: pt.NodeGroup, socket_type: SocketType, in_out: str
|
|
56
|
+
) -> bpy.types.NodeSocket:
|
|
57
|
+
return next(
|
|
58
|
+
(
|
|
59
|
+
item
|
|
60
|
+
for item in nodegroup.interface.items_tree.values()
|
|
61
|
+
if item.socket_type == socket_type.value and item.in_out == in_out
|
|
62
|
+
),
|
|
63
|
+
None,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _nodegroup_to_output(
|
|
68
|
+
node_tree: bpy.types.NodeTree,
|
|
69
|
+
from_nodegroup: bpy.types.NodeGroup,
|
|
70
|
+
output_node_type: str,
|
|
71
|
+
output_keys: list[str],
|
|
72
|
+
):
|
|
73
|
+
bpy_nodegroup_call = instantiate_nodegroup(node_tree, from_nodegroup)
|
|
74
|
+
output_node = node_tree.nodes.new(output_node_type)
|
|
75
|
+
|
|
76
|
+
for key in output_keys:
|
|
77
|
+
if key.lower() not in bpy_nodegroup_call.outputs:
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
from_socket = bpy_nodegroup_call.outputs[key.lower()]
|
|
81
|
+
to_socket = output_node.inputs[key.capitalize()]
|
|
82
|
+
|
|
83
|
+
node_tree.links.new(from_socket, to_socket)
|
|
84
|
+
|
|
85
|
+
unused_keys = set(bpy_nodegroup_call.outputs.keys()) - {
|
|
86
|
+
k.lower() for k in output_keys
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if unused_keys:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"Shader had unused extra outputs {unused_keys} while connecting to {output_node_type}. "
|
|
92
|
+
f"output only makes use of {[x.lower() for x in output_keys]}"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return output_node
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _build_bpy_material(
|
|
99
|
+
surface: nt.ProcNode[nt.Shader] | None = None,
|
|
100
|
+
displacement: nt.ProcNode[pt.Vector] | None = None,
|
|
101
|
+
volume: nt.ProcNode[nt.Shader] | None = None,
|
|
102
|
+
) -> bpy.types.Material:
|
|
103
|
+
if all(x is None for x in [surface, displacement, volume]):
|
|
104
|
+
raise ValueError(
|
|
105
|
+
"at least one of surface, displacement, or volume must be provided"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
outputs = _unwrap_and_drop(
|
|
109
|
+
{"surface": surface, "displacement": displacement, "volume": volume}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
material = bpy.data.materials.new(
|
|
113
|
+
bpy_nocollide_data_name("material", bpy.data.materials)
|
|
114
|
+
)
|
|
115
|
+
material.use_nodes = True
|
|
116
|
+
mnt = material.node_tree
|
|
117
|
+
mnt.nodes.clear()
|
|
118
|
+
|
|
119
|
+
outputs = pytree.PyTree(outputs)
|
|
120
|
+
graph = cg.ComputeGraph(
|
|
121
|
+
inputs=pytree.PyTree({}),
|
|
122
|
+
outputs=outputs,
|
|
123
|
+
name="to_material",
|
|
124
|
+
metadata={},
|
|
125
|
+
)
|
|
126
|
+
body = as_nodegroup(graph, NodeGroupType.SHADER)
|
|
127
|
+
_nodegroup_to_output(mnt, body, "ShaderNodeOutputMaterial", list(outputs.names()))
|
|
128
|
+
|
|
129
|
+
return material
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@tracer_primitive
|
|
133
|
+
def to_environment(
|
|
134
|
+
surface: nt.ProcNode[nt.Shader] | None = None,
|
|
135
|
+
volume: nt.ProcNode[nt.Shader] | None = None,
|
|
136
|
+
) -> pt.World:
|
|
137
|
+
# Get or create world
|
|
138
|
+
if bpy.context.scene.world is None:
|
|
139
|
+
world = bpy.data.worlds.new("World")
|
|
140
|
+
bpy.context.scene.world = world
|
|
141
|
+
else:
|
|
142
|
+
world = bpy.context.scene.world
|
|
143
|
+
|
|
144
|
+
# Enable use of nodes for the world
|
|
145
|
+
world.use_nodes = True
|
|
146
|
+
world.node_tree.nodes.clear()
|
|
147
|
+
|
|
148
|
+
outputs = _unwrap_and_drop({"surface": surface, "volume": volume})
|
|
149
|
+
|
|
150
|
+
graph = cg.ComputeGraph(
|
|
151
|
+
inputs=pytree.PyTree({}),
|
|
152
|
+
outputs=pytree.PyTree(outputs),
|
|
153
|
+
name="to_environment",
|
|
154
|
+
metadata={},
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
body = as_nodegroup(graph, NodeGroupType.SHADER)
|
|
158
|
+
|
|
159
|
+
_nodegroup_to_output(
|
|
160
|
+
world.node_tree, body, "ShaderNodeOutputWorld", list(outputs.keys())
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return pt.World(world)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@tracer_primitive
|
|
167
|
+
def to_light(
|
|
168
|
+
light: pt.LightObject,
|
|
169
|
+
surface: nt.ProcNode[nt.Shader],
|
|
170
|
+
) -> pt.LightObject:
|
|
171
|
+
"""Apply a shader node graph to a light's internal node tree."""
|
|
172
|
+
lamp_data = light.item().data
|
|
173
|
+
lamp_data.use_nodes = True
|
|
174
|
+
lamp_data.node_tree.nodes.clear()
|
|
175
|
+
|
|
176
|
+
outputs = _unwrap_and_drop({"surface": surface})
|
|
177
|
+
|
|
178
|
+
graph = cg.ComputeGraph(
|
|
179
|
+
inputs=pytree.PyTree({}),
|
|
180
|
+
outputs=pytree.PyTree(outputs),
|
|
181
|
+
name="to_light",
|
|
182
|
+
metadata={},
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
body = as_nodegroup(graph, NodeGroupType.SHADER)
|
|
186
|
+
|
|
187
|
+
_nodegroup_to_output(
|
|
188
|
+
lamp_data.node_tree, body, "ShaderNodeOutputLight", list(outputs.keys())
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return light
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@tracer_primitive
|
|
195
|
+
def to_compositor(
|
|
196
|
+
results: dict[str, nt.ProcNode],
|
|
197
|
+
):
|
|
198
|
+
bpy.context.scene.use_nodes = True
|
|
199
|
+
nt = bpy.context.scene.node_tree
|
|
200
|
+
nt.nodes.clear()
|
|
201
|
+
|
|
202
|
+
cache = {}
|
|
203
|
+
for k, v in results.items():
|
|
204
|
+
construct_procnode_to_bpy(v.item(), nt, cache)
|
|
205
|
+
|
|
206
|
+
return bpy.context.scene
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _extract_geometry_singlekey(
|
|
210
|
+
nodegroup: pt.NodeGroup,
|
|
211
|
+
output_key: str,
|
|
212
|
+
attribute_keys: list[str] | None = None,
|
|
213
|
+
is_curve: bool = False,
|
|
214
|
+
realize: bool = False,
|
|
215
|
+
_skip_apply: bool = False,
|
|
216
|
+
) -> pt.MeshObject | pt.CurveObject:
|
|
217
|
+
"""
|
|
218
|
+
Extract the object corresponding to the 'output_key' output socket of 'nodegroup'
|
|
219
|
+
|
|
220
|
+
NOTE: we do this by executing the whole nodegroup with just that output socket connected, which may be inefficient.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
if attribute_keys is None:
|
|
224
|
+
attribute_keys = []
|
|
225
|
+
|
|
226
|
+
if get_interface_by_name(nodegroup, output_key, "OUTPUT") is None:
|
|
227
|
+
raise ValueError(
|
|
228
|
+
f"Node group {nodegroup.name} has no output {output_key}, available are {nodegroup.interface.items_tree.keys()}"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
outer_nodegroup = bpy.data.node_groups.new("wrapper", "GeometryNodeTree")
|
|
232
|
+
outer_nodegroup_output_node = outer_nodegroup.nodes.new("NodeGroupOutput")
|
|
233
|
+
nodegroup_instance = instantiate_nodegroup(outer_nodegroup, nodegroup)
|
|
234
|
+
|
|
235
|
+
if _skip_apply:
|
|
236
|
+
nodegroup.use_fake_user = True
|
|
237
|
+
outer_nodegroup.use_fake_user = True
|
|
238
|
+
|
|
239
|
+
outer_nodegroup.interface.new_socket(
|
|
240
|
+
name="Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
for attr_key in attribute_keys:
|
|
244
|
+
if attr_key not in nodegroup.interface.items_tree:
|
|
245
|
+
raise ValueError(
|
|
246
|
+
f"Node group {nodegroup.name} has no output {attr_key=}, available are {nodegroup.interface.items_tree.keys()}"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
if attr_key not in nodegroup_instance.outputs:
|
|
250
|
+
raise ValueError(
|
|
251
|
+
f"Node group {nodegroup.name} has no output {attr_key=}, available are {nodegroup.interface.items_tree.keys()}"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
item = nodegroup.interface.items_tree.get(attr_key)
|
|
255
|
+
if item is None or item.in_out != "OUTPUT":
|
|
256
|
+
raise ValueError(
|
|
257
|
+
f"Attempted to output {attr_key=} from {nodegroup.interface.items_tree.keys()=}, "
|
|
258
|
+
f"but it wasnt an output! {item is None or item.in_out=}"
|
|
259
|
+
)
|
|
260
|
+
outer_nodegroup.interface.new_socket(
|
|
261
|
+
name=attr_key, in_out="OUTPUT", socket_type=item.socket_type
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if attr_key not in outer_nodegroup_output_node.inputs:
|
|
265
|
+
raise ValueError(
|
|
266
|
+
f"Node group {nodegroup.name} has no output {attr_key=}, available are {outer_nodegroup_output_node.inputs.keys()}"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
outer_nodegroup.links.new(
|
|
270
|
+
nodegroup_instance.outputs[attr_key],
|
|
271
|
+
outer_nodegroup_output_node.inputs[attr_key],
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if is_curve:
|
|
275
|
+
curve_to_mesh = outer_nodegroup.nodes.new("GeometryNodeCurveToMesh")
|
|
276
|
+
outer_nodegroup.links.new(
|
|
277
|
+
nodegroup_instance.outputs[output_key], curve_to_mesh.inputs["Curve"]
|
|
278
|
+
)
|
|
279
|
+
outer_nodegroup.links.new(
|
|
280
|
+
curve_to_mesh.outputs["Mesh"],
|
|
281
|
+
outer_nodegroup_output_node.inputs["Geometry"],
|
|
282
|
+
)
|
|
283
|
+
else:
|
|
284
|
+
geo_output = nodegroup_instance.outputs[output_key]
|
|
285
|
+
if realize:
|
|
286
|
+
realize_node = outer_nodegroup.nodes.new("GeometryNodeRealizeInstances")
|
|
287
|
+
outer_nodegroup.links.new(geo_output, realize_node.inputs["Geometry"])
|
|
288
|
+
geo_output = realize_node.outputs["Geometry"]
|
|
289
|
+
outer_nodegroup.links.new(
|
|
290
|
+
geo_output,
|
|
291
|
+
outer_nodegroup_output_node.inputs["Geometry"],
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
res = modify(
|
|
295
|
+
mesh_single_vertex(),
|
|
296
|
+
"NODES",
|
|
297
|
+
node_group=outer_nodegroup,
|
|
298
|
+
_skip_apply=_skip_apply,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
if _skip_apply:
|
|
302
|
+
return res
|
|
303
|
+
|
|
304
|
+
for attr_key in attribute_keys:
|
|
305
|
+
if attr_key not in res.item().data.attributes:
|
|
306
|
+
raise ValueError(
|
|
307
|
+
f"Expected {nodegroup.name} to output {attr_key=} but for {res} but it was not found. "
|
|
308
|
+
f"available are {res.item().data.attributes.keys()}"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
return res
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@tracer_primitive
|
|
315
|
+
def to_mesh_object(
|
|
316
|
+
geometry: nt.ProcNode[pt.MeshObject],
|
|
317
|
+
_skip_apply: bool = False,
|
|
318
|
+
) -> pt.MeshObject:
|
|
319
|
+
graph = cg.ComputeGraph(
|
|
320
|
+
inputs=pytree.PyTree({}),
|
|
321
|
+
outputs=pytree.PyTree({"geometry": geometry.item()}),
|
|
322
|
+
name="to_mesh_object",
|
|
323
|
+
metadata={},
|
|
324
|
+
)
|
|
325
|
+
ng = as_nodegroup(graph, NodeGroupType.GEOMETRY)
|
|
326
|
+
ng_inouts = ng.interface.items_tree
|
|
327
|
+
|
|
328
|
+
item = ng_inouts.get("geometry")
|
|
329
|
+
if item is None:
|
|
330
|
+
raise ValueError(
|
|
331
|
+
f"Node group {ng.name} has no output geometry, available are {ng_inouts.keys()}"
|
|
332
|
+
)
|
|
333
|
+
if item.in_out != "OUTPUT":
|
|
334
|
+
raise ValueError(
|
|
335
|
+
f"Node group attempted to extract geometry but it wasnt an output! {item.in_out=}"
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
try:
|
|
339
|
+
obj_result = _extract_geometry_singlekey(
|
|
340
|
+
ng,
|
|
341
|
+
"geometry",
|
|
342
|
+
attribute_keys=[],
|
|
343
|
+
realize=True,
|
|
344
|
+
_skip_apply=_skip_apply,
|
|
345
|
+
)
|
|
346
|
+
except RuntimeError as e:
|
|
347
|
+
if "does not contain a mesh" not in str(e):
|
|
348
|
+
raise
|
|
349
|
+
mode = context.globals.warn_mode_empty_geonodes
|
|
350
|
+
if mode == "throw":
|
|
351
|
+
raise
|
|
352
|
+
if mode == "warn":
|
|
353
|
+
logger.warning(
|
|
354
|
+
f"to_mesh_object produced no mesh geometry, returning empty mesh: {e}"
|
|
355
|
+
)
|
|
356
|
+
return mesh_single_vertex()
|
|
357
|
+
|
|
358
|
+
assert isinstance(obj_result, pt.MeshObject)
|
|
359
|
+
|
|
360
|
+
return obj_result
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@tracer_primitive
|
|
364
|
+
def to_mesh_object_with_attributes(
|
|
365
|
+
geometry: nt.ProcNode[pt.MeshObject],
|
|
366
|
+
attributes: dict[str, nt.ProcNode[nt.AnyDataVal]] | None = None,
|
|
367
|
+
) -> tuple[pt.MeshObject, dict[str, Any]]:
|
|
368
|
+
for k, v in attributes.items():
|
|
369
|
+
geometry = store_named_attribute(geometry, name=k, value=v)
|
|
370
|
+
|
|
371
|
+
obj = to_mesh_object(geometry)
|
|
372
|
+
|
|
373
|
+
if attributes is None:
|
|
374
|
+
return obj, {}
|
|
375
|
+
|
|
376
|
+
attr_dict = {k: obj.item().data.attributes[k] for k in attributes.keys()}
|
|
377
|
+
return obj, attr_dict
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@tracer_primitive
|
|
381
|
+
def to_curve_object(
|
|
382
|
+
geometry: nt.ProcNode[pt.CurveObject],
|
|
383
|
+
) -> pt.CurveObject:
|
|
384
|
+
"""
|
|
385
|
+
WARNING: currently discards any bezier config or knots, only extracts the points.Any
|
|
386
|
+
|
|
387
|
+
TODO: Need a better geonodes -> curve op from blender, or need to engineer this in via attribute extraction.
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
obj = to_mesh_object(geo.curve_to_mesh(geometry, profile_curve=None))
|
|
391
|
+
return ops_mesh_to_curve(obj)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@tracer_primitive
|
|
395
|
+
def to_objects_multi(
|
|
396
|
+
geometries: dict[str, nt.ProcNode[pt.MeshObject]],
|
|
397
|
+
attributes: dict[str, dict[str, nt.ProcNode[nt.AnyDataVal]]] | None = None,
|
|
398
|
+
) -> OrderedDict[str, pt.MeshObject]:
|
|
399
|
+
"""
|
|
400
|
+
Convert a nodegroup which has multiple output geometries into multiple realized objects.
|
|
401
|
+
|
|
402
|
+
If the objects should have any attributes, provide node definitions for them in the 'attributes' argument.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
geometries: named output geometry nodes to be converted into objects
|
|
406
|
+
attributes: named data attributes which should be annotated on those objects. keys must be a subset of the keys of 'geometries'
|
|
407
|
+
input_obj: optional input object to use for the geometry nodegroups
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
- result_objects: dict of {output_key: object}. output keys are the same as the keys of 'geometries'
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
if attributes is None:
|
|
414
|
+
attributes = {}
|
|
415
|
+
|
|
416
|
+
extra_attr_keys = set(attributes.keys()) - set(geometries.keys())
|
|
417
|
+
if extra_attr_keys:
|
|
418
|
+
raise ValueError(
|
|
419
|
+
f"{to_objects_multi.__name__} got {attributes.keys()=} but these are not a subset of {geometries.keys()=} due to {extra_attr_keys=}"
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
attr_keys_dedup = {
|
|
423
|
+
kobj: {f"{kobj}_{kattr}": v for kattr, v in attrs.items()}
|
|
424
|
+
for kobj, attrs in attributes.items()
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
all_ng_outputs: dict[str, nt.ProcNode] = geometries.copy()
|
|
428
|
+
for kobj, attrs in attr_keys_dedup.items():
|
|
429
|
+
all_ng_outputs.update(attrs)
|
|
430
|
+
|
|
431
|
+
all_ng_outputs = _unwrap_and_drop(all_ng_outputs) # type: ignore
|
|
432
|
+
graph = cg.ComputeGraph(
|
|
433
|
+
inputs=pytree.PyTree({}),
|
|
434
|
+
outputs=pytree.PyTree(all_ng_outputs),
|
|
435
|
+
name="to_objects_multi",
|
|
436
|
+
metadata={},
|
|
437
|
+
)
|
|
438
|
+
nodegroup = as_nodegroup(
|
|
439
|
+
graph,
|
|
440
|
+
NodeGroupType.GEOMETRY,
|
|
441
|
+
)
|
|
442
|
+
ng_inouts = nodegroup.interface.items_tree
|
|
443
|
+
|
|
444
|
+
result_objects = OrderedDict()
|
|
445
|
+
|
|
446
|
+
for k, v in geometries.items():
|
|
447
|
+
item = ng_inouts.get(k)
|
|
448
|
+
if item is None:
|
|
449
|
+
raise ValueError(
|
|
450
|
+
f"Node group {nodegroup.name} has no output {k}, available are {ng_inouts.keys()}"
|
|
451
|
+
)
|
|
452
|
+
if item.in_out != "OUTPUT":
|
|
453
|
+
raise ValueError(
|
|
454
|
+
f"Node group attempted to extract {k=} but it wasnt an output! {item.in_out=}"
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
result_objects[k] = _extract_geometry_singlekey(
|
|
458
|
+
nodegroup,
|
|
459
|
+
k,
|
|
460
|
+
attribute_keys=attr_keys_dedup.get(k, {}).keys(),
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
return result_objects
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
@tracer_primitive
|
|
467
|
+
def to_aliases(
|
|
468
|
+
geometry: nt.ProcNode[pt.MeshObject],
|
|
469
|
+
) -> list[pt.MeshObject]:
|
|
470
|
+
"""
|
|
471
|
+
Convert instanced geometry into aliases - separate objects sharing the same mesh data.
|
|
472
|
+
|
|
473
|
+
Uses depsgraph to extract instance transforms and creates one bpy.data.object per instance.
|
|
474
|
+
All aliases point directly to the original mesh data from the scene - no copying occurs.
|
|
475
|
+
Each alias has its own transform (position, rotation, scale) but shares mesh data.
|
|
476
|
+
|
|
477
|
+
Note: Requires geometry nodes that use actual bpy.data.collections with real objects
|
|
478
|
+
(via collection_info node), not just joined geometry. The instances must be visible
|
|
479
|
+
in the viewport (show_viewport=True) to be detected by the depsgraph.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
geometry: node producing instanced geometry (e.g., from instance_on_points)
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
list of MeshObject, one per instance, with each instance pointing to the original
|
|
486
|
+
mesh data from the scene
|
|
487
|
+
"""
|
|
488
|
+
|
|
489
|
+
graph = cg.ComputeGraph(
|
|
490
|
+
inputs=pytree.PyTree({}),
|
|
491
|
+
outputs=pytree.PyTree({"geometry": geometry.item()}),
|
|
492
|
+
name="to_aliases",
|
|
493
|
+
metadata={},
|
|
494
|
+
)
|
|
495
|
+
nodegroup = as_nodegroup(graph, NodeGroupType.GEOMETRY)
|
|
496
|
+
|
|
497
|
+
obj_with_modifier = modify(
|
|
498
|
+
mesh_single_vertex(), "NODES", node_group=nodegroup, _skip_apply=True
|
|
499
|
+
)
|
|
500
|
+
temp_obj = obj_with_modifier.item()
|
|
501
|
+
|
|
502
|
+
for mod in temp_obj.modifiers:
|
|
503
|
+
mod.show_viewport = True
|
|
504
|
+
|
|
505
|
+
bpy.context.view_layer.update()
|
|
506
|
+
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|
507
|
+
|
|
508
|
+
eval_mesh_to_instances = {}
|
|
509
|
+
eval_mesh_to_copy = {}
|
|
510
|
+
|
|
511
|
+
for deps_instance in depsgraph.object_instances:
|
|
512
|
+
if not deps_instance.is_instance:
|
|
513
|
+
continue
|
|
514
|
+
|
|
515
|
+
obj = deps_instance.object
|
|
516
|
+
if obj.type != "MESH":
|
|
517
|
+
continue
|
|
518
|
+
|
|
519
|
+
if deps_instance.parent is None or deps_instance.parent.original != temp_obj:
|
|
520
|
+
continue
|
|
521
|
+
|
|
522
|
+
eval_mesh = obj.data
|
|
523
|
+
if eval_mesh is None:
|
|
524
|
+
continue
|
|
525
|
+
|
|
526
|
+
mesh_id = eval_mesh.as_pointer()
|
|
527
|
+
|
|
528
|
+
if mesh_id not in eval_mesh_to_instances:
|
|
529
|
+
eval_mesh_to_instances[mesh_id] = []
|
|
530
|
+
eval_mesh_to_copy[mesh_id] = eval_mesh.copy()
|
|
531
|
+
|
|
532
|
+
eval_mesh_to_instances[mesh_id].append(deps_instance.matrix_world.copy())
|
|
533
|
+
|
|
534
|
+
bpy.data.objects.remove(temp_obj, do_unlink=True)
|
|
535
|
+
|
|
536
|
+
result_objects = []
|
|
537
|
+
|
|
538
|
+
for mesh_id, matrices in eval_mesh_to_instances.items():
|
|
539
|
+
mesh_data = eval_mesh_to_copy[mesh_id]
|
|
540
|
+
for matrix in matrices:
|
|
541
|
+
alias_obj = bpy.data.objects.new(
|
|
542
|
+
bpy_nocollide_data_name("alias", bpy.data.objects), mesh_data
|
|
543
|
+
)
|
|
544
|
+
alias_obj.matrix_world = matrix
|
|
545
|
+
bpy.context.collection.objects.link(alias_obj)
|
|
546
|
+
result_objects.append(pt.MeshObject(alias_obj))
|
|
547
|
+
|
|
548
|
+
return result_objects
|