nodebpy 520.32.0__tar.gz → 520.33.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 (106) hide show
  1. {nodebpy-520.32.0 → nodebpy-520.33.0}/PKG-INFO +1 -1
  2. {nodebpy-520.32.0 → nodebpy-520.33.0}/pyproject.toml +1 -1
  3. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/builder/items.py +23 -2
  4. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/builder/mixins.py +9 -0
  5. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/builder/node.py +4 -0
  6. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/builder/socket.py +14 -6
  7. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/export/codegen.py +12 -3
  8. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/groups.py +1 -1
  9. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/zone.py +19 -1
  10. {nodebpy-520.32.0 → nodebpy-520.33.0}/README.md +0 -0
  11. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/__init__.py +0 -0
  12. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/__main__.py +0 -0
  13. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/assets/__init__.py +0 -0
  14. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/assets/__main__.py +0 -0
  15. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/assets/_codegen.py +0 -0
  16. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/assets/_library.py +0 -0
  17. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/assets/_pipeline.py +0 -0
  18. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/assets/_textconv.py +0 -0
  19. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/builder/__init__.py +0 -0
  20. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/builder/_registry.py +0 -0
  21. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/builder/_socket_order.py +0 -0
  22. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/builder/_utils.py +0 -0
  23. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/builder/accessor.py +0 -0
  24. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/builder/asset.py +0 -0
  25. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/builder/tree.py +0 -0
  26. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/export/__init__.py +0 -0
  27. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/export/_floats.py +0 -0
  28. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/export/diagram.py +0 -0
  29. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/export/parity.py +0 -0
  30. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/export/plot.py +0 -0
  31. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/export/web_render.py +0 -0
  32. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/DESIGN.md +0 -0
  33. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/__init__.py +0 -0
  34. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/api.py +0 -0
  35. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/apply.py +0 -0
  36. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/balancing.py +0 -0
  37. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/build.py +0 -0
  38. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/common.py +0 -0
  39. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/config.py +0 -0
  40. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/digraph.py +0 -0
  41. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/dna.py +0 -0
  42. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/edits.py +0 -0
  43. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/extract.py +0 -0
  44. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/long_links.py +0 -0
  45. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/model.py +0 -0
  46. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/node_size.py +0 -0
  47. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/ordering.py +0 -0
  48. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/packing.py +0 -0
  49. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/pipeline.py +0 -0
  50. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/placement.py +0 -0
  51. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/priority.py +0 -0
  52. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/ranking.py +0 -0
  53. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/realize.py +0 -0
  54. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/reroutes.py +0 -0
  55. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/routing.py +0 -0
  56. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/snapping.py +0 -0
  57. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/spacing.py +0 -0
  58. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/stacking.py +0 -0
  59. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/sugiyama.py +0 -0
  60. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/layout/zones.py +0 -0
  61. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/live.py +0 -0
  62. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/__init__.py +0 -0
  63. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/_mixins.py +0 -0
  64. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/__init__.py +0 -0
  65. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/assets.py +0 -0
  66. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/color.py +0 -0
  67. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/converter.py +0 -0
  68. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/distort.py +0 -0
  69. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/filter.py +0 -0
  70. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/group.py +0 -0
  71. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/input.py +0 -0
  72. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/interface.py +0 -0
  73. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/manual.py +0 -0
  74. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/matte.py +0 -0
  75. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/output.py +0 -0
  76. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
  77. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/__init__.py +0 -0
  78. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/assets.py +0 -0
  79. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/attribute.py +0 -0
  80. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/color.py +0 -0
  81. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/converter.py +0 -0
  82. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/geometry.py +0 -0
  83. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/grid.py +0 -0
  84. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/group.py +0 -0
  85. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/input.py +0 -0
  86. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/interface.py +0 -0
  87. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/manual.py +0 -0
  88. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/output.py +0 -0
  89. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/texture.py +0 -0
  90. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/utilities.py +0 -0
  91. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/geometry/vector.py +0 -0
  92. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/__init__.py +0 -0
  93. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/assets.py +0 -0
  94. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/color.py +0 -0
  95. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/converter.py +0 -0
  96. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/grid.py +0 -0
  97. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/group.py +0 -0
  98. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/input.py +0 -0
  99. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/manual.py +0 -0
  100. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/output.py +0 -0
  101. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/script.py +0 -0
  102. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/shader.py +0 -0
  103. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/texture.py +0 -0
  104. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/nodes/shader/vector.py +0 -0
  105. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/py.typed +0 -0
  106. {nodebpy-520.32.0 → nodebpy-520.33.0}/src/nodebpy/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nodebpy
3
- Version: 520.32.0
3
+ Version: 520.33.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 = "520.32.0"
3
+ version = "520.33.0"
4
4
  description = "Build nodes trees in Blender more elegantly with code"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -184,6 +184,8 @@ def _resolve_source(
184
184
  """The source socket, its socket type and the item name for a linkable.
185
185
 
186
186
  The name is the source socket's unless one is given."""
187
+ if isinstance(value, Item):
188
+ value = value.output
187
189
  accessor = getattr(value, "o", None)
188
190
  if accessor is None and not isinstance(value, (NodeSocket, _SocketLike)):
189
191
  raise TypeError(f"{value!r} is not a socket, node or default value")
@@ -199,6 +201,7 @@ def _resolve_source(
199
201
 
200
202
  _InT = TypeVar("_InT", bound=BaseSocket, default=Socket)
201
203
  _OutT = TypeVar("_OutT", bound=BaseSocket, default=_InT)
204
+ _RShiftT = TypeVar("_RShiftT")
202
205
 
203
206
 
204
207
  class Item(Generic[_InT, _OutT]):
@@ -209,9 +212,14 @@ class Item(Generic[_InT, _OutT]):
209
212
  from. The type parameters are the socket classes of the two roles; one
210
213
  parameter means both roles share it, none means plain :class:`Socket`.
211
214
 
212
- Linking into ``input`` with ``>>`` continues the chain from ``output``::
215
+ The handle stands in for its sockets: as a value or on the left of
216
+ ``>>`` it is ``output``, on the right of ``>>`` it is ``input``, and the
217
+ chain then continues from ``output``::
213
218
 
214
- g.Position() >> capture.items.vector().input >> g.SetPosition()
219
+ g.Position() >> capture.items.vector() >> g.SetPosition()
220
+ g.SetPosition(position=capture.items.vector(g.Position()))
221
+
222
+ An item with only one side raises for the side it does not have.
215
223
 
216
224
  Holds the item's collection index rather than the bpy item itself:
217
225
  bpy collection item references are invalidated when the collection
@@ -260,6 +268,19 @@ class Item(Generic[_InT, _OutT]):
260
268
  "_OutT", _wrap_socket(self._items._item_socket(self._item, output=True))
261
269
  )
262
270
 
271
+ # -- the handle as a link source or target --
272
+
273
+ @property
274
+ def _default_output_socket(self) -> NodeSocket:
275
+ return self.output.socket
276
+
277
+ def _as_rshift_target(self) -> _InT:
278
+ return self.input
279
+
280
+ def __rshift__(self, other: _RShiftT) -> _RShiftT:
281
+ """``item >> target`` links from the item's output socket."""
282
+ return cast("_RShiftT", self.output >> cast(Any, other))
283
+
263
284
 
264
285
  def _chained(socket: NodeSocket, continues: bool, follow) -> Socket:
265
286
  """Wrap a link-target socket so ``>>`` into it carries on from the
@@ -10,14 +10,17 @@ from ._registry import _wrap_socket
10
10
  from ._utils import SocketError, _resolve_promotion, _SocketLike
11
11
 
12
12
  _RShiftT = TypeVar("_RShiftT")
13
+ _OutT = TypeVar("_OutT", bound="BaseSocket")
13
14
 
14
15
  if TYPE_CHECKING:
15
16
  from ..nodes.geometry import CombineTransform
16
17
  from ..nodes.geometry.converter import BooleanMath
17
18
  from ..types import InputLinkable
18
19
  from .accessor import SocketAccessor
20
+ from .items import Item
19
21
  from .node import BaseNode
20
22
  from .socket import (
23
+ BaseSocket,
21
24
  BooleanSocket,
22
25
  FloatSocket,
23
26
  MatrixSocket,
@@ -367,6 +370,8 @@ class LinkingMixin:
367
370
  @overload
368
371
  def __rshift__(self, other: None) -> Self: ...
369
372
  @overload
373
+ def __rshift__(self, other: Item[Any, _OutT]) -> _OutT: ...
374
+ @overload
370
375
  def __rshift__(self, other: _RShiftT) -> _RShiftT: ...
371
376
  def __rshift__(self, other: _RShiftT | None) -> _RShiftT | Self:
372
377
  """Chain nodes using >> operator. Links output to input.
@@ -387,6 +392,10 @@ class LinkingMixin:
387
392
  """
388
393
  if other is None:
389
394
  return self
395
+ as_target = getattr(other, "_as_rshift_target", None)
396
+ if as_target is not None:
397
+ # an item handle stands for its input socket on the right of >>
398
+ return self >> as_target()
390
399
  if isinstance(other, _SocketLike):
391
400
  source = self._default_output_socket
392
401
  target = other.socket
@@ -238,6 +238,10 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
238
238
  elif isinstance(value, _NodeLike):
239
239
  target_type = target.type if not named else self.i._get(target).type
240
240
  self._link_from(value.o._best_match(target_type), target)
241
+ elif getattr(value, "_as_rshift_target", None) is not None:
242
+ # an item handle stands for its output socket (and says so when
243
+ # it has none)
244
+ self._link_from(cast(Any, value)._default_output_socket, target)
241
245
  else:
242
246
  # TODO: explicitly skipping the sockets for BooleanMath as they are default false,
243
247
  # but this needs to be a more generic solution for sockets which aren't available
@@ -1553,12 +1553,12 @@ class _FloatMixin[IntegerResult: (IntegerSocket, IntegerSocketGrid, IntegerSocke
1553
1553
 
1554
1554
  return Clamp.min_max(self.socket, min, max).o.result # ty: ignore[invalid-return-type]
1555
1555
 
1556
- def min(self, value: InputFloat = 0.0) -> Self:
1556
+ def min(self, value: InputFloat) -> Self:
1557
1557
  """Create Math with operation 'Minimum'. The minimum from self and value"""
1558
1558
  self._assert_output("min")
1559
1559
  return self._math.minimum(self.socket, value).o.value # ty: ignore[invalid-return-type]
1560
1560
 
1561
- def max(self, value: InputFloat = 1.0) -> Self:
1561
+ def max(self, value: InputFloat) -> Self:
1562
1562
  """Create Math with operation 'Maximum'. The maximum from self and value"""
1563
1563
  self._assert_output("max")
1564
1564
  return self._math.maximum(self.socket, value).o.value # ty: ignore[invalid-return-type]
@@ -1727,11 +1727,19 @@ class _IntegerMixin[FloatResult: (FloatSocket, FloatSocketGrid, FloatSocketList)
1727
1727
  return IntegerMath
1728
1728
 
1729
1729
  def clamp(self, min: InputInteger = 0, max: InputInteger = 1) -> Self:
1730
- """Clamp the value to *[min, max]*."""
1730
+ """Clamp the value to *[min, max]*. There isn't a built-in node for this, so it uses .max().min()"""
1731
1731
  self._assert_output("clamp")
1732
- return self._imath.minimum(
1733
- self._imath.maximum(self.socket, min).o.value, max
1734
- ).o.value # ty: ignore[invalid-return-type]
1732
+ return self.max(min).min(max)
1733
+
1734
+ def min(self, value: InputInteger) -> Self:
1735
+ """Create IntegerMath with operation 'Minimum'. The minimum from self and value"""
1736
+ self._assert_output("min")
1737
+ return self._imath.minimum(self.socket, value).o.value # ty: ignore[invalid-return-type]
1738
+
1739
+ def max(self, value: InputInteger) -> Self:
1740
+ """Create IntegerMath with operation 'Maximum'. The maximum from self and value"""
1741
+ self._assert_output("max")
1742
+ return self._imath.maximum(self.socket, value).o.value # ty: ignore[invalid-return-type]
1735
1743
 
1736
1744
  def modulo(self, divisor: InputInteger) -> Self:
1737
1745
  """Remainder after dividing by *divisor* (always non-negative)."""
@@ -2316,14 +2316,16 @@ _SOCKET_METHODS: dict[str, list[SocketMethodSpec]] = {
2316
2316
  ("RADIANS", "to_radians"),
2317
2317
  ("DEGREES", "to_degrees"),
2318
2318
  ("SIGN", "sign"),
2319
- ("MINIMUM", "min", "value_001"),
2320
- ("MAXIMUM", "max", "value_001"),
2321
2319
  ("MULTIPLY_ADD", "mul_add", "multiplier", "addend"),
2322
2320
  ("PINGPONG", "ping_pong", "value"),
2323
2321
  ("LOGARITHM", "log", "base"),
2324
2322
  ("ARCTAN2", "atan2", "value"),
2325
2323
  ]
2326
2324
  ]
2325
+ + [
2326
+ _math_unary_spec(op, method, "value", always_args=1)
2327
+ for op, method in (("MINIMUM", "min"), ("MAXIMUM", "max"))
2328
+ ]
2327
2329
  # No float modulo lift: the float mixin's modulo() builds FLOORED_MODULO,
2328
2330
  # which the % operator lift already covers, and plain MODULO has no
2329
2331
  # faithful method equivalent so it stays a factory call.
@@ -2348,7 +2350,14 @@ _SOCKET_METHODS: dict[str, list[SocketMethodSpec]] = {
2348
2350
  ("MULTIPLY_ADD", "mul_add", "multiplier", "addend"),
2349
2351
  )
2350
2352
  ]
2351
- + [_int_math_unary_spec("MODULO", "modulo", "divisor", always_args=1)],
2353
+ + [
2354
+ _int_math_unary_spec(op, method, *args, always_args=1)
2355
+ for op, method, *args in (
2356
+ ("MODULO", "modulo", "divisor"),
2357
+ ("MINIMUM", "min", "value"),
2358
+ ("MAXIMUM", "max", "value"),
2359
+ )
2360
+ ],
2352
2361
  "GeometryNodeListLength": [
2353
2362
  _list_spec("list_length", "Length", type_prop="data_type"),
2354
2363
  ],
@@ -70,7 +70,7 @@ class SliceToIndices(CustomGeometryGroup):
70
70
 
71
71
  range = stop - start
72
72
  length = IntegerMath.divide_ceiling(range, step).o.value.abs()
73
- indices = FieldToList(length).integer((start + step) * Index())
73
+ indices = FieldToList(length).items.integer((start + step) * Index())
74
74
 
75
75
  indices >> tree.outputs.integer("Indices", structure_type="LIST")
76
76
 
@@ -1,6 +1,6 @@
1
1
  from abc import ABC
2
2
  from collections.abc import Iterable, Iterator, Mapping
3
- from typing import TYPE_CHECKING, ClassVar, TypeVar, cast
3
+ from typing import TYPE_CHECKING, ClassVar, NoReturn, TypeVar, cast
4
4
 
5
5
  import bpy
6
6
  from bpy.types import (
@@ -152,6 +152,24 @@ class ZoneItem(Item[_SocketT]):
152
152
  """Output-node output socket — read the item after the zone."""
153
153
  return self.output
154
154
 
155
+ # A state item has two inputs and two outputs, so the handle itself
156
+ # cannot stand for a socket: name the role.
157
+ def _no_role(self, side: str) -> NoReturn:
158
+ raise TypeError(
159
+ f"{self!r} has two {side} sockets; link through .initial / .current / "
160
+ ".next / .result rather than the handle"
161
+ )
162
+
163
+ @property
164
+ def _default_output_socket(self) -> bpy.types.NodeSocket:
165
+ self._no_role("output")
166
+
167
+ def _as_rshift_target(self) -> _SocketT:
168
+ self._no_role("input")
169
+
170
+ def __rshift__(self, other: object) -> NoReturn:
171
+ self._no_role("output")
172
+
155
173
 
156
174
  class _ZonePair:
157
175
  """Zone wrapper holding the paired input and output builder nodes.
File without changes