procfunc 0.33.1__tar.gz → 0.34.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.
Files changed (107) hide show
  1. {procfunc-0.33.1/src/procfunc.egg-info → procfunc-0.34.0}/PKG-INFO +3 -1
  2. {procfunc-0.33.1 → procfunc-0.34.0}/pyproject.toml +3 -1
  3. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/__init__.py +1 -1
  4. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/color.py +8 -11
  5. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/compute_graph/__init__.py +0 -1
  6. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/compute_graph/util.py +96 -14
  7. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/context.py +0 -40
  8. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/control/__init__.py +2 -5
  9. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/color.py +4 -4
  10. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/execute/construct_standard.py +5 -9
  11. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/execute/util.py +2 -0
  12. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/func.py +21 -17
  13. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/geo.py +10 -55
  14. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/manifest.json +13 -627
  15. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/math.py +72 -4
  16. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/shader.py +0 -39
  17. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/texture.py +99 -97
  18. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/util/bindings_util.py +0 -35
  19. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/attr.py +6 -3
  20. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transforms/extract_materials.py +38 -16
  21. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transpiler/bpy_to_computegraph.py +21 -15
  22. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transpiler/parse_special_cases.py +23 -6
  23. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/util/bpy_info.py +18 -16
  24. {procfunc-0.33.1 → procfunc-0.34.0/src/procfunc.egg-info}/PKG-INFO +3 -1
  25. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc.egg-info/requires.txt +2 -0
  26. {procfunc-0.33.1 → procfunc-0.34.0}/tests/test_ops.py +28 -0
  27. {procfunc-0.33.1 → procfunc-0.34.0}/LICENSE.md +0 -0
  28. {procfunc-0.33.1 → procfunc-0.34.0}/README.md +0 -0
  29. {procfunc-0.33.1 → procfunc-0.34.0}/setup.cfg +0 -0
  30. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/cli/__init__.py +0 -0
  31. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/cli/main.py +0 -0
  32. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/codegen/__init__.py +0 -0
  33. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/codegen/codegen.py +0 -0
  34. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/codegen/identifiers.py +0 -0
  35. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/codegen/repr.py +0 -0
  36. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/compute_graph/compute_graph.py +0 -0
  37. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/compute_graph/node.py +0 -0
  38. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/compute_graph/operators_info.py +0 -0
  39. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/compute_graph/proxy.py +0 -0
  40. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/__init__.py +0 -0
  41. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/compositor.py +0 -0
  42. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/execute/construct_nodes.py +0 -0
  43. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/execute/construct_operator.py +0 -0
  44. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/execute/construct_special_cases.py +0 -0
  45. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/execute/execute.py +0 -0
  46. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/execute/infer_runtime_data_type.py +0 -0
  47. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/execute/realize.py +0 -0
  48. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/types.py +0 -0
  49. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/util/__init__.py +0 -0
  50. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/util/bpy_node_info.py +0 -0
  51. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/nodes/util/node_function.py +0 -0
  52. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/__init__.py +0 -0
  53. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/_util.py +0 -0
  54. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/addons.py +0 -0
  55. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/collection.py +0 -0
  56. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/curve.py +0 -0
  57. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/file.py +0 -0
  58. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/manifest.json +0 -0
  59. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/mesh.py +0 -0
  60. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/modifier.py +0 -0
  61. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/object.py +0 -0
  62. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/primitives/__init__.py +0 -0
  63. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/primitives/camera.py +0 -0
  64. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/primitives/curve.py +0 -0
  65. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/primitives/light.py +0 -0
  66. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/primitives/mesh.py +0 -0
  67. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/ops/uv.py +0 -0
  68. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/random.py +0 -0
  69. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/tracer/__init__.py +0 -0
  70. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/tracer/decorator.py +0 -0
  71. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/tracer/patch.py +0 -0
  72. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/tracer/proxy.py +0 -0
  73. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/tracer/trace.py +0 -0
  74. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transforms/__init__.py +0 -0
  75. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transforms/cleanup.py +0 -0
  76. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transforms/convert.py +0 -0
  77. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transforms/distribution.py +0 -0
  78. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transforms/infer_distribution.py +0 -0
  79. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transforms/parameters.py +0 -0
  80. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transforms/util.py +0 -0
  81. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transpiler/__init__.py +0 -0
  82. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transpiler/main.py +0 -0
  83. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/transpiler/parse_default_values.py +0 -0
  84. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/types.py +0 -0
  85. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/util/__init__.py +0 -0
  86. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/util/bpy_data.py +0 -0
  87. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/util/camera.py +0 -0
  88. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/util/keyframe.py +0 -0
  89. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/util/log.py +0 -0
  90. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/util/manifest.py +0 -0
  91. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/util/pytree.py +0 -0
  92. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc/util/teardown.py +0 -0
  93. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc.egg-info/SOURCES.txt +0 -0
  94. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc.egg-info/dependency_links.txt +0 -0
  95. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc.egg-info/entry_points.txt +0 -0
  96. {procfunc-0.33.1 → procfunc-0.34.0}/src/procfunc.egg-info/top_level.txt +0 -0
  97. {procfunc-0.33.1 → procfunc-0.34.0}/tests/test_asset.py +0 -0
  98. {procfunc-0.33.1 → procfunc-0.34.0}/tests/test_bpy_data_cleanup.py +0 -0
  99. {procfunc-0.33.1 → procfunc-0.34.0}/tests/test_cli_transpile.py +0 -0
  100. {procfunc-0.33.1 → procfunc-0.34.0}/tests/test_codegen.py +0 -0
  101. {procfunc-0.33.1 → procfunc-0.34.0}/tests/test_codegen_matrix.py +0 -0
  102. {procfunc-0.33.1 → procfunc-0.34.0}/tests/test_compute_graph.py +0 -0
  103. {procfunc-0.33.1 → procfunc-0.34.0}/tests/test_node_function.py +0 -0
  104. {procfunc-0.33.1 → procfunc-0.34.0}/tests/test_pytree.py +0 -0
  105. {procfunc-0.33.1 → procfunc-0.34.0}/tests/test_random.py +0 -0
  106. {procfunc-0.33.1 → procfunc-0.34.0}/tests/test_trace.py +0 -0
  107. {procfunc-0.33.1 → procfunc-0.34.0}/tests/test_transforms.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: procfunc
3
- Version: 0.33.1
3
+ Version: 0.34.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
@@ -16,6 +16,8 @@ Requires-Dist: pytest; extra == "dev"
16
16
  Requires-Dist: pytest-xdist; extra == "dev"
17
17
  Requires-Dist: ruff<0.16,>=0.15; extra == "dev"
18
18
  Requires-Dist: ty; extra == "dev"
19
+ Requires-Dist: imageio; extra == "dev"
20
+ Requires-Dist: matplotlib; extra == "dev"
19
21
  Provides-Extra: docs
20
22
  Requires-Dist: sphinx; extra == "docs"
21
23
  Requires-Dist: sphinx-rtd-theme; extra == "docs"
@@ -27,7 +27,9 @@ dev = [
27
27
  "pytest",
28
28
  "pytest-xdist",
29
29
  "ruff>=0.15,<0.16",
30
- "ty"
30
+ "ty",
31
+ "imageio",
32
+ "matplotlib",
31
33
  ]
32
34
  docs = [
33
35
  "sphinx",
@@ -2,7 +2,7 @@
2
2
  # ensure this gets imported first so that mathutils etc is available even if later modules dont import bpy
3
3
  import bpy
4
4
 
5
- __version__ = "0.33.1"
5
+ __version__ = "0.34.0"
6
6
 
7
7
  from numpy.random import Generator as RNG
8
8
 
@@ -10,6 +10,8 @@ def hsv_color(
10
10
  saturation: float | None = None,
11
11
  value: float | None = None,
12
12
  ) -> t.Color:
13
+ if hsv is None and (hue is None or saturation is None or value is None):
14
+ raise ValueError("provide hsv=... or all of hue/saturation/value")
13
15
  color = t.Color()
14
16
  if hsv is not None:
15
17
  color.hsv = hsv
@@ -27,12 +29,9 @@ def rgb_color(
27
29
  b: float | None = None,
28
30
  rgb: np.ndarray | None = None,
29
31
  ) -> t.Color:
30
- color = t.Color()
31
- if rgb is not None:
32
- color.r, color.g, color.b = rgb
33
- else:
34
- color.r, color.g, color.b = r, g, b
35
- return color
32
+ if rgb is None and (r is None or g is None or b is None):
33
+ raise ValueError("provide rgb=... or all of r/g/b")
34
+ return t.Color(rgb if rgb is not None else (r, g, b))
36
35
 
37
36
 
38
37
  def _srgb_to_linearrgb(c):
@@ -44,14 +43,12 @@ def _srgb_to_linearrgb(c):
44
43
  return ((c + 0.055) / 1.055) ** 2.4
45
44
 
46
45
 
47
- def _hex_to_rgb(h: int, alpha: float = 1):
46
+ def _hex_to_rgb(h: int):
48
47
  r = (h & 0xFF0000) >> 16
49
48
  g = (h & 0x00FF00) >> 8
50
49
  b = h & 0x0000FF
51
50
  return tuple([_srgb_to_linearrgb(c / 0xFF) for c in (r, g, b)])
52
51
 
53
52
 
54
- def hex_color(h: int, alpha: float = 1):
55
- c = t.Color()
56
- c.r, c.g, c.b = _hex_to_rgb(h, alpha)
57
- return c
53
+ def hex_color(h: int) -> t.Color:
54
+ return t.Color(_hex_to_rgb(h))
@@ -45,7 +45,6 @@ __all__ = [
45
45
  "LiteralConstant",
46
46
  "graph_nodes_equal",
47
47
  "transform_compute_graph",
48
- "transform_nodetree",
49
48
  "traverse_breadth_first",
50
49
  "traverse_depth_first",
51
50
  "traverse_depth_first_node",
@@ -3,10 +3,21 @@ import logging
3
3
  from collections import defaultdict, deque
4
4
  from typing import Any, Callable, Generator, Literal, TypeVar
5
5
 
6
+ import numpy as np
7
+
6
8
  from procfunc.util import pytree
7
9
 
8
10
  from .compute_graph import ComputeGraph
9
- from .node import Node, SubgraphCallNode
11
+ from .node import (
12
+ ConstantNode,
13
+ FunctionCallNode,
14
+ GetAttributeNode,
15
+ InputPlaceholderNode,
16
+ MethodCallNode,
17
+ Node,
18
+ ProceduralNode,
19
+ SubgraphCallNode,
20
+ )
10
21
 
11
22
  logger = logging.getLogger(__name__)
12
23
 
@@ -40,6 +51,7 @@ def traverse_breadth_first(
40
51
 
41
52
  visited = set()
42
53
  frontier = deque((None, name, node) for name, node in graph.outputs.items())
54
+ visited.update(id(node) for _, _, node in frontier)
43
55
  # logger.debug(f"{traverse_breadth_first.__name__} {graph.name} {len(frontier)=}")
44
56
 
45
57
  def res(parent, name, child):
@@ -61,10 +73,6 @@ def traverse_breadth_first(
61
73
  if not isinstance(node, Node):
62
74
  continue
63
75
 
64
- if id(node) in visited:
65
- continue
66
- visited.add(id(node))
67
-
68
76
  yield res(parent, name, node)
69
77
 
70
78
  children = list(pytree.PyTree(node.args).items()) + list(
@@ -75,6 +83,7 @@ def traverse_breadth_first(
75
83
  continue
76
84
  if id(arg) in visited:
77
85
  continue
86
+ visited.add(id(arg))
78
87
  frontier.append((node, key, arg))
79
88
 
80
89
 
@@ -201,22 +210,95 @@ def usages_per_node(
201
210
  return dict(usages)
202
211
 
203
212
 
204
- def graph_nodes_equal(graph1: ComputeGraph, graph2: ComputeGraph) -> bool:
205
- nodes1 = list(traverse_depth_first(graph1))
206
- nodes2 = list(traverse_depth_first(graph2))
207
- if len(nodes1) != len(nodes2):
213
+ def _value_equal(a: Any, b: Any) -> bool:
214
+ """Array-safe equality for non-node values (constants, attrs, defaults)."""
215
+ if a is b:
216
+ return True
217
+ if isinstance(a, np.ndarray) or isinstance(b, np.ndarray):
218
+ return np.array_equal(a, b)
219
+ if isinstance(a, dict) and isinstance(b, dict):
220
+ return a.keys() == b.keys() and all(_value_equal(a[k], b[k]) for k in a)
221
+ if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):
222
+ return (
223
+ type(a) is type(b)
224
+ and len(a) == len(b)
225
+ and all(_value_equal(x, y) for x, y in zip(a, b))
226
+ )
227
+ try:
228
+ return bool(a == b)
229
+ except (ValueError, TypeError):
230
+ return False
231
+
232
+
233
+ def _args_equal(a: Any, b: Any, memo: dict[tuple[int, int], bool]) -> bool:
234
+ """Structural equality of an args/kwargs tree: same container shape, with
235
+ node-valued leaves compared recursively and other leaves by value."""
236
+ tree_a = pytree.PyTree(a)
237
+ tree_b = pytree.PyTree(b)
238
+ if tree_a.spec != tree_b.spec:
208
239
  return False
209
- for node1, node2 in zip(nodes1, nodes2):
210
- if type(node1) is not type(node2):
240
+ for leaf_a, leaf_b in zip(tree_a.values(), tree_b.values()):
241
+ a_is_node = isinstance(leaf_a, Node)
242
+ b_is_node = isinstance(leaf_b, Node)
243
+ if a_is_node != b_is_node:
211
244
  return False
212
- if isinstance(node1, SubgraphCallNode):
213
- if not graph_nodes_equal(node1.subgraph, node2.subgraph):
245
+ if a_is_node:
246
+ if not _nodes_equal(leaf_a, leaf_b, memo):
214
247
  return False
215
- elif node1.args != node2.args or node1.kwargs != node2.kwargs:
248
+ elif not _value_equal(leaf_a, leaf_b):
216
249
  return False
217
250
  return True
218
251
 
219
252
 
253
+ def _nodes_equal(node1: Node, node2: Node, memo: dict[tuple[int, int], bool]) -> bool:
254
+ if node1 is node2:
255
+ return True
256
+ if type(node1) is not type(node2):
257
+ return False
258
+
259
+ key = (id(node1), id(node2))
260
+ cached = memo.get(key)
261
+ if cached is not None:
262
+ return cached
263
+ memo[key] = True # optimistic, breaks cycles in shared DAGs
264
+
265
+ result = True
266
+ if isinstance(node1, SubgraphCallNode):
267
+ result = graph_nodes_equal(node1.subgraph, node2.subgraph)
268
+ if result and isinstance(node1, FunctionCallNode):
269
+ result = node1.func is node2.func
270
+ if result and isinstance(node1, MethodCallNode):
271
+ result = node1.method_name == node2.method_name
272
+ if result and isinstance(node1, GetAttributeNode):
273
+ result = node1.attribute_name == node2.attribute_name
274
+ if result and isinstance(node1, ProceduralNode):
275
+ result = node1.node_type == node2.node_type and _value_equal(
276
+ node1.attrs, node2.attrs
277
+ )
278
+ if result and isinstance(node1, ConstantNode):
279
+ result = _value_equal(node1.value, node2.value)
280
+ if result and isinstance(node1, InputPlaceholderNode):
281
+ result = node1.input_name == node2.input_name and _value_equal(
282
+ node1.default_value, node2.default_value
283
+ )
284
+ if result:
285
+ result = _args_equal(node1.args, node2.args, memo) and _args_equal(
286
+ node1.kwargs, node2.kwargs, memo
287
+ )
288
+
289
+ memo[key] = result
290
+ return result
291
+
292
+
293
+ def graph_nodes_equal(graph1: ComputeGraph, graph2: ComputeGraph) -> bool:
294
+ nodes1 = list(traverse_depth_first(graph1))
295
+ nodes2 = list(traverse_depth_first(graph2))
296
+ if len(nodes1) != len(nodes2):
297
+ return False
298
+ memo: dict[tuple[int, int], bool] = {}
299
+ return all(_nodes_equal(node1, node2, memo) for node1, node2 in zip(nodes1, nodes2))
300
+
301
+
220
302
  def transform_nodetree(
221
303
  root: Node,
222
304
  transform_fn: Callable[[Node], Any],
@@ -13,24 +13,6 @@ class ProcfuncContext:
13
13
  num_cpu_cores: int
14
14
  current_trace_level: int | None # compared against to TraceLevel int values
15
15
 
16
- warn_mode_avoid_normal_bump: Literal["ignore", "warn", "throw"]
17
- """
18
- Set to 'throw' for strict usage. Using a normal map / bump map / BSDF vector input _can_ be useful,
19
- but its always better to do it via the Displacement output of the shader (so that it at least can also be done as displacement)
20
- """
21
-
22
- warn_mode_avoid_implicit_vector: Literal["ignore", "warn", "throw"]
23
- """
24
- Set to 'throw' to force the user to specify exactly what vector to sample.
25
- This prevents materials from being incorrect when on a moving object, or ignoring uv coordinates
26
- """
27
-
28
- warn_mode_avoid_io_nodes: Literal["ignore", "warn", "throw"]
29
- """
30
- Set to 'throw' to prevent floating Value Vector Color nodes, or strange output nodes like AOV in shader
31
- Intent is to force nodegroups to be more functional - all inputs and outputs go through nodegroup interface
32
- """
33
-
34
16
  warn_mode_empty_geonodes: Literal["ignore", "warn", "throw"]
35
17
  """
36
18
  Controls behavior when a geometry node graph produces no mesh geometry (e.g. unconnected inputs).
@@ -44,16 +26,10 @@ class ProcfuncContext:
44
26
 
45
27
  def set_strict(self):
46
28
  """Set all warning modes to 'throw'"""
47
- self.warn_mode_avoid_normal_bump = "throw"
48
- self.warn_mode_avoid_implicit_vector = "throw"
49
- self.warn_mode_avoid_io_nodes = "throw"
50
29
  self.warn_mode_empty_geonodes = "throw"
51
30
 
52
31
  def set_warn(self):
53
32
  """Set all warning modes to 'warn'"""
54
- self.warn_mode_avoid_normal_bump = "warn"
55
- self.warn_mode_avoid_implicit_vector = "warn"
56
- self.warn_mode_avoid_io_nodes = "warn"
57
33
  self.warn_mode_empty_geonodes = "warn"
58
34
 
59
35
 
@@ -61,27 +37,11 @@ class ProcfuncContext:
61
37
 
62
38
  warn_modes = ["ignore", "warn", "throw"]
63
39
 
64
- _warn_mode_avoid_normal_bump = os.environ.get(
65
- "PROCFUNC_WARN_MODE_AVOID_NORMAL_BUMP", "ignore"
66
- )
67
- assert _warn_mode_avoid_normal_bump in warn_modes
68
-
69
- warn_mode_avoid_implicit_vector = os.environ.get(
70
- "PROCFUNC_WARN_MODE_AVOID_IMPLICIT_VECTOR", "ignore"
71
- )
72
- assert warn_mode_avoid_implicit_vector in warn_modes
73
-
74
- warn_mode_avoid_io_nodes = os.environ.get("PROCFUNC_WARN_MODE_AVOID_IO_NODES", "ignore")
75
- assert warn_mode_avoid_io_nodes in warn_modes
76
-
77
40
  _warn_mode_empty_geonodes = os.environ.get("PROCFUNC_WARN_MODE_EMPTY_GEONODES", "warn")
78
41
  assert _warn_mode_empty_geonodes in warn_modes
79
42
 
80
43
  globals = ProcfuncContext(
81
44
  num_cpu_cores=int(os.environ.get("PROCFUNC_NUM_CPU_CORES", 0)),
82
- warn_mode_avoid_normal_bump=_warn_mode_avoid_normal_bump, # type: ignore[invalid-assignment]
83
- warn_mode_avoid_implicit_vector=warn_mode_avoid_implicit_vector, # type: ignore[invalid-assignment]
84
- warn_mode_avoid_io_nodes=warn_mode_avoid_io_nodes, # type: ignore[invalid-assignment]
85
45
  warn_mode_empty_geonodes=_warn_mode_empty_geonodes, # type: ignore[invalid-assignment]
86
46
  current_trace_level=None,
87
47
  )
@@ -127,12 +127,9 @@ def choice(
127
127
  ) -> T:
128
128
  """
129
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.
130
+ choice_rng: random number generator
131
+ choice_options: list of (value, weight) tuples; weights are normalized to a probability distribution
134
132
  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
133
 
137
134
  TODO: this function should really take args and kwargs as tuple & dict, not via expansion
138
135
  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
@@ -39,7 +39,7 @@ def mix_rgb(
39
39
  clamp_factor: bool = True,
40
40
  ) -> nt.ProcNode[pt.Color]:
41
41
  """
42
- Uses a Mix Node with datatype Color
42
+ Uses a Mix Shader Node with data_type='RGBA'.
43
43
 
44
44
  NOTE: separated from float/vector mix() due to extra arguments
45
45
 
@@ -80,7 +80,7 @@ def combine_rgb(
80
80
  blue: nt.SocketOrVal[float] = 0.0,
81
81
  ) -> nt.ProcNode[pt.Color]:
82
82
  """
83
- Uses a CombineColor Shader Node.
83
+ Uses a CombineColor Shader Node in RGB mode.
84
84
 
85
85
  See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/combine_color.html
86
86
  """
@@ -97,7 +97,7 @@ def combine_hsv(
97
97
  value: nt.SocketOrVal[float] = 0.0,
98
98
  ) -> nt.ProcNode[pt.Color]:
99
99
  """
100
- Uses a CombineColor Shader Node.
100
+ Uses a CombineColor Shader Node in HSV mode.
101
101
 
102
102
  See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/combine_color.html
103
103
  """
@@ -115,7 +115,7 @@ def combine_hsl(
115
115
  lightness: nt.SocketOrVal[float] = 0.0,
116
116
  ) -> nt.ProcNode[pt.Color]:
117
117
  """
118
- Uses a CombineHSV Shader Node.
118
+ Uses a CombineColor Shader Node in HSL mode.
119
119
 
120
120
  See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/combine_color.html
121
121
  """
@@ -320,26 +320,22 @@ def _construct_procnode_standard(
320
320
  for input_name, input_py in kwargs.items():
321
321
  input_result = input_results[input_name]
322
322
  if input_result is None:
323
- # Strict-None policy: None means "leave disconnected" and is only
324
- # allowed for sockets with no default_value attr at all (Geometry,
325
- # Shader, Matrix, Virtual) - never rely on Blender's internal
326
- # defaults, whose values may change across versions. A disabled or
327
- # missing socket name is a binding bug and propagates as ValueError.
328
323
  to_socket = get_input_socket_to_connect_to(
329
324
  bl_node_tree, bl_node, input_name, None
330
325
  )
331
- # IMAGE default_value is a datablock pointer; None faithfully means "no image assigned"
332
326
  if (
333
327
  to_socket.is_multi_input
334
328
  or not hasattr(to_socket, "default_value")
335
- or to_socket.type == "IMAGE"
329
+ or to_socket.hide_value
330
+ or to_socket.default_value is None
336
331
  ):
337
332
  continue
338
333
  raise ValueError(
339
334
  f"Node {bl_node.name!r} input {to_socket.name!r} (socket type "
340
335
  f"{to_socket.type}) received None. Explicitly pass a value; None is "
341
- f"only allowed for sockets with no default_value attribute "
342
- f"(e.g. Geometry/Shader) or with a datablock pointer one (Image)."
336
+ f"only allowed for disconnected implicit-field sockets (hide_value), "
337
+ f"datablock pointers, or sockets with no default_value attribute "
338
+ f"(e.g. Geometry/Shader)."
343
339
  )
344
340
  to_socket = get_input_socket_to_connect_to(
345
341
  bl_node_tree, bl_node, input_name, input_result
@@ -327,6 +327,8 @@ def assign_default_value(
327
327
  target_socket.default_value = t.Vector(input_val)
328
328
  case bni.NodeDataType.FLOAT:
329
329
  target_socket.default_value = float(input_val)
330
+ case bni.NodeDataType.INT:
331
+ target_socket.default_value = int(input_val)
330
332
  case bni.NodeDataType.FLOAT_MATRIX:
331
333
  # matrix values travel as numpy arrays in the compute graph;
332
334
  # coerce back to mathutils.Matrix for bpy
@@ -6,7 +6,6 @@ from procfunc.nodes import types as nt
6
6
  from procfunc.nodes.util.bindings_util import (
7
7
  ContextualNode,
8
8
  RuntimeResolveDataType,
9
- raise_io_error,
10
9
  )
11
10
  from procfunc.nodes.util.bpy_node_info import NodeDataType
12
11
 
@@ -92,6 +91,7 @@ def boolean_or(
92
91
  a: nt.SocketOrVal[bool],
93
92
  b: nt.SocketOrVal[bool],
94
93
  ) -> nt.ProcNode[bool]:
94
+ """Uses a BooleanMath Function Node with operation='OR'."""
95
95
  return nt.ProcNode.from_nodetype(
96
96
  node_type="FunctionNodeBooleanMath",
97
97
  inputs={("Boolean", 0): a, ("Boolean", 1): b},
@@ -103,6 +103,7 @@ def boolean_and(
103
103
  a: nt.SocketOrVal[bool],
104
104
  b: nt.SocketOrVal[bool],
105
105
  ) -> nt.ProcNode[bool]:
106
+ """Uses a BooleanMath Function Node with operation='AND'."""
106
107
  return nt.ProcNode.from_nodetype(
107
108
  node_type="FunctionNodeBooleanMath",
108
109
  inputs={("Boolean", 0): a, ("Boolean", 1): b},
@@ -114,6 +115,7 @@ def boolean_xor(
114
115
  a: nt.SocketOrVal[bool],
115
116
  b: nt.SocketOrVal[bool],
116
117
  ) -> nt.ProcNode[bool]:
118
+ """Uses a BooleanMath Function Node with operation='XOR'."""
117
119
  return nt.ProcNode.from_nodetype(
118
120
  node_type="FunctionNodeBooleanMath",
119
121
  inputs={("Boolean", 0): a, ("Boolean", 1): b},
@@ -125,7 +127,7 @@ def boolean_not(
125
127
  a: nt.SocketOrVal[bool],
126
128
  ) -> nt.ProcNode[bool]:
127
129
  """
128
- Uses a BooleanNot Function Node.
130
+ Uses a BooleanMath Function Node with operation='NOT'.
129
131
 
130
132
  See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/math/boolean_math.html
131
133
  """
@@ -275,6 +277,7 @@ def less_than(
275
277
  a: TCompareNumeric,
276
278
  b: TCompareNumeric,
277
279
  ) -> nt.ProcNode[bool]:
280
+ """Uses a Compare Function Node with operation='LESS_THAN'."""
278
281
  return _compare(a, b, operation="LESS_THAN")
279
282
 
280
283
 
@@ -282,6 +285,7 @@ def less_equal(
282
285
  a: TCompareNumeric,
283
286
  b: TCompareNumeric,
284
287
  ) -> nt.ProcNode[bool]:
288
+ """Uses a Compare Function Node with operation='LESS_EQUAL'."""
285
289
  return _compare(a, b, operation="LESS_EQUAL")
286
290
 
287
291
 
@@ -289,6 +293,7 @@ def greater_than(
289
293
  a: TCompareNumeric,
290
294
  b: TCompareNumeric,
291
295
  ) -> nt.ProcNode[bool]:
296
+ """Uses a Compare Function Node with operation='GREATER_THAN'."""
292
297
  return _compare(a, b, operation="GREATER_THAN")
293
298
 
294
299
 
@@ -296,6 +301,7 @@ def greater_equal(
296
301
  a: TCompareNumeric,
297
302
  b: TCompareNumeric,
298
303
  ) -> nt.ProcNode[bool]:
304
+ """Uses a Compare Function Node with operation='GREATER_EQUAL'."""
299
305
  return _compare(a, b, operation="GREATER_EQUAL")
300
306
 
301
307
 
@@ -311,6 +317,7 @@ def equal(
311
317
  b: TCompareEqual,
312
318
  epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
313
319
  ) -> nt.ProcNode[bool]:
320
+ """Uses a Compare Function Node with operation='EQUAL'."""
314
321
  return _compare(a, b, epsilon, operation="EQUAL")
315
322
 
316
323
 
@@ -319,6 +326,7 @@ def not_equal(
319
326
  b: TCompareEqual,
320
327
  epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
321
328
  ) -> nt.ProcNode[bool]:
329
+ """Uses a Compare Function Node with operation='NOT_EQUAL'."""
322
330
  return _compare(a, b, epsilon, operation="NOT_EQUAL")
323
331
 
324
332
 
@@ -332,6 +340,7 @@ def vector_elementwise_less_than(
332
340
  a: nt.SocketOrVal[pt.Vector],
333
341
  b: nt.SocketOrVal[pt.Vector],
334
342
  ) -> nt.ProcNode[bool]:
343
+ """Uses a Compare Function Node with data_type='VECTOR', mode='ELEMENT', operation='LESS_THAN'."""
335
344
  return _compare(a, b, operation="LESS_THAN", data_type=NodeDataType.FLOAT_VECTOR)
336
345
 
337
346
 
@@ -339,6 +348,7 @@ def vector_elementwise_less_equal(
339
348
  a: nt.SocketOrVal[pt.Vector],
340
349
  b: nt.SocketOrVal[pt.Vector],
341
350
  ) -> nt.ProcNode[bool]:
351
+ """Uses a Compare Function Node with data_type='VECTOR', mode='ELEMENT', operation='LESS_EQUAL'."""
342
352
  return _compare(a, b, operation="LESS_EQUAL", data_type=NodeDataType.FLOAT_VECTOR)
343
353
 
344
354
 
@@ -346,6 +356,7 @@ def vector_elementwise_greater_than(
346
356
  a: nt.SocketOrVal[pt.Vector],
347
357
  b: nt.SocketOrVal[pt.Vector],
348
358
  ) -> nt.ProcNode[bool]:
359
+ """Uses a Compare Function Node with data_type='VECTOR', mode='ELEMENT', operation='GREATER_THAN'."""
349
360
  return _compare(a, b, operation="GREATER_THAN", data_type=NodeDataType.FLOAT_VECTOR)
350
361
 
351
362
 
@@ -353,6 +364,7 @@ def vector_elementwise_greater_equal(
353
364
  a: nt.SocketOrVal[pt.Vector],
354
365
  b: nt.SocketOrVal[pt.Vector],
355
366
  ) -> nt.ProcNode[bool]:
367
+ """Uses a Compare Function Node with data_type='VECTOR', mode='ELEMENT', operation='GREATER_EQUAL'."""
356
368
  return _compare(
357
369
  a, b, operation="GREATER_EQUAL", data_type=NodeDataType.FLOAT_VECTOR
358
370
  )
@@ -363,6 +375,7 @@ def vector_elementwise_equal(
363
375
  b: nt.SocketOrVal[pt.Vector],
364
376
  epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
365
377
  ) -> nt.ProcNode[bool]:
378
+ """Uses a Compare Function Node with data_type='VECTOR', mode='ELEMENT', operation='EQUAL'."""
366
379
  return _compare(
367
380
  a, b, epsilon, operation="EQUAL", data_type=NodeDataType.FLOAT_VECTOR
368
381
  )
@@ -373,6 +386,7 @@ def vector_elementwise_not_equal(
373
386
  b: nt.SocketOrVal[pt.Vector],
374
387
  epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
375
388
  ) -> nt.ProcNode[bool]:
389
+ """Uses a Compare Function Node with data_type='VECTOR', mode='ELEMENT', operation='NOT_EQUAL'."""
376
390
  return _compare(
377
391
  a, b, epsilon, operation="NOT_EQUAL", data_type=NodeDataType.FLOAT_VECTOR
378
392
  )
@@ -383,6 +397,7 @@ def color_equal(
383
397
  b: nt.SocketOrVal[pt.Color],
384
398
  epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
385
399
  ) -> nt.ProcNode[bool]:
400
+ """Uses a Compare Function Node with data_type='RGBA', operation='EQUAL'."""
386
401
  return _compare(a, b, epsilon, operation="EQUAL", data_type=NodeDataType.RGBA)
387
402
 
388
403
 
@@ -391,6 +406,7 @@ def color_not_equal(
391
406
  b: nt.SocketOrVal[pt.Color],
392
407
  epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
393
408
  ) -> nt.ProcNode[bool]:
409
+ """Uses a Compare Function Node with data_type='RGBA', operation='NOT_EQUAL'."""
394
410
  return _compare(a, b, epsilon, operation="NOT_EQUAL", data_type=NodeDataType.RGBA)
395
411
 
396
412
 
@@ -398,6 +414,7 @@ def color_brighter(
398
414
  a: nt.SocketOrVal[pt.Color],
399
415
  b: nt.SocketOrVal[pt.Color],
400
416
  ) -> nt.ProcNode[bool]:
417
+ """Uses a Compare Function Node with data_type='RGBA', operation='BRIGHTER'."""
401
418
  return _compare(a, b, operation="BRIGHTER", data_type=NodeDataType.RGBA)
402
419
 
403
420
 
@@ -405,6 +422,7 @@ def color_darker(
405
422
  a: nt.SocketOrVal[pt.Color],
406
423
  b: nt.SocketOrVal[pt.Color],
407
424
  ) -> nt.ProcNode[bool]:
425
+ """Uses a Compare Function Node with data_type='RGBA', operation='DARKER'."""
408
426
  return _compare(a, b, operation="DARKER", data_type=NodeDataType.RGBA)
409
427
 
410
428
 
@@ -447,8 +465,6 @@ def input_bool(boolean: bool = False) -> t.ProcNode[bool]:
447
465
  See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/input/constant/boolean.html
448
466
  """
449
467
 
450
- raise_io_error("input_bool", logger=logger)
451
-
452
468
  return t.ProcNode.from_nodetype(
453
469
  node_type="FunctionNodeInputBool",
454
470
  inputs={},
@@ -463,8 +479,6 @@ def input_color(value: tuple = (0.5, 0.5, 0.5, 1.0)) -> t.ProcNode[pt.Color]:
463
479
  See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/input/constant/color.html
464
480
  """
465
481
 
466
- raise_io_error("input_color", logger=logger)
467
-
468
482
  return t.ProcNode.from_nodetype(
469
483
  node_type="FunctionNodeInputColor",
470
484
  inputs={},
@@ -479,8 +493,6 @@ def input_int(integer: int = 0) -> t.ProcNode[int]:
479
493
  See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/input/constant/integer.html
480
494
  """
481
495
 
482
- raise_io_error("input_int", logger=logger)
483
-
484
496
  return t.ProcNode.from_nodetype(
485
497
  node_type="FunctionNodeInputInt",
486
498
  inputs={},
@@ -495,8 +507,6 @@ def input_rotation(rotation_euler: tuple = (0.0, 0.0, 0.0)) -> t.ProcNode[pt.Vec
495
507
  See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/input/constant/rotation.html
496
508
  """
497
509
 
498
- raise_io_error("input_rotation", logger=logger)
499
-
500
510
  return t.ProcNode.from_nodetype(
501
511
  node_type="FunctionNodeInputRotation",
502
512
  inputs={},
@@ -517,8 +527,6 @@ def input_special_characters() -> InputSpecialCharactersResult:
517
527
  See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/text/special_characters.html
518
528
  """
519
529
 
520
- raise_io_error("input_special_characters", logger=logger)
521
-
522
530
  node = nt.ProcNode.from_nodetype(
523
531
  node_type="FunctionNodeInputSpecialCharacters",
524
532
  inputs={},
@@ -536,8 +544,6 @@ def input_string(string: str = "") -> nt.ProcNode[str]:
536
544
  See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/input/constant/string.html
537
545
  """
538
546
 
539
- raise_io_error("input_string", logger=logger)
540
-
541
547
  return nt.ProcNode.from_nodetype(
542
548
  node_type="FunctionNodeInputString",
543
549
  inputs={},
@@ -552,8 +558,6 @@ def input_vector(vector: tuple = (0.0, 0.0, 0.0)) -> nt.ProcNode[pt.Vector]:
552
558
  See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/input/constant/vector.html
553
559
  """
554
560
 
555
- raise_io_error("input_vector", logger=logger)
556
-
557
561
  return nt.ProcNode.from_nodetype(
558
562
  node_type="FunctionNodeInputVector",
559
563
  inputs={},
@@ -693,7 +697,7 @@ def random_boolean(
693
697
  seed: nt.SocketOrVal[int] = 0,
694
698
  ) -> nt.ProcNode[bool]:
695
699
  """
696
- Uses a RandomBoolean Function Node.
700
+ Uses a RandomValue Function Node with data_type='BOOLEAN'.
697
701
  """
698
702
  return nt.ProcNode.from_nodetype(
699
703
  node_type="FunctionNodeRandomValue",