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,116 @@
1
+ """
2
+ Transform to extract material SubgraphCallNodes from geometry node functions,
3
+ making them pure by moving material calls to the caller.
4
+ """
5
+
6
+ import logging
7
+
8
+ import procfunc as pf
9
+ from procfunc import compute_graph as cg
10
+ from procfunc.nodes import types as nt
11
+ from procfunc.util import pytree
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def _is_material_subgraph(subgraph: cg.ComputeGraph) -> bool:
17
+ return subgraph.outputs.toplevel_type() is pf.Material
18
+
19
+
20
+ def _sanitize_name(name: str) -> str:
21
+ return f"material_{name.replace('.', '_').replace(' ', '_').lower()}"
22
+
23
+
24
+ def _add_input(graph: cg.ComputeGraph, name: str) -> cg.InputPlaceholderNode:
25
+ inp = cg.InputPlaceholderNode(
26
+ name=name,
27
+ default_value=None,
28
+ metadata={"known_value_type": nt.ProcNode[pf.Material], "varname": name},
29
+ )
30
+ inputs = graph.inputs.obj()
31
+ inputs[name] = inp
32
+ graph.inputs = pytree.PyTree(inputs)
33
+ return inp
34
+
35
+
36
+ def _build_parent_map(
37
+ top_graph: cg.ComputeGraph,
38
+ ) -> dict[int, tuple[cg.ComputeGraph, cg.SubgraphCallNode]]:
39
+ parent_map = {}
40
+ for call_node, graph in cg.traverse_nested_graphs(top_graph, yield_call_nodes=True):
41
+ for node in cg.traverse_depth_first(graph):
42
+ if isinstance(node, cg.SubgraphCallNode):
43
+ parent_map[id(node.subgraph)] = (graph, node)
44
+ return parent_map
45
+
46
+
47
+ def _replace_node_in_graph(
48
+ graph: cg.ComputeGraph,
49
+ old_node: cg.Node,
50
+ new_node: cg.Node,
51
+ ) -> None:
52
+ for node in cg.traverse_depth_first(graph):
53
+ new_args = tuple(new_node if arg is old_node else arg for arg in node.args)
54
+ if new_args != node.args:
55
+ node.args = new_args
56
+
57
+ for key, val in list(node.kwargs.items()):
58
+ if val is old_node:
59
+ node.kwargs[key] = new_node
60
+
61
+
62
+ def extract_materials_from_graph(
63
+ top_graph: cg.ComputeGraph,
64
+ ) -> dict[str, cg.SubgraphCallNode]:
65
+ parent_map = _build_parent_map(top_graph)
66
+ extracted_materials = {}
67
+
68
+ for graph in cg.traverse_nested_graphs(top_graph):
69
+ if not graph.metadata.get("is_node_function", False):
70
+ continue
71
+
72
+ material_calls = []
73
+ for node in cg.traverse_depth_first(graph):
74
+ if isinstance(node, cg.SubgraphCallNode) and _is_material_subgraph(
75
+ node.subgraph
76
+ ):
77
+ material_calls.append(node)
78
+
79
+ for mat_call in material_calls:
80
+ input_name = _sanitize_name(mat_call.subgraph.name)
81
+
82
+ inp = _add_input(graph, input_name)
83
+ _replace_node_in_graph(graph, mat_call, inp)
84
+
85
+ current_graph = graph
86
+ while id(current_graph) in parent_map:
87
+ parent_graph, call_node = parent_map[id(current_graph)]
88
+
89
+ if not parent_graph.metadata.get("is_node_function", False):
90
+ item_node = cg.MethodCallNode(mat_call, "item", args=(), kwargs={})
91
+ call_node.kwargs[input_name] = item_node
92
+ break
93
+
94
+ if input_name not in call_node.kwargs:
95
+ parent_inp = _add_input(parent_graph, input_name)
96
+ call_node.kwargs[input_name] = parent_inp
97
+ current_graph = parent_graph
98
+
99
+ extracted_materials[input_name] = mat_call
100
+ logger.debug(
101
+ f"Extracted material '{mat_call.subgraph.name}' from {graph.name}"
102
+ )
103
+
104
+ return extracted_materials
105
+
106
+
107
+ def extract_materials_from_graphs(
108
+ graphs: list[cg.ComputeGraph],
109
+ ) -> list[cg.ComputeGraph]:
110
+ for graph in graphs:
111
+ materials = extract_materials_from_graph(graph)
112
+ if materials:
113
+ logger.info(
114
+ f"Extracted materials from {graph.name}: {list(materials.keys())}"
115
+ )
116
+ return graphs
@@ -0,0 +1,326 @@
1
+ import logging
2
+ from collections import defaultdict
3
+ from typing import Any
4
+
5
+ import numpy as np
6
+
7
+ from procfunc import compute_graph as cg
8
+ from procfunc import types as t
9
+ from procfunc.color import hsv_color
10
+ from procfunc.random import randint, uniform
11
+ from procfunc.util import pytree
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class TODO:
17
+ """
18
+ An undefined type we leave in the graph so the codegen clearly marks that it should be
19
+ filled in by the user.
20
+ """
21
+
22
+ def __repr__(self):
23
+ return "TODO()"
24
+
25
+
26
+ def todo():
27
+ return cg.ConstantNode(value=TODO())
28
+
29
+
30
+ _REDUCE_TYPES = (
31
+ float,
32
+ int,
33
+ tuple,
34
+ t.Color,
35
+ np.ndarray,
36
+ )
37
+
38
+
39
+ def _minmax_arraylike(
40
+ values: list[float | int | tuple | np.ndarray | t.Color | t.Vector],
41
+ ) -> tuple[np.ndarray, np.ndarray]:
42
+ low = np.array(values[0])
43
+ high = np.array(values[0])
44
+ for v in values[1:]:
45
+ low = np.minimum(low, np.array(v))
46
+ high = np.maximum(high, np.array(v))
47
+ return low, high
48
+
49
+
50
+ def _infer_hypercube_differing(
51
+ rng_node: cg.Node, key: str, all_kwargs_k: list[Any]
52
+ ) -> Any | TODO:
53
+ # if logger.isEnabledFor(logging.DEBUG):
54
+ # logger.debug(f"{_infer_hypercube_differing.__name__} {key=} {all_kwargs_k=}")
55
+
56
+ if len(set(type(v) for v in all_kwargs_k)) != 1:
57
+ return todo()
58
+
59
+ if all(np.array(v == all_kwargs_k[0]).all() for v in all_kwargs_k):
60
+ return all_kwargs_k[0]
61
+
62
+ if not all(isinstance(v, _REDUCE_TYPES) for v in all_kwargs_k):
63
+ return todo()
64
+
65
+ low, high = _minmax_arraylike(all_kwargs_k)
66
+ res = cg.FunctionCallNode(uniform, args=(rng_node, low, high), kwargs={})
67
+ # res.metadata["prefer_inline"] = isinstance(low, (float, int)) # TODO currently ignored
68
+ return res
69
+
70
+
71
+ def infer_hypercube_differing_node(
72
+ rng_node: cg.Node,
73
+ nodes: list[cg.Node],
74
+ memo: dict[int, cg.Node] | None = None,
75
+ ) -> cg.Node | TODO | None:
76
+ if memo is None:
77
+ memo = {}
78
+
79
+ if all(node is None for node in nodes):
80
+ return None
81
+ elif any(node is None for node in nodes):
82
+ logger.debug(f"{infer_hypercube_differing_node=} exiting due to None")
83
+ return todo()
84
+
85
+ k = id(nodes[0])
86
+ if k in memo:
87
+ return memo[k]
88
+
89
+ # logger.debug(f"{infer_hypercube_differing_node=} {nodes[0]=}")
90
+
91
+ kinds = {type(node) for node in nodes}
92
+ if len(kinds) > 1:
93
+ logger.debug(
94
+ f"{infer_hypercube_differing_node=} exiting for mismatching {kinds=}"
95
+ )
96
+ return todo()
97
+
98
+ targets = {node.target for node in nodes}
99
+ if len(targets) > 1:
100
+ logger.debug(
101
+ f"{infer_hypercube_differing_node=} exiting for mismatching {targets=}"
102
+ )
103
+ return todo()
104
+
105
+ nargs = set(len(node.args) for node in nodes)
106
+ nkwargs = set(len(node.kwargs) for node in nodes)
107
+ if len(nargs) > 1 or len(nkwargs) > 1:
108
+ logger.debug(
109
+ f"{infer_hypercube_differing_node=} exiting for differing {nargs=} {nkwargs=}"
110
+ )
111
+ return todo()
112
+
113
+ def _infer_differing(key, argvals):
114
+ if all(isinstance(v, cg.Node) for v in argvals):
115
+ return infer_hypercube_differing_node(rng_node, argvals, memo)
116
+ elif all(v is None for v in argvals):
117
+ return None
118
+ else:
119
+ return _infer_hypercube_differing(rng_node, key, argvals)
120
+
121
+ args = [
122
+ _infer_differing(str(i), [nodes[j].args[i] for j in range(len(nodes))])
123
+ for i in range(len(nodes[0].args))
124
+ ]
125
+ kwargs = {
126
+ k: _infer_differing(k, [nodes[j].kwargs[k] for j in range(len(nodes))])
127
+ for k in nodes[0].kwargs.keys()
128
+ }
129
+
130
+ res = cg.Node(nodes[0].target, nodes[0].kind, tuple(args), kwargs)
131
+ memo[id(nodes[0])] = res
132
+
133
+ return res
134
+
135
+
136
+ def infer_distribution_hypercube(
137
+ graphs: list[cg.ComputeGraph],
138
+ memo: dict[int, cg.Node] | None = None,
139
+ ) -> list[cg.ComputeGraph]:
140
+ memo = {}
141
+
142
+ rng_node = cg.InputPlaceholderNode(
143
+ default_value=None,
144
+ metadata={
145
+ "varname": "rng",
146
+ "known_value_type": "pf.RNG", # TODO use actual type and resolve to string
147
+ },
148
+ )
149
+
150
+ if len(graphs) <= 1:
151
+ raise ValueError(
152
+ f"{infer_distribution_hypercube.__name__} expected at least 2 graphs, got {len(graphs)}"
153
+ )
154
+
155
+ all_outputs = [g.outputs.dict() for g in graphs]
156
+ outputs_mapped = {
157
+ k: infer_hypercube_differing_node(rng_node, [g[k] for g in all_outputs], memo)
158
+ for k in all_outputs[0].keys()
159
+ }
160
+
161
+ all_inputs = [g.inputs.obj() for g in graphs]
162
+ inputs_mapped = {k: memo.get(id(all_inputs[0][k])) for k in all_inputs[0].keys()}
163
+
164
+ n_inputs_missing = sum(1 for v in inputs_mapped.values() if v is None)
165
+ if n_inputs_missing > 0:
166
+ logger.warning(
167
+ f"{infer_distribution_hypercube=} {n_inputs_missing=} {inputs_mapped} {all_inputs[0]}"
168
+ )
169
+ inputs_mapped = {k: v for k, v in inputs_mapped.items() if v is not None}
170
+
171
+ # find a common prefix of the graph names
172
+ lens = [len(graph.name) for graph in graphs]
173
+ for i in range(min(lens), 0, -1):
174
+ new_names = {graph.name[:i] for graph in graphs}
175
+ if len(new_names) == 1:
176
+ break
177
+ prefix = graphs[0].name[:i].strip("_")
178
+
179
+ res = cg.ComputeGraph(
180
+ inputs=pytree.PyTree({**inputs_mapped, "rng": rng_node}),
181
+ outputs=pytree.PyTree(t.Material(**outputs_mapped)),
182
+ name=f"{prefix}_distribution",
183
+ metadata={"func": infer_distribution_hypercube},
184
+ )
185
+
186
+ if logger.isEnabledFor(logging.DEBUG):
187
+ n_inp_graph_nodes = len(list(cg.traverse_depth_first(graphs[0])))
188
+ n_out_graph_nodes = len(list(cg.traverse_depth_first(res)))
189
+ logger.debug(
190
+ f"{infer_distribution_hypercube=} transformed {n_inp_graph_nodes=} to {n_out_graph_nodes=}"
191
+ )
192
+
193
+ return res
194
+
195
+
196
+ def _reduce_const(value: Any) -> cg.Node:
197
+ if isinstance(value, np.ndarray) and len(value) == 1:
198
+ value = value[0]
199
+ return cg.ConstantNode(value=value)
200
+
201
+
202
+ def _infer_argument_distribution(
203
+ values: list[Any],
204
+ rng_node: cg.Node,
205
+ colors_to_hsv: bool = True,
206
+ use_randint: bool = False,
207
+ ) -> cg.Node:
208
+ if all(np.allclose(values[0], x) for x in values[1:]):
209
+ # argument had only one valude (usually the default_value)
210
+ return _reduce_const(values[0])
211
+ elif use_randint and all(
212
+ isinstance(x, (int, float)) and np.isclose(x, int(x)) for x in values
213
+ ):
214
+ # argument is integer range
215
+ low, high = _minmax_arraylike(values)
216
+ return cg.FunctionCallNode(
217
+ randint, args=(rng_node, int(low), int(high)), kwargs={}
218
+ )
219
+ elif colors_to_hsv and any(isinstance(kv, t.Color) for kv in values):
220
+ # treat color ranges as hsv ranges
221
+ assert all(isinstance(kv, t.Color) for kv in values), values
222
+ low, high = _minmax_arraylike([c.hsv for c in values])
223
+ hsv = cg.FunctionCallNode(uniform, args=(rng_node, low, high), kwargs={})
224
+ return cg.FunctionCallNode(hsv_color, args=(), kwargs={"hsv": hsv})
225
+ else:
226
+ # regular argument - usually float and numpy arrays
227
+ low, high = _minmax_arraylike(values)
228
+ return cg.FunctionCallNode(uniform, args=(rng_node, low, high), kwargs={})
229
+
230
+
231
+ def _infer_distribution_from_callnodes(
232
+ callnodes: list[cg.Node],
233
+ subgraph: cg.ComputeGraph,
234
+ supported_types: tuple[type],
235
+ colors_to_hsv: bool,
236
+ use_randint: bool,
237
+ ) -> cg.ComputeGraph:
238
+ new_inputs = {}
239
+ rng_node = cg.InputPlaceholderNode(
240
+ name="rng",
241
+ default_value=None,
242
+ metadata={
243
+ "known_value_type": "pf.RNG", # TODO use actual type and resolve to string
244
+ },
245
+ )
246
+ base_inputs = subgraph.inputs.dict()
247
+
248
+ for k in base_inputs.keys():
249
+ default_value = base_inputs[k].kwargs.get("default_value")
250
+ kwarg_values = [cn.kwargs.get(k, default_value) for cn in callnodes]
251
+ kwarg_values = [x for x in kwarg_values if x is not None]
252
+
253
+ if any(isinstance(v, cg.Node) for v in kwarg_values):
254
+ # argument had dynamic values connected up, make it a functionar gument
255
+ orig_input_type = base_inputs[k].metadata.get("known_value_type", None)
256
+ new_inputs[k] = cg.InputPlaceholderNode(
257
+ name=k,
258
+ default_value=None,
259
+ metadata={
260
+ "known_value_type": orig_input_type,
261
+ },
262
+ )
263
+ elif all(isinstance(v, supported_types) for v in kwarg_values):
264
+ new_inputs[k] = _infer_argument_distribution(
265
+ kwarg_values, rng_node, colors_to_hsv, use_randint=use_randint
266
+ )
267
+ else:
268
+ uniq_types = set(type(v) for v in kwarg_values)
269
+ logger.warning(
270
+ f"Could not infer distribution for {subgraph.name=} {k=} {uniq_types=}"
271
+ )
272
+ new_inputs[k] = todo()
273
+
274
+ result_call = cg.SubgraphCallNode(subgraph=subgraph, args=(), kwargs=new_inputs)
275
+
276
+ placeholders = {"rng": rng_node}
277
+ placeholders.update(
278
+ {k: v for k, v in new_inputs.items() if isinstance(v, cg.InputPlaceholderNode)}
279
+ )
280
+
281
+ if len(placeholders) == len(new_inputs):
282
+ return None
283
+
284
+ graph = cg.ComputeGraph(
285
+ inputs=pytree.PyTree(placeholders),
286
+ outputs=pytree.PyTree(result_call),
287
+ name=f"{subgraph.name}_distribution",
288
+ metadata={"func": infer_nodegroup_distributions},
289
+ )
290
+
291
+ return graph
292
+
293
+
294
+ def infer_nodegroup_distributions(
295
+ graphs: list[cg.ComputeGraph],
296
+ supported_types: tuple[type] = (float, int, tuple, t.Vector, t.Color, np.ndarray),
297
+ colors_to_hsv: bool = True,
298
+ use_randint: bool = False,
299
+ ) -> list[cg.ComputeGraph]:
300
+ """
301
+ Find all multi-use subgraphs within the graphs, and compute the hypercube distribution
302
+ for all the numeric parameters ever used with each subgraph
303
+ """
304
+
305
+ subgraph_usages = defaultdict(list) # id to list of call nodes
306
+ for graph in graphs:
307
+ for callnode, subgraph in cg.traverse_nested_graphs(
308
+ graph, yield_call_nodes=True
309
+ ):
310
+ subgraph_usages[id(subgraph)].append((callnode, subgraph))
311
+
312
+ result_distrib_fns = []
313
+ for subgraph_id, calltuples in subgraph_usages.items():
314
+ if len(calltuples) <= 1:
315
+ continue
316
+ distrib_fn = _infer_distribution_from_callnodes(
317
+ callnodes=[ct[0] for ct in calltuples],
318
+ subgraph=calltuples[0][1],
319
+ supported_types=supported_types,
320
+ colors_to_hsv=colors_to_hsv,
321
+ use_randint=use_randint,
322
+ )
323
+ if distrib_fn is not None:
324
+ result_distrib_fns.append(distrib_fn)
325
+
326
+ return result_distrib_fns
@@ -0,0 +1,15 @@
1
+ from procfunc import compute_graph as cg
2
+
3
+ from .distribution import as_distribution
4
+
5
+
6
+ def extract_parameter_distributions(
7
+ compute_graph: cg.ComputeGraph,
8
+ ) -> list[cg.Node]:
9
+ return [
10
+ child
11
+ for _, _, child in cg.traverse_depth_first(
12
+ compute_graph, yield_consts=True, yield_name=True, yield_parent=True
13
+ )
14
+ if isinstance(child, cg.Node) and as_distribution(child) is not None
15
+ ]
@@ -0,0 +1,35 @@
1
+ import functools
2
+ from typing import Callable
3
+
4
+ from procfunc import compute_graph as cg
5
+
6
+
7
+ def map_graph_list(
8
+ func: Callable[[cg.ComputeGraph], cg.ComputeGraph],
9
+ ) -> Callable[[list[cg.ComputeGraph]], list[cg.ComputeGraph]]:
10
+ @functools.wraps(func)
11
+ def wrapper(graphs):
12
+ return [func(g) for g in graphs]
13
+
14
+ return wrapper
15
+
16
+
17
+ def map_subgraphs(
18
+ func: Callable[[cg.Node, cg.ComputeGraph], cg.ComputeGraph],
19
+ ) -> Callable[[list[cg.ComputeGraph]], list[cg.ComputeGraph]]:
20
+ @functools.wraps(func)
21
+ def wrapper(graphs: list[cg.ComputeGraph]) -> list[cg.ComputeGraph]:
22
+ for g in graphs:
23
+ for node, g in cg.traverse_nested_graphs(g, yield_call_nodes=True):
24
+ if node is None:
25
+ continue
26
+ assert isinstance(node, cg.SubgraphCallNode)
27
+ res = func(node, g)
28
+ if not isinstance(res, cg.ComputeGraph):
29
+ raise ValueError(
30
+ f"Transform {func.__name__} produced {res=} for {g=}"
31
+ )
32
+ node.subgraph = res
33
+ return graphs
34
+
35
+ return wrapper
@@ -0,0 +1,24 @@
1
+ from .bpy_to_computegraph import (
2
+ parse_material,
3
+ parse_modifier,
4
+ parse_node_tree,
5
+ parse_object,
6
+ parse_primitive,
7
+ parse_scene,
8
+ )
9
+ from .codegen import (
10
+ default_func_resolution_map,
11
+ to_python,
12
+ )
13
+
14
+ __all__ = [
15
+ "parse_material",
16
+ "parse_modifier",
17
+ "parse_nodetree",
18
+ "parse_object_and_pose",
19
+ "parse_object",
20
+ "parse_primitive",
21
+ "parse_scene",
22
+ "default_func_resolution_map",
23
+ "to_python",
24
+ ]