procfunc 0.30.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. procfunc/__init__.py +87 -0
  2. procfunc/color.py +57 -0
  3. procfunc/compute_graph/__init__.py +28 -0
  4. procfunc/compute_graph/compute_graph.py +115 -0
  5. procfunc/compute_graph/node.py +200 -0
  6. procfunc/compute_graph/operators_info.py +92 -0
  7. procfunc/compute_graph/proxy.py +173 -0
  8. procfunc/compute_graph/util.py +282 -0
  9. procfunc/context.py +115 -0
  10. procfunc/control.py +174 -0
  11. procfunc/nodes/__init__.py +66 -0
  12. procfunc/nodes/bindings_util.py +196 -0
  13. procfunc/nodes/bpy_node_info.py +280 -0
  14. procfunc/nodes/compositor.py +2242 -0
  15. procfunc/nodes/execute/construct_nodes.py +571 -0
  16. procfunc/nodes/execute/construct_special_cases.py +246 -0
  17. procfunc/nodes/execute/execute.py +548 -0
  18. procfunc/nodes/execute/infer_runtime_data_type.py +195 -0
  19. procfunc/nodes/execute/util.py +247 -0
  20. procfunc/nodes/func.py +1417 -0
  21. procfunc/nodes/geo.py +4240 -0
  22. procfunc/nodes/manifest.json +8769 -0
  23. procfunc/nodes/math.py +644 -0
  24. procfunc/nodes/node_function.py +160 -0
  25. procfunc/nodes/shader.py +2359 -0
  26. procfunc/nodes/types.py +347 -0
  27. procfunc/ops/__init__.py +35 -0
  28. procfunc/ops/_util.py +275 -0
  29. procfunc/ops/addons.py +59 -0
  30. procfunc/ops/attr.py +426 -0
  31. procfunc/ops/collection.py +90 -0
  32. procfunc/ops/curve.py +18 -0
  33. procfunc/ops/file.py +126 -0
  34. procfunc/ops/manifest.json +39149 -0
  35. procfunc/ops/mesh.py +1510 -0
  36. procfunc/ops/modifier.py +603 -0
  37. procfunc/ops/object.py +258 -0
  38. procfunc/ops/primitives/__init__.py +31 -0
  39. procfunc/ops/primitives/camera.py +45 -0
  40. procfunc/ops/primitives/curve.py +71 -0
  41. procfunc/ops/primitives/light.py +114 -0
  42. procfunc/ops/primitives/mesh.py +358 -0
  43. procfunc/ops/uv.py +271 -0
  44. procfunc/random.py +247 -0
  45. procfunc/tracer/__init__.py +43 -0
  46. procfunc/tracer/decorator.py +121 -0
  47. procfunc/tracer/patch.py +494 -0
  48. procfunc/tracer/proxy.py +127 -0
  49. procfunc/tracer/trace.py +222 -0
  50. procfunc/transforms/__init__.py +49 -0
  51. procfunc/transforms/cleanup.py +214 -0
  52. procfunc/transforms/convert.py +20 -0
  53. procfunc/transforms/distribution.py +191 -0
  54. procfunc/transforms/extract_materials.py +116 -0
  55. procfunc/transforms/infer_distribution.py +326 -0
  56. procfunc/transforms/parameters.py +15 -0
  57. procfunc/transforms/util.py +35 -0
  58. procfunc/transpiler/__init__.py +24 -0
  59. procfunc/transpiler/bpy_to_computegraph.py +1348 -0
  60. procfunc/transpiler/codegen.py +919 -0
  61. procfunc/transpiler/identifiers.py +595 -0
  62. procfunc/transpiler/main.py +299 -0
  63. procfunc/types.py +380 -0
  64. procfunc/util/__init__.py +0 -0
  65. procfunc/util/bpy_info.py +145 -0
  66. procfunc/util/camera.py +0 -0
  67. procfunc/util/keyframe.py +70 -0
  68. procfunc/util/log.py +96 -0
  69. procfunc/util/manifest.py +121 -0
  70. procfunc/util/pytree.py +343 -0
  71. procfunc/util/teardown.py +37 -0
  72. procfunc-0.30.0.dist-info/METADATA +120 -0
  73. procfunc-0.30.0.dist-info/RECORD +76 -0
  74. procfunc-0.30.0.dist-info/WHEEL +5 -0
  75. procfunc-0.30.0.dist-info/licenses/LICENSE.md +11 -0
  76. procfunc-0.30.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,195 @@
1
+ import logging
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+
5
+ import bpy
6
+ import numpy as np
7
+
8
+ from procfunc import compute_graph as cg
9
+ from procfunc.nodes import bpy_node_info as bni
10
+ from procfunc.nodes import types as nt
11
+ from procfunc.nodes.bindings_util import (
12
+ RuntimeResolveDataType,
13
+ )
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class VectorLike:
20
+ # named/typed version of None to be used with _infer_value_math_type
21
+ pass
22
+
23
+
24
+ def _infer_value_math_type(
25
+ val: bpy.types.NodeSocket | Any,
26
+ py_input: cg.Node | Any,
27
+ coerce_integers: bool = False,
28
+ ) -> bni.NodeDataType | VectorLike | None:
29
+ """
30
+ What data type of math should we do on this `val`?
31
+
32
+ Args:
33
+ val: The value to infer the data type of
34
+ coerce_integers: Whether to coerce integers to floats. Usually used only for shader context, since bl4.2 shaders dont support integer math
35
+
36
+ Returns:
37
+ bni.NodeDataType: The data type of math to do on this `val`
38
+ VectorLike: The argument is vaguely vector-like, but we leave it up to other vals to decide between Vector Color etc.
39
+ """
40
+
41
+ assert not isinstance(py_input, nt.ProcNode), py_input
42
+
43
+ logger.debug(
44
+ f"{_infer_value_math_type.__name__} starting for {type(val)=} {py_input=} {type(py_input)=}"
45
+ )
46
+
47
+ if (
48
+ isinstance(py_input, cg.Node)
49
+ and (vt := py_input.metadata.get("known_value_type", None)) is not None
50
+ ):
51
+ socket_type = bni.PYTHON_TYPE_TO_SOCKET_TYPE[vt]
52
+ res = bni.SOCKET_CLASS_TO_DATATYPE[socket_type.value]
53
+ logger.debug(f"used known_value_type={vt} from {py_input=} to infer {res=}")
54
+ elif isinstance(val, bpy.types.NodeSocket):
55
+ res = bni.SOCKET_DTYPE_TO_DATATYPE[bni.SocketDType(val.type)]
56
+ logger.debug(f"used {val.type=} to infer {res=}")
57
+ elif type(val) in bni.PYTHON_TYPE_TO_SOCKET_TYPE:
58
+ res = bni.PYTHON_TYPE_TO_SOCKET_TYPE[type(val)]
59
+ res = bni.SOCKET_CLASS_TO_DATATYPE[res.value]
60
+ elif isinstance(val, (list, tuple, np.ndarray)):
61
+ return VectorLike()
62
+ elif val is None:
63
+ return None
64
+ else:
65
+ raise ValueError(
66
+ f"{_infer_value_math_type.__name__} got {val=} of type {type(val)} "
67
+ f"which is not handled by either {bni.PYTHON_TYPE_TO_SOCKET_TYPE.keys()} "
68
+ f"or {bni.SOCKET_DTYPE_TO_DATATYPE.keys()}"
69
+ )
70
+
71
+ if coerce_integers and res == bni.NodeDataType.INT:
72
+ res = bni.NodeDataType.FLOAT
73
+
74
+ return res
75
+
76
+
77
+ _vectorlike_types = {bni.NodeDataType.FLOAT_VECTOR, bni.NodeDataType.RGBA}
78
+
79
+
80
+ def infer_operation_type(
81
+ node: cg.Node,
82
+ inputs: dict[str | int, Any],
83
+ coerce_integers: bool,
84
+ filter_keys: list[str] | None = None,
85
+ vectorlike_default: bni.NodeDataType | None = None,
86
+ ) -> bni.NodeDataType:
87
+ filtered_keys = [
88
+ k for k in node.kwargs.keys() if filter_keys is None or k in filter_keys
89
+ ]
90
+ input_data_types: list[bni.NodeDataType | VectorLike] = [
91
+ _infer_value_math_type(inputs[k], node.kwargs[k], coerce_integers)
92
+ for k in filtered_keys
93
+ ]
94
+
95
+ input_data_types.extend(
96
+ [
97
+ _infer_value_math_type(inputs[i], arg, coerce_integers)
98
+ for i, arg in enumerate(node.args)
99
+ ]
100
+ )
101
+
102
+ specific_types = [
103
+ v for v in input_data_types if not isinstance(v, VectorLike) and v is not None
104
+ ]
105
+
106
+ if len(specific_types) == 0 and len(input_data_types) > 0:
107
+ if vectorlike_default is not None:
108
+ return vectorlike_default
109
+ # we had ALL VectorLike
110
+ raise NotImplementedError(
111
+ f"Need to handle case where all arguments to {node=} are non-type-specific tuples/lists. Potentially just assume Vector?"
112
+ )
113
+
114
+ data_type = specific_types[0]
115
+
116
+ if any(v != data_type for v in specific_types[1:]):
117
+ dtypes_msg = " ".join([str(v) for v in input_data_types])
118
+ raise ValueError(
119
+ f"Attempted to create operator with non-matching input types {dtypes_msg}. "
120
+ f"Need to use .astype to hint to make arg types match a single operation datatype, e.g. .astype(float) or .astype(pf.Color)"
121
+ )
122
+
123
+ if (
124
+ len(specific_types) > 0
125
+ and any(isinstance(v, VectorLike) for v in specific_types)
126
+ and data_type not in [bni.NodeDataType.FLOAT_VECTOR, bni.NodeDataType.RGBA]
127
+ ):
128
+ raise ValueError(
129
+ f"{input_data_types=} has compatibile types EXCEPT that {data_type=} "
130
+ "is not compatible with unspecified tuples/lists (shown as VectorLike)."
131
+ )
132
+
133
+ logger.debug(f"inferred {data_type=} for {node=}")
134
+
135
+ return data_type
136
+
137
+
138
+ _vectorlike_types = {bni.NodeDataType.FLOAT_VECTOR, bni.NodeDataType.RGBA}
139
+
140
+
141
+ def resolve_operation_data_type(
142
+ node: cg.Node,
143
+ input_results: dict[str | int, Any],
144
+ resolve_options: RuntimeResolveDataType,
145
+ coerce_integers: bool,
146
+ ) -> bni.NodeDataType:
147
+ vectorlike_options = [
148
+ o for o in resolve_options.data_types if o in _vectorlike_types
149
+ ]
150
+ vectorlike_default = vectorlike_options[0] if len(vectorlike_options) > 0 else None
151
+
152
+ data_type = infer_operation_type(
153
+ node,
154
+ input_results,
155
+ coerce_integers,
156
+ filter_keys=resolve_options.dependent_input_names,
157
+ vectorlike_default=vectorlike_default,
158
+ )
159
+
160
+ if logger.isEnabledFor(logging.DEBUG):
161
+ logger.debug(
162
+ f"{resolve_operation_data_type.__name__} for {node=} inferred {data_type=}"
163
+ )
164
+
165
+ return data_type
166
+
167
+
168
+ def map_data_type_for_differing_node_interface(
169
+ data_type: bni.NodeDataType,
170
+ bl_node: bpy.types.Node,
171
+ attr_key: str,
172
+ ) -> str:
173
+ """
174
+ As of bl4.2, some nodes use differing sets of strings to specify data types.Any
175
+
176
+ We look at the options to pick which convention to UserWarning
177
+
178
+ NOTE: this may one day become unnecessary in future blender versions,
179
+ but this function will end up silently always doing the same option
180
+ """
181
+
182
+ data_type_options: list[str] = list(
183
+ bl_node.bl_rna.properties[attr_key].enum_items.keys()
184
+ )
185
+ if data_type.value in data_type_options:
186
+ # MapRange seems to use the DataType naming convention
187
+ return data_type.value
188
+ as_socket_dtype = bni.DATATYPE_TO_SOCKET_DTYPE[data_type]
189
+ if as_socket_dtype.value in data_type_options:
190
+ # wheras others e.g. Mix use the SocketType naming convention
191
+ return as_socket_dtype.value
192
+ raise ValueError(
193
+ f"Failed resolve {data_type=} or {as_socket_dtype=} to an available {data_type_options=} "
194
+ f"for {bl_node=} {attr_key=}"
195
+ )
@@ -0,0 +1,247 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Callable
3
+
4
+ import bpy
5
+
6
+ from procfunc import compute_graph as cg
7
+ from procfunc import types as t
8
+ from procfunc.nodes import bpy_node_info as bni
9
+ from procfunc.nodes import func, math
10
+ from procfunc.nodes import types as nt
11
+ from procfunc.util.log import add_exception_context_msg
12
+
13
+
14
+ @dataclass
15
+ class NodeOperatorResolution:
16
+ pf_func: Callable[..., Any]
17
+ value_type: bni.NodeDataType
18
+ operator_type: cg.OperatorType
19
+
20
+
21
+ def _float_math_defs() -> list[NodeOperatorResolution]:
22
+ return [
23
+ NodeOperatorResolution(math.add, bni.NodeDataType.FLOAT, cg.OperatorType.ADD),
24
+ NodeOperatorResolution(
25
+ math.subtract, bni.NodeDataType.FLOAT, cg.OperatorType.SUB
26
+ ),
27
+ NodeOperatorResolution(
28
+ math.multiply, bni.NodeDataType.FLOAT, cg.OperatorType.MUL
29
+ ),
30
+ NodeOperatorResolution(
31
+ math.divide, bni.NodeDataType.FLOAT, cg.OperatorType.DIV
32
+ ),
33
+ NodeOperatorResolution(math.power, bni.NodeDataType.FLOAT, cg.OperatorType.POW),
34
+ NodeOperatorResolution(
35
+ math.modulo, bni.NodeDataType.FLOAT, cg.OperatorType.MOD
36
+ ),
37
+ NodeOperatorResolution(
38
+ math.greater_than, bni.NodeDataType.FLOAT, cg.OperatorType.GREATER_THAN
39
+ ),
40
+ NodeOperatorResolution(
41
+ math.less_than, bni.NodeDataType.FLOAT, cg.OperatorType.LESS_THAN
42
+ ),
43
+ ]
44
+
45
+
46
+ def _vector_math_defs(node_data_type: bni.NodeDataType) -> list[NodeOperatorResolution]:
47
+ return [
48
+ NodeOperatorResolution(math.vector_add, node_data_type, cg.OperatorType.ADD),
49
+ NodeOperatorResolution(
50
+ math.vector_subtract, node_data_type, cg.OperatorType.SUB
51
+ ),
52
+ NodeOperatorResolution(
53
+ math.vector_multiply, node_data_type, cg.OperatorType.MUL
54
+ ),
55
+ NodeOperatorResolution(math.vector_divide, node_data_type, cg.OperatorType.DIV),
56
+ ]
57
+
58
+
59
+ NODE_OPERATOR_TABLE = [
60
+ *_float_math_defs(),
61
+ *_vector_math_defs(bni.NodeDataType.FLOAT_VECTOR),
62
+ # *_vector_math_defs(NodeDataType.RGBA), # no longer used, instead we will require explicit conversion to vector before math
63
+ NodeOperatorResolution(
64
+ math.vector_modulo, bni.NodeDataType.FLOAT_VECTOR, cg.OperatorType.MOD
65
+ ),
66
+ # NodeOperatorResolution(
67
+ # func.combine_xyz, bni.NodeDataType.FLOAT_VECTOR, cg.OperatorType.VECTOR_PACK
68
+ # ),
69
+ NodeOperatorResolution(
70
+ func.separate_xyz, bni.NodeDataType.FLOAT_VECTOR, cg.OperatorType.NOOP
71
+ ),
72
+ ]
73
+
74
+
75
+ def normalize_socket_type(socket_type: str) -> str:
76
+ # Map all vector subtypes to basic NodeSocketVector
77
+ if socket_type.startswith("NodeSocketVector"):
78
+ return "NodeSocketVector"
79
+ elif socket_type.startswith("NodeSocketFloat"):
80
+ return "NodeSocketFloat"
81
+ elif socket_type.startswith("NodeSocketInt"):
82
+ return "NodeSocketInt"
83
+ elif socket_type == "NodeSocketVirtual":
84
+ raise NotImplementedError(
85
+ f"Virtual sockets are not supported, got {socket_type}"
86
+ )
87
+ else:
88
+ return socket_type
89
+
90
+
91
+ def infer_socket_type_from_value(input_val: Any) -> str:
92
+ """Infer the appropriate Blender socket type from an input value."""
93
+ match input_val:
94
+ case bpy.types.NodeSocket():
95
+ socket_type = (
96
+ input_val.bl_idname
97
+ if hasattr(input_val, "bl_idname")
98
+ else input_val.__class__.__name__
99
+ )
100
+ return normalize_socket_type(socket_type)
101
+ case x if isinstance(x, bpy.types.NodeInternal):
102
+ try:
103
+ first_enabled = next(o for o in x.outputs if o.enabled)
104
+ return infer_socket_type_from_value(first_enabled)
105
+ except StopIteration:
106
+ return "NodeSocketFloat"
107
+ case int():
108
+ return "NodeSocketInt"
109
+ case float():
110
+ return "NodeSocketFloat"
111
+ case bool():
112
+ return "NodeSocketBool"
113
+ case str():
114
+ return "NodeSocketString"
115
+ case x if hasattr(x, "__len__"):
116
+ match len(x):
117
+ case 3:
118
+ return "NodeSocketVector"
119
+ case 4:
120
+ return "NodeSocketColor"
121
+ case 1 | 2:
122
+ return "NodeSocketFloat"
123
+ case _:
124
+ return "NodeSocketFloat"
125
+ case _:
126
+ return "NodeSocketFloat"
127
+
128
+
129
+ def get_active_sockets(
130
+ sockets: bpy.types.bpy_prop_collection,
131
+ ) -> list[bpy.types.NodeSocket]:
132
+ if len(set(s.is_output for s in sockets)) > 1:
133
+ raise ValueError(f"{sockets=} had a mix of input and output sockets")
134
+
135
+ return [
136
+ socket
137
+ for socket in sockets
138
+ if (
139
+ socket.enabled
140
+ and socket.identifier != "__extend__"
141
+ and socket.name != "" # empty-name sockets are internal to nodegroup wiring
142
+ )
143
+ ]
144
+
145
+
146
+ def get_nth_socket(
147
+ sockets: bpy.types.bpy_prop_collection, # of bpy.types.NodeSocket
148
+ socket_name: str,
149
+ index: int,
150
+ debug_node_name: str = "",
151
+ ) -> bpy.types.NodeSocket:
152
+ """
153
+ Get the nth occurrence of a socket with the given name
154
+
155
+ """
156
+
157
+ socket_name_fuzzy = socket_name.lower()
158
+ socket_spaces = socket_name_fuzzy.replace("_", " ")
159
+
160
+ count = 0
161
+ for socket in sockets:
162
+ name_lower = socket.name.lower()
163
+ if socket.enabled and (
164
+ name_lower == socket_spaces or name_lower == socket_name_fuzzy
165
+ ):
166
+ if count == index:
167
+ return socket
168
+ count += 1
169
+
170
+ enabled = [s.name for s in sockets if s.enabled]
171
+ disabled = [s.name for s in sockets if not s.enabled]
172
+
173
+ if any(d.lower() == socket_spaces for d in disabled):
174
+ raise ValueError(
175
+ f"User attempted to use input {socket_name.lower()!r} for node {debug_node_name} but it is disabled. "
176
+ "We may have failed to set the node's `data_type` or `mode` input arguments"
177
+ )
178
+
179
+ raise ValueError(
180
+ f"Node input {debug_node_name} has no enabled output socket "
181
+ f"which fuzzmatches {socket_name=} {index=}, sockets were {enabled=} and {disabled=}"
182
+ )
183
+
184
+
185
+ def get_input_socket_to_connect_to(
186
+ node_tree: bpy.types.NodeTree,
187
+ node: bpy.types.Node,
188
+ socket_id: tuple[str, int] | str,
189
+ input_val: bpy.types.NodeSocket | bpy.types.Node | Any | None,
190
+ ) -> bpy.types.NodeSocket:
191
+ assert isinstance(node_tree, bpy.types.NodeTree), node_tree
192
+
193
+ if isinstance(socket_id, tuple):
194
+ socket_name, socket_index = socket_id
195
+ elif isinstance(socket_id, str):
196
+ socket_name = socket_id
197
+ socket_index = 0
198
+ else:
199
+ raise ValueError(f"Invalid socket id {socket_id=}")
200
+
201
+ socket = get_nth_socket(node.inputs, socket_name, socket_index, node.name)
202
+ assert socket is not None, f"Node {node.name=} has no input {socket_name=}"
203
+
204
+ return socket
205
+
206
+
207
+ def assign_default_value(
208
+ target_socket: bpy.types.NodeSocket,
209
+ input_val: Any,
210
+ data_type: bni.NodeDataType | None = None,
211
+ ):
212
+ if input_val is None:
213
+ return
214
+
215
+ if data_type is None:
216
+ data_type = bni.SOCKET_DTYPE_TO_DATATYPE[bni.SocketDType(target_socket.type)]
217
+
218
+ assert not isinstance(input_val, nt.ProcNode), input_val
219
+ assert isinstance(data_type, bni.NodeDataType), data_type
220
+
221
+ match data_type:
222
+ case bni.NodeDataType.RGBA:
223
+ if len(input_val) == 3:
224
+ input_val = (input_val[0], input_val[1], input_val[2], 1.0)
225
+ assert not any(isinstance(x, nt.ProcNode) for x in input_val), input_val
226
+ target_socket.default_value = input_val
227
+ case bni.NodeDataType.FLOAT_VECTOR:
228
+ if hasattr(input_val, "__len__") and len(input_val) == 3:
229
+ input_val = (input_val[0], input_val[1], input_val[2])
230
+ assert not any(isinstance(x, nt.ProcNode) for x in input_val), input_val
231
+ target_socket.default_value = t.Vector(input_val)
232
+ case bni.NodeDataType.FLOAT:
233
+ target_socket.default_value = float(input_val)
234
+ case (
235
+ bni.NodeDataType.OBJECT
236
+ | bni.NodeDataType.MATERIAL
237
+ | bni.NodeDataType.COLLECTION
238
+ ):
239
+ if isinstance(input_val, (t.MeshObject, t.Material, t.Collection)):
240
+ target_socket.default_value = input_val.item()
241
+ else:
242
+ target_socket.default_value = input_val
243
+ case _:
244
+ with add_exception_context_msg(
245
+ prefix=f"While assigning {input_val=} to {target_socket.name=} with specified {data_type=}"
246
+ ):
247
+ target_socket.default_value = input_val