nodebpy 0.11.1__tar.gz → 0.13.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.
- {nodebpy-0.11.1 → nodebpy-0.13.0}/PKG-INFO +1 -1
- {nodebpy-0.11.1 → nodebpy-0.13.0}/pyproject.toml +1 -1
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/__init__.py +4 -3
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/builder/__init__.py +14 -1
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/builder/_utils.py +15 -6
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/builder/accessor.py +1 -1
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/builder/mixins.py +10 -2
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/builder/node.py +153 -31
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/builder/socket.py +62 -26
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/builder/tree.py +45 -26
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/compositor/__init__.py +29 -13
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/compositor/converter.py +1 -1
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/compositor/input.py +197 -0
- nodebpy-0.13.0/src/nodebpy/nodes/compositor/manual.py +336 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/__init__.py +16 -2
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/geometry.py +0 -576
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/groups.py +6 -8
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/manual.py +749 -1
- nodebpy-0.13.0/src/nodebpy/nodes/geometry/utilities.py +69 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/zone.py +137 -5
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/shader/__init__.py +18 -14
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/shader/converter.py +1 -75
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/shader/manual.py +2 -1
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/types.py +3 -0
- nodebpy-0.11.1/src/nodebpy/nodes/compositor/manual.py +0 -30
- nodebpy-0.11.1/src/nodebpy/nodes/shader/interface.py +0 -100
- {nodebpy-0.11.1 → nodebpy-0.13.0}/README.md +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/arrange.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/builder/_registry.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/builder/interface.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/diagram.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/lib/nodearrange/config.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/__init__.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/compositor/color.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/compositor/distort.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/compositor/filter.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/compositor/group.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/compositor/interface.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/compositor/matte.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/compositor/output.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/attribute.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/color.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/converter.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/grid.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/group.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/input.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/interface.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/output.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/texture.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/geometry/vector.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/shader/color.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/shader/grid.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/shader/group.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/shader/input.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/shader/output.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/shader/script.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/shader/shader.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/shader/texture.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/nodes/shader/vector.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.13.0}/src/nodebpy/sockets.py +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
from . import
|
|
2
|
-
from .builder import
|
|
1
|
+
from . import diagram, nodes, sockets
|
|
2
|
+
from .builder import (
|
|
3
|
+
TreeBuilder,
|
|
4
|
+
)
|
|
3
5
|
from .nodes import compositor, geometry, shader
|
|
4
6
|
|
|
5
7
|
__all__ = [
|
|
@@ -10,5 +12,4 @@ __all__ = [
|
|
|
10
12
|
"sockets",
|
|
11
13
|
"diagram",
|
|
12
14
|
"TreeBuilder",
|
|
13
|
-
"NodeGroupBuilder",
|
|
14
15
|
]
|
|
@@ -27,7 +27,14 @@ from .interface import (
|
|
|
27
27
|
SocketVector,
|
|
28
28
|
)
|
|
29
29
|
from .mixins import LinkingMixin, OperatorMixin
|
|
30
|
-
from .node import
|
|
30
|
+
from .node import (
|
|
31
|
+
BaseNode,
|
|
32
|
+
CustomCompositorGroup,
|
|
33
|
+
CustomGeometryGroup,
|
|
34
|
+
CustomShaderGroup,
|
|
35
|
+
DynamicInputsMixin,
|
|
36
|
+
NodeGroupBuilder,
|
|
37
|
+
)
|
|
31
38
|
from .socket import (
|
|
32
39
|
BooleanSocket,
|
|
33
40
|
BundleSocket,
|
|
@@ -82,6 +89,12 @@ __all__ = [
|
|
|
82
89
|
"DynamicInputsMixin",
|
|
83
90
|
# Node groups
|
|
84
91
|
"NodeGroupBuilder",
|
|
92
|
+
"CustomCompositorGroup",
|
|
93
|
+
"CustomGeometryGroup",
|
|
94
|
+
"CustomShaderGroup",
|
|
95
|
+
"GeometryNodeGroup",
|
|
96
|
+
"ShaderNodeGroup",
|
|
97
|
+
"CompositorNodeGroup",
|
|
85
98
|
# Type-specific socket classes (runtime)
|
|
86
99
|
"FloatSocket",
|
|
87
100
|
"VectorSocket",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any, Protocol, runtime_checkable
|
|
4
4
|
|
|
5
5
|
import bpy
|
|
6
6
|
from bpy.types import NodeSocket
|
|
@@ -67,15 +67,24 @@ def _resolve_promotion(
|
|
|
67
67
|
|
|
68
68
|
if other_prec > self_prec:
|
|
69
69
|
# Other side is dominant — swap so the linker wraps the vector/higher socket
|
|
70
|
-
|
|
70
|
+
if isinstance(other, NodeSocket):
|
|
71
|
+
other_socket = other
|
|
72
|
+
else:
|
|
73
|
+
other_socket = other._default_output_socket
|
|
71
74
|
return other_socket, self_socket, not reverse
|
|
72
75
|
|
|
73
76
|
return self_socket, other, reverse
|
|
74
77
|
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
@runtime_checkable
|
|
80
|
+
class _NodeLike(Protocol):
|
|
81
|
+
"""Protocol for objects that wrap a Blender node and expose an ``outputs`` accessor."""
|
|
78
82
|
|
|
83
|
+
outputs: Any # SocketAccessor at runtime; typed as Any to avoid circular import
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
|
|
86
|
+
@runtime_checkable
|
|
87
|
+
class _SocketLike(Protocol):
|
|
88
|
+
"""Protocol for objects that wrap a single Blender NodeSocket and expose ``.socket``."""
|
|
89
|
+
|
|
90
|
+
socket: NodeSocket
|
|
@@ -28,7 +28,7 @@ class SocketAccessor:
|
|
|
28
28
|
|
|
29
29
|
def __init__(
|
|
30
30
|
self,
|
|
31
|
-
collection: bpy.types.NodeInputs | bpy.types.NodeOutputs,
|
|
31
|
+
collection: bpy.types.NodeInputs | bpy.types.NodeOutputs | list[NodeSocket],
|
|
32
32
|
direction: Literal["input", "output"],
|
|
33
33
|
):
|
|
34
34
|
self._direction = direction
|
|
@@ -100,9 +100,12 @@ class OperatorMixin:
|
|
|
100
100
|
)
|
|
101
101
|
|
|
102
102
|
def _apply_compare_operation(self, other: Any, operation: str) -> "Math":
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
socket, other, _ = _resolve_promotion(
|
|
104
|
+
self._default_output_socket, # type: ignore[attr-defined]
|
|
105
|
+
other,
|
|
106
|
+
False,
|
|
105
107
|
)
|
|
108
|
+
return _get_socket_linker(socket)._dispatch_compare(other, operation)
|
|
106
109
|
|
|
107
110
|
def __lt__(self, other: Any) -> "Compare":
|
|
108
111
|
return self._apply_compare_operation(other, "less_than")
|
|
@@ -236,6 +239,11 @@ class LinkingMixin:
|
|
|
236
239
|
else:
|
|
237
240
|
inputs = [target]
|
|
238
241
|
|
|
242
|
+
# NodeReroute adapts its type to whatever is linked — skip type matching
|
|
243
|
+
if getattr(getattr(target, "node", None), "bl_idname", None) == "NodeReroute":
|
|
244
|
+
if outputs and inputs:
|
|
245
|
+
return inputs[0], outputs[0]
|
|
246
|
+
|
|
239
247
|
for output in outputs:
|
|
240
248
|
compat_sockets = SOCKET_COMPATIBILITY.get(output.type, ())
|
|
241
249
|
for input in inputs:
|
|
@@ -1,19 +1,47 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import (
|
|
5
|
+
TYPE_CHECKING,
|
|
6
|
+
Any,
|
|
7
|
+
Generic,
|
|
8
|
+
Iterable,
|
|
9
|
+
Literal,
|
|
10
|
+
Protocol,
|
|
11
|
+
Self,
|
|
12
|
+
TypeVar,
|
|
13
|
+
cast,
|
|
14
|
+
)
|
|
5
15
|
|
|
6
16
|
import bpy
|
|
7
|
-
from bpy.types import
|
|
8
|
-
|
|
9
|
-
|
|
17
|
+
from bpy.types import (
|
|
18
|
+
CompositorNodeGroup,
|
|
19
|
+
CompositorNodeTree,
|
|
20
|
+
GeometryNodeGroup,
|
|
21
|
+
GeometryNodeTree,
|
|
22
|
+
Node,
|
|
23
|
+
NodeSocket,
|
|
24
|
+
ShaderNodeGroup,
|
|
25
|
+
ShaderNodeTree,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from ..types import SOCKET_COMPATIBILITY, SOCKET_TYPES, InputAny
|
|
10
29
|
from ._utils import SocketError, _NodeLike, _SocketLike
|
|
11
30
|
from .accessor import SocketAccessor
|
|
12
31
|
from .mixins import LinkingMixin, OperatorMixin
|
|
13
32
|
from .tree import TreeBuilder
|
|
14
33
|
|
|
34
|
+
_T = TypeVar("_T", bound=bpy.types.NodeTree)
|
|
35
|
+
|
|
15
36
|
if TYPE_CHECKING:
|
|
16
|
-
|
|
37
|
+
|
|
38
|
+
class _DynamicTarget(Protocol):
|
|
39
|
+
"""Structural type for a node that supports dynamic socket addition."""
|
|
40
|
+
|
|
41
|
+
def _add_inputs(self, *args: Any, **kwargs: Any) -> dict[str, NodeSocket]: ...
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def inputs(self) -> SocketAccessor: ...
|
|
17
45
|
|
|
18
46
|
|
|
19
47
|
class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
@@ -25,7 +53,7 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
25
53
|
_default_output_id: str | None = None
|
|
26
54
|
_placeholder_inputs: list[str]
|
|
27
55
|
|
|
28
|
-
def __init__(self, node:
|
|
56
|
+
def __init__(self, node: Node | None = None):
|
|
29
57
|
tree = (
|
|
30
58
|
TreeBuilder._tree_contexts[-1] if len(TreeBuilder._tree_contexts) else None
|
|
31
59
|
)
|
|
@@ -72,7 +100,7 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
72
100
|
return socket
|
|
73
101
|
|
|
74
102
|
@classmethod
|
|
75
|
-
def _from_node(cls, node:
|
|
103
|
+
def _from_node(cls, node: Node) -> Self:
|
|
76
104
|
builder = cls()
|
|
77
105
|
builder.tree.nodes.remove(builder.node)
|
|
78
106
|
builder.node = node
|
|
@@ -86,7 +114,9 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
86
114
|
assert link.to_node
|
|
87
115
|
if link.to_node.bl_idname == cls._bl_idname:
|
|
88
116
|
return cls._from_node(link.to_node)
|
|
89
|
-
|
|
117
|
+
node = cls()
|
|
118
|
+
node.tree.link(socket, node.inputs._best_match(socket.type))
|
|
119
|
+
return node
|
|
90
120
|
else:
|
|
91
121
|
if socket.links:
|
|
92
122
|
for link in socket.links:
|
|
@@ -98,8 +128,9 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
98
128
|
node >> socket
|
|
99
129
|
return node
|
|
100
130
|
|
|
101
|
-
def _set_input_default_value(self, input, value):
|
|
131
|
+
def _set_input_default_value(self, input: NodeSocket, value: Any) -> None:
|
|
102
132
|
"""Set the default value for an input socket, handling type conversions."""
|
|
133
|
+
assert hasattr(input, "default_value")
|
|
103
134
|
if (
|
|
104
135
|
hasattr(input, "type")
|
|
105
136
|
and input.type == "VECTOR"
|
|
@@ -115,7 +146,7 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
115
146
|
if value is None or (
|
|
116
147
|
"GridPrune" in self._bl_idname
|
|
117
148
|
and name == "Threshold"
|
|
118
|
-
and self.node
|
|
149
|
+
and getattr(self.node, "data_type", None) == "BOOLEAN"
|
|
119
150
|
):
|
|
120
151
|
continue
|
|
121
152
|
if isinstance(value, Node):
|
|
@@ -128,7 +159,7 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
128
159
|
continue
|
|
129
160
|
|
|
130
161
|
elif isinstance(value, _SocketLike):
|
|
131
|
-
self._link_from(value, name)
|
|
162
|
+
self._link_from(value.socket, name)
|
|
132
163
|
elif isinstance(value, NodeSocket):
|
|
133
164
|
self._link_from(value, name)
|
|
134
165
|
elif isinstance(value, _NodeLike):
|
|
@@ -165,7 +196,7 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
165
196
|
return SocketAccessor(self.node.inputs, "input")
|
|
166
197
|
|
|
167
198
|
|
|
168
|
-
class DynamicInputsMixin:
|
|
199
|
+
class DynamicInputsMixin(ABC):
|
|
169
200
|
_socket_data_types: tuple[str, ...]
|
|
170
201
|
_type_map: dict[str, str] = {}
|
|
171
202
|
|
|
@@ -192,10 +223,14 @@ class DynamicInputsMixin:
|
|
|
192
223
|
try:
|
|
193
224
|
return super()._find_best_socket_pair(source, target) # type: ignore
|
|
194
225
|
except SocketError:
|
|
195
|
-
|
|
196
|
-
|
|
226
|
+
dyn = cast("_DynamicTarget", target)
|
|
227
|
+
target_name, source_socket = list(dyn._add_inputs(source).items())[0]
|
|
228
|
+
return (source_socket, dyn.inputs[target_name].socket)
|
|
229
|
+
|
|
230
|
+
@abstractmethod
|
|
231
|
+
def _add_socket(self, name: str, *args: Any, **kwargs: Any) -> NodeSocket: ...
|
|
197
232
|
|
|
198
|
-
def _add_inputs(self, *args, **kwargs) -> dict[str,
|
|
233
|
+
def _add_inputs(self, *args, **kwargs) -> dict[str, NodeSocket]:
|
|
199
234
|
"""Dictionary with {new_socket.name: from_linkable} for link creation"""
|
|
200
235
|
new_sockets = {}
|
|
201
236
|
items = {}
|
|
@@ -212,14 +247,15 @@ class DynamicInputsMixin:
|
|
|
212
247
|
return new_sockets
|
|
213
248
|
|
|
214
249
|
|
|
215
|
-
class NodeGroupBuilder(BaseNode, ABC):
|
|
250
|
+
class NodeGroupBuilder(BaseNode, ABC, Generic[_T]):
|
|
216
251
|
"""Base class for custom node groups.
|
|
217
252
|
|
|
218
253
|
Subclasses implement :meth:`_build_group` with the node-graph logic.
|
|
254
|
+
Subclass one of the editor-specific variants: :class:`GeometryNodeGroup`,
|
|
255
|
+
:class:`ShaderNodeGroup`, or :class:`CompositorNodeGroup`.
|
|
219
256
|
"""
|
|
220
257
|
|
|
221
258
|
_name: str
|
|
222
|
-
_bl_idname = "GeometryNodeGroup"
|
|
223
259
|
_warning_propagation: Literal["ALL", "ERRORS_AND_WARNINGS", "ERRORS", "NONE"] = (
|
|
224
260
|
"ALL"
|
|
225
261
|
)
|
|
@@ -234,27 +270,113 @@ class NodeGroupBuilder(BaseNode, ABC):
|
|
|
234
270
|
"TEXTURE",
|
|
235
271
|
"VECTOR",
|
|
236
272
|
] = "NONE"
|
|
237
|
-
node: bpy.types.GeometryNodeGroup
|
|
238
273
|
|
|
239
274
|
def __init__(self, **kwargs):
|
|
240
275
|
super().__init__()
|
|
241
|
-
self.
|
|
276
|
+
self._setup_node_group()
|
|
242
277
|
self.node.show_options = False
|
|
243
|
-
self.node.warning_propagation = self._warning_propagation
|
|
244
278
|
self._establish_links(**kwargs)
|
|
245
279
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
280
|
+
@property
|
|
281
|
+
@abstractmethod
|
|
282
|
+
def node_tree(self) -> _T:
|
|
283
|
+
"""The internal node tree for this group node."""
|
|
284
|
+
...
|
|
250
285
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
286
|
+
@abstractmethod
|
|
287
|
+
def _setup_node_group(self) -> None:
|
|
288
|
+
"""Set ``self.node.node_tree`` and any node-type-specific properties.
|
|
254
289
|
|
|
255
|
-
|
|
290
|
+
Called by ``__init__`` after the node is created but before links are
|
|
291
|
+
established. Concrete subclasses have a narrowed ``self.node`` type,
|
|
292
|
+
so the ``node_tree`` assignment is type-safe here rather than in the
|
|
293
|
+
base class where ``self.node`` is only ``bpy.types.Node``.
|
|
294
|
+
"""
|
|
295
|
+
...
|
|
256
296
|
|
|
257
|
-
@classmethod
|
|
258
297
|
@abstractmethod
|
|
259
|
-
def _build_group(
|
|
260
|
-
"""
|
|
298
|
+
def _build_group(self, tree: TreeBuilder) -> None:
|
|
299
|
+
"""Build the node group internals and interface."""
|
|
300
|
+
|
|
301
|
+
def _get_or_create_tree(self) -> _T:
|
|
302
|
+
existing = bpy.data.node_groups[self._name]
|
|
303
|
+
if existing.bl_idname == self.tree.tree.bl_idname:
|
|
304
|
+
return cast(_T, existing)
|
|
305
|
+
raise TypeError(
|
|
306
|
+
f"Node group '{self._name}' already exists as "
|
|
307
|
+
f"{type(existing).__name__}, not {self._bl_idname}. "
|
|
308
|
+
f"Use a unique _name for this group."
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class CustomGeometryGroup(NodeGroupBuilder[GeometryNodeTree]):
|
|
313
|
+
"""Node group in a Geometry Nodes tree."""
|
|
314
|
+
|
|
315
|
+
_bl_idname = "GeometryNodeGroup"
|
|
316
|
+
node: GeometryNodeGroup
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def node_tree(self) -> GeometryNodeTree:
|
|
320
|
+
assert self.node.node_tree is not None
|
|
321
|
+
return self.node.node_tree
|
|
322
|
+
|
|
323
|
+
def _setup_node_group(self) -> None:
|
|
324
|
+
self.node.node_tree = self._get_or_create_group()
|
|
325
|
+
self.node.warning_propagation = self._warning_propagation
|
|
326
|
+
|
|
327
|
+
def _get_or_create_group(self) -> GeometryNodeTree:
|
|
328
|
+
try:
|
|
329
|
+
return self._get_or_create_tree()
|
|
330
|
+
except KeyError:
|
|
331
|
+
with TreeBuilder.geometry(self._name) as tree:
|
|
332
|
+
self._build_group(tree)
|
|
333
|
+
tree.tree.color_tag = self._color_tag
|
|
334
|
+
return tree.tree
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class CustomShaderGroup(NodeGroupBuilder[ShaderNodeTree]):
|
|
338
|
+
"""Node group in a Shader (Material) node tree."""
|
|
339
|
+
|
|
340
|
+
_bl_idname = "ShaderNodeGroup"
|
|
341
|
+
node: ShaderNodeGroup
|
|
342
|
+
|
|
343
|
+
@property
|
|
344
|
+
def node_tree(self) -> ShaderNodeTree:
|
|
345
|
+
assert self.node.node_tree is not None
|
|
346
|
+
return self.node.node_tree
|
|
347
|
+
|
|
348
|
+
def _setup_node_group(self) -> None:
|
|
349
|
+
self.node.node_tree = self._get_or_create_group()
|
|
350
|
+
|
|
351
|
+
def _get_or_create_group(self) -> ShaderNodeTree:
|
|
352
|
+
try:
|
|
353
|
+
return self._get_or_create_tree()
|
|
354
|
+
except KeyError:
|
|
355
|
+
with TreeBuilder.shader(self._name) as tree:
|
|
356
|
+
self._build_group(tree)
|
|
357
|
+
tree.tree.color_tag = self._color_tag
|
|
358
|
+
return tree.tree
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class CustomCompositorGroup(NodeGroupBuilder[CompositorNodeTree]):
|
|
362
|
+
"""Node group in a Compositor node tree."""
|
|
363
|
+
|
|
364
|
+
_bl_idname = "CompositorNodeGroup"
|
|
365
|
+
node: CompositorNodeGroup
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def node_tree(self) -> CompositorNodeTree:
|
|
369
|
+
assert self.node.node_tree is not None
|
|
370
|
+
return self.node.node_tree
|
|
371
|
+
|
|
372
|
+
def _setup_node_group(self) -> None:
|
|
373
|
+
self.node.node_tree = self._get_or_create_group()
|
|
374
|
+
|
|
375
|
+
def _get_or_create_group(self) -> CompositorNodeTree:
|
|
376
|
+
try:
|
|
377
|
+
return self._get_or_create_tree()
|
|
378
|
+
except KeyError:
|
|
379
|
+
with TreeBuilder.compositor(self._name) as tree:
|
|
380
|
+
self._build_group(tree)
|
|
381
|
+
tree.tree.color_tag = self._color_tag
|
|
382
|
+
return tree.tree
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, Any, Iterator, overload
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Iterator, cast, overload
|
|
4
4
|
|
|
5
5
|
import bpy
|
|
6
6
|
from bpy.types import (
|
|
@@ -25,6 +25,7 @@ from bpy.types import (
|
|
|
25
25
|
NodeSocketShader,
|
|
26
26
|
NodeSocketString,
|
|
27
27
|
NodeSocketVector,
|
|
28
|
+
NodeTree,
|
|
28
29
|
)
|
|
29
30
|
from mathutils import Euler
|
|
30
31
|
|
|
@@ -55,7 +56,7 @@ class Socket(_SocketLike, OperatorMixin, LinkingMixin):
|
|
|
55
56
|
self.socket = socket
|
|
56
57
|
self.node = socket.node
|
|
57
58
|
self._default_output_id = socket.identifier
|
|
58
|
-
self._tree = TreeBuilder(socket.node.id_data)
|
|
59
|
+
self._tree = TreeBuilder(cast(NodeTree, socket.node.id_data))
|
|
59
60
|
|
|
60
61
|
@property
|
|
61
62
|
def tree(self) -> TreeBuilder:
|
|
@@ -100,7 +101,7 @@ class Socket(_SocketLike, OperatorMixin, LinkingMixin):
|
|
|
100
101
|
|
|
101
102
|
def _dispatch_math(
|
|
102
103
|
self, other: Any, operation: str, reverse: bool = False
|
|
103
|
-
) -> "
|
|
104
|
+
) -> "Math":
|
|
104
105
|
"""Scalar math dispatch (float). Uses the Math node."""
|
|
105
106
|
from ..nodes.geometry.converter import Math
|
|
106
107
|
|
|
@@ -108,7 +109,7 @@ class Socket(_SocketLike, OperatorMixin, LinkingMixin):
|
|
|
108
109
|
math_operation = "floored_modulo" if operation == "modulo" else operation
|
|
109
110
|
return getattr(Math, math_operation)(*values)
|
|
110
111
|
|
|
111
|
-
def _dispatch_unary(self, operation: str) -> "
|
|
112
|
+
def _dispatch_unary(self, operation: str) -> "Math":
|
|
112
113
|
"""Scalar unary dispatch (float). Uses the Math node."""
|
|
113
114
|
from ..nodes.geometry.converter import Math
|
|
114
115
|
|
|
@@ -118,7 +119,7 @@ class Socket(_SocketLike, OperatorMixin, LinkingMixin):
|
|
|
118
119
|
return Math.absolute(self.socket)
|
|
119
120
|
raise ValueError(f"Unknown unary operation: {operation}")
|
|
120
121
|
|
|
121
|
-
def _dispatch_floordiv(self, other: Any, reverse: bool = False) -> "
|
|
122
|
+
def _dispatch_floordiv(self, other: Any, reverse: bool = False) -> "Math":
|
|
122
123
|
"""Scalar floor division: divide then floor."""
|
|
123
124
|
from ..nodes.geometry.converter import Math
|
|
124
125
|
|
|
@@ -126,7 +127,7 @@ class Socket(_SocketLike, OperatorMixin, LinkingMixin):
|
|
|
126
127
|
divided = Math.divide(*values)
|
|
127
128
|
return Math.floor(divided)
|
|
128
129
|
|
|
129
|
-
def _dispatch_compare(self, other: Any, operation: str) -> "
|
|
130
|
+
def _dispatch_compare(self, other: Any, operation: str) -> "Compare | Math":
|
|
130
131
|
"""Scalar comparison dispatch."""
|
|
131
132
|
if isinstance(self._tree.tree, GeometryNodeTree):
|
|
132
133
|
from ..nodes.geometry.manual import Compare
|
|
@@ -140,9 +141,12 @@ class Socket(_SocketLike, OperatorMixin, LinkingMixin):
|
|
|
140
141
|
"greater_than": ("greater_than", False),
|
|
141
142
|
"less_equal": ("greater_than", True),
|
|
142
143
|
"greater_equal": ("less_than", True),
|
|
144
|
+
"equal": ("compare", False),
|
|
143
145
|
}
|
|
144
146
|
math_op, negate = _MATH_COMPARE_MAP[operation]
|
|
145
147
|
result = getattr(Math, math_op)(self.socket, other)
|
|
148
|
+
if operation == "equal":
|
|
149
|
+
result.i.value_002.default_value = 0.00001
|
|
146
150
|
if negate:
|
|
147
151
|
result = Math.subtract(1.0, result._default_output_socket)
|
|
148
152
|
return result
|
|
@@ -167,8 +171,8 @@ class Socket(_SocketLike, OperatorMixin, LinkingMixin):
|
|
|
167
171
|
def __gt__(self, other: Any) -> "Compare": ...
|
|
168
172
|
def __le__(self, other: Any) -> "Compare": ...
|
|
169
173
|
def __ge__(self, other: Any) -> "Compare": ...
|
|
170
|
-
def __eq__(self, other: Any) -> "Compare": ...
|
|
171
|
-
def __ne__(self, other: Any) -> "Compare": ...
|
|
174
|
+
def __eq__(self, other: Any) -> "Compare": ...
|
|
175
|
+
def __ne__(self, other: Any) -> "Compare": ...
|
|
172
176
|
|
|
173
177
|
|
|
174
178
|
# ---------------------------------------------------------------------------
|
|
@@ -308,7 +312,7 @@ class _VectorMixin:
|
|
|
308
312
|
|
|
309
313
|
return getattr(Compare.vector, operation)(self.socket, other)
|
|
310
314
|
else:
|
|
311
|
-
return Socket._dispatch_compare(self, other, operation)
|
|
315
|
+
return Socket._dispatch_compare(cast("Socket", self), other, operation)
|
|
312
316
|
|
|
313
317
|
if TYPE_CHECKING:
|
|
314
318
|
|
|
@@ -324,12 +328,10 @@ class _VectorMixin:
|
|
|
324
328
|
def __rfloordiv__(self, other: Any) -> "VectorMath": ...
|
|
325
329
|
def __neg__(self) -> "VectorMath": ...
|
|
326
330
|
def __abs__(self) -> "VectorMath": ...
|
|
327
|
-
def __lt__(self, other: Any) -> "Compare": ...
|
|
328
|
-
def __gt__(self, other: Any) -> "Compare": ...
|
|
329
|
-
def __le__(self, other: Any) -> "Compare": ...
|
|
330
|
-
def __ge__(self, other: Any) -> "Compare": ...
|
|
331
|
-
def __eq__(self, other: Any) -> "Compare": ... # type: ignore[override]
|
|
332
|
-
def __ne__(self, other: Any) -> "Compare": ... # type: ignore[override]
|
|
331
|
+
def __lt__(self, other: Any) -> "Compare[NodeSocketVector]": ...
|
|
332
|
+
def __gt__(self, other: Any) -> "Compare[NodeSocketVector]": ...
|
|
333
|
+
def __le__(self, other: Any) -> "Compare[NodeSocketVector]": ...
|
|
334
|
+
def __ge__(self, other: Any) -> "Compare[NodeSocketVector]": ...
|
|
333
335
|
|
|
334
336
|
|
|
335
337
|
_SEPARATE_COLOR_IDNAMES = (
|
|
@@ -356,7 +358,9 @@ class _ColorMixin:
|
|
|
356
358
|
return SeparateColor
|
|
357
359
|
|
|
358
360
|
def _separated_channel(self, channel: str) -> Socket:
|
|
361
|
+
assert self.socket.links is not None
|
|
359
362
|
for link in self.socket.links:
|
|
363
|
+
assert link.to_node is not None
|
|
360
364
|
if link.to_node.bl_idname in _SEPARATE_COLOR_IDNAMES:
|
|
361
365
|
return Socket(link.to_node.outputs[channel])
|
|
362
366
|
|
|
@@ -438,6 +442,40 @@ class _ColorMixin:
|
|
|
438
442
|
def __len__(self) -> int:
|
|
439
443
|
return 4
|
|
440
444
|
|
|
445
|
+
def _dispatch_math(
|
|
446
|
+
self, other: Any, operation: str, reverse: bool = False
|
|
447
|
+
) -> "BaseNode":
|
|
448
|
+
from ..nodes.geometry import VectorMath
|
|
449
|
+
|
|
450
|
+
values = (self.socket, other) if not reverse else (other, self.socket)
|
|
451
|
+
|
|
452
|
+
if operation == "multiply":
|
|
453
|
+
if isinstance(other, (int, float)):
|
|
454
|
+
return VectorMath.scale(self.socket, other)
|
|
455
|
+
elif isinstance(other, NodeSocket) and other.type in (
|
|
456
|
+
"VALUE",
|
|
457
|
+
"FLOAT",
|
|
458
|
+
"INT",
|
|
459
|
+
):
|
|
460
|
+
return VectorMath.scale(self.socket, other)
|
|
461
|
+
elif isinstance(other, (_SocketLike, _NodeLike)) and getattr(
|
|
462
|
+
other, "type", None
|
|
463
|
+
) in ("VALUE", "FLOAT", "INT"):
|
|
464
|
+
return VectorMath.scale(self.socket, other._default_output_socket)
|
|
465
|
+
else:
|
|
466
|
+
return VectorMath.multiply(*values)
|
|
467
|
+
else:
|
|
468
|
+
vector_method = getattr(VectorMath, operation, None)
|
|
469
|
+
assert vector_method is not None
|
|
470
|
+
if isinstance(other, (int, float)):
|
|
471
|
+
scalar_vector = (other, other, other)
|
|
472
|
+
return (
|
|
473
|
+
vector_method(self.socket, scalar_vector)
|
|
474
|
+
if not reverse
|
|
475
|
+
else vector_method(scalar_vector, self.socket)
|
|
476
|
+
)
|
|
477
|
+
return vector_method(*values)
|
|
478
|
+
|
|
441
479
|
|
|
442
480
|
class _IntegerMixin:
|
|
443
481
|
"""Integer-specific dispatch — uses IntegerMath in geometry trees."""
|
|
@@ -475,7 +513,7 @@ class _IntegerMixin:
|
|
|
475
513
|
|
|
476
514
|
values = (self.socket, other) if not reverse else (other, self.socket)
|
|
477
515
|
return getattr(IntegerMath, operation)(*values)
|
|
478
|
-
return Socket._dispatch_math(self, other, operation, reverse)
|
|
516
|
+
return Socket._dispatch_math(cast("Socket", self), other, operation, reverse)
|
|
479
517
|
|
|
480
518
|
def _dispatch_unary(self, operation: str) -> "BaseNode":
|
|
481
519
|
if self._is_geometry_tree:
|
|
@@ -485,7 +523,7 @@ class _IntegerMixin:
|
|
|
485
523
|
return IntegerMath.negate(self.socket)
|
|
486
524
|
elif operation == "absolute":
|
|
487
525
|
return IntegerMath.absolute(self.socket)
|
|
488
|
-
return Socket._dispatch_unary(self, operation)
|
|
526
|
+
return Socket._dispatch_unary(cast("Socket", self), operation)
|
|
489
527
|
|
|
490
528
|
def _dispatch_floordiv(self, other: Any, reverse: bool = False) -> "BaseNode":
|
|
491
529
|
if self._is_geometry_tree and self._other_is_integer(other):
|
|
@@ -493,14 +531,14 @@ class _IntegerMixin:
|
|
|
493
531
|
|
|
494
532
|
values = (self.socket, other) if not reverse else (other, self.socket)
|
|
495
533
|
return IntegerMath.divide_floor(*values)
|
|
496
|
-
return Socket._dispatch_floordiv(self, other, reverse)
|
|
534
|
+
return Socket._dispatch_floordiv(cast("Socket", self), other, reverse)
|
|
497
535
|
|
|
498
|
-
def _dispatch_compare(self, other: Any, operation: str) -> "
|
|
536
|
+
def _dispatch_compare(self, other: Any, operation: str) -> "Compare | Math":
|
|
499
537
|
if isinstance(self._tree.tree, GeometryNodeTree):
|
|
500
538
|
from ..nodes.geometry.manual import Compare
|
|
501
539
|
|
|
502
540
|
return getattr(Compare.integer, operation)(self.socket, other)
|
|
503
|
-
return Socket._dispatch_compare(self, other, operation)
|
|
541
|
+
return Socket._dispatch_compare(cast("Socket", self), other, operation)
|
|
504
542
|
|
|
505
543
|
if TYPE_CHECKING:
|
|
506
544
|
|
|
@@ -516,12 +554,10 @@ class _IntegerMixin:
|
|
|
516
554
|
def __rfloordiv__(self, other: Any) -> "IntegerMath": ...
|
|
517
555
|
def __neg__(self) -> "IntegerMath": ...
|
|
518
556
|
def __abs__(self) -> "IntegerMath": ...
|
|
519
|
-
def __lt__(self, other: Any) -> "Compare": ...
|
|
520
|
-
def __gt__(self, other: Any) -> "Compare": ...
|
|
521
|
-
def __le__(self, other: Any) -> "Compare": ...
|
|
522
|
-
def __ge__(self, other: Any) -> "Compare": ...
|
|
523
|
-
def __eq__(self, other: Any) -> "Compare": ... # type: ignore[override]
|
|
524
|
-
def __ne__(self, other: Any) -> "Compare": ... # type: ignore[override]
|
|
557
|
+
def __lt__(self, other: Any) -> "Compare[NodeSocketInt]": ...
|
|
558
|
+
def __gt__(self, other: Any) -> "Compare[NodeSocketInt]": ...
|
|
559
|
+
def __le__(self, other: Any) -> "Compare[NodeSocketInt]": ...
|
|
560
|
+
def __ge__(self, other: Any) -> "Compare[NodeSocketInt]": ...
|
|
525
561
|
|
|
526
562
|
|
|
527
563
|
# ---------------------------------------------------------------------------
|