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 +52 -36
- nodebpy/lib/nodearrange/__init__.py +2 -0
- nodebpy/lib/nodearrange/arrange/graph.py +583 -0
- nodebpy/lib/nodearrange/arrange/ordering.py +512 -0
- nodebpy/lib/nodearrange/arrange/ranking.py +256 -0
- nodebpy/lib/nodearrange/arrange/realize.py +231 -0
- nodebpy/lib/nodearrange/arrange/stacking.py +313 -0
- nodebpy/lib/nodearrange/arrange/structs.py +132 -0
- nodebpy/lib/nodearrange/arrange/sugiyama.py +256 -0
- nodebpy/lib/nodearrange/arrange/x_coords.py +211 -0
- nodebpy/lib/nodearrange/arrange/y_coords.py +339 -0
- nodebpy/lib/nodearrange/config.py +45 -0
- nodebpy/lib/nodearrange/utils.py +109 -0
- nodebpy/nodes/__init__.py +6 -0
- nodebpy/nodes/attribute.py +98 -4
- nodebpy/nodes/color.py +6 -3
- nodebpy/nodes/converter.py +357 -54
- nodebpy/nodes/experimental.py +9 -3
- nodebpy/nodes/geometry.py +390 -94
- nodebpy/nodes/grid.py +90 -30
- nodebpy/nodes/group.py +3 -2
- nodebpy/nodes/input.py +239 -78
- nodebpy/nodes/interface.py +21 -7
- nodebpy/nodes/manual.py +6 -6
- nodebpy/nodes/output.py +8 -1
- nodebpy/nodes/texture.py +30 -10
- nodebpy/nodes/vector.py +41 -4
- nodebpy/nodes/zone.py +125 -99
- {nodebpy-0.3.0.dist-info → nodebpy-0.4.0.dist-info}/METADATA +16 -56
- nodebpy-0.4.0.dist-info/RECORD +38 -0
- nodebpy-0.3.0.dist-info/RECORD +0 -26
- {nodebpy-0.3.0.dist-info → nodebpy-0.4.0.dist-info}/WHEEL +0 -0
- {nodebpy-0.3.0.dist-info → nodebpy-0.4.0.dist-info}/entry_points.txt +0 -0
nodebpy/builder.py
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
365
|
+
elif isinstance(source, NodeSocket):
|
|
357
366
|
outputs = [source]
|
|
358
|
-
|
|
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(
|
|
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]
|
|
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
|
-
|
|
634
|
-
|
|
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(
|
|
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
|