nodebpy 0.11.1__tar.gz → 0.12.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.12.0}/PKG-INFO +1 -1
- {nodebpy-0.11.1 → nodebpy-0.12.0}/pyproject.toml +1 -1
- nodebpy-0.12.0/src/nodebpy/__init__.py +23 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/__init__.py +11 -1
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/_utils.py +11 -5
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/node.py +127 -29
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/tree.py +38 -25
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/groups.py +6 -8
- nodebpy-0.11.1/src/nodebpy/__init__.py +0 -14
- {nodebpy-0.11.1 → nodebpy-0.12.0}/README.md +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/arrange.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/_registry.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/accessor.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/interface.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/mixins.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/socket.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/diagram.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/config.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/__init__.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/__init__.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/color.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/converter.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/distort.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/filter.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/group.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/input.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/interface.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/manual.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/matte.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/output.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/__init__.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/attribute.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/color.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/converter.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/geometry.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/grid.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/group.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/input.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/interface.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/manual.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/output.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/texture.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/vector.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/zone.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/__init__.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/color.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/converter.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/grid.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/group.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/input.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/interface.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/manual.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/output.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/script.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/shader.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/texture.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/vector.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/sockets.py +0 -0
- {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/types.py +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from . import diagram, nodes, sockets
|
|
2
|
+
from .builder import (
|
|
3
|
+
CustomCompositorGroup,
|
|
4
|
+
CustomGeometryGroup,
|
|
5
|
+
CustomShaderGroup,
|
|
6
|
+
NodeGroupBuilder,
|
|
7
|
+
TreeBuilder,
|
|
8
|
+
)
|
|
9
|
+
from .nodes import compositor, geometry, shader
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"nodes",
|
|
13
|
+
"compositor",
|
|
14
|
+
"geometry",
|
|
15
|
+
"shader",
|
|
16
|
+
"sockets",
|
|
17
|
+
"diagram",
|
|
18
|
+
"TreeBuilder",
|
|
19
|
+
"NodeGroupBuilder",
|
|
20
|
+
"GeometryNodeGroup",
|
|
21
|
+
"ShaderNodeGroup",
|
|
22
|
+
"CompositorNodeGroup",
|
|
23
|
+
]
|
|
@@ -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,9 @@ __all__ = [
|
|
|
82
89
|
"DynamicInputsMixin",
|
|
83
90
|
# Node groups
|
|
84
91
|
"NodeGroupBuilder",
|
|
92
|
+
"GeometryNodeGroup",
|
|
93
|
+
"ShaderNodeGroup",
|
|
94
|
+
"CompositorNodeGroup",
|
|
85
95
|
# Type-specific socket classes (runtime)
|
|
86
96
|
"FloatSocket",
|
|
87
97
|
"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
|
|
@@ -73,9 +73,15 @@ def _resolve_promotion(
|
|
|
73
73
|
return self_socket, other, reverse
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
@runtime_checkable
|
|
77
|
+
class _NodeLike(Protocol):
|
|
78
|
+
"""Protocol for objects that wrap a Blender node and expose an ``outputs`` accessor."""
|
|
78
79
|
|
|
80
|
+
outputs: Any # SocketAccessor at runtime; typed as Any to avoid circular import
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
|
|
83
|
+
@runtime_checkable
|
|
84
|
+
class _SocketLike(Protocol):
|
|
85
|
+
"""Protocol for objects that wrap a single Blender NodeSocket and expose ``.socket``."""
|
|
86
|
+
|
|
87
|
+
socket: NodeSocket
|
|
@@ -1,19 +1,44 @@
|
|
|
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
|
+
CompositorNodeTree,
|
|
19
|
+
GeometryNodeTree,
|
|
20
|
+
Node,
|
|
21
|
+
NodeSocket,
|
|
22
|
+
ShaderNodeTree,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from ..types import SOCKET_COMPATIBILITY, SOCKET_TYPES, InputAny
|
|
10
26
|
from ._utils import SocketError, _NodeLike, _SocketLike
|
|
11
27
|
from .accessor import SocketAccessor
|
|
12
28
|
from .mixins import LinkingMixin, OperatorMixin
|
|
13
29
|
from .tree import TreeBuilder
|
|
14
30
|
|
|
31
|
+
_T = TypeVar("_T", bound=bpy.types.NodeTree)
|
|
32
|
+
|
|
15
33
|
if TYPE_CHECKING:
|
|
16
|
-
|
|
34
|
+
|
|
35
|
+
class _DynamicTarget(Protocol):
|
|
36
|
+
"""Structural type for a node that supports dynamic socket addition."""
|
|
37
|
+
|
|
38
|
+
def _add_inputs(self, *args: Any, **kwargs: Any) -> dict[str, NodeSocket]: ...
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def inputs(self) -> SocketAccessor: ...
|
|
17
42
|
|
|
18
43
|
|
|
19
44
|
class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
@@ -86,7 +111,9 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
86
111
|
assert link.to_node
|
|
87
112
|
if link.to_node.bl_idname == cls._bl_idname:
|
|
88
113
|
return cls._from_node(link.to_node)
|
|
89
|
-
|
|
114
|
+
node = cls()
|
|
115
|
+
node.tree.link(socket, node.inputs._best_match(socket.type))
|
|
116
|
+
return node
|
|
90
117
|
else:
|
|
91
118
|
if socket.links:
|
|
92
119
|
for link in socket.links:
|
|
@@ -98,8 +125,9 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
98
125
|
node >> socket
|
|
99
126
|
return node
|
|
100
127
|
|
|
101
|
-
def _set_input_default_value(self, input, value):
|
|
128
|
+
def _set_input_default_value(self, input: NodeSocket, value: Any) -> None:
|
|
102
129
|
"""Set the default value for an input socket, handling type conversions."""
|
|
130
|
+
assert hasattr(input, "default_value")
|
|
103
131
|
if (
|
|
104
132
|
hasattr(input, "type")
|
|
105
133
|
and input.type == "VECTOR"
|
|
@@ -115,7 +143,7 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
115
143
|
if value is None or (
|
|
116
144
|
"GridPrune" in self._bl_idname
|
|
117
145
|
and name == "Threshold"
|
|
118
|
-
and self.node
|
|
146
|
+
and getattr(self.node, "data_type", None) == "BOOLEAN"
|
|
119
147
|
):
|
|
120
148
|
continue
|
|
121
149
|
if isinstance(value, Node):
|
|
@@ -128,7 +156,7 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
128
156
|
continue
|
|
129
157
|
|
|
130
158
|
elif isinstance(value, _SocketLike):
|
|
131
|
-
self._link_from(value, name)
|
|
159
|
+
self._link_from(value.socket, name)
|
|
132
160
|
elif isinstance(value, NodeSocket):
|
|
133
161
|
self._link_from(value, name)
|
|
134
162
|
elif isinstance(value, _NodeLike):
|
|
@@ -165,7 +193,7 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
165
193
|
return SocketAccessor(self.node.inputs, "input")
|
|
166
194
|
|
|
167
195
|
|
|
168
|
-
class DynamicInputsMixin:
|
|
196
|
+
class DynamicInputsMixin(ABC):
|
|
169
197
|
_socket_data_types: tuple[str, ...]
|
|
170
198
|
_type_map: dict[str, str] = {}
|
|
171
199
|
|
|
@@ -192,10 +220,14 @@ class DynamicInputsMixin:
|
|
|
192
220
|
try:
|
|
193
221
|
return super()._find_best_socket_pair(source, target) # type: ignore
|
|
194
222
|
except SocketError:
|
|
195
|
-
|
|
196
|
-
|
|
223
|
+
dyn = cast("_DynamicTarget", target)
|
|
224
|
+
target_name, source_socket = list(dyn._add_inputs(source).items())[0]
|
|
225
|
+
return (source_socket, dyn.inputs[target_name].socket)
|
|
197
226
|
|
|
198
|
-
|
|
227
|
+
@abstractmethod
|
|
228
|
+
def _add_socket(self, name: str, *args: Any, **kwargs: Any) -> NodeSocket: ...
|
|
229
|
+
|
|
230
|
+
def _add_inputs(self, *args, **kwargs) -> dict[str, NodeSocket]:
|
|
199
231
|
"""Dictionary with {new_socket.name: from_linkable} for link creation"""
|
|
200
232
|
new_sockets = {}
|
|
201
233
|
items = {}
|
|
@@ -212,14 +244,15 @@ class DynamicInputsMixin:
|
|
|
212
244
|
return new_sockets
|
|
213
245
|
|
|
214
246
|
|
|
215
|
-
class NodeGroupBuilder(BaseNode, ABC):
|
|
247
|
+
class NodeGroupBuilder(BaseNode, ABC, Generic[_T]):
|
|
216
248
|
"""Base class for custom node groups.
|
|
217
249
|
|
|
218
250
|
Subclasses implement :meth:`_build_group` with the node-graph logic.
|
|
251
|
+
Subclass one of the editor-specific variants: :class:`GeometryNodeGroup`,
|
|
252
|
+
:class:`ShaderNodeGroup`, or :class:`CompositorNodeGroup`.
|
|
219
253
|
"""
|
|
220
254
|
|
|
221
255
|
_name: str
|
|
222
|
-
_bl_idname = "GeometryNodeGroup"
|
|
223
256
|
_warning_propagation: Literal["ALL", "ERRORS_AND_WARNINGS", "ERRORS", "NONE"] = (
|
|
224
257
|
"ALL"
|
|
225
258
|
)
|
|
@@ -234,27 +267,92 @@ class NodeGroupBuilder(BaseNode, ABC):
|
|
|
234
267
|
"TEXTURE",
|
|
235
268
|
"VECTOR",
|
|
236
269
|
] = "NONE"
|
|
237
|
-
node: bpy.types.GeometryNodeGroup
|
|
238
270
|
|
|
239
271
|
def __init__(self, **kwargs):
|
|
240
272
|
super().__init__()
|
|
241
|
-
self.
|
|
273
|
+
self._setup_node_group()
|
|
242
274
|
self.node.show_options = False
|
|
243
|
-
self.node.warning_propagation = self._warning_propagation
|
|
244
275
|
self._establish_links(**kwargs)
|
|
245
276
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
277
|
+
@abstractmethod
|
|
278
|
+
def _setup_node_group(self) -> None:
|
|
279
|
+
"""Set ``self.node.node_tree`` and any node-type-specific properties.
|
|
280
|
+
|
|
281
|
+
Called by ``__init__`` after the node is created but before links are
|
|
282
|
+
established. Concrete subclasses have a narrowed ``self.node`` type,
|
|
283
|
+
so the ``node_tree`` assignment is type-safe here rather than in the
|
|
284
|
+
base class where ``self.node`` is only ``bpy.types.Node``.
|
|
285
|
+
"""
|
|
286
|
+
...
|
|
287
|
+
|
|
288
|
+
@abstractmethod
|
|
289
|
+
def _build_group(self, tree: TreeBuilder) -> None:
|
|
290
|
+
"""Build the node group internals and interface."""
|
|
291
|
+
|
|
292
|
+
def _get_or_create_tree(self) -> _T:
|
|
293
|
+
existing = bpy.data.node_groups[self._name]
|
|
294
|
+
if existing.bl_idname == self.tree.tree.bl_idname:
|
|
295
|
+
return cast(_T, existing)
|
|
296
|
+
raise TypeError(
|
|
297
|
+
f"Node group '{self._name}' already exists as "
|
|
298
|
+
f"{type(existing).__name__}, not {self._bl_idname}. "
|
|
299
|
+
f"Use a unique _name for this group."
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class CustomGeometryGroup(NodeGroupBuilder[GeometryNodeTree]):
|
|
304
|
+
"""Node group in a Geometry Nodes tree."""
|
|
305
|
+
|
|
306
|
+
_bl_idname = "GeometryNodeGroup"
|
|
307
|
+
node: bpy.types.GeometryNodeGroup
|
|
308
|
+
|
|
309
|
+
def _setup_node_group(self) -> None:
|
|
310
|
+
self.node.node_tree = self._get_or_create_group()
|
|
311
|
+
self.node.warning_propagation = self._warning_propagation
|
|
250
312
|
|
|
251
|
-
|
|
252
|
-
|
|
313
|
+
def _get_or_create_group(self) -> GeometryNodeTree:
|
|
314
|
+
try:
|
|
315
|
+
return self._get_or_create_tree()
|
|
316
|
+
except KeyError:
|
|
317
|
+
with TreeBuilder.geometry(self._name) as tree:
|
|
318
|
+
self._build_group(tree)
|
|
253
319
|
tree.tree.color_tag = self._color_tag
|
|
320
|
+
return tree.tree
|
|
254
321
|
|
|
255
|
-
return tree.tree
|
|
256
322
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
323
|
+
class CustomShaderGroup(NodeGroupBuilder[ShaderNodeTree]):
|
|
324
|
+
"""Node group in a Shader (Material) node tree."""
|
|
325
|
+
|
|
326
|
+
_bl_idname = "ShaderNodeGroup"
|
|
327
|
+
node: bpy.types.ShaderNodeGroup
|
|
328
|
+
|
|
329
|
+
def _setup_node_group(self) -> None:
|
|
330
|
+
self.node.node_tree = self._get_or_create_group()
|
|
331
|
+
|
|
332
|
+
def _get_or_create_group(self) -> ShaderNodeTree:
|
|
333
|
+
try:
|
|
334
|
+
return self._get_or_create_tree()
|
|
335
|
+
except KeyError:
|
|
336
|
+
with TreeBuilder.shader(self._name) as tree:
|
|
337
|
+
self._build_group(tree)
|
|
338
|
+
tree.tree.color_tag = self._color_tag
|
|
339
|
+
return tree.tree
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class CustomCompositorGroup(NodeGroupBuilder[CompositorNodeTree]):
|
|
343
|
+
"""Node group in a Compositor node tree."""
|
|
344
|
+
|
|
345
|
+
_bl_idname = "CompositorNodeGroup"
|
|
346
|
+
node: bpy.types.CompositorNodeGroup
|
|
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) -> CompositorNodeTree:
|
|
352
|
+
try:
|
|
353
|
+
return self._get_or_create_tree()
|
|
354
|
+
except KeyError:
|
|
355
|
+
with TreeBuilder.compositor(self._name) as tree:
|
|
356
|
+
self._build_group(tree)
|
|
357
|
+
tree.tree.color_tag = self._color_tag
|
|
358
|
+
return tree.tree
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Self
|
|
3
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, Self, TypeVar, cast
|
|
4
4
|
|
|
5
5
|
import bpy
|
|
6
6
|
from bpy.types import (
|
|
@@ -623,12 +623,16 @@ class OutputInterfaceContext(DirectionalContext):
|
|
|
623
623
|
_direction = "OUTPUT"
|
|
624
624
|
|
|
625
625
|
|
|
626
|
-
|
|
626
|
+
_TreeT = TypeVar("_TreeT", bound=NodeTree)
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
class TreeBuilder(Generic[_TreeT]):
|
|
627
630
|
"""Builder for creating Blender node trees with a clean Python API.
|
|
628
631
|
|
|
629
632
|
Supports geometry, shader, and compositor node trees.
|
|
630
633
|
"""
|
|
631
634
|
|
|
635
|
+
tree: _TreeT
|
|
632
636
|
_tree_contexts: ClassVar["list[TreeBuilder]"] = []
|
|
633
637
|
_frame_contexts: ClassVar["list[NodeFrame]"] = []
|
|
634
638
|
|
|
@@ -645,9 +649,9 @@ class TreeBuilder:
|
|
|
645
649
|
ignore_visibility: bool = False,
|
|
646
650
|
):
|
|
647
651
|
if isinstance(tree, str):
|
|
648
|
-
self.tree = bpy.data.node_groups.new(tree, tree_type)
|
|
652
|
+
self.tree = bpy.data.node_groups.new(tree, tree_type) # type: ignore[assignment]
|
|
649
653
|
else:
|
|
650
|
-
self.tree = tree
|
|
654
|
+
self.tree = tree # type: ignore[assignment]
|
|
651
655
|
|
|
652
656
|
self._menu_defaults: dict[str, str] = {}
|
|
653
657
|
self.inputs = InputInterfaceContext(self)
|
|
@@ -665,14 +669,17 @@ class TreeBuilder:
|
|
|
665
669
|
collapse: bool = False,
|
|
666
670
|
arrange: Literal["sugiyama", "simple"] | None = "sugiyama",
|
|
667
671
|
fake_user: bool = False,
|
|
668
|
-
) -> "TreeBuilder":
|
|
672
|
+
) -> "TreeBuilder[GeometryNodeTree]":
|
|
669
673
|
"""Create a geometry node tree."""
|
|
670
|
-
return
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
674
|
+
return cast(
|
|
675
|
+
"TreeBuilder[GeometryNodeTree]",
|
|
676
|
+
cls(
|
|
677
|
+
name,
|
|
678
|
+
tree_type="GeometryNodeTree",
|
|
679
|
+
collapse=collapse,
|
|
680
|
+
arrange=arrange,
|
|
681
|
+
fake_user=fake_user,
|
|
682
|
+
),
|
|
676
683
|
)
|
|
677
684
|
|
|
678
685
|
@classmethod
|
|
@@ -683,14 +690,17 @@ class TreeBuilder:
|
|
|
683
690
|
collapse: bool = False,
|
|
684
691
|
arrange: Literal["sugiyama", "simple"] | None = "sugiyama",
|
|
685
692
|
fake_user: bool = False,
|
|
686
|
-
) -> "TreeBuilder":
|
|
693
|
+
) -> "TreeBuilder[ShaderNodeTree]":
|
|
687
694
|
"""Create a shader node tree."""
|
|
688
|
-
return
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
695
|
+
return cast(
|
|
696
|
+
"TreeBuilder[ShaderNodeTree]",
|
|
697
|
+
cls(
|
|
698
|
+
name,
|
|
699
|
+
tree_type="ShaderNodeTree",
|
|
700
|
+
collapse=collapse,
|
|
701
|
+
arrange=arrange,
|
|
702
|
+
fake_user=fake_user,
|
|
703
|
+
),
|
|
694
704
|
)
|
|
695
705
|
|
|
696
706
|
@classmethod
|
|
@@ -701,14 +711,17 @@ class TreeBuilder:
|
|
|
701
711
|
collapse: bool = False,
|
|
702
712
|
arrange: Literal["sugiyama", "simple"] | None = "sugiyama",
|
|
703
713
|
fake_user: bool = False,
|
|
704
|
-
) -> "TreeBuilder":
|
|
714
|
+
) -> "TreeBuilder[CompositorNodeTree]":
|
|
705
715
|
"""Create a compositor node tree."""
|
|
706
|
-
return
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
716
|
+
return cast(
|
|
717
|
+
"TreeBuilder[CompositorNodeTree]",
|
|
718
|
+
cls(
|
|
719
|
+
name,
|
|
720
|
+
tree_type="CompositorNodeTree",
|
|
721
|
+
collapse=collapse,
|
|
722
|
+
arrange=arrange,
|
|
723
|
+
fake_user=fake_user,
|
|
724
|
+
),
|
|
712
725
|
)
|
|
713
726
|
|
|
714
727
|
@property
|
|
@@ -5,8 +5,8 @@ from nodebpy.nodes.compositor import CombineXYZ
|
|
|
5
5
|
from nodebpy.types import InputInteger, InputVector
|
|
6
6
|
|
|
7
7
|
from ...builder import (
|
|
8
|
+
CustomGeometryGroup,
|
|
8
9
|
IntegerSocket,
|
|
9
|
-
NodeGroupBuilder,
|
|
10
10
|
RotationSocket,
|
|
11
11
|
SocketAccessor,
|
|
12
12
|
VectorSocket,
|
|
@@ -25,7 +25,7 @@ from . import (
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class OtherVertex(
|
|
28
|
+
class OtherVertex(CustomGeometryGroup):
|
|
29
29
|
"""
|
|
30
30
|
Given a vertex and an edge number from that vertex, returns the other
|
|
31
31
|
vertex of that edge.
|
|
@@ -58,8 +58,7 @@ class OtherVertex(NodeGroupBuilder):
|
|
|
58
58
|
kwargs = {"Vertex Index": vertex_index, "Edge Number": edge_number}
|
|
59
59
|
super().__init__(**kwargs)
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
def _build_group(cls, tree):
|
|
61
|
+
def _build_group(self, tree: TreeBuilder):
|
|
63
62
|
vertex_index = tree.inputs.integer("Vertex Index", default_input="INDEX")
|
|
64
63
|
edge_number = tree.inputs.integer("Edge Number")
|
|
65
64
|
|
|
@@ -72,7 +71,7 @@ class OtherVertex(NodeGroupBuilder):
|
|
|
72
71
|
_ = switch >> tree.outputs.integer("Other Vertex")
|
|
73
72
|
|
|
74
73
|
|
|
75
|
-
class OffsetVector(
|
|
74
|
+
class OffsetVector(CustomGeometryGroup):
|
|
76
75
|
"""
|
|
77
76
|
Evaluate a given vector field at an offset to the current ``Index``.
|
|
78
77
|
"""
|
|
@@ -108,8 +107,7 @@ class OffsetVector(NodeGroupBuilder):
|
|
|
108
107
|
):
|
|
109
108
|
super().__init__(index=index, vector=vector, offset=offset)
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
def _build_group(cls, tree):
|
|
110
|
+
def _build_group(self, tree: TreeBuilder):
|
|
113
111
|
index = tree.inputs.integer("Index", default_input="INDEX")
|
|
114
112
|
vector = tree.inputs.vector("Vector", default_input="POSITION")
|
|
115
113
|
offset = tree.inputs.integer("Offset")
|
|
@@ -119,7 +117,7 @@ class OffsetVector(NodeGroupBuilder):
|
|
|
119
117
|
_ = value >> tree.outputs.vector("Vector")
|
|
120
118
|
|
|
121
119
|
|
|
122
|
-
class PrincipalComponents(
|
|
120
|
+
class PrincipalComponents(CustomGeometryGroup):
|
|
123
121
|
"""
|
|
124
122
|
Compute PCA on a given vector field.
|
|
125
123
|
"""
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
from . import nodes, diagram, sockets
|
|
2
|
-
from .builder import TreeBuilder, NodeGroupBuilder
|
|
3
|
-
from .nodes import compositor, geometry, shader
|
|
4
|
-
|
|
5
|
-
__all__ = [
|
|
6
|
-
"nodes",
|
|
7
|
-
"compositor",
|
|
8
|
-
"geometry",
|
|
9
|
-
"shader",
|
|
10
|
-
"sockets",
|
|
11
|
-
"diagram",
|
|
12
|
-
"TreeBuilder",
|
|
13
|
-
"NodeGroupBuilder",
|
|
14
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|