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.
Files changed (71) hide show
  1. {nodebpy-0.11.1 → nodebpy-0.12.0}/PKG-INFO +1 -1
  2. {nodebpy-0.11.1 → nodebpy-0.12.0}/pyproject.toml +1 -1
  3. nodebpy-0.12.0/src/nodebpy/__init__.py +23 -0
  4. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/__init__.py +11 -1
  5. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/_utils.py +11 -5
  6. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/node.py +127 -29
  7. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/tree.py +38 -25
  8. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/groups.py +6 -8
  9. nodebpy-0.11.1/src/nodebpy/__init__.py +0 -14
  10. {nodebpy-0.11.1 → nodebpy-0.12.0}/README.md +0 -0
  11. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/arrange.py +0 -0
  12. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/_registry.py +0 -0
  13. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/accessor.py +0 -0
  14. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/interface.py +0 -0
  15. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/mixins.py +0 -0
  16. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/builder/socket.py +0 -0
  17. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/diagram.py +0 -0
  18. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
  19. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +0 -0
  20. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
  21. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
  22. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +0 -0
  23. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
  24. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
  25. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +0 -0
  26. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
  27. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
  28. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/config.py +0 -0
  29. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
  30. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/__init__.py +0 -0
  31. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/__init__.py +0 -0
  32. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/color.py +0 -0
  33. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/converter.py +0 -0
  34. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/distort.py +0 -0
  35. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/filter.py +0 -0
  36. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/group.py +0 -0
  37. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/input.py +0 -0
  38. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/interface.py +0 -0
  39. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/manual.py +0 -0
  40. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/matte.py +0 -0
  41. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/output.py +0 -0
  42. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
  43. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/__init__.py +0 -0
  44. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/attribute.py +0 -0
  45. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/color.py +0 -0
  46. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/converter.py +0 -0
  47. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/geometry.py +0 -0
  48. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/grid.py +0 -0
  49. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/group.py +0 -0
  50. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/input.py +0 -0
  51. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/interface.py +0 -0
  52. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/manual.py +0 -0
  53. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/output.py +0 -0
  54. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/texture.py +0 -0
  55. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/vector.py +0 -0
  56. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/geometry/zone.py +0 -0
  57. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/__init__.py +0 -0
  58. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/color.py +0 -0
  59. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/converter.py +0 -0
  60. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/grid.py +0 -0
  61. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/group.py +0 -0
  62. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/input.py +0 -0
  63. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/interface.py +0 -0
  64. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/manual.py +0 -0
  65. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/output.py +0 -0
  66. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/script.py +0 -0
  67. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/shader.py +0 -0
  68. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/texture.py +0 -0
  69. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/nodes/shader/vector.py +0 -0
  70. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/sockets.py +0 -0
  71. {nodebpy-0.11.1 → nodebpy-0.12.0}/src/nodebpy/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nodebpy
3
- Version: 0.11.1
3
+ Version: 0.12.0
4
4
  Summary: Build nodes trees in Blender more elegantly with code
5
5
  Author: Brady Johnston
6
6
  Author-email: Brady Johnston <brady.johnston@me.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nodebpy"
3
- version = "0.11.1"
3
+ version = "0.12.0"
4
4
  description = "Build nodes trees in Blender more elegantly with code"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -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 BaseNode, DynamicInputsMixin, NodeGroupBuilder
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
- class _NodeLike:
77
- """Marker base for objects that wrap a Blender node (have .node, .inputs, .outputs)."""
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
- class _SocketLike:
81
- """Marker base for objects that wrap a single Blender NodeSocket (have .socket)."""
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 TYPE_CHECKING, Iterable, Literal, Self
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 Node, NodeSocket
8
-
9
- from ..types import SOCKET_COMPATIBILITY, SOCKET_TYPES, InputAny, InputLinkable
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
- pass
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
- return cls(socket)
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.data_type == "BOOLEAN"
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
- target_name, source_socket = list(target._add_inputs(source).items())[0]
196
- return (source_socket, target.inputs[target_name].socket)
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
- def _add_inputs(self, *args, **kwargs) -> dict[str, InputLinkable]:
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.node.node_tree = self._get_or_create_group()
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
- def _get_or_create_group(self) -> bpy.types.GeometryNodeTree:
247
- name = self._name
248
- if name in bpy.data.node_groups:
249
- return bpy.data.node_groups[name]
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
- with TreeBuilder(name) as tree:
252
- self._build_group(tree)
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
- @classmethod
258
- @abstractmethod
259
- def _build_group(cls, tree: TreeBuilder) -> None:
260
- """Code that builds the node group internals and interface"""
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
- class TreeBuilder:
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 cls(
671
- name,
672
- tree_type="GeometryNodeTree",
673
- collapse=collapse,
674
- arrange=arrange,
675
- fake_user=fake_user,
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 cls(
689
- name,
690
- tree_type="ShaderNodeTree",
691
- collapse=collapse,
692
- arrange=arrange,
693
- fake_user=fake_user,
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 cls(
707
- name,
708
- tree_type="CompositorNodeTree",
709
- collapse=collapse,
710
- arrange=arrange,
711
- fake_user=fake_user,
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(NodeGroupBuilder):
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
- @classmethod
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(NodeGroupBuilder):
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
- @classmethod
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(NodeGroupBuilder):
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