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 +37 -25
- nodebpy/nodes/__init__.py +6 -0
- nodebpy/nodes/attribute.py +98 -4
- nodebpy/nodes/color.py +6 -2
- 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 -1
- 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.3.1.dist-info}/METADATA +11 -1
- nodebpy-0.3.1.dist-info/RECORD +26 -0
- nodebpy-0.3.0.dist-info/RECORD +0 -26
- {nodebpy-0.3.0.dist-info → nodebpy-0.3.1.dist-info}/WHEEL +0 -0
- {nodebpy-0.3.0.dist-info → nodebpy-0.3.1.dist-info}/entry_points.txt +0 -0
nodebpy/builder.py
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
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
|
|
@@ -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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
361
|
+
elif isinstance(source, NodeSocket):
|
|
357
362
|
outputs = [source]
|
|
358
|
-
|
|
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(
|
|
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]
|
|
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
|
-
|
|
634
|
-
|
|
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(
|
|
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",
|
nodebpy/nodes/attribute.py
CHANGED
|
@@ -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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
46
|
+
"""
|
|
47
|
+
Apply color corrections for each color channel
|
|
48
|
+
"""
|
|
45
49
|
|
|
46
50
|
_bl_idname = "ShaderNodeRGBCurve"
|
|
47
51
|
node: bpy.types.ShaderNodeRGBCurve
|