procfunc 0.31.0__tar.gz → 0.33.0__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.31.0/src/procfunc.egg-info → procfunc-0.33.0}/PKG-INFO +2 -2
- {procfunc-0.31.0 → procfunc-0.33.0}/pyproject.toml +5 -2
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/__init__.py +1 -1
- procfunc-0.33.0/src/procfunc/cli/__init__.py +3 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/codegen/codegen.py +98 -152
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/codegen/identifiers.py +10 -0
- procfunc-0.33.0/src/procfunc/codegen/repr.py +121 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/compute_graph/__init__.py +26 -0
- procfunc-0.33.0/src/procfunc/compute_graph/compute_graph.py +32 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/compute_graph/node.py +0 -8
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/compute_graph/operators_info.py +0 -2
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/compute_graph/util.py +26 -23
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/context.py +8 -7
- procfunc-0.31.0/src/procfunc/control.py → procfunc-0.33.0/src/procfunc/control/__init__.py +1 -1
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/__init__.py +3 -3
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/color.py +20 -9
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/compositor.py +119 -53
- procfunc-0.33.0/src/procfunc/nodes/execute/construct_nodes.py +268 -0
- procfunc-0.33.0/src/procfunc/nodes/execute/construct_operator.py +277 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/execute/construct_special_cases.py +108 -4
- procfunc-0.33.0/src/procfunc/nodes/execute/construct_standard.py +357 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/execute/execute.py +17 -218
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/execute/infer_runtime_data_type.py +70 -32
- procfunc-0.33.0/src/procfunc/nodes/execute/realize.py +225 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/execute/util.py +107 -6
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/func.py +183 -76
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/geo.py +394 -85
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/manifest.json +292 -629
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/math.py +36 -30
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/shader.py +120 -62
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/texture.py +80 -46
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/nodes/types.py +5 -9
- {procfunc-0.31.0/src/procfunc/nodes → procfunc-0.33.0/src/procfunc/nodes/util}/bindings_util.py +161 -40
- {procfunc-0.31.0/src/procfunc/nodes → procfunc-0.33.0/src/procfunc/nodes/util}/bpy_node_info.py +83 -7
- {procfunc-0.31.0/src/procfunc/nodes → procfunc-0.33.0/src/procfunc/nodes/util}/node_function.py +13 -2
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/__init__.py +1 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/_util.py +1 -1
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/mesh.py +5 -1
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/modifier.py +1 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/primitives/mesh.py +2 -2
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/random.py +4 -2
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/tracer/__init__.py +2 -10
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/tracer/decorator.py +6 -9
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/tracer/patch.py +0 -17
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/tracer/proxy.py +1 -1
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/transforms/cleanup.py +4 -1
- procfunc-0.33.0/src/procfunc/transforms/convert.py +20 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/transforms/distribution.py +25 -26
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/transforms/infer_distribution.py +37 -7
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/transpiler/__init__.py +1 -2
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/transpiler/bpy_to_computegraph.py +89 -153
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/transpiler/main.py +21 -53
- procfunc-0.33.0/src/procfunc/transpiler/parse_default_values.py +35 -0
- procfunc-0.33.0/src/procfunc/transpiler/parse_special_cases.py +141 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/types.py +21 -73
- procfunc-0.33.0/src/procfunc/util/bpy_data.py +39 -0
- procfunc-0.33.0/src/procfunc/util/camera.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/util/pytree.py +30 -24
- {procfunc-0.31.0 → procfunc-0.33.0/src/procfunc.egg-info}/PKG-INFO +2 -2
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc.egg-info/SOURCES.txt +21 -6
- procfunc-0.33.0/tests/test_bpy_data_cleanup.py +50 -0
- procfunc-0.33.0/tests/test_cli_transpile.py +81 -0
- procfunc-0.33.0/tests/test_codegen_matrix.py +110 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/tests/test_compute_graph.py +25 -0
- procfunc-0.33.0/tests/test_node_function.py +28 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/tests/test_ops.py +29 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/tests/test_pytree.py +10 -0
- procfunc-0.33.0/tests/test_random.py +29 -0
- procfunc-0.33.0/tests/test_transforms.py +389 -0
- procfunc-0.31.0/src/procfunc/compute_graph/compute_graph.py +0 -115
- procfunc-0.31.0/src/procfunc/nodes/execute/construct_nodes.py +0 -602
- procfunc-0.31.0/src/procfunc/transforms/convert.py +0 -20
- {procfunc-0.31.0 → procfunc-0.33.0}/LICENSE.md +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/README.md +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/setup.cfg +0 -0
- /procfunc-0.31.0/src/procfunc/cli.py → /procfunc-0.33.0/src/procfunc/cli/main.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/codegen/__init__.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/color.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/compute_graph/proxy.py +0 -0
- {procfunc-0.31.0/src/procfunc → procfunc-0.33.0/src/procfunc/nodes}/util/__init__.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/addons.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/attr.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/collection.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/curve.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/file.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/manifest.json +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/object.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/primitives/__init__.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/primitives/camera.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/primitives/curve.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/primitives/light.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/ops/uv.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/tracer/trace.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/transforms/__init__.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/transforms/extract_materials.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/transforms/parameters.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/transforms/util.py +0 -0
- /procfunc-0.31.0/src/procfunc/util/camera.py → /procfunc-0.33.0/src/procfunc/util/__init__.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/util/bpy_info.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/util/keyframe.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/util/log.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/util/manifest.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc/util/teardown.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc.egg-info/dependency_links.txt +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc.egg-info/entry_points.txt +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc.egg-info/requires.txt +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/src/procfunc.egg-info/top_level.txt +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/tests/test_asset.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/tests/test_codegen.py +0 -0
- {procfunc-0.31.0 → procfunc-0.33.0}/tests/test_trace.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: procfunc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.33.0
|
|
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
|
|
7
7
|
Project-URL: Repository, https://github.com/princeton-vl/procfunc
|
|
8
|
-
Requires-Python:
|
|
8
|
+
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
|
|
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "procfunc"
|
|
7
|
-
|
|
7
|
+
dynamic = ["version"]
|
|
8
8
|
description = "Function-Oriented Abstractions for Procedural 3D Generation in Python"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "BSD-3-Clause"
|
|
11
|
-
requires-python = ">=3.11"
|
|
11
|
+
requires-python = ">=3.11,<3.12"
|
|
12
12
|
dependencies = [
|
|
13
13
|
"bpy==4.2.0",
|
|
14
14
|
"numpy<2",
|
|
@@ -35,6 +35,9 @@ docs = [
|
|
|
35
35
|
"sphinx-argparse",
|
|
36
36
|
]
|
|
37
37
|
|
|
38
|
+
[tool.setuptools.dynamic]
|
|
39
|
+
version = {attr = "procfunc.__version__"}
|
|
40
|
+
|
|
38
41
|
[tool.setuptools.packages.find]
|
|
39
42
|
where = ["src"]
|
|
40
43
|
|
|
@@ -5,19 +5,19 @@ import itertools
|
|
|
5
5
|
import logging
|
|
6
6
|
from collections import OrderedDict, defaultdict
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any, Callable, Generator
|
|
8
|
+
from typing import Any, Callable, Generator
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
|
|
12
12
|
import procfunc as pf
|
|
13
13
|
from procfunc import compute_graph as cg
|
|
14
14
|
from procfunc.codegen import identifiers
|
|
15
|
+
from procfunc.codegen.repr import repr_type, repr_value
|
|
15
16
|
from procfunc.compute_graph.operators_info import (
|
|
16
17
|
FUNCTIONS_TO_OPERATORS,
|
|
17
18
|
OPERATOR_TEMPLATES,
|
|
18
19
|
OperatorType,
|
|
19
20
|
)
|
|
20
|
-
from procfunc.nodes import types as nt
|
|
21
21
|
from procfunc.util import pytree
|
|
22
22
|
|
|
23
23
|
logger = logging.getLogger(__name__)
|
|
@@ -29,97 +29,6 @@ def indent_lines(lines: list[str], indent: str = INDENT) -> list[str]:
|
|
|
29
29
|
return [indent + line for line in lines]
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
def _repr_type(x: Any) -> str:
|
|
33
|
-
# TODO: make the user pass in special resolutions for types, or else we will just do verbose types
|
|
34
|
-
|
|
35
|
-
if isinstance(x, str):
|
|
36
|
-
return x
|
|
37
|
-
|
|
38
|
-
if x.__name__ == "NoneType":
|
|
39
|
-
return "None"
|
|
40
|
-
|
|
41
|
-
origin = get_origin(x)
|
|
42
|
-
args = get_args(x)
|
|
43
|
-
|
|
44
|
-
if x.__name__ == "ProcNode":
|
|
45
|
-
if len(args) == 1:
|
|
46
|
-
return f"pf.ProcNode[{_repr_type(args[0])}]"
|
|
47
|
-
elif len(args) == 0:
|
|
48
|
-
return "pf.ProcNode"
|
|
49
|
-
else:
|
|
50
|
-
raise ValueError(f"Unsupported ProcNode type: {x} {args=}")
|
|
51
|
-
|
|
52
|
-
if hasattr(pf, x.__name__):
|
|
53
|
-
if len(args):
|
|
54
|
-
raise ValueError(f"procfunc type had unhandled annotations: {x} {args=}")
|
|
55
|
-
return f"pf.{x.__name__}"
|
|
56
|
-
|
|
57
|
-
if x.__module__ == "builtins":
|
|
58
|
-
return x.__name__
|
|
59
|
-
|
|
60
|
-
origin = get_origin(x)
|
|
61
|
-
args = get_args(x)
|
|
62
|
-
|
|
63
|
-
if origin is Union:
|
|
64
|
-
args_0 = get_args(args[0])
|
|
65
|
-
if get_origin(args[0]) is nt.ProcNode and args_0[0] is args[1]:
|
|
66
|
-
return f"t.SocketOrVal[{_repr_type(args_0[0])}]"
|
|
67
|
-
else:
|
|
68
|
-
return " | ".join([_repr_type(a) for a in args])
|
|
69
|
-
|
|
70
|
-
if getattr(x, "__module__", None) == "procfunc.nodes.types":
|
|
71
|
-
return f"t.{x.__name__}"
|
|
72
|
-
|
|
73
|
-
return x.__name__
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def _repr_value(value: Any) -> str:
|
|
77
|
-
if hasattr(value, "__wrapped__"):
|
|
78
|
-
value = value.__wrapped__
|
|
79
|
-
|
|
80
|
-
if isinstance(value, cg.Proxy):
|
|
81
|
-
logger.warning(
|
|
82
|
-
f"Proxy object {value} should never appear as a raw value in codegen - "
|
|
83
|
-
f"its underlying node {value.node} was not resolved to a variable"
|
|
84
|
-
)
|
|
85
|
-
if isinstance(value, nt.ProcNode):
|
|
86
|
-
logger.warning(
|
|
87
|
-
f"Procnode object {value} should never be treated as a raw value in codegen"
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
if isinstance(value, np.random.Generator):
|
|
91
|
-
return "np.random.default_rng()"
|
|
92
|
-
elif isinstance(value, type):
|
|
93
|
-
return _repr_type(value)
|
|
94
|
-
elif isinstance(value, np.ndarray):
|
|
95
|
-
nprepr = repr(value).replace("\n", "")
|
|
96
|
-
return f"np.{nprepr}"
|
|
97
|
-
elif isinstance(value, np.dtype):
|
|
98
|
-
return f"np.dtype('{value}')"
|
|
99
|
-
elif isinstance(value, (pf.Color, pf.Vector, pf.Euler, pf.Quaternion, pf.Matrix)):
|
|
100
|
-
x = tuple(round(x, 8) for x in value)
|
|
101
|
-
return f"pf.{value.__class__.__name__}({x})"
|
|
102
|
-
elif isinstance(value, enum.Enum):
|
|
103
|
-
return f"{type(value).__name__}.{value.name}"
|
|
104
|
-
elif isinstance(value, Path):
|
|
105
|
-
return f"Path({str(value)!r})"
|
|
106
|
-
elif dataclasses.is_dataclass(value) and not isinstance(value, type):
|
|
107
|
-
args_str = ", ".join(
|
|
108
|
-
f"{f.name}={_repr_value(getattr(value, f.name))}"
|
|
109
|
-
for f in dataclasses.fields(value)
|
|
110
|
-
)
|
|
111
|
-
return f"{type(value).__name__}({args_str})"
|
|
112
|
-
elif isinstance(value, list):
|
|
113
|
-
return f"[{', '.join([_repr_value(x) for x in value])}]"
|
|
114
|
-
elif isinstance(value, tuple):
|
|
115
|
-
inner = ", ".join(_repr_value(x) for x in value)
|
|
116
|
-
return f"({inner},)" if len(value) == 1 else f"({inner})"
|
|
117
|
-
elif isinstance(value, float) and not isinstance(value, bool):
|
|
118
|
-
return repr(round(value, 8))
|
|
119
|
-
else:
|
|
120
|
-
return repr(value)
|
|
121
|
-
|
|
122
|
-
|
|
123
32
|
def _repr_inp(
|
|
124
33
|
arg: Any,
|
|
125
34
|
scope_expressions: dict[int, str | list[str]],
|
|
@@ -132,7 +41,7 @@ def _repr_inp(
|
|
|
132
41
|
)
|
|
133
42
|
expr = scope_expressions[id(arg)]
|
|
134
43
|
else:
|
|
135
|
-
expr =
|
|
44
|
+
expr = repr_value(arg)
|
|
136
45
|
|
|
137
46
|
if isinstance(expr, list):
|
|
138
47
|
if len(expr) > 1:
|
|
@@ -189,7 +98,7 @@ def _repr_args(
|
|
|
189
98
|
|
|
190
99
|
argreprs = pytree.PyTree(args).map(lambda x: _repr_inp(x, scope_expressions))
|
|
191
100
|
argreprs = [
|
|
192
|
-
pytree.repr_tree_to_str(v, type_namer=
|
|
101
|
+
pytree.repr_tree_to_str(v, type_namer=repr_type)
|
|
193
102
|
for v in argreprs.unflatten_one_level()
|
|
194
103
|
]
|
|
195
104
|
|
|
@@ -199,7 +108,7 @@ def _repr_args(
|
|
|
199
108
|
.unflatten_one_level()
|
|
200
109
|
)
|
|
201
110
|
kwargreprs = {
|
|
202
|
-
k: pytree.repr_tree_to_str(v, type_namer=
|
|
111
|
+
k: pytree.repr_tree_to_str(v, type_namer=repr_type)
|
|
203
112
|
for k, v in kwargreprs.items()
|
|
204
113
|
}
|
|
205
114
|
|
|
@@ -227,11 +136,12 @@ def _repr_function_call(
|
|
|
227
136
|
node: cg.FunctionCallNode | cg.MethodCallNode | cg.SubgraphCallNode,
|
|
228
137
|
scope_expressions: dict[int, str | list[str]],
|
|
229
138
|
line_limit: int = 80,
|
|
139
|
+
func_str: str | None = None,
|
|
230
140
|
) -> list[str]:
|
|
231
141
|
match node:
|
|
232
142
|
case cg.FunctionCallNode():
|
|
233
143
|
func = node.func
|
|
234
|
-
func_str = scope_expressions[id(func)]
|
|
144
|
+
func_str = func_str or scope_expressions[id(func)]
|
|
235
145
|
case cg.MethodCallNode(args=(target, *_), method_name=method_name):
|
|
236
146
|
if not isinstance(target, cg.Node):
|
|
237
147
|
raise ValueError(f"Method call {node=} has non-node target {target=}")
|
|
@@ -266,22 +176,44 @@ def _repr_function_call(
|
|
|
266
176
|
return [f"{func_str}({', '.join(arg_reprs)})"]
|
|
267
177
|
|
|
268
178
|
|
|
269
|
-
def
|
|
179
|
+
def _operator_call_operands(
|
|
270
180
|
node: cg.FunctionCallNode,
|
|
181
|
+
template: str,
|
|
182
|
+
) -> list | None:
|
|
183
|
+
"""Operand values for rendering `node` in infix/operator form, or None to
|
|
184
|
+
decline it. The operator template only has slots for the operands, so any
|
|
185
|
+
extra argument beyond them (e.g. a non-default epsilon on func.equal) would
|
|
186
|
+
be silently dropped by the infix form - decline unless it merely restates
|
|
187
|
+
the signature default, in which case it is redundant and dropped."""
|
|
188
|
+
n_slots = template.count("{}")
|
|
189
|
+
try:
|
|
190
|
+
sig = inspect.signature(node.func)
|
|
191
|
+
bound = sig.bind(*node.args, **node.kwargs)
|
|
192
|
+
except (TypeError, ValueError):
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
operand_names = list(sig.parameters)[:n_slots]
|
|
196
|
+
if any(name not in bound.arguments for name in operand_names):
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
for name, value in bound.arguments.items():
|
|
200
|
+
if name in operand_names:
|
|
201
|
+
continue
|
|
202
|
+
if not _kwarg_matches_default(sig, name, value):
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
return [bound.arguments[name] for name in operand_names]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _repr_operator_call(
|
|
209
|
+
operands: list,
|
|
210
|
+
template: str,
|
|
271
211
|
scope_expressions: dict[int, str | list[str]],
|
|
272
212
|
) -> list[str]:
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
# Support both positional args and kwargs for operator templates
|
|
276
|
-
all_args = [
|
|
277
|
-
_repr_inp(v, scope_expressions, extra_parens=True) for v in node.args
|
|
278
|
-
] + [
|
|
279
|
-
_repr_inp(v, scope_expressions, extra_parens=True) for v in node.kwargs.values()
|
|
213
|
+
operand_reprs = [
|
|
214
|
+
_repr_inp(v, scope_expressions, extra_parens=True) for v in operands
|
|
280
215
|
]
|
|
281
|
-
|
|
282
|
-
operator_template = scope_expressions[id(node.func)]
|
|
283
|
-
assert isinstance(operator_template, str), operator_template
|
|
284
|
-
return [operator_template.format(*all_args)]
|
|
216
|
+
return [template.format(*operand_reprs)]
|
|
285
217
|
|
|
286
218
|
|
|
287
219
|
def _codegen_for_node(
|
|
@@ -298,7 +230,17 @@ def _codegen_for_node(
|
|
|
298
230
|
elif funcres == OperatorType.NOOP:
|
|
299
231
|
return [] # no code needed
|
|
300
232
|
elif "{}" in funcres:
|
|
301
|
-
|
|
233
|
+
operands = _operator_call_operands(node, funcres)
|
|
234
|
+
if operands is None:
|
|
235
|
+
# args the infix form cannot express: emit a named call.
|
|
236
|
+
# scope_expressions holds the operator template, so re-derive
|
|
237
|
+
# the callsite name (import already present via
|
|
238
|
+
# default_func_resolution_map)
|
|
239
|
+
_, callsite = _resolve_func(func)
|
|
240
|
+
return _repr_function_call(
|
|
241
|
+
node, scope_expressions, func_str=callsite
|
|
242
|
+
)
|
|
243
|
+
return _repr_operator_call(operands, funcres, scope_expressions)
|
|
302
244
|
else:
|
|
303
245
|
return _repr_function_call(node, scope_expressions)
|
|
304
246
|
case cg.MethodCallNode() if node.method_name == "__getitem__":
|
|
@@ -325,7 +267,7 @@ def _codegen_for_node(
|
|
|
325
267
|
)
|
|
326
268
|
return [f"{arg_expr}.{attribute_name}"]
|
|
327
269
|
case cg.ConstantNode(value=value):
|
|
328
|
-
return [
|
|
270
|
+
return [repr_value(value)]
|
|
329
271
|
case _:
|
|
330
272
|
raise TypeError(f"Unsupported {node=}")
|
|
331
273
|
|
|
@@ -358,17 +300,17 @@ def _codegen_graph_inputs(
|
|
|
358
300
|
|
|
359
301
|
known_value_type = node.metadata.get("known_value_type", None)
|
|
360
302
|
line = (
|
|
361
|
-
f"{name}: {
|
|
303
|
+
f"{name}: {repr_type(known_value_type)}"
|
|
362
304
|
if known_value_type is not None
|
|
363
305
|
else f"{name}"
|
|
364
306
|
)
|
|
365
307
|
|
|
366
308
|
if (default := node.kwargs.get("default_value")) is not None:
|
|
367
|
-
line += f" = {
|
|
309
|
+
line += f" = {repr_value(default)}"
|
|
368
310
|
|
|
369
311
|
args_lines.append(line + ",")
|
|
370
312
|
|
|
371
|
-
end_statement = "):" if typename is None else f") -> {typename}:
|
|
313
|
+
end_statement = "):" if typename is None else f") -> {typename}:"
|
|
372
314
|
|
|
373
315
|
return [f"def {func_name}("] + indent_lines(args_lines) + [end_statement]
|
|
374
316
|
|
|
@@ -384,7 +326,7 @@ def _codegen_namedtuple_def(outputs: pytree.PyTree):
|
|
|
384
326
|
if vt is None:
|
|
385
327
|
type_lines.append(f"{name}: Any")
|
|
386
328
|
else:
|
|
387
|
-
type_lines.append(f"{name}: {
|
|
329
|
+
type_lines.append(f"{name}: {repr_type(vt)}")
|
|
388
330
|
|
|
389
331
|
return [f"class {tupletype.__name__}(NamedTuple):"] + indent_lines(type_lines)
|
|
390
332
|
|
|
@@ -400,11 +342,11 @@ def _codegen_for_outputs(
|
|
|
400
342
|
if len(graph.outputs) == 1:
|
|
401
343
|
single_output = next(graph.outputs.values())
|
|
402
344
|
vt = single_output.metadata.get("known_value_type", None)
|
|
403
|
-
type_name =
|
|
345
|
+
type_name = repr_type(vt) if vt is not None else None
|
|
404
346
|
return type_name, [], [f"return {_repr_inp(single_output, scope_expressions)}"]
|
|
405
347
|
|
|
406
348
|
graph_output_type = graph.outputs.toplevel_type()
|
|
407
|
-
type_name =
|
|
349
|
+
type_name = repr_type(graph_output_type)
|
|
408
350
|
|
|
409
351
|
is_pf_type = hasattr(pf, graph_output_type.__name__)
|
|
410
352
|
if is_pf_type:
|
|
@@ -423,7 +365,7 @@ def _codegen_for_outputs(
|
|
|
423
365
|
|
|
424
366
|
reprs_tree = graph.outputs.map(lambda node: _repr_inp(node, scope_expressions))
|
|
425
367
|
return_lines = [
|
|
426
|
-
f"return {pytree.repr_tree_to_str(reprs_tree, type_namer=
|
|
368
|
+
f"return {pytree.repr_tree_to_str(reprs_tree, type_namer=repr_type)}"
|
|
427
369
|
]
|
|
428
370
|
|
|
429
371
|
return type_name, type_def, return_lines
|
|
@@ -579,6 +521,7 @@ def _code_paragraphing_predicate(
|
|
|
579
521
|
def _codegen_for_assignment(
|
|
580
522
|
assign_varname: str,
|
|
581
523
|
node_code: list[str] | str,
|
|
524
|
+
node: cg.Node,
|
|
582
525
|
add_line_comments: bool,
|
|
583
526
|
) -> list[str]:
|
|
584
527
|
assert isinstance(assign_varname, str)
|
|
@@ -589,7 +532,7 @@ def _codegen_for_assignment(
|
|
|
589
532
|
else:
|
|
590
533
|
node_code = [f"{assign_varname} = {node_code}"]
|
|
591
534
|
if add_line_comments:
|
|
592
|
-
node_code[
|
|
535
|
+
node_code[-1] += f" # {str(node).replace(chr(10), ' ')}"
|
|
593
536
|
|
|
594
537
|
return node_code
|
|
595
538
|
|
|
@@ -669,7 +612,7 @@ def _codegen_for_graph(
|
|
|
669
612
|
continue
|
|
670
613
|
|
|
671
614
|
varname = expressions[id(node)]
|
|
672
|
-
node_code = _codegen_for_assignment(varname, node_code, add_line_comments)
|
|
615
|
+
node_code = _codegen_for_assignment(varname, node_code, node, add_line_comments)
|
|
673
616
|
code_lines.extend(node_code)
|
|
674
617
|
|
|
675
618
|
if last_varname.split("_")[0] != varname.split("_")[0]:
|
|
@@ -778,43 +721,46 @@ def graphs_to_python_functions(
|
|
|
778
721
|
np_linewidth = np.get_printoptions()["linewidth"]
|
|
779
722
|
np.set_printoptions(linewidth=100000)
|
|
780
723
|
|
|
781
|
-
|
|
724
|
+
try:
|
|
725
|
+
targets = _topo_sort_subgraphs(graph)
|
|
782
726
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
727
|
+
def _clean_graph_name(name: str) -> str:
|
|
728
|
+
for suffix in identifiers.NONDESCRIPTIVE_NODE_NAME_PARTS:
|
|
729
|
+
if name.endswith("_" + suffix):
|
|
730
|
+
name = name[: -(len(suffix) + 1)]
|
|
731
|
+
return name
|
|
788
732
|
|
|
789
|
-
|
|
790
|
-
|
|
733
|
+
for subgraph in cg.traverse_nested_graphs(graph):
|
|
734
|
+
subgraph.name = _clean_graph_name(subgraph.name)
|
|
791
735
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
scope_expressions = subgraph_names.copy()
|
|
798
|
-
for k, v in func_resolution.items():
|
|
799
|
-
if isinstance(v, OperatorType):
|
|
800
|
-
scope_expressions[id(k)] = OPERATOR_TEMPLATES[v]
|
|
801
|
-
else:
|
|
802
|
-
scope_expressions[id(k)] = v
|
|
803
|
-
|
|
804
|
-
lines_for_modules = []
|
|
805
|
-
for subgraph in targets:
|
|
806
|
-
func_name = subgraph_names[id(subgraph)]
|
|
807
|
-
result = _codegen_for_graph(
|
|
808
|
-
subgraph,
|
|
809
|
-
scope_expressions=scope_expressions.copy(),
|
|
810
|
-
as_maincall=(subgraph is graph and toplevel_as_maincall),
|
|
811
|
-
add_version_comment=add_version_comment,
|
|
812
|
-
add_line_comments=add_line_comments,
|
|
813
|
-
func_name=func_name,
|
|
736
|
+
subgraph_names = {
|
|
737
|
+
id(subgraph): subgraph.name for subgraph in cg.traverse_nested_graphs(graph)
|
|
738
|
+
}
|
|
739
|
+
subgraph_names = identifiers.dedup_names_with_suffix(
|
|
740
|
+
subgraph_names, separator="_"
|
|
814
741
|
)
|
|
815
|
-
lines_for_modules.append((subgraph_names[id(subgraph)], result))
|
|
816
742
|
|
|
817
|
-
|
|
743
|
+
scope_expressions = subgraph_names.copy()
|
|
744
|
+
for k, v in func_resolution.items():
|
|
745
|
+
if isinstance(v, OperatorType):
|
|
746
|
+
scope_expressions[id(k)] = OPERATOR_TEMPLATES[v]
|
|
747
|
+
else:
|
|
748
|
+
scope_expressions[id(k)] = v
|
|
749
|
+
|
|
750
|
+
lines_for_modules = []
|
|
751
|
+
for subgraph in targets:
|
|
752
|
+
func_name = subgraph_names[id(subgraph)]
|
|
753
|
+
result = _codegen_for_graph(
|
|
754
|
+
subgraph,
|
|
755
|
+
scope_expressions=scope_expressions.copy(),
|
|
756
|
+
as_maincall=(subgraph is graph and toplevel_as_maincall),
|
|
757
|
+
add_version_comment=add_version_comment,
|
|
758
|
+
add_line_comments=add_line_comments,
|
|
759
|
+
func_name=func_name,
|
|
760
|
+
)
|
|
761
|
+
lines_for_modules.append((subgraph_names[id(subgraph)], result))
|
|
762
|
+
finally:
|
|
763
|
+
np.set_printoptions(linewidth=np_linewidth)
|
|
818
764
|
|
|
819
765
|
return OrderedDict(lines_for_modules)
|
|
820
766
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import keyword
|
|
1
2
|
import logging
|
|
2
3
|
import re
|
|
3
4
|
from collections import Counter, defaultdict
|
|
@@ -36,6 +37,10 @@ def bpy_name_to_pythonid(name: str) -> str:
|
|
|
36
37
|
|
|
37
38
|
name = name.lower()
|
|
38
39
|
|
|
40
|
+
# drop anything that isn't valid in a python identifier (e.g. parentheses
|
|
41
|
+
# in socket names like "Joint ID (do not set)")
|
|
42
|
+
name = re.sub(r"[^0-9a-z_]", "_", name)
|
|
43
|
+
|
|
39
44
|
name = re.sub(r"_+", "_", name).strip("_")
|
|
40
45
|
|
|
41
46
|
# move number terms to end
|
|
@@ -46,6 +51,9 @@ def bpy_name_to_pythonid(name: str) -> str:
|
|
|
46
51
|
|
|
47
52
|
name = "_".join(parts)
|
|
48
53
|
|
|
54
|
+
if keyword.iskeyword(name):
|
|
55
|
+
name = name + "_"
|
|
56
|
+
|
|
49
57
|
return name
|
|
50
58
|
|
|
51
59
|
|
|
@@ -60,6 +68,8 @@ def is_valid_snake_identifier(name: str) -> bool:
|
|
|
60
68
|
return False
|
|
61
69
|
if name != name.lower():
|
|
62
70
|
return False # this is opinionated
|
|
71
|
+
if not name.isidentifier() or keyword.iskeyword(name):
|
|
72
|
+
return False
|
|
63
73
|
return True
|
|
64
74
|
|
|
65
75
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import enum
|
|
3
|
+
import logging
|
|
4
|
+
import math
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Union, get_args, get_origin
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
import procfunc as pf
|
|
11
|
+
from procfunc import compute_graph as cg
|
|
12
|
+
from procfunc.nodes import types as nt
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def repr_type(x: Any) -> str:
|
|
18
|
+
# TODO: make the user pass in special resolutions for types, or else we will just do verbose types
|
|
19
|
+
|
|
20
|
+
if isinstance(x, str):
|
|
21
|
+
return x
|
|
22
|
+
|
|
23
|
+
if x.__name__ == "NoneType":
|
|
24
|
+
return "None"
|
|
25
|
+
|
|
26
|
+
origin = get_origin(x)
|
|
27
|
+
args = get_args(x)
|
|
28
|
+
|
|
29
|
+
if x.__name__ == "ProcNode":
|
|
30
|
+
if len(args) == 1:
|
|
31
|
+
return f"pf.ProcNode[{repr_type(args[0])}]"
|
|
32
|
+
elif len(args) == 0:
|
|
33
|
+
return "pf.ProcNode"
|
|
34
|
+
else:
|
|
35
|
+
raise ValueError(f"Unsupported ProcNode type: {x} {args=}")
|
|
36
|
+
|
|
37
|
+
if hasattr(pf, x.__name__):
|
|
38
|
+
if len(args):
|
|
39
|
+
raise ValueError(f"procfunc type had unhandled annotations: {x} {args=}")
|
|
40
|
+
return f"pf.{x.__name__}"
|
|
41
|
+
|
|
42
|
+
if x.__module__ == "builtins":
|
|
43
|
+
return x.__name__
|
|
44
|
+
|
|
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
|
+
if getattr(x, "__module__", None) == "procfunc.nodes.types":
|
|
56
|
+
return f"t.{x.__name__}"
|
|
57
|
+
|
|
58
|
+
return x.__name__
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def repr_float(value: float) -> str:
|
|
62
|
+
if not math.isfinite(value):
|
|
63
|
+
return f'float("{value}")'
|
|
64
|
+
# float32 socket values: shortest exact round-trip (round(x, 8) would destroy small magnitudes)
|
|
65
|
+
return str(np.float32(value))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def repr_value(value: Any) -> str:
|
|
69
|
+
if hasattr(value, "__wrapped__"):
|
|
70
|
+
value = value.__wrapped__
|
|
71
|
+
|
|
72
|
+
if isinstance(value, cg.Proxy):
|
|
73
|
+
logger.warning(
|
|
74
|
+
f"Proxy object {value} should never appear as a raw value in codegen - "
|
|
75
|
+
f"its underlying node {value.node} was not resolved to a variable"
|
|
76
|
+
)
|
|
77
|
+
if isinstance(value, nt.ProcNode):
|
|
78
|
+
logger.warning(
|
|
79
|
+
f"Procnode object {value} should never be treated as a raw value in codegen"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if isinstance(value, np.random.Generator):
|
|
83
|
+
return "np.random.default_rng()"
|
|
84
|
+
elif isinstance(value, type):
|
|
85
|
+
return repr_type(value)
|
|
86
|
+
elif isinstance(value, np.ndarray):
|
|
87
|
+
# tolist + per-element repr is exact, unlike repr(value) which truncates
|
|
88
|
+
# to numpy printoptions precision
|
|
89
|
+
body = (
|
|
90
|
+
repr_value(value.tolist())
|
|
91
|
+
if value.dtype == np.float32
|
|
92
|
+
else repr(value.tolist())
|
|
93
|
+
)
|
|
94
|
+
return f"np.array({body}, dtype=np.{value.dtype.name})"
|
|
95
|
+
elif isinstance(value, np.dtype):
|
|
96
|
+
return f"np.dtype('{value}')"
|
|
97
|
+
elif isinstance(value, pf.Matrix):
|
|
98
|
+
# matrix constants travel as numpy arrays; stray Matrix values delegate
|
|
99
|
+
return repr_value(np.array(value, dtype=np.float32))
|
|
100
|
+
elif isinstance(value, (pf.Color, pf.Vector, pf.Euler, pf.Quaternion)):
|
|
101
|
+
comps = ", ".join(repr_float(c) for c in value)
|
|
102
|
+
return f"pf.{value.__class__.__name__}(({comps}))"
|
|
103
|
+
elif isinstance(value, enum.Enum):
|
|
104
|
+
return f"{type(value).__name__}.{value.name}"
|
|
105
|
+
elif isinstance(value, Path):
|
|
106
|
+
return f"Path({str(value)!r})"
|
|
107
|
+
elif dataclasses.is_dataclass(value) and not isinstance(value, type):
|
|
108
|
+
args_str = ", ".join(
|
|
109
|
+
f"{f.name}={repr_value(getattr(value, f.name))}"
|
|
110
|
+
for f in dataclasses.fields(value)
|
|
111
|
+
)
|
|
112
|
+
return f"{type(value).__name__}({args_str})"
|
|
113
|
+
elif isinstance(value, list):
|
|
114
|
+
return f"[{', '.join([repr_value(x) for x in value])}]"
|
|
115
|
+
elif isinstance(value, tuple):
|
|
116
|
+
inner = ", ".join(repr_value(x) for x in value)
|
|
117
|
+
return f"({inner},)" if len(value) == 1 else f"({inner})"
|
|
118
|
+
elif isinstance(value, float) and not isinstance(value, bool):
|
|
119
|
+
return repr_float(value)
|
|
120
|
+
else:
|
|
121
|
+
return repr(value)
|
|
@@ -26,3 +26,29 @@ from .util import (
|
|
|
26
26
|
traverse_nested_graphs,
|
|
27
27
|
usages_per_node,
|
|
28
28
|
)
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"ComputeGraph",
|
|
32
|
+
"ConstantNode",
|
|
33
|
+
"FunctionCallNode",
|
|
34
|
+
"GetAttributeNode",
|
|
35
|
+
"InputPlaceholderNode",
|
|
36
|
+
"MethodCallNode",
|
|
37
|
+
"MutatedArgumentNode",
|
|
38
|
+
"Node",
|
|
39
|
+
"ProceduralNode",
|
|
40
|
+
"SubgraphCallNode",
|
|
41
|
+
"normalize_args_to_kwargs",
|
|
42
|
+
"OperatorType",
|
|
43
|
+
"AttributeProxy",
|
|
44
|
+
"Proxy",
|
|
45
|
+
"LiteralConstant",
|
|
46
|
+
"graph_nodes_equal",
|
|
47
|
+
"transform_compute_graph",
|
|
48
|
+
"transform_nodetree",
|
|
49
|
+
"traverse_breadth_first",
|
|
50
|
+
"traverse_depth_first",
|
|
51
|
+
"traverse_depth_first_node",
|
|
52
|
+
"traverse_nested_graphs",
|
|
53
|
+
"usages_per_node",
|
|
54
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from procfunc.util.pytree import PyTree
|
|
6
|
+
|
|
7
|
+
from .node import Node
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ComputeGraph:
|
|
14
|
+
inputs: PyTree[Any, Node]
|
|
15
|
+
outputs: PyTree[Any, Node]
|
|
16
|
+
name: str
|
|
17
|
+
metadata: dict[str, Any]
|
|
18
|
+
|
|
19
|
+
def __repr__(self):
|
|
20
|
+
return f"{self.__class__.__name__}({self.name!r})"
|
|
21
|
+
|
|
22
|
+
def __call__(
|
|
23
|
+
self,
|
|
24
|
+
*args,
|
|
25
|
+
**kwargs,
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Execute the compute graph. If this graph came from a tracer,
|
|
29
|
+
this should be exactly equivelant to executing the original python function.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
raise NotImplementedError("Not implemented")
|