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,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
|
+
]
|