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
procfunc/control.py ADDED
@@ -0,0 +1,174 @@
1
+ import functools
2
+ import logging
3
+ from typing import Callable, TypeVar
4
+
5
+ import numpy as np
6
+
7
+ import procfunc.compute_graph as cg
8
+ from procfunc import context
9
+ from procfunc import types as t
10
+ from procfunc.tracer import (
11
+ PatchFunctionTarget,
12
+ RngProxy,
13
+ TraceLevel,
14
+ register_trace_target,
15
+ )
16
+ from procfunc.util import pytree
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ T = TypeVar("T")
21
+
22
+
23
+ def _unwrap_proxy(x):
24
+ return x.node if isinstance(x, cg.Proxy) else x
25
+
26
+
27
+ def choice_idx(rng: np.random.Generator, weights: list[float]) -> int:
28
+ weights = np.array(weights)
29
+ weights = weights / weights.sum()
30
+ return rng.choice(len(weights), p=weights)
31
+
32
+
33
+ def _peekthrough_execute_all_choices(
34
+ choice_options: list[tuple[Callable[..., T], float]],
35
+ chosen_idx: int,
36
+ args: tuple,
37
+ kwargs: dict,
38
+ ) -> cg.Proxy:
39
+ evald = []
40
+ for func, weight in choice_options:
41
+ res = func(*args, **kwargs)
42
+ res = pytree.PyTree(res).map(_unwrap_proxy).obj()
43
+ evald.append((res, weight))
44
+
45
+ new_kwargs = dict(
46
+ choice_options=evald,
47
+ chosen_idx=chosen_idx,
48
+ )
49
+ node = cg.FunctionCallNode(
50
+ func=choice,
51
+ args=(),
52
+ kwargs=new_kwargs,
53
+ )
54
+ return cg.Proxy(node)
55
+
56
+
57
+ class ChoiceResultProxy(cg.Proxy):
58
+ def __init__(self, node: cg.FunctionCallNode):
59
+ assert node.func is choice
60
+ super().__init__(node)
61
+
62
+ def __call__(self, *args, **kwargs) -> cg.Proxy:
63
+ current_level = context.globals.current_trace_level
64
+ if current_level is None:
65
+ raise ValueError(f"Executed {self} while not in a tracing context?")
66
+ if current_level >= TraceLevel.RANDOM_CONTROL:
67
+ return _peekthrough_execute_all_choices(
68
+ self.node.kwargs["choice_options"],
69
+ self.node.kwargs["chosen_idx"],
70
+ args,
71
+ kwargs,
72
+ )
73
+ else:
74
+ idx = self.node.kwargs["chosen_idx"]
75
+ chosen_value = self.node.kwargs["choice_options"][idx][0]
76
+ return chosen_value(*args, **kwargs)
77
+
78
+
79
+ def _choice_create_custom_tracer_wrapper(
80
+ target: PatchFunctionTarget,
81
+ patcher,
82
+ **_kwargs,
83
+ ):
84
+ """
85
+ choice needs a custom tracing wrapper since we want to trace what happens inside its sub-options.
86
+
87
+ Behavior depends on trace_level vs RANDOM_CONTROL:
88
+ - >= RANDOM_CONTROL: peekthrough all options exhaustively
89
+ - < RANDOM_CONTROL: trace only the branch that would actually be chosen (active resolution)
90
+ """
91
+
92
+ @functools.wraps(choice)
93
+ def wrapper(
94
+ choice_rng: RngProxy,
95
+ choice_options: list[tuple[T, float]] | None = None,
96
+ chosen_idx: int | None = None,
97
+ ):
98
+ choice_values, choice_weights = zip(*choice_options)
99
+ if chosen_idx is None:
100
+ chosen_idx = choice_idx(choice_rng.rng, choice_weights)
101
+
102
+ unwrapped_options = [
103
+ (pytree.PyTree(v).map(_unwrap_proxy).obj(), w) for v, w in choice_options
104
+ ]
105
+ new_kwargs = dict(
106
+ choice_rng=choice_rng.node,
107
+ choice_options=unwrapped_options,
108
+ chosen_idx=chosen_idx,
109
+ )
110
+ node = cg.FunctionCallNode(
111
+ func=choice,
112
+ args=(),
113
+ kwargs=new_kwargs,
114
+ )
115
+ return ChoiceResultProxy(node)
116
+
117
+ return wrapper
118
+
119
+
120
+ TArgs = TypeVar("TArgs")
121
+
122
+
123
+ def choice(
124
+ choice_rng: np.random.Generator,
125
+ choice_options: list[tuple[T, float]],
126
+ chosen_idx: int | None = None,
127
+ ) -> T:
128
+ """
129
+ Args:
130
+ rng: random number generator
131
+ weights: list of weights for each option - will be normalized to sum to 1 as a probability distribution
132
+ options: list of callables to choose from
133
+ chosen: if not None, use this value instead of choosing randomly. Cannot be traced, but may appear in the output of the tracer.
134
+ chosen_idx: if not None, use this index instead of choosing randomly. Cannot be traced, but may appear in the output of the tracer.
135
+ **child_kwargs: keyword arguments to pass to the chosen callable
136
+
137
+ TODO: this function should really take args and kwargs as tuple & dict, not via expansion
138
+ but this requires our Node to handle cases with pytrees as inputs. Currently it would fail to execute the children if they are hidden in a pytree
139
+ """
140
+
141
+ choice_values, choice_weights = zip(*choice_options)
142
+ if chosen_idx is None:
143
+ chosen_idx = choice_idx(choice_rng, choice_weights)
144
+ option = choice_values[chosen_idx]
145
+ return option
146
+
147
+
148
+ _ORIG_EXECUTE_CHOICE = choice
149
+ register_trace_target(
150
+ func=choice,
151
+ trace_level=TraceLevel.RANDOM_CONTROL,
152
+ allow_exec=False,
153
+ custom_trace_wrapper_create=_choice_create_custom_tracer_wrapper,
154
+ )
155
+
156
+
157
+ def sample_collection(
158
+ rng: np.random.Generator,
159
+ func: Callable[[np.random.Generator], T],
160
+ n: int,
161
+ skip_none: bool = False,
162
+ ) -> t.Collection:
163
+ rngs = rng.spawn(n)
164
+ objects = [func(rng) for rng in rngs]
165
+ if skip_none:
166
+ objects = [obj for obj in objects if obj is not None]
167
+ return t.Collection(objects, name=func.__name__)
168
+
169
+
170
+ __all__ = [
171
+ "choice",
172
+ "choice_idx",
173
+ "choice",
174
+ ]
@@ -0,0 +1,66 @@
1
+ from pandas import read_json as _read_json
2
+
3
+ from procfunc.tracer import autowrap_module as _autowrap
4
+ from procfunc.util.manifest import module_path
5
+
6
+ from . import compositor, func, geo, math, shader
7
+ from .bpy_node_info import NodeDataType, NodeGroupType, SocketType
8
+
9
+ # ruff: noqa: E402
10
+ _autowrap(compositor, allow_exec=False)
11
+ _autowrap(func, allow_exec=False)
12
+ _autowrap(geo, allow_exec=False)
13
+ _autowrap(math, allow_exec=False)
14
+ _autowrap(shader, allow_exec=False)
15
+
16
+ from .execute.execute import (
17
+ as_nodegroup,
18
+ to_aliases,
19
+ to_compositor,
20
+ to_curve_object,
21
+ to_environment,
22
+ to_light,
23
+ to_mesh_object,
24
+ to_mesh_object_with_attributes,
25
+ to_objects_multi,
26
+ )
27
+ from .execute.util import NODE_OPERATOR_TABLE
28
+ from .node_function import node_function
29
+ from .types import (
30
+ ProcNode,
31
+ Shader,
32
+ SocketOrVal,
33
+ )
34
+
35
+ NODES_MANIFEST_PATH = module_path() / "nodes" / "manifest.json"
36
+ assert NODES_MANIFEST_PATH.exists(), f"Manifest not found at {NODES_MANIFEST_PATH}"
37
+ NODES_MANIFEST = _read_json(NODES_MANIFEST_PATH)
38
+
39
+ __all__ = [
40
+ # Node category submodules
41
+ "compositor",
42
+ "func",
43
+ "geo",
44
+ "math",
45
+ "shader",
46
+ # Core types
47
+ "ProcNode",
48
+ "Shader",
49
+ "SocketOrVal",
50
+ "NodeDataType",
51
+ "NodeGroupType",
52
+ "SocketType",
53
+ # Graph execution
54
+ "as_nodegroup",
55
+ "to_aliases",
56
+ "to_compositor",
57
+ "to_curve_object",
58
+ "to_environment",
59
+ "to_light",
60
+ "to_material",
61
+ "to_mesh_object",
62
+ "to_mesh_object_with_attributes",
63
+ "to_objects_multi",
64
+ # User-facing decorator for custom node functions
65
+ "node_function",
66
+ ]
@@ -0,0 +1,196 @@
1
+ import logging
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from typing import Self
5
+
6
+ import procfunc as pf
7
+ from procfunc.nodes.bpy_node_info import NodeDataType, NodeGroupType
8
+ from procfunc.util.log import raise_error_or_warn
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @dataclass
14
+ class RuntimeResolveDataType:
15
+ data_types: list[NodeDataType]
16
+ dependent_input_names: list[str]
17
+
18
+
19
+ class ContextualNode(Enum):
20
+ """Special node types that map to different implementations based on context."""
21
+
22
+ COMBINE_COLOR = "ContextualCombineColor"
23
+ SEPARATE_COLOR = "ContextualSeparateColor"
24
+ GROUP = "ContextualGroup"
25
+ MIX_RGB = "ContextualMixRGB"
26
+ MATH = "ContextualMath"
27
+
28
+ @classmethod
29
+ def parse_name(cls, from_name: str) -> Self | None:
30
+ try:
31
+ return cls(from_name)
32
+ except ValueError:
33
+ return None
34
+
35
+
36
+ @dataclass
37
+ class NodeContextResolution:
38
+ contextual_node: ContextualNode
39
+ node_group_type: NodeGroupType
40
+ node_type: str
41
+ input_keys_map: dict[str, tuple[str, int] | str | None] | None
42
+
43
+
44
+ # Mapping to map unified version of nodes to context-specific blender versions
45
+ # Input keys are (ContextualNode, NodeGroupType) - the unifiied version + the context we need to use it in
46
+ # Output is the (NodeType, input map) - tells what specific node to construct, then how to change the inputs for that context, if applicable
47
+
48
+ CONTEXTUAL_NODE_MAPPING = [
49
+ NodeContextResolution(
50
+ contextual_node=ContextualNode.COMBINE_COLOR,
51
+ node_group_type=NodeGroupType.SHADER,
52
+ node_type="ShaderNodeCombineColor",
53
+ input_keys_map=None,
54
+ ),
55
+ NodeContextResolution(
56
+ contextual_node=ContextualNode.COMBINE_COLOR,
57
+ node_group_type=NodeGroupType.TEXTURE,
58
+ node_type="TextureNodeCombineColor",
59
+ input_keys_map=None,
60
+ ),
61
+ NodeContextResolution(
62
+ contextual_node=ContextualNode.COMBINE_COLOR,
63
+ node_group_type=NodeGroupType.GEOMETRY,
64
+ node_type="FunctionNodeCombineColor",
65
+ input_keys_map=None,
66
+ ),
67
+ NodeContextResolution(
68
+ contextual_node=ContextualNode.SEPARATE_COLOR,
69
+ node_group_type=NodeGroupType.SHADER,
70
+ node_type="ShaderNodeSeparateColor",
71
+ input_keys_map=None,
72
+ ),
73
+ NodeContextResolution(
74
+ contextual_node=ContextualNode.SEPARATE_COLOR,
75
+ node_group_type=NodeGroupType.TEXTURE,
76
+ node_type="TextureNodeSeparateColor",
77
+ input_keys_map=None,
78
+ ),
79
+ NodeContextResolution(
80
+ contextual_node=ContextualNode.SEPARATE_COLOR,
81
+ node_group_type=NodeGroupType.GEOMETRY,
82
+ node_type="FunctionNodeSeparateColor",
83
+ input_keys_map=None,
84
+ ),
85
+ NodeContextResolution(
86
+ contextual_node=ContextualNode.SEPARATE_COLOR,
87
+ node_group_type=NodeGroupType.COMPOSITOR,
88
+ node_type="CompositorNodeSeparateColor",
89
+ input_keys_map={"Color": "Image"},
90
+ ),
91
+ NodeContextResolution(
92
+ contextual_node=ContextualNode.GROUP,
93
+ node_group_type=NodeGroupType.COMPOSITOR,
94
+ node_type="CompositorNodeGroup",
95
+ input_keys_map=None,
96
+ ),
97
+ NodeContextResolution(
98
+ contextual_node=ContextualNode.GROUP,
99
+ node_group_type=NodeGroupType.TEXTURE,
100
+ node_type="TextureNodeGroup",
101
+ input_keys_map=None,
102
+ ),
103
+ NodeContextResolution(
104
+ contextual_node=ContextualNode.GROUP,
105
+ node_group_type=NodeGroupType.GEOMETRY,
106
+ node_type="GeometryNodeGroup",
107
+ input_keys_map=None,
108
+ ),
109
+ NodeContextResolution(
110
+ contextual_node=ContextualNode.MIX_RGB,
111
+ node_group_type=NodeGroupType.SHADER,
112
+ node_type="ShaderNodeMixRGB",
113
+ input_keys_map=None,
114
+ ),
115
+ NodeContextResolution(
116
+ contextual_node=ContextualNode.MIX_RGB,
117
+ node_group_type=NodeGroupType.TEXTURE,
118
+ node_type="TextureNodeMixRGB",
119
+ input_keys_map={"Fac": "Factor"},
120
+ ),
121
+ NodeContextResolution(
122
+ contextual_node=ContextualNode.MIX_RGB,
123
+ node_group_type=NodeGroupType.GEOMETRY,
124
+ node_type="ShaderNodeMixRGB",
125
+ input_keys_map=None,
126
+ ),
127
+ NodeContextResolution(
128
+ contextual_node=ContextualNode.MATH,
129
+ node_group_type=NodeGroupType.SHADER,
130
+ node_type="ShaderNodeMath",
131
+ input_keys_map=None,
132
+ ),
133
+ NodeContextResolution(
134
+ contextual_node=ContextualNode.MATH,
135
+ node_group_type=NodeGroupType.COMPOSITOR,
136
+ node_type="CompositorNodeMath",
137
+ input_keys_map=None,
138
+ ),
139
+ NodeContextResolution(
140
+ contextual_node=ContextualNode.MATH,
141
+ node_group_type=NodeGroupType.TEXTURE,
142
+ node_type="TextureNodeMath",
143
+ input_keys_map=None,
144
+ ),
145
+ ]
146
+
147
+
148
+ def resolve_contextual_node(
149
+ node: list[NodeContextResolution], group: NodeGroupType
150
+ ) -> tuple[str, dict[str, str]]:
151
+ item = next(
152
+ (
153
+ item
154
+ for item in CONTEXTUAL_NODE_MAPPING
155
+ if (item.node_group_type == group and item.contextual_node == node)
156
+ ),
157
+ None,
158
+ )
159
+
160
+ if item is None:
161
+ raise ValueError(f"No contextual node found for {node} in {group}")
162
+
163
+ return item.node_type, item.input_keys_map
164
+
165
+
166
+ def raise_shader_normal_error(node_func_name: str, logger: logging.Logger):
167
+ """Helper to handle normal socket usage in shaders based on context setting."""
168
+
169
+ message = (
170
+ f"Using 'normal' input in {node_func_name} is not recommended. "
171
+ f"Use displacement instead. To suppress this message, set "
172
+ f"pf.context.globals.warn_mode_avoid_normal_bump = 'ignore'"
173
+ )
174
+
175
+ raise_error_or_warn(message, pf.context.globals.warn_mode_avoid_normal_bump, logger)
176
+
177
+
178
+ def raise_explicit_noise_vector_error(node_func_name: str, logger: logging.Logger):
179
+ """Helper to handle missing vector input in noise functions based on context setting."""
180
+ message = (
181
+ f"The 'vector' input is required for {node_func_name}. "
182
+ f"Infinigen requires an explicit vector input - node will not default to using texture coordinate or world coordinate like blender. "
183
+ f"To suppress this message, set pf.context.globals.warn_mode_avoid_implicit_vector = 'ignore'"
184
+ )
185
+ raise_error_or_warn(
186
+ message, pf.context.globals.warn_mode_avoid_implicit_vector, logger
187
+ )
188
+
189
+
190
+ def raise_io_error(node_func_name: str, logger: logging.Logger):
191
+ """Helper to handle IO nodes based on context setting."""
192
+ message = (
193
+ f"IO nodes are not allowed in nodegroups but we found {node_func_name} in the nodegroup. Use the nodegroup interface instead. "
194
+ "To suppress this message, set pf.context.globals.warn_mode_avoid_io_nodes = 'ignore'"
195
+ )
196
+ raise_error_or_warn(message, pf.context.globals.warn_mode_avoid_io_nodes, logger)