nodebpy 0.3.0__py3-none-any.whl → 0.4.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.
nodebpy/builder.py CHANGED
@@ -1,15 +1,10 @@
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
11
7
 
12
- import arrangebpy
13
8
  import bpy
14
9
  from bpy.types import (
15
10
  GeometryNodeTree,
@@ -18,6 +13,7 @@ from bpy.types import (
18
13
  NodeSocket,
19
14
  )
20
15
 
16
+ from .lib.nodearrange import arrange
21
17
  from .types import (
22
18
  LINKABLE,
23
19
  SOCKET_COMPATIBILITY,
@@ -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,12 +116,16 @@ 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]"] = []
126
120
  just_added: "Node | None" = None
121
+ collapse: bool = False
127
122
 
128
123
  def __init__(
129
- self, tree: GeometryNodeTree | str = "Geometry Nodes", arrange: bool = True
124
+ self,
125
+ tree: GeometryNodeTree | str = "Geometry Nodes",
126
+ *,
127
+ collapse: bool = False,
128
+ arrange: bool = True,
130
129
  ):
131
130
  if isinstance(tree, str):
132
131
  self.tree = bpy.data.node_groups.new(tree, "GeometryNodeTree")
@@ -138,30 +137,35 @@ class TreeBuilder:
138
137
  self.inputs = InputInterfaceContext(self)
139
138
  self.outputs = OutputInterfaceContext(self)
140
139
  self._arrange = arrange
140
+ self.collapse = collapse
141
+
142
+ @property
143
+ def nodes(self) -> Nodes:
144
+ return self.tree.nodes
145
+
146
+ def activate_tree(self) -> None:
147
+ """Make this tree the active tree for all new node creation."""
148
+ TreeBuilder._tree_contexts.append(self)
149
+
150
+ def deactivate_tree(self) -> None:
151
+ """Whatever tree ws previously active is set to be the active one (or None if no previously active tree)."""
152
+ TreeBuilder._tree_contexts.pop()
141
153
 
142
154
  def __enter__(self):
143
- TreeBuilder._previous_tree = TreeBuilder._active_tree
144
- TreeBuilder._active_tree = self
155
+ self.activate_tree()
145
156
  return self
146
157
 
147
158
  def __exit__(self, *args):
148
159
  if self._arrange:
149
160
  self.arrange()
150
- TreeBuilder._active_tree = TreeBuilder._previous_tree
151
- TreeBuilder._previous_tree = None
152
-
153
- @property
154
- def nodes(self) -> Nodes:
155
- return self.tree.nodes
161
+ self.deactivate_tree()
156
162
 
157
163
  def __len__(self) -> int:
158
164
  return len(self.nodes)
159
165
 
160
166
  def arrange(self):
161
- settings = arrangebpy.LayoutSettings(
162
- horizontal_spacing=200, vertical_spacing=200, align_top_layer=True
163
- )
164
- arrangebpy.sugiyama_layout(self.tree, settings)
167
+ arrange.sugiyama.sugiyama_layout(self.tree)
168
+ arrange.sugiyama.config.reset()
165
169
 
166
170
  def _repr_markdown_(self) -> str | None:
167
171
  """
@@ -228,7 +232,9 @@ class TreeBuilder:
228
232
  return link
229
233
 
230
234
  def add(self, name: str) -> Node:
231
- return self.tree.nodes.new(name)
235
+ node = self.tree.nodes.new(name)
236
+ node.hide = self.collapse
237
+ return node
232
238
 
233
239
 
234
240
  class NodeBuilder:
@@ -244,7 +250,9 @@ class NodeBuilder:
244
250
 
245
251
  def __init__(self):
246
252
  # Get active tree from context manager
247
- tree = TreeBuilder._active_tree
253
+ tree = (
254
+ TreeBuilder._tree_contexts[-1] if len(TreeBuilder._tree_contexts) else None
255
+ )
248
256
  if tree is None:
249
257
  raise RuntimeError(
250
258
  f"Node '{self.__class__.__name__}' must be created within a TreeBuilder context manager.\n"
@@ -344,18 +352,20 @@ class NodeBuilder:
344
352
 
345
353
  raise SocketError("No compatible output sockets found")
346
354
 
347
- @staticmethod
348
355
  def _find_best_socket_pair(
349
- source: "NodeBuilder | NodeSocket", target: "NodeBuilder | NodeSocket"
356
+ self,
357
+ source: "NodeBuilder | SocketLinker | NodeSocket",
358
+ target: "NodeBuilder | SocketLinker | NodeSocket",
350
359
  ) -> tuple[NodeSocket, NodeSocket]:
351
360
  """Find the best possible compatible pair of sockets between two nodes, looking only at the
352
361
  the currently available outputs from the source and the inputs from the target"""
353
362
  possible_combos = []
354
- if isinstance(source, NodeBuilder):
363
+ if isinstance(source, (NodeBuilder, SocketLinker)):
355
364
  outputs = source._available_outputs
356
- else:
365
+ elif isinstance(source, NodeSocket):
357
366
  outputs = [source]
358
- if isinstance(target, NodeBuilder):
367
+
368
+ if isinstance(target, (NodeBuilder, SocketLinker)):
359
369
  inputs = target._available_inputs
360
370
  else:
361
371
  inputs = [target]
@@ -596,7 +606,9 @@ class DynamicInputsMixin:
596
606
  _socket_data_types: tuple[str]
597
607
  _type_map: dict[str, str] = {}
598
608
 
599
- def _match_compatible_data(self, *sockets: NodeSocket) -> tuple[NodeSocket, str]:
609
+ def _match_compatible_data(
610
+ self, sockets: Iterable[NodeSocket]
611
+ ) -> tuple[NodeSocket, str]:
600
612
  possible = []
601
613
  for socket in sockets:
602
614
  compatible = SOCKET_COMPATIBILITY.get(socket.type, ())
@@ -612,8 +624,8 @@ class DynamicInputsMixin:
612
624
  raise SocketError("No compatible socket found")
613
625
 
614
626
  def _find_best_socket_pair(
615
- self, source: NodeBuilder, target: NodeBuilder
616
- ) -> tuple[NodeSocket, NodeSocket] | None:
627
+ self, source: NodeBuilder | NodeSocket, target: NodeBuilder | NodeSocket
628
+ ) -> tuple[NodeSocket, NodeSocket]:
617
629
  try:
618
630
  return super()._find_best_socket_pair(source, target)
619
631
  except SocketError:
@@ -629,9 +641,9 @@ class DynamicInputsMixin:
629
641
  target.inputs[target_name].socket,
630
642
  )
631
643
 
632
- for target_name, source_socket in new_sockets.items():
633
- target_socket = target.inputs[target_name].socket
634
- return (source_socket, target_socket)
644
+ # for target_name, source_socket in new_sockets.items():
645
+ # target_socket = target.inputs[target_name].socket
646
+ # return (source_socket, target_socket)
635
647
 
636
648
  # def _best_output_socket(self, type: str) -> NodeSocket:
637
649
  # # compatible = SOCKET_COMPATIBILITY.get(type, ())
@@ -655,7 +667,7 @@ class DynamicInputsMixin:
655
667
  items[arg._default_output_socket.name] = arg
656
668
  items.update(kwargs)
657
669
  for key, source in items.items():
658
- socket_source, type = self._match_compatible_data(*source.node.outputs)
670
+ socket_source, type = self._match_compatible_data(source._available_outputs)
659
671
  if type in self._type_map:
660
672
  type = self._type_map[type]
661
673
  socket = self._add_socket(name=key, type=type)
@@ -676,6 +688,10 @@ class SocketLinker(NodeBuilder):
676
688
  def _available_outputs(self) -> list[NodeSocket]:
677
689
  return [self.socket]
678
690
 
691
+ @property
692
+ def _available_inputs(self) -> list[NodeSocket]:
693
+ return [self.socket]
694
+
679
695
  @property
680
696
  def type(self) -> SOCKET_TYPES:
681
697
  return self.socket.type # type: ignore
@@ -0,0 +1,2 @@
1
+ # SPDX-License-Identifier: GPL-2.0-or-later
2
+ from .arrange import sugiyama