nodebpy 0.3.0__py3-none-any.whl → 0.3.1__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.
nodebpy/builder.py CHANGED
@@ -1,10 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from ast import Return
4
- from typing import TYPE_CHECKING, Any, ClassVar, Literal
5
-
6
- from arrangebpy.arrange.routing import Socket
7
- from numpy import isin
3
+ from typing import TYPE_CHECKING, Any, ClassVar, Iterable, Literal
8
4
 
9
5
  if TYPE_CHECKING:
10
6
  from .nodes import Math, VectorMath
@@ -25,7 +21,6 @@ from .types import (
25
21
  TYPE_INPUT_ALL,
26
22
  FloatInterfaceSubtypes,
27
23
  IntegerInterfaceSubtypes,
28
- NodeBooleanMathItems,
29
24
  StringInterfaceSubtypes,
30
25
  VectorInterfaceSubtypes,
31
26
  _AttributeDomains,
@@ -121,8 +116,9 @@ class OutputInterfaceContext(DirectionalContext):
121
116
  class TreeBuilder:
122
117
  """Builder for creating Blender geometry node trees with a clean Python API."""
123
118
 
124
- _active_tree: ClassVar["TreeBuilder | None"] = None
125
- _previous_tree: ClassVar["TreeBuilder | None"] = None
119
+ _tree_contexts: ClassVar["list[TreeBuilder]"] = []
120
+ # _active_tree: ClassVar["TreeBuilder | None"] = None
121
+ # _previous_tree: ClassVar["list[TreeBuilder]"] = list()
126
122
  just_added: "Node | None" = None
127
123
 
128
124
  def __init__(
@@ -139,16 +135,22 @@ class TreeBuilder:
139
135
  self.outputs = OutputInterfaceContext(self)
140
136
  self._arrange = arrange
141
137
 
138
+ def activate_tree(self) -> None:
139
+ """Make this tree the active tree for all new node creation."""
140
+ TreeBuilder._tree_contexts.append(self)
141
+
142
+ def deactivate_tree(self) -> None:
143
+ """Whatever tree ws previously active is set to be the active one (or None if no previously active tree)."""
144
+ TreeBuilder._tree_contexts.pop()
145
+
142
146
  def __enter__(self):
143
- TreeBuilder._previous_tree = TreeBuilder._active_tree
144
- TreeBuilder._active_tree = self
147
+ self.activate_tree()
145
148
  return self
146
149
 
147
150
  def __exit__(self, *args):
148
151
  if self._arrange:
149
152
  self.arrange()
150
- TreeBuilder._active_tree = TreeBuilder._previous_tree
151
- TreeBuilder._previous_tree = None
153
+ self.deactivate_tree()
152
154
 
153
155
  @property
154
156
  def nodes(self) -> Nodes:
@@ -244,7 +246,9 @@ class NodeBuilder:
244
246
 
245
247
  def __init__(self):
246
248
  # Get active tree from context manager
247
- tree = TreeBuilder._active_tree
249
+ tree = (
250
+ TreeBuilder._tree_contexts[-1] if len(TreeBuilder._tree_contexts) else None
251
+ )
248
252
  if tree is None:
249
253
  raise RuntimeError(
250
254
  f"Node '{self.__class__.__name__}' must be created within a TreeBuilder context manager.\n"
@@ -344,18 +348,20 @@ class NodeBuilder:
344
348
 
345
349
  raise SocketError("No compatible output sockets found")
346
350
 
347
- @staticmethod
348
351
  def _find_best_socket_pair(
349
- source: "NodeBuilder | NodeSocket", target: "NodeBuilder | NodeSocket"
352
+ self,
353
+ source: "NodeBuilder | SocketLinker | NodeSocket",
354
+ target: "NodeBuilder | SocketLinker | NodeSocket",
350
355
  ) -> tuple[NodeSocket, NodeSocket]:
351
356
  """Find the best possible compatible pair of sockets between two nodes, looking only at the
352
357
  the currently available outputs from the source and the inputs from the target"""
353
358
  possible_combos = []
354
- if isinstance(source, NodeBuilder):
359
+ if isinstance(source, (NodeBuilder, SocketLinker)):
355
360
  outputs = source._available_outputs
356
- else:
361
+ elif isinstance(source, NodeSocket):
357
362
  outputs = [source]
358
- if isinstance(target, NodeBuilder):
363
+
364
+ if isinstance(target, (NodeBuilder, SocketLinker)):
359
365
  inputs = target._available_inputs
360
366
  else:
361
367
  inputs = [target]
@@ -596,7 +602,9 @@ class DynamicInputsMixin:
596
602
  _socket_data_types: tuple[str]
597
603
  _type_map: dict[str, str] = {}
598
604
 
599
- def _match_compatible_data(self, *sockets: NodeSocket) -> tuple[NodeSocket, str]:
605
+ def _match_compatible_data(
606
+ self, sockets: Iterable[NodeSocket]
607
+ ) -> tuple[NodeSocket, str]:
600
608
  possible = []
601
609
  for socket in sockets:
602
610
  compatible = SOCKET_COMPATIBILITY.get(socket.type, ())
@@ -612,8 +620,8 @@ class DynamicInputsMixin:
612
620
  raise SocketError("No compatible socket found")
613
621
 
614
622
  def _find_best_socket_pair(
615
- self, source: NodeBuilder, target: NodeBuilder
616
- ) -> tuple[NodeSocket, NodeSocket] | None:
623
+ self, source: NodeBuilder | NodeSocket, target: NodeBuilder | NodeSocket
624
+ ) -> tuple[NodeSocket, NodeSocket]:
617
625
  try:
618
626
  return super()._find_best_socket_pair(source, target)
619
627
  except SocketError:
@@ -629,9 +637,9 @@ class DynamicInputsMixin:
629
637
  target.inputs[target_name].socket,
630
638
  )
631
639
 
632
- for target_name, source_socket in new_sockets.items():
633
- target_socket = target.inputs[target_name].socket
634
- return (source_socket, target_socket)
640
+ # for target_name, source_socket in new_sockets.items():
641
+ # target_socket = target.inputs[target_name].socket
642
+ # return (source_socket, target_socket)
635
643
 
636
644
  # def _best_output_socket(self, type: str) -> NodeSocket:
637
645
  # # compatible = SOCKET_COMPATIBILITY.get(type, ())
@@ -655,7 +663,7 @@ class DynamicInputsMixin:
655
663
  items[arg._default_output_socket.name] = arg
656
664
  items.update(kwargs)
657
665
  for key, source in items.items():
658
- socket_source, type = self._match_compatible_data(*source.node.outputs)
666
+ socket_source, type = self._match_compatible_data(source._available_outputs)
659
667
  if type in self._type_map:
660
668
  type = self._type_map[type]
661
669
  socket = self._add_socket(name=key, type=type)
@@ -676,6 +684,10 @@ class SocketLinker(NodeBuilder):
676
684
  def _available_outputs(self) -> list[NodeSocket]:
677
685
  return [self.socket]
678
686
 
687
+ @property
688
+ def _available_inputs(self) -> list[NodeSocket]:
689
+ return [self.socket]
690
+
679
691
  @property
680
692
  def type(self) -> SOCKET_TYPES:
681
693
  return self.socket.type # type: ignore
nodebpy/nodes/__init__.py CHANGED
@@ -18,6 +18,9 @@ from .manual import (
18
18
  SimulationInput,
19
19
  SimulationOutput,
20
20
  SimulationZone,
21
+ ForEachGeometryElementInput,
22
+ ForEachGeometryElementOutput,
23
+ ForEachGeometryElementZone,
21
24
  FormatString,
22
25
  Value,
23
26
  AccumulateField,
@@ -414,6 +417,9 @@ __all__ = (
414
417
  "FlipFaces",
415
418
  "FloatCurve",
416
419
  "FloatToInteger",
420
+ "ForEachGeometryElementInput",
421
+ "ForEachGeometryElementOutput",
422
+ "ForEachGeometryElementZone",
417
423
  "FormatString",
418
424
  "GaborTexture",
419
425
  "Gamma",
@@ -11,13 +11,16 @@ from ..types import (
11
11
  TYPE_INPUT_STRING,
12
12
  TYPE_INPUT_ROTATION,
13
13
  TYPE_INPUT_COLOR,
14
+ TYPE_INPUT_MATRIX,
14
15
  TYPE_INPUT_VALUE,
15
16
  TYPE_INPUT_VECTOR,
16
17
  )
17
18
 
18
19
 
19
20
  class BlurAttribute(NodeBuilder):
20
- """Mix attribute values of neighboring elements"""
21
+ """
22
+ Mix attribute values of neighboring elements
23
+ """
21
24
 
22
25
  _bl_idname = "GeometryNodeBlurAttribute"
23
26
  node: bpy.types.GeometryNodeBlurAttribute
@@ -109,7 +112,9 @@ class BlurAttribute(NodeBuilder):
109
112
 
110
113
 
111
114
  class DomainSize(NodeBuilder):
112
- """Retrieve the number of elements in a geometry for each attribute domain"""
115
+ """
116
+ Retrieve the number of elements in a geometry for each attribute domain
117
+ """
113
118
 
114
119
  _bl_idname = "GeometryNodeAttributeDomainSize"
115
120
  node: bpy.types.GeometryNodeAttributeDomainSize
@@ -181,7 +186,9 @@ class DomainSize(NodeBuilder):
181
186
 
182
187
 
183
188
  class RemoveNamedAttribute(NodeBuilder):
184
- """Delete an attribute with a specified name from a geometry. Typically used to optimize performance"""
189
+ """
190
+ Delete an attribute with a specified name from a geometry. Typically used to optimize performance
191
+ """
185
192
 
186
193
  _bl_idname = "GeometryNodeRemoveAttribute"
187
194
  node: bpy.types.GeometryNodeRemoveAttribute
@@ -219,7 +226,9 @@ class RemoveNamedAttribute(NodeBuilder):
219
226
 
220
227
 
221
228
  class StoreNamedAttribute(NodeBuilder):
222
- """Store the result of a field on a geometry as an attribute with the specified name"""
229
+ """
230
+ Store the result of a field on a geometry as an attribute with the specified name
231
+ """
223
232
 
224
233
  _bl_idname = "GeometryNodeStoreNamedAttribute"
225
234
  node: bpy.types.GeometryNodeStoreNamedAttribute
@@ -360,6 +369,74 @@ class StoreNamedAttribute(NodeBuilder):
360
369
  value=value,
361
370
  )
362
371
 
372
+ @classmethod
373
+ def matrix(
374
+ cls,
375
+ geometry: TYPE_INPUT_GEOMETRY = None,
376
+ selection: TYPE_INPUT_BOOLEAN = True,
377
+ name: TYPE_INPUT_STRING = "",
378
+ value: TYPE_INPUT_MATRIX = None,
379
+ ) -> "StoreNamedAttribute":
380
+ """Create Store Named Attribute with operation '4x4 Matrix'."""
381
+ return cls(
382
+ data_type="FLOAT4X4",
383
+ geometry=geometry,
384
+ selection=selection,
385
+ name=name,
386
+ value=value,
387
+ )
388
+
389
+ @classmethod
390
+ def int8(
391
+ cls,
392
+ geometry: TYPE_INPUT_GEOMETRY = None,
393
+ selection: TYPE_INPUT_BOOLEAN = True,
394
+ name: TYPE_INPUT_STRING = "",
395
+ value: TYPE_INPUT_INT = 0,
396
+ ) -> "StoreNamedAttribute":
397
+ """Create Store Named Attribute with operation '8-Bit Integer'."""
398
+ return cls(
399
+ data_type="INT8",
400
+ geometry=geometry,
401
+ selection=selection,
402
+ name=name,
403
+ value=value,
404
+ )
405
+
406
+ @classmethod
407
+ def vector2(
408
+ cls,
409
+ geometry: TYPE_INPUT_GEOMETRY = None,
410
+ selection: TYPE_INPUT_BOOLEAN = True,
411
+ name: TYPE_INPUT_STRING = "",
412
+ value: TYPE_INPUT_VECTOR = None,
413
+ ) -> "StoreNamedAttribute":
414
+ """Create Store Named Attribute with operation '2D Vector'."""
415
+ return cls(
416
+ data_type="FLOAT2",
417
+ geometry=geometry,
418
+ selection=selection,
419
+ name=name,
420
+ value=value,
421
+ )
422
+
423
+ @classmethod
424
+ def byte_color(
425
+ cls,
426
+ geometry: TYPE_INPUT_GEOMETRY = None,
427
+ selection: TYPE_INPUT_BOOLEAN = True,
428
+ name: TYPE_INPUT_STRING = "",
429
+ value: TYPE_INPUT_COLOR = None,
430
+ ) -> "StoreNamedAttribute":
431
+ """Create Store Named Attribute with operation 'Byte Color'."""
432
+ return cls(
433
+ data_type="BYTE_COLOR",
434
+ geometry=geometry,
435
+ selection=selection,
436
+ name=name,
437
+ value=value,
438
+ )
439
+
363
440
  @classmethod
364
441
  def point(
365
442
  cls,
@@ -411,6 +488,23 @@ class StoreNamedAttribute(NodeBuilder):
411
488
  value=value,
412
489
  )
413
490
 
491
+ @classmethod
492
+ def face_corner(
493
+ cls,
494
+ geometry: TYPE_INPUT_GEOMETRY = None,
495
+ selection: TYPE_INPUT_BOOLEAN = True,
496
+ name: TYPE_INPUT_STRING = "",
497
+ value: TYPE_INPUT_COLOR = None,
498
+ ) -> "StoreNamedAttribute":
499
+ """Create Store Named Attribute with operation 'Face Corner'."""
500
+ return cls(
501
+ domain="CORNER",
502
+ geometry=geometry,
503
+ selection=selection,
504
+ name=name,
505
+ value=value,
506
+ )
507
+
414
508
  @classmethod
415
509
  def spline(
416
510
  cls,
nodebpy/nodes/color.py CHANGED
@@ -9,7 +9,9 @@ from ..types import (
9
9
 
10
10
 
11
11
  class Gamma(NodeBuilder):
12
- """Apply a gamma correction"""
12
+ """
13
+ Apply a gamma correction
14
+ """
13
15
 
14
16
  _bl_idname = "ShaderNodeGamma"
15
17
  node: bpy.types.ShaderNodeGamma
@@ -41,7 +43,9 @@ class Gamma(NodeBuilder):
41
43
 
42
44
 
43
45
  class RgbCurves(NodeBuilder):
44
- """Apply color corrections for each color channel"""
46
+ """
47
+ Apply color corrections for each color channel
48
+ """
45
49
 
46
50
  _bl_idname = "ShaderNodeRGBCurve"
47
51
  node: bpy.types.ShaderNodeRGBCurve