procfunc 0.34.0__tar.gz → 0.35.1__tar.gz
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-0.34.0/src/procfunc.egg-info → procfunc-0.35.1}/PKG-INFO +7 -4
- {procfunc-0.34.0 → procfunc-0.35.1}/pyproject.toml +7 -3
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/__init__.py +1 -1
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/codegen/codegen.py +77 -14
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/codegen/repr.py +16 -15
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/compute_graph/proxy.py +4 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/context.py +9 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/execute/execute.py +1 -1
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/execute/realize.py +31 -12
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/types.py +10 -3
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/util/node_function.py +6 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/primitives/__init__.py +1 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/random.py +12 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/tracer/trace.py +16 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transpiler/bpy_to_computegraph.py +45 -6
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transpiler/main.py +5 -1
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/types.py +22 -7
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/util/manifest.py +2 -1
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/util/teardown.py +15 -1
- {procfunc-0.34.0 → procfunc-0.35.1/src/procfunc.egg-info}/PKG-INFO +7 -4
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc.egg-info/SOURCES.txt +1 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc.egg-info/requires.txt +7 -3
- procfunc-0.35.1/tests/test_codegen.py +89 -0
- procfunc-0.35.1/tests/test_random.py +73 -0
- procfunc-0.35.1/tests/test_teardown.py +51 -0
- procfunc-0.34.0/tests/test_codegen.py +0 -18
- procfunc-0.34.0/tests/test_random.py +0 -29
- {procfunc-0.34.0 → procfunc-0.35.1}/LICENSE.md +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/README.md +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/setup.cfg +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/cli/__init__.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/cli/main.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/codegen/__init__.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/codegen/identifiers.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/color.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/compute_graph/__init__.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/compute_graph/compute_graph.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/compute_graph/node.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/compute_graph/operators_info.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/compute_graph/util.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/control/__init__.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/__init__.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/color.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/compositor.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/execute/construct_nodes.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/execute/construct_operator.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/execute/construct_special_cases.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/execute/construct_standard.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/execute/infer_runtime_data_type.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/execute/util.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/func.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/geo.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/manifest.json +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/math.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/shader.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/texture.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/util/__init__.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/util/bindings_util.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/nodes/util/bpy_node_info.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/__init__.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/_util.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/addons.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/attr.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/collection.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/curve.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/file.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/manifest.json +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/mesh.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/modifier.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/object.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/primitives/camera.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/primitives/curve.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/primitives/light.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/primitives/mesh.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/ops/uv.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/tracer/__init__.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/tracer/decorator.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/tracer/patch.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/tracer/proxy.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transforms/__init__.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transforms/cleanup.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transforms/convert.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transforms/distribution.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transforms/extract_materials.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transforms/infer_distribution.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transforms/parameters.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transforms/util.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transpiler/__init__.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transpiler/parse_default_values.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/transpiler/parse_special_cases.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/util/__init__.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/util/bpy_data.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/util/bpy_info.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/util/camera.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/util/keyframe.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/util/log.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc/util/pytree.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc.egg-info/dependency_links.txt +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc.egg-info/entry_points.txt +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/src/procfunc.egg-info/top_level.txt +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/tests/test_asset.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/tests/test_bpy_data_cleanup.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/tests/test_cli_transpile.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/tests/test_codegen_matrix.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/tests/test_compute_graph.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/tests/test_node_function.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/tests/test_ops.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/tests/test_pytree.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/tests/test_trace.py +0 -0
- {procfunc-0.34.0 → procfunc-0.35.1}/tests/test_transforms.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: procfunc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.35.1
|
|
4
4
|
Summary: Function-Oriented Abstractions for Procedural 3D Generation in Python
|
|
5
5
|
License-Expression: BSD-3-Clause
|
|
6
6
|
Project-URL: Homepage, https://github.com/princeton-vl/procfunc
|
|
@@ -9,15 +9,18 @@ Requires-Python: <3.12,>=3.11
|
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE.md
|
|
11
11
|
Requires-Dist: bpy==4.2.0
|
|
12
|
-
Requires-Dist: numpy<2
|
|
13
|
-
Requires-Dist: pandas
|
|
12
|
+
Requires-Dist: numpy<2,>=1.23.5
|
|
13
|
+
Requires-Dist: pandas>=1.5
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest<10,>=7.2; extra == "test"
|
|
14
16
|
Provides-Extra: dev
|
|
15
|
-
Requires-Dist: pytest; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest<10,>=7.2; extra == "dev"
|
|
16
18
|
Requires-Dist: pytest-xdist; extra == "dev"
|
|
17
19
|
Requires-Dist: ruff<0.16,>=0.15; extra == "dev"
|
|
18
20
|
Requires-Dist: ty; extra == "dev"
|
|
19
21
|
Requires-Dist: imageio; extra == "dev"
|
|
20
22
|
Requires-Dist: matplotlib; extra == "dev"
|
|
23
|
+
Requires-Dist: coverage; extra == "dev"
|
|
21
24
|
Provides-Extra: docs
|
|
22
25
|
Requires-Dist: sphinx; extra == "docs"
|
|
23
26
|
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
@@ -11,8 +11,8 @@ license = "BSD-3-Clause"
|
|
|
11
11
|
requires-python = ">=3.11,<3.12"
|
|
12
12
|
dependencies = [
|
|
13
13
|
"bpy==4.2.0",
|
|
14
|
-
"numpy
|
|
15
|
-
"pandas",
|
|
14
|
+
"numpy>=1.23.5,<2",
|
|
15
|
+
"pandas>=1.5",
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
[project.urls]
|
|
@@ -23,13 +23,17 @@ Repository = "https://github.com/princeton-vl/procfunc"
|
|
|
23
23
|
procfunc = "procfunc.cli:cli"
|
|
24
24
|
|
|
25
25
|
[project.optional-dependencies]
|
|
26
|
+
test = [
|
|
27
|
+
"pytest>=7.2,<10",
|
|
28
|
+
]
|
|
26
29
|
dev = [
|
|
27
|
-
"pytest",
|
|
30
|
+
"pytest>=7.2,<10",
|
|
28
31
|
"pytest-xdist",
|
|
29
32
|
"ruff>=0.15,<0.16",
|
|
30
33
|
"ty",
|
|
31
34
|
"imageio",
|
|
32
35
|
"matplotlib",
|
|
36
|
+
"coverage",
|
|
33
37
|
]
|
|
34
38
|
docs = [
|
|
35
39
|
"sphinx",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ast
|
|
1
2
|
import dataclasses
|
|
2
3
|
import enum
|
|
3
4
|
import inspect
|
|
@@ -5,7 +6,7 @@ import itertools
|
|
|
5
6
|
import logging
|
|
6
7
|
from collections import OrderedDict, defaultdict
|
|
7
8
|
from pathlib import Path
|
|
8
|
-
from typing import Any, Callable, Generator
|
|
9
|
+
from typing import Any, Callable, Generator, get_type_hints
|
|
9
10
|
|
|
10
11
|
import numpy as np
|
|
11
12
|
|
|
@@ -24,6 +25,30 @@ logger = logging.getLogger(__name__)
|
|
|
24
25
|
|
|
25
26
|
INDENT = " "
|
|
26
27
|
|
|
28
|
+
# UnaryOp included since ** binds tighter than unary minus: -2.0 ** x == -(2.0 ** x)
|
|
29
|
+
_PAREN_SENSITIVE_NODES = (ast.BinOp, ast.UnaryOp, ast.Compare, ast.BoolOp, ast.IfExp)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _has_enclosing_parens(expr: str) -> bool:
|
|
33
|
+
if not (expr.startswith("(") and expr.endswith(")")):
|
|
34
|
+
return False
|
|
35
|
+
depth = 0
|
|
36
|
+
for i, char in enumerate(expr):
|
|
37
|
+
depth += (char == "(") - (char == ")")
|
|
38
|
+
if depth == 0:
|
|
39
|
+
return i == len(expr) - 1
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _needs_parens(expr: str) -> bool:
|
|
44
|
+
if _has_enclosing_parens(expr):
|
|
45
|
+
return False
|
|
46
|
+
try:
|
|
47
|
+
parsed = ast.parse(expr, mode="eval")
|
|
48
|
+
except SyntaxError:
|
|
49
|
+
return " " in expr
|
|
50
|
+
return isinstance(parsed.body, _PAREN_SENSITIVE_NODES)
|
|
51
|
+
|
|
27
52
|
|
|
28
53
|
def indent_lines(lines: list[str], indent: str = INDENT) -> list[str]:
|
|
29
54
|
return [indent + line for line in lines]
|
|
@@ -52,7 +77,7 @@ def _repr_inp(
|
|
|
52
77
|
expr = expr[0]
|
|
53
78
|
assert isinstance(expr, str)
|
|
54
79
|
|
|
55
|
-
if
|
|
80
|
+
if extra_parens and _needs_parens(expr):
|
|
56
81
|
return f"({expr})"
|
|
57
82
|
else:
|
|
58
83
|
return expr
|
|
@@ -146,7 +171,8 @@ def _repr_function_call(
|
|
|
146
171
|
if not isinstance(target, cg.Node):
|
|
147
172
|
raise ValueError(f"Method call {node=} has non-node target {target=}")
|
|
148
173
|
func = None
|
|
149
|
-
|
|
174
|
+
target_expr = _repr_inp(target, scope_expressions, extra_parens=True)
|
|
175
|
+
func_str = f"{target_expr}.{method_name}"
|
|
150
176
|
case cg.SubgraphCallNode(subgraph=subgraph):
|
|
151
177
|
func = None
|
|
152
178
|
func_str = scope_expressions.get(id(subgraph))
|
|
@@ -176,6 +202,22 @@ def _repr_function_call(
|
|
|
176
202
|
return [f"{func_str}({', '.join(arg_reprs)})"]
|
|
177
203
|
|
|
178
204
|
|
|
205
|
+
# ==/!= only match Blender Compare for exact dtypes; float/vector stay epsilon-tolerant.
|
|
206
|
+
_EPSILON_EQUALITY_TEMPLATES = frozenset(
|
|
207
|
+
OPERATOR_TEMPLATES[op] for op in (OperatorType.EQUAL, OperatorType.NOT_EQUAL)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _is_exact_compare_operand(value: Any) -> bool:
|
|
212
|
+
if isinstance(value, (cg.Node, cg.Proxy)):
|
|
213
|
+
return False
|
|
214
|
+
if isinstance(value, str):
|
|
215
|
+
return True
|
|
216
|
+
if isinstance(value, bool):
|
|
217
|
+
return True
|
|
218
|
+
return isinstance(value, int) and not isinstance(value, float)
|
|
219
|
+
|
|
220
|
+
|
|
179
221
|
def _operator_call_operands(
|
|
180
222
|
node: cg.FunctionCallNode,
|
|
181
223
|
template: str,
|
|
@@ -196,13 +238,21 @@ def _operator_call_operands(
|
|
|
196
238
|
if any(name not in bound.arguments for name in operand_names):
|
|
197
239
|
return None
|
|
198
240
|
|
|
241
|
+
operands = [bound.arguments[name] for name in operand_names]
|
|
242
|
+
|
|
243
|
+
# a default epsilon stays tolerant unlike exact ==/!=, so require exact operands
|
|
244
|
+
if template in _EPSILON_EQUALITY_TEMPLATES and not all(
|
|
245
|
+
_is_exact_compare_operand(v) for v in operands
|
|
246
|
+
):
|
|
247
|
+
return None
|
|
248
|
+
|
|
199
249
|
for name, value in bound.arguments.items():
|
|
200
250
|
if name in operand_names:
|
|
201
251
|
continue
|
|
202
252
|
if not _kwarg_matches_default(sig, name, value):
|
|
203
253
|
return None
|
|
204
254
|
|
|
205
|
-
return
|
|
255
|
+
return operands
|
|
206
256
|
|
|
207
257
|
|
|
208
258
|
def _repr_operator_call(
|
|
@@ -244,7 +294,7 @@ def _codegen_for_node(
|
|
|
244
294
|
else:
|
|
245
295
|
return _repr_function_call(node, scope_expressions)
|
|
246
296
|
case cg.MethodCallNode() if node.method_name == "__getitem__":
|
|
247
|
-
callee_expr = _repr_inp(node.args[0], scope_expressions)
|
|
297
|
+
callee_expr = _repr_inp(node.args[0], scope_expressions, extra_parens=True)
|
|
248
298
|
idx_expr = _repr_inp(node.args[1], scope_expressions)
|
|
249
299
|
return [f"{callee_expr}[{idx_expr}]"]
|
|
250
300
|
case cg.MethodCallNode():
|
|
@@ -280,7 +330,7 @@ def _codegen_graph_inputs(
|
|
|
280
330
|
) -> list[str]:
|
|
281
331
|
args = sorted(
|
|
282
332
|
list(graph.inputs.values()),
|
|
283
|
-
key=lambda x:
|
|
333
|
+
key=lambda x: "default_value" in x.kwargs,
|
|
284
334
|
)
|
|
285
335
|
|
|
286
336
|
func_name = func_name or graph.name
|
|
@@ -305,8 +355,8 @@ def _codegen_graph_inputs(
|
|
|
305
355
|
else f"{name}"
|
|
306
356
|
)
|
|
307
357
|
|
|
308
|
-
if
|
|
309
|
-
line += f" = {repr_value(
|
|
358
|
+
if "default_value" in node.kwargs:
|
|
359
|
+
line += f" = {repr_value(node.kwargs['default_value'])}"
|
|
310
360
|
|
|
311
361
|
args_lines.append(line + ",")
|
|
312
362
|
|
|
@@ -315,18 +365,31 @@ def _codegen_graph_inputs(
|
|
|
315
365
|
return [f"def {func_name}("] + indent_lines(args_lines) + [end_statement]
|
|
316
366
|
|
|
317
367
|
|
|
368
|
+
def _namedtuple_field_type(node, annotation) -> str:
|
|
369
|
+
vt = node.metadata.get("known_value_type", None)
|
|
370
|
+
if vt is None:
|
|
371
|
+
vt = annotation
|
|
372
|
+
if vt is None:
|
|
373
|
+
return "Any"
|
|
374
|
+
try:
|
|
375
|
+
return repr_type(vt)
|
|
376
|
+
except Exception:
|
|
377
|
+
return "Any"
|
|
378
|
+
|
|
379
|
+
|
|
318
380
|
def _codegen_namedtuple_def(outputs: pytree.PyTree):
|
|
319
381
|
tupletype = outputs.toplevel_type()
|
|
382
|
+
try:
|
|
383
|
+
annotations = get_type_hints(tupletype)
|
|
384
|
+
except Exception:
|
|
385
|
+
annotations = getattr(tupletype, "__annotations__", {})
|
|
320
386
|
|
|
321
387
|
type_lines = []
|
|
322
388
|
for name, node in outputs.items():
|
|
323
389
|
if node is None:
|
|
324
390
|
continue
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
type_lines.append(f"{name}: Any")
|
|
328
|
-
else:
|
|
329
|
-
type_lines.append(f"{name}: {repr_type(vt)}")
|
|
391
|
+
field_type = _namedtuple_field_type(node, annotations.get(name))
|
|
392
|
+
type_lines.append(f"{name}: {field_type}")
|
|
330
393
|
|
|
331
394
|
return [f"class {tupletype.__name__}(NamedTuple):"] + indent_lines(type_lines)
|
|
332
395
|
|
|
@@ -840,7 +903,7 @@ def to_python(
|
|
|
840
903
|
add_line_comments: bool = False,
|
|
841
904
|
) -> str:
|
|
842
905
|
code_lines = []
|
|
843
|
-
code_lines.append("from typing import NamedTuple, Annotated")
|
|
906
|
+
code_lines.append("from typing import Any, NamedTuple, Annotated")
|
|
844
907
|
code_lines.append("import numpy as np")
|
|
845
908
|
code_lines.append("import bpy")
|
|
846
909
|
# code_lines.append("import logging; logging.basicConfig(level=logging.DEBUG)")
|
|
@@ -2,6 +2,7 @@ import dataclasses
|
|
|
2
2
|
import enum
|
|
3
3
|
import logging
|
|
4
4
|
import math
|
|
5
|
+
import types
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any, Union, get_args, get_origin
|
|
7
8
|
|
|
@@ -20,12 +21,22 @@ def repr_type(x: Any) -> str:
|
|
|
20
21
|
if isinstance(x, str):
|
|
21
22
|
return x
|
|
22
23
|
|
|
23
|
-
if x.__name__ == "NoneType":
|
|
24
|
-
return "None"
|
|
25
|
-
|
|
26
24
|
origin = get_origin(x)
|
|
27
25
|
args = get_args(x)
|
|
28
26
|
|
|
27
|
+
if origin is Union or origin is types.UnionType:
|
|
28
|
+
first_args = get_args(args[0])
|
|
29
|
+
if (
|
|
30
|
+
get_origin(args[0]) is nt.ProcNode
|
|
31
|
+
and first_args
|
|
32
|
+
and first_args[0] is args[1]
|
|
33
|
+
):
|
|
34
|
+
return f"t.SocketOrVal[{repr_type(first_args[0])}]"
|
|
35
|
+
return " | ".join([repr_type(a) for a in args])
|
|
36
|
+
|
|
37
|
+
if x.__name__ == "NoneType":
|
|
38
|
+
return "None"
|
|
39
|
+
|
|
29
40
|
if x.__name__ == "ProcNode":
|
|
30
41
|
if len(args) == 1:
|
|
31
42
|
return f"pf.ProcNode[{repr_type(args[0])}]"
|
|
@@ -42,16 +53,6 @@ def repr_type(x: Any) -> str:
|
|
|
42
53
|
if x.__module__ == "builtins":
|
|
43
54
|
return x.__name__
|
|
44
55
|
|
|
45
|
-
origin = get_origin(x)
|
|
46
|
-
args = get_args(x)
|
|
47
|
-
|
|
48
|
-
if origin is Union:
|
|
49
|
-
args_0 = get_args(args[0])
|
|
50
|
-
if get_origin(args[0]) is nt.ProcNode and args_0[0] is args[1]:
|
|
51
|
-
return f"t.SocketOrVal[{repr_type(args_0[0])}]"
|
|
52
|
-
else:
|
|
53
|
-
return " | ".join([repr_type(a) for a in args])
|
|
54
|
-
|
|
55
56
|
if getattr(x, "__module__", None) == "procfunc.nodes.types":
|
|
56
57
|
return f"t.{x.__name__}"
|
|
57
58
|
|
|
@@ -70,12 +71,12 @@ def repr_value(value: Any) -> str:
|
|
|
70
71
|
value = value.__wrapped__
|
|
71
72
|
|
|
72
73
|
if isinstance(value, cg.Proxy):
|
|
73
|
-
|
|
74
|
+
raise ValueError(
|
|
74
75
|
f"Proxy object {value} should never appear as a raw value in codegen - "
|
|
75
76
|
f"its underlying node {value.node} was not resolved to a variable"
|
|
76
77
|
)
|
|
77
78
|
if isinstance(value, nt.ProcNode):
|
|
78
|
-
|
|
79
|
+
raise ValueError(
|
|
79
80
|
f"Procnode object {value} should never be treated as a raw value in codegen"
|
|
80
81
|
)
|
|
81
82
|
|
|
@@ -15,6 +15,10 @@ class Proxy(Generic[T]):
|
|
|
15
15
|
|
|
16
16
|
node: Node
|
|
17
17
|
|
|
18
|
+
# Tell numpy to defer (ndarray + Proxy -> Proxy.__radd__) instead of probing the
|
|
19
|
+
# Proxy as an array, which would fabricate a bogus __array_struct__ via __getattr__.
|
|
20
|
+
__array_ufunc__ = None
|
|
21
|
+
|
|
18
22
|
def __repr__(self):
|
|
19
23
|
return f"Proxy({self.node!r})"
|
|
20
24
|
|
|
@@ -19,6 +19,13 @@ class ProcfuncContext:
|
|
|
19
19
|
'ignore' silently returns an empty mesh, 'warn' logs a warning, 'throw' raises an error.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
record_node_definitions: bool = False
|
|
23
|
+
"""
|
|
24
|
+
Record the user-space file/line/function that constructed each ProcNode, used only to
|
|
25
|
+
enrich node-instantiation error messages. Walks the stack once per node, which can
|
|
26
|
+
dominate build time in node-heavy callers, so off by default.
|
|
27
|
+
"""
|
|
28
|
+
|
|
22
29
|
def __post_init__(self):
|
|
23
30
|
"""Initialize computed fields after dataclass creation."""
|
|
24
31
|
if self.num_cpu_cores <= 0:
|
|
@@ -44,6 +51,8 @@ globals = ProcfuncContext(
|
|
|
44
51
|
num_cpu_cores=int(os.environ.get("PROCFUNC_NUM_CPU_CORES", 0)),
|
|
45
52
|
warn_mode_empty_geonodes=_warn_mode_empty_geonodes, # type: ignore[invalid-assignment]
|
|
46
53
|
current_trace_level=None,
|
|
54
|
+
record_node_definitions=os.environ.get("PROCFUNC_RECORD_NODE_DEFINITIONS", "0")
|
|
55
|
+
== "1",
|
|
47
56
|
)
|
|
48
57
|
|
|
49
58
|
|
|
@@ -5,13 +5,13 @@ import bpy
|
|
|
5
5
|
from procfunc import compute_graph as cg
|
|
6
6
|
from procfunc import types as pt
|
|
7
7
|
from procfunc.nodes import types as nt
|
|
8
|
-
from procfunc.nodes.util.bpy_node_info import
|
|
8
|
+
from procfunc.nodes.util.bpy_node_info import SocketType
|
|
9
9
|
from procfunc.ops._util import modify
|
|
10
10
|
from procfunc.ops.primitives.mesh import mesh_single_vertex
|
|
11
|
-
from procfunc.util import pytree
|
|
12
11
|
from procfunc.util.bpy_info import bpy_nocollide_data_name
|
|
13
12
|
|
|
14
|
-
from .
|
|
13
|
+
from . import construct_standard
|
|
14
|
+
from .construct_nodes import construct_procnode_to_bpy, instantiate_nodegroup
|
|
15
15
|
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
17
17
|
|
|
@@ -81,11 +81,38 @@ def nodegroup_to_output(
|
|
|
81
81
|
return output_node
|
|
82
82
|
|
|
83
83
|
|
|
84
|
+
def construct_outputs_to_output_node(
|
|
85
|
+
node_tree: bpy.types.NodeTree,
|
|
86
|
+
outputs: dict[str, cg.Node],
|
|
87
|
+
output_node_type: str,
|
|
88
|
+
) -> bpy.types.Node:
|
|
89
|
+
output_node = node_tree.nodes.new(output_node_type)
|
|
90
|
+
cache = {}
|
|
91
|
+
for key, node in outputs.items():
|
|
92
|
+
res = construct_procnode_to_bpy(node, node_tree, cache)
|
|
93
|
+
if isinstance(res, bpy.types.Node):
|
|
94
|
+
res = construct_standard._get_primary_output_socket(node, res)
|
|
95
|
+
construct_standard.connect_single_input(
|
|
96
|
+
node_tree, output_node.inputs[key.capitalize()], res
|
|
97
|
+
)
|
|
98
|
+
return output_node
|
|
99
|
+
|
|
100
|
+
|
|
84
101
|
def build_bpy_material(
|
|
85
102
|
surface: nt.ProcNode[nt.Shader] | None = None,
|
|
86
103
|
displacement: nt.ProcNode[pt.Vector] | None = None,
|
|
87
104
|
volume: nt.ProcNode[nt.Shader] | None = None,
|
|
88
105
|
) -> bpy.types.Material:
|
|
106
|
+
for key, val in {
|
|
107
|
+
"surface": surface,
|
|
108
|
+
"displacement": displacement,
|
|
109
|
+
"volume": volume,
|
|
110
|
+
}.items():
|
|
111
|
+
if val is not None and not isinstance(val, (nt.ProcNode, cg.Node)):
|
|
112
|
+
raise TypeError(
|
|
113
|
+
f"Material {key} must be a ProcNode or None, got {type(val)}: {val!r}"
|
|
114
|
+
)
|
|
115
|
+
|
|
89
116
|
# optimization: a constant zero displacement has no effect, so drop it and
|
|
90
117
|
# leave the output socket disconnected rather than emitting a dead subgraph
|
|
91
118
|
if displacement is not None and pt.is_zero_displacement(displacement):
|
|
@@ -107,15 +134,7 @@ def build_bpy_material(
|
|
|
107
134
|
mnt = material.node_tree
|
|
108
135
|
mnt.nodes.clear()
|
|
109
136
|
|
|
110
|
-
outputs
|
|
111
|
-
graph = cg.ComputeGraph(
|
|
112
|
-
inputs=pytree.PyTree({}),
|
|
113
|
-
outputs=outputs,
|
|
114
|
-
name="to_material",
|
|
115
|
-
metadata={},
|
|
116
|
-
)
|
|
117
|
-
body = as_nodegroup(graph, NodeGroupType.SHADER)
|
|
118
|
-
nodegroup_to_output(mnt, body, "ShaderNodeOutputMaterial", list(outputs.names()))
|
|
137
|
+
construct_outputs_to_output_node(mnt, outputs, "ShaderNodeOutputMaterial")
|
|
119
138
|
|
|
120
139
|
return material
|
|
121
140
|
|
|
@@ -5,6 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Any, Generic, TypeVar, Union
|
|
6
6
|
|
|
7
7
|
from procfunc import compute_graph as cg
|
|
8
|
+
from procfunc import context
|
|
8
9
|
from procfunc import types as pt
|
|
9
10
|
from procfunc.compute_graph.operators_info import (
|
|
10
11
|
OPERATORS_TO_FUNCTIONS,
|
|
@@ -79,7 +80,8 @@ class ProcNode(Generic[T]):
|
|
|
79
80
|
f"these should have been unwrapped to cg.Node {node.args} {node.kwargs}"
|
|
80
81
|
)
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
if context.globals.record_node_definitions:
|
|
84
|
+
self._node.metadata["definition"] = _node_definition_metadata()
|
|
83
85
|
|
|
84
86
|
def astype(self, dtype: type) -> "ProcNode":
|
|
85
87
|
node = copy.copy(self._node)
|
|
@@ -114,7 +116,8 @@ class ProcNode(Generic[T]):
|
|
|
114
116
|
)
|
|
115
117
|
|
|
116
118
|
node = cg.ProceduralNode(node_type=node_type, attrs=attrs, kwargs=inputs)
|
|
117
|
-
|
|
119
|
+
if context.globals.record_node_definitions:
|
|
120
|
+
node.metadata["definition"] = _node_definition_metadata()
|
|
118
121
|
|
|
119
122
|
return cls(node=node)
|
|
120
123
|
|
|
@@ -262,7 +265,11 @@ def node_definition_context_message(node: cg.Node):
|
|
|
262
265
|
metadata = object.__getattribute__(node, "metadata")
|
|
263
266
|
lineno_metadata = metadata.get("definition", None)
|
|
264
267
|
if lineno_metadata is None:
|
|
265
|
-
return
|
|
268
|
+
return (
|
|
269
|
+
" (set PROCFUNC_RECORD_NODE_DEFINITIONS=1 or "
|
|
270
|
+
"override_globals(record_node_definitions=True) to see the "
|
|
271
|
+
"file/line that defined this node) "
|
|
272
|
+
)
|
|
266
273
|
file, lineno, procfunc_name = lineno_metadata
|
|
267
274
|
return f" {procfunc_name}() call on {file}:{lineno} "
|
|
268
275
|
|
|
@@ -144,6 +144,12 @@ def _subgraph_call_procnode(func: Callable, subgraph: cg.ComputeGraph, *args, **
|
|
|
144
144
|
def node_function(func: Callable):
|
|
145
145
|
@functools.wraps(func)
|
|
146
146
|
def node_function_wrapper(*args, **kwargs):
|
|
147
|
+
active = pf.context.globals.current_trace_level
|
|
148
|
+
if active is not None and active >= TraceLevel.NODEGROUPS.value:
|
|
149
|
+
from procfunc.tracer.trace import capture_as_function_call
|
|
150
|
+
|
|
151
|
+
return capture_as_function_call(node_function_wrapper, args, kwargs)
|
|
152
|
+
|
|
147
153
|
subgraph = _execute_procnode_func_to_computegraph(func)
|
|
148
154
|
subgraph.metadata["operations"] = [
|
|
149
155
|
(node_function, {"func": func}),
|
|
@@ -75,6 +75,7 @@ def clip_gaussian(
|
|
|
75
75
|
)
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
@pf.tracer.primitive(allow_exec=True)
|
|
78
79
|
def wrap_gaussian(
|
|
79
80
|
rng: np.random.Generator,
|
|
80
81
|
mean: float,
|
|
@@ -92,6 +93,7 @@ def wrap_gaussian(
|
|
|
92
93
|
return x
|
|
93
94
|
|
|
94
95
|
|
|
96
|
+
@pf.tracer.primitive(allow_exec=True)
|
|
95
97
|
def exponential(
|
|
96
98
|
rng: np.random.Generator,
|
|
97
99
|
scale: float,
|
|
@@ -99,6 +101,7 @@ def exponential(
|
|
|
99
101
|
return rng.exponential(scale)
|
|
100
102
|
|
|
101
103
|
|
|
104
|
+
@pf.tracer.primitive(allow_exec=True)
|
|
102
105
|
def log_uniform(
|
|
103
106
|
rng: np.random.Generator,
|
|
104
107
|
low: float,
|
|
@@ -108,6 +111,7 @@ def log_uniform(
|
|
|
108
111
|
return np.exp(rng.uniform(np.log(low), np.log(high), size=size))
|
|
109
112
|
|
|
110
113
|
|
|
114
|
+
@pf.tracer.primitive(allow_exec=True)
|
|
111
115
|
def log_normal(
|
|
112
116
|
rng: np.random.Generator,
|
|
113
117
|
mean: float,
|
|
@@ -117,6 +121,7 @@ def log_normal(
|
|
|
117
121
|
return np.exp(rng.normal(np.log(mean), std, size=size))
|
|
118
122
|
|
|
119
123
|
|
|
124
|
+
@pf.tracer.primitive(allow_exec=True)
|
|
120
125
|
def spherical_sample(
|
|
121
126
|
rng: np.random.Generator,
|
|
122
127
|
min_elevation: float | None = None,
|
|
@@ -137,6 +142,7 @@ def spherical_sample(
|
|
|
137
142
|
)
|
|
138
143
|
|
|
139
144
|
|
|
145
|
+
@pf.tracer.primitive(allow_exec=True)
|
|
140
146
|
def mixture_of_gaussian(
|
|
141
147
|
rng: np.random.Generator,
|
|
142
148
|
means: np.ndarray,
|
|
@@ -161,6 +167,7 @@ def mixture_of_gaussian(
|
|
|
161
167
|
return res
|
|
162
168
|
|
|
163
169
|
|
|
170
|
+
@pf.tracer.primitive(allow_exec=True)
|
|
164
171
|
def beta(
|
|
165
172
|
rng: np.random.Generator,
|
|
166
173
|
a: float,
|
|
@@ -169,6 +176,7 @@ def beta(
|
|
|
169
176
|
return rng.beta(a, b)
|
|
170
177
|
|
|
171
178
|
|
|
179
|
+
@pf.tracer.primitive(allow_exec=True)
|
|
172
180
|
def poisson(
|
|
173
181
|
rng: np.random.Generator,
|
|
174
182
|
lam: float,
|
|
@@ -176,6 +184,7 @@ def poisson(
|
|
|
176
184
|
return rng.poisson(lam)
|
|
177
185
|
|
|
178
186
|
|
|
187
|
+
@pf.tracer.primitive(allow_exec=True)
|
|
179
188
|
def triangular(
|
|
180
189
|
rng: np.random.Generator,
|
|
181
190
|
low: float,
|
|
@@ -185,6 +194,7 @@ def triangular(
|
|
|
185
194
|
return rng.triangular(low, mode, high)
|
|
186
195
|
|
|
187
196
|
|
|
197
|
+
@pf.tracer.primitive(allow_exec=True)
|
|
188
198
|
def gamma(
|
|
189
199
|
rng: np.random.Generator,
|
|
190
200
|
shape: float,
|
|
@@ -193,6 +203,7 @@ def gamma(
|
|
|
193
203
|
return rng.gamma(shape, scale)
|
|
194
204
|
|
|
195
205
|
|
|
206
|
+
@pf.tracer.primitive(allow_exec=True)
|
|
196
207
|
def binomial(
|
|
197
208
|
rng: np.random.Generator,
|
|
198
209
|
n: int,
|
|
@@ -201,6 +212,7 @@ def binomial(
|
|
|
201
212
|
return rng.binomial(n, p)
|
|
202
213
|
|
|
203
214
|
|
|
215
|
+
@pf.tracer.primitive(allow_exec=True)
|
|
204
216
|
def geometric(
|
|
205
217
|
rng: np.random.Generator,
|
|
206
218
|
p: float,
|
|
@@ -139,6 +139,22 @@ def _map_args(
|
|
|
139
139
|
return res
|
|
140
140
|
|
|
141
141
|
|
|
142
|
+
def capture_as_function_call(func: Callable, args: tuple, kwargs: dict) -> cg.Proxy:
|
|
143
|
+
"""Record a call as a single FunctionCallNode leaf without executing func's body.
|
|
144
|
+
|
|
145
|
+
The Proxy's attribute/item access becomes graph getattrs, so multi-output results
|
|
146
|
+
(e.g. a NamedTuple) work through it. Codegen re-emits this as an imported call.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def _unwrap(v):
|
|
150
|
+
return v.node if isinstance(v, cg.Proxy) else v
|
|
151
|
+
|
|
152
|
+
node_args = tuple(pytree.PyTree(a).map(_unwrap).obj() for a in args)
|
|
153
|
+
node_kwargs = {k: pytree.PyTree(v).map(_unwrap).obj() for k, v in kwargs.items()}
|
|
154
|
+
node = cg.FunctionCallNode(func=func, args=node_args, kwargs=node_kwargs)
|
|
155
|
+
return cg.Proxy(node)
|
|
156
|
+
|
|
157
|
+
|
|
142
158
|
def trace(
|
|
143
159
|
func: Callable,
|
|
144
160
|
trace_level: TraceLevel = TraceLevel.GENERATORS,
|