nodebpy 0.16.0__tar.gz → 0.17.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 (68) hide show
  1. {nodebpy-0.16.0 → nodebpy-0.17.0}/PKG-INFO +1 -1
  2. {nodebpy-0.16.0 → nodebpy-0.17.0}/pyproject.toml +1 -1
  3. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/builder/accessor.py +2 -2
  4. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/builder/mixins.py +1 -1
  5. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/builder/socket.py +517 -46
  6. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/groups.py +12 -12
  7. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/manual.py +32 -31
  8. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/types.py +1 -1
  9. {nodebpy-0.16.0 → nodebpy-0.17.0}/README.md +0 -0
  10. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/__init__.py +0 -0
  11. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/arrange.py +0 -0
  12. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/builder/__init__.py +0 -0
  13. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/builder/_registry.py +0 -0
  14. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/builder/_utils.py +0 -0
  15. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/builder/node.py +0 -0
  16. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/builder/tree.py +0 -0
  17. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/diagram.py +0 -0
  18. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
  19. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +0 -0
  20. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
  21. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
  22. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +0 -0
  23. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
  24. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
  25. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +0 -0
  26. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
  27. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
  28. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/lib/nodearrange/config.py +0 -0
  29. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
  30. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/__init__.py +0 -0
  31. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/compositor/__init__.py +0 -0
  32. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/compositor/color.py +0 -0
  33. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/compositor/converter.py +0 -0
  34. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/compositor/distort.py +0 -0
  35. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/compositor/filter.py +0 -0
  36. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/compositor/group.py +0 -0
  37. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/compositor/input.py +0 -0
  38. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/compositor/interface.py +0 -0
  39. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/compositor/manual.py +0 -0
  40. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/compositor/matte.py +0 -0
  41. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/compositor/output.py +0 -0
  42. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
  43. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/__init__.py +0 -0
  44. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/attribute.py +0 -0
  45. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/color.py +0 -0
  46. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/converter.py +0 -0
  47. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/geometry.py +0 -0
  48. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/grid.py +0 -0
  49. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/group.py +0 -0
  50. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/input.py +0 -0
  51. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/interface.py +0 -0
  52. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/output.py +0 -0
  53. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/texture.py +0 -0
  54. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/utilities.py +0 -0
  55. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/vector.py +0 -0
  56. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/geometry/zone.py +0 -0
  57. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/shader/__init__.py +0 -0
  58. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/shader/color.py +0 -0
  59. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/shader/converter.py +0 -0
  60. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/shader/grid.py +0 -0
  61. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/shader/group.py +0 -0
  62. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/shader/input.py +0 -0
  63. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/shader/manual.py +0 -0
  64. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/shader/output.py +0 -0
  65. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/shader/script.py +0 -0
  66. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/shader/shader.py +0 -0
  67. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/shader/texture.py +0 -0
  68. {nodebpy-0.16.0 → nodebpy-0.17.0}/src/nodebpy/nodes/shader/vector.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nodebpy
3
- Version: 0.16.0
3
+ Version: 0.17.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.16.0"
3
+ version = "0.17.0"
4
4
  description = "Build nodes trees in Blender more elegantly with code"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Literal, overload
3
+ from typing import TYPE_CHECKING, Iterator, Literal, overload
4
4
 
5
5
  import bpy
6
6
  from bpy.types import NodeSocket
@@ -200,7 +200,7 @@ class SocketAccessor:
200
200
  def __len__(self) -> int:
201
201
  return len(self._items())
202
202
 
203
- def __iter__(self):
203
+ def __iter__(self) -> Iterator["Socket"]:
204
204
  return iter(self._values())
205
205
 
206
206
  def __getattr__(self, name: str) -> "Socket":
@@ -11,7 +11,7 @@ from ._utils import SocketError, _resolve_promotion, _SocketLike
11
11
  _RShiftT = TypeVar("_RShiftT")
12
12
 
13
13
  if TYPE_CHECKING:
14
- from ..nodes.geometry import Compare, Math, MultiplyMatrices, TransformPoint
14
+ from ..nodes.geometry import Compare
15
15
  from ..types import InputLinkable
16
16
  from .node import BaseNode
17
17
  from .socket import (
@@ -3,9 +3,13 @@ from __future__ import annotations
3
3
  from typing import (
4
4
  TYPE_CHECKING,
5
5
  Any,
6
+ Generic,
6
7
  Iterable,
7
8
  Iterator,
9
+ Literal,
8
10
  Mapping,
11
+ NamedTuple,
12
+ TypeVar,
9
13
  cast,
10
14
  overload,
11
15
  )
@@ -68,14 +72,45 @@ if TYPE_CHECKING:
68
72
  IntegerMath,
69
73
  MatchString,
70
74
  Math,
71
- MultiplyMatrices,
72
- TransformPoint,
73
75
  )
74
76
  from ..nodes.geometry.manual import Compare
75
77
  from ..nodes.geometry.vector import VectorMath
76
78
  from .node import BaseNode
77
79
  from .tree import TreeBuilder
78
80
 
81
+ _T = TypeVar("_T")
82
+
83
+
84
+ class QuaternionComponents(NamedTuple):
85
+ """Quaternion components returned by `RotationSocket.to_quaternion()`."""
86
+
87
+ w: "FloatSocket"
88
+ x: "FloatSocket"
89
+ y: "FloatSocket"
90
+ z: "FloatSocket"
91
+
92
+
93
+ class AxisAngle(NamedTuple):
94
+ """Axis-angle components returned by `RotationSocket.to_axis_angle()`."""
95
+
96
+ axis: "VectorSocket"
97
+ angle: "FloatSocket"
98
+
99
+
100
+ class FindResult(NamedTuple):
101
+ """Result of `StringSocket.find()`."""
102
+
103
+ first_found: "IntegerSocket"
104
+ count: "IntegerSocket"
105
+
106
+
107
+ class SVDResult(NamedTuple):
108
+ """SVD components returned by `MatrixSocket.svd()`."""
109
+
110
+ u: "MatrixSocket"
111
+ s: "VectorSocket"
112
+ v: "MatrixSocket"
113
+
79
114
 
80
115
  class BaseSocket:
81
116
  def __init__(self, socket: NodeSocket):
@@ -243,6 +278,89 @@ class Socket(BaseSocket, _SocketLike, OperatorMixin, LinkingMixin):
243
278
  # ---------------------------------------------------------------------------
244
279
 
245
280
 
281
+ class _FieldDomain(Generic[_T]):
282
+ """Domain-bound factory available on all socket types.
283
+
284
+ Access via a domain property (e.g. ``socket.point``). Provides field
285
+ evaluation methods; subclasses add statistics for numeric socket types.
286
+ """
287
+
288
+ def __init__(self, socket: NodeSocket, dtype: str, domain: str) -> None:
289
+ self._socket = socket
290
+ self._dtype = dtype
291
+ self._domain = domain
292
+
293
+ def evaluate(self) -> "_T":
294
+ """Force evaluation of this field on the bound domain via ``EvaluateOnDomain``."""
295
+ from ..nodes.geometry import EvaluateOnDomain
296
+
297
+ return getattr(getattr(EvaluateOnDomain, self._domain), self._dtype)(
298
+ self._socket
299
+ ).o.value
300
+
301
+ def at(self, index: InputInteger = 0) -> "_T":
302
+ """Evaluate this field's value at *index* on the bound domain via ``EvaluateAtIndex``."""
303
+ from ..nodes.geometry import EvaluateAtIndex
304
+
305
+ return getattr(getattr(EvaluateAtIndex, self._domain), self._dtype)(
306
+ self._socket, index
307
+ ).o.value
308
+
309
+
310
+ class _MinMaxDomain(_FieldDomain[_T]):
311
+ """Extends ``_FieldDomain`` with min/max aggregation for Integer sockets."""
312
+
313
+ def _minmax(self, field: str, group_index: InputInteger) -> "_T":
314
+ from ..nodes.geometry import FieldMinAndMax
315
+
316
+ node = getattr(getattr(FieldMinAndMax, self._domain), self._dtype)(
317
+ self._socket, group_index
318
+ )
319
+ return getattr(node.o, field)
320
+
321
+ def min(self, group_index: InputInteger = None) -> "_T":
322
+ return self._minmax("min", group_index)
323
+
324
+ def max(self, group_index: InputInteger = None) -> "_T":
325
+ return self._minmax("max", group_index)
326
+
327
+
328
+ class _StatsDomain(_MinMaxDomain[_T]):
329
+ """Extends ``_MinMaxDomain`` with full statistics for Float and Vector sockets."""
330
+
331
+ def mean(self, group_index: InputInteger = None) -> "_T":
332
+ from ..nodes.geometry import FieldAverage
333
+
334
+ node = getattr(getattr(FieldAverage, self._domain), self._dtype)(
335
+ self._socket, group_index
336
+ )
337
+ return node.o.mean
338
+
339
+ def median(self, group_index: InputInteger = None) -> "_T":
340
+ from ..nodes.geometry import FieldAverage
341
+
342
+ node = getattr(getattr(FieldAverage, self._domain), self._dtype)(
343
+ self._socket, group_index
344
+ )
345
+ return node.o.median
346
+
347
+ def std_dev(self, group_index: InputInteger = None) -> "_T":
348
+ from ..nodes.geometry import FieldVariance
349
+
350
+ node = getattr(getattr(FieldVariance, self._domain), self._dtype)(
351
+ self._socket, group_index
352
+ )
353
+ return node.o.standard_deviation
354
+
355
+ def variance(self, group_index: InputInteger = None) -> "_T":
356
+ from ..nodes.geometry import FieldVariance
357
+
358
+ node = getattr(getattr(FieldVariance, self._domain), self._dtype)(
359
+ self._socket, group_index
360
+ )
361
+ return node.o.variance
362
+
363
+
246
364
  class _VectorMixin(BaseSocket):
247
365
  """Vector-specific properties (.x, .y, .z) and dispatch."""
248
366
 
@@ -286,6 +404,34 @@ class _VectorMixin(BaseSocket):
286
404
  else:
287
405
  return self._combine().i.z
288
406
 
407
+ @property
408
+ def point(self) -> "_StatsDomain[VectorSocket]":
409
+ return _StatsDomain(self.socket, "vector", "point")
410
+
411
+ @property
412
+ def edge(self) -> "_StatsDomain[VectorSocket]":
413
+ return _StatsDomain(self.socket, "vector", "edge")
414
+
415
+ @property
416
+ def face(self) -> "_StatsDomain[VectorSocket]":
417
+ return _StatsDomain(self.socket, "vector", "face")
418
+
419
+ @property
420
+ def corner(self) -> "_StatsDomain[VectorSocket]":
421
+ return _StatsDomain(self.socket, "vector", "corner")
422
+
423
+ @property
424
+ def spline(self) -> "_StatsDomain[VectorSocket]":
425
+ return _StatsDomain(self.socket, "vector", "spline")
426
+
427
+ @property
428
+ def instance(self) -> "_StatsDomain[VectorSocket]":
429
+ return _StatsDomain(self.socket, "vector", "instance")
430
+
431
+ @property
432
+ def layer(self) -> "_StatsDomain[VectorSocket]":
433
+ return _StatsDomain(self.socket, "vector", "layer")
434
+
289
435
  def dot(self, vector: InputVector) -> "FloatSocket":
290
436
  """Dot product with another vector. The other vector can be a Socket, a NodeSocket, or a 3-tuple of floats.
291
437
 
@@ -307,6 +453,61 @@ class _VectorMixin(BaseSocket):
307
453
  """
308
454
  return self._vmath.normalize(self.socket).o.vector
309
455
 
456
+ def cross(self, other: InputVector) -> "VectorSocket":
457
+ """Cross product of this vector with *other*. Returns a vector perpendicular to both."""
458
+ return self._vmath.cross_product(self.socket, other).o.vector
459
+
460
+ def distance(self, other: InputVector) -> "FloatSocket":
461
+ """Euclidean distance between this vector and *other*."""
462
+ return self._vmath.distance(self.socket, other).o.value
463
+
464
+ def project(self, other: InputVector) -> "VectorSocket":
465
+ """Project this vector onto *other*."""
466
+ return self._vmath.project(self.socket, other).o.vector
467
+
468
+ def reflect(self, normal: InputVector) -> "VectorSocket":
469
+ """Reflect this vector around *normal*. *normal* does not need to be normalised."""
470
+ return self._vmath.reflect(self.socket, normal).o.vector
471
+
472
+ def map_range(
473
+ self,
474
+ from_min: InputVector = (0.0, 0.0, 0.0),
475
+ from_max: InputVector = (1.0, 1.0, 1.0),
476
+ to_min: InputVector = (0.0, 0.0, 0.0),
477
+ to_max: InputVector = (1.0, 1.0, 1.0),
478
+ *,
479
+ clamp: bool = True,
480
+ interpolation_type: Literal[
481
+ "LINEAR", "STEPPED", "SMOOTHSTEP", "SMOOTHERSTEP"
482
+ ] = "LINEAR",
483
+ steps: InputVector = (4.0, 4.0, 4.0),
484
+ ) -> "VectorSocket":
485
+ """Convenience method to remap a vector socket using the `MapRange.vector()` node with this socket as input"""
486
+ from ..nodes.geometry import MapRange
487
+
488
+ node = MapRange.vector(self.socket, from_min, from_max, to_min, to_max)
489
+ node.clamp = clamp
490
+ node.interpolation_type = interpolation_type
491
+ if interpolation_type == "STEPPED":
492
+ kwargs = {"Steps_FLOAT3": steps}
493
+ node._establish_links(**kwargs)
494
+ return node.o.vector
495
+
496
+ def rotate(
497
+ self,
498
+ rotation: InputRotation,
499
+ ) -> "VectorSocket":
500
+ "Rotate this vector by the given rotation."
501
+ from ..nodes.geometry import RotateVector
502
+
503
+ return RotateVector(self.socket, rotation).o.vector
504
+
505
+ def transform(self, matrix: InputMatrix) -> "VectorSocket":
506
+ "Transform this vector by the given matrix."
507
+ from ..nodes.geometry import TransformPoint
508
+
509
+ return TransformPoint(self.socket, matrix).o.vector
510
+
310
511
  @property
311
512
  def default_value(self) -> list[float]:
312
513
  return list(self.socket.default_value)
@@ -428,10 +629,10 @@ class _VectorMixin(BaseSocket):
428
629
  def __rfloordiv__(self, other: Any) -> "VectorSocket": ...
429
630
  def __neg__(self) -> "VectorSocket": ...
430
631
  def __abs__(self) -> "VectorSocket": ...
431
- def __lt__(self, other: Any) -> "Compare[NodeSocketVector]": ...
432
- def __gt__(self, other: Any) -> "Compare[NodeSocketVector]": ...
433
- def __le__(self, other: Any) -> "Compare[NodeSocketVector]": ...
434
- def __ge__(self, other: Any) -> "Compare[NodeSocketVector]": ...
632
+ def __lt__(self, other: Any) -> "Compare[VectorSocket]": ...
633
+ def __gt__(self, other: Any) -> "Compare[VectorSocket]": ...
634
+ def __le__(self, other: Any) -> "Compare[VectorSocket]": ...
635
+ def __ge__(self, other: Any) -> "Compare[VectorSocket]": ...
435
636
 
436
637
 
437
638
  _SEPARATE_COLOR_IDNAMES = (
@@ -710,9 +911,37 @@ class _BooleanMixin(BaseSocket):
710
911
 
711
912
  return _BooleanSwitchSocketFactory(self.socket)
712
913
 
914
+ @property
915
+ def point(self) -> "_FieldDomain[BooleanSocket]":
916
+ return _FieldDomain(self.socket, "boolean", "point")
917
+
918
+ @property
919
+ def edge(self) -> "_FieldDomain[BooleanSocket]":
920
+ return _FieldDomain(self.socket, "boolean", "edge")
921
+
922
+ @property
923
+ def face(self) -> "_FieldDomain[BooleanSocket]":
924
+ return _FieldDomain(self.socket, "boolean", "face")
925
+
926
+ @property
927
+ def corner(self) -> "_FieldDomain[BooleanSocket]":
928
+ return _FieldDomain(self.socket, "boolean", "corner")
929
+
930
+ @property
931
+ def spline(self) -> "_FieldDomain[BooleanSocket]":
932
+ return _FieldDomain(self.socket, "boolean", "spline")
933
+
934
+ @property
935
+ def instance(self) -> "_FieldDomain[BooleanSocket]":
936
+ return _FieldDomain(self.socket, "boolean", "instance")
937
+
938
+ @property
939
+ def layer(self) -> "_FieldDomain[BooleanSocket]":
940
+ return _FieldDomain(self.socket, "boolean", "layer")
941
+
713
942
 
714
943
  class _RotationMixin(BaseSocket):
715
- """Rotation-specific properties (.w, .x, .y, .z) via RotationToQuaternion."""
944
+ """Rotation-specific methods."""
716
945
 
717
946
  socket: NodeSocketRotation
718
947
 
@@ -724,43 +953,101 @@ class _RotationMixin(BaseSocket):
724
953
  def default_value(self, value: Euler) -> None:
725
954
  self.socket.default_value = value
726
955
 
727
- @property
728
- def _quaternion(self) -> "geometry.RotationToQuaternion":
956
+ def invert(self) -> "RotationSocket":
957
+ "Invert the rotation of the socket."
958
+ from ..nodes.geometry import InvertRotation
959
+
960
+ return InvertRotation._find_or_create_linked(self.socket).o.rotation
961
+
962
+ def rotate(
963
+ self,
964
+ rotation: InputRotation,
965
+ rotation_space: Literal["GLOBAL", "LOCAL"] = "GLOBAL",
966
+ ) -> "RotationSocket":
967
+ "Rotate this rotation by the given rotation in the specified rotation space."
968
+ from ..nodes.geometry import RotateRotation
969
+
970
+ return RotateRotation(
971
+ self.socket, rotation, rotation_space=rotation_space
972
+ ).o.rotation
973
+
974
+ def to_euler(self) -> "VectorSocket":
975
+ "Convert the rotation to an XYZ euler rotation and return `VectorSocket`."
976
+ from ..nodes.geometry.converter import RotationToEuler
977
+
978
+ return RotationToEuler._find_or_create_linked(self.socket).o.euler
979
+
980
+ def to_quaternion(self) -> QuaternionComponents:
981
+ "Decompose the rotation into quaternion components `(w, x, y, z)`."
729
982
  from ..nodes.geometry import RotationToQuaternion
730
983
 
731
- return RotationToQuaternion._find_or_create_linked(self.socket)
984
+ o = RotationToQuaternion._find_or_create_linked(self.socket).o
985
+ return QuaternionComponents(o.w, o.x, o.y, o.z)
986
+
987
+ def to_axis_angle(self) -> AxisAngle:
988
+ "Decompose the rotation into axis-angle components `(axis, angle)`."
989
+ from ..nodes.geometry import RotationToAxisAngle
990
+
991
+ o = RotationToAxisAngle(self.socket).o
992
+ return AxisAngle(o.axis, o.angle)
732
993
 
733
994
  @property
734
- def w(self) -> FloatSocket:
735
- "Separate the rotation into a quaternion and return the `w` component"
736
- return self._quaternion.o.w
995
+ def point(self) -> "_FieldDomain[RotationSocket]":
996
+ return _FieldDomain(self.socket, "quaternion", "point")
737
997
 
738
998
  @property
739
- def x(self) -> FloatSocket:
740
- "Separate the rotation into a quaternion and return the `x` component"
741
- return self._quaternion.o.x
999
+ def edge(self) -> "_FieldDomain[RotationSocket]":
1000
+ return _FieldDomain(self.socket, "quaternion", "edge")
742
1001
 
743
1002
  @property
744
- def y(self) -> FloatSocket:
745
- "Separate the rotation into a quaternion and return the `y` component"
746
- return self._quaternion.o.y
1003
+ def face(self) -> "_FieldDomain[RotationSocket]":
1004
+ return _FieldDomain(self.socket, "quaternion", "face")
747
1005
 
748
1006
  @property
749
- def z(self) -> FloatSocket:
750
- "Separate the rotation into a quaternion and return the `z` component"
751
- return self._quaternion.o.z
1007
+ def corner(self) -> "_FieldDomain[RotationSocket]":
1008
+ return _FieldDomain(self.socket, "quaternion", "corner")
752
1009
 
753
- def euler(self) -> "VectorSocket":
754
- "Convert the rotation to an XYZ euler rotation and return `VectorSocket`."
755
- from ..nodes.geometry.converter import RotationToEuler
1010
+ @property
1011
+ def spline(self) -> "_FieldDomain[RotationSocket]":
1012
+ return _FieldDomain(self.socket, "quaternion", "spline")
756
1013
 
757
- return RotationToEuler._find_or_create_linked(self.socket).o.euler
1014
+ @property
1015
+ def instance(self) -> "_FieldDomain[RotationSocket]":
1016
+ return _FieldDomain(self.socket, "quaternion", "instance")
758
1017
 
759
- def invert(self) -> "RotationSocket":
760
- "Invert the rotation of the socket."
761
- from ..nodes.geometry import InvertRotation
1018
+ @property
1019
+ def layer(self) -> "_FieldDomain[RotationSocket]":
1020
+ return _FieldDomain(self.socket, "quaternion", "layer")
762
1021
 
763
- return InvertRotation._find_or_create_linked(self.socket).o.rotation
1022
+
1023
+ class _FloatMixDataTypeFactory:
1024
+ """Factory for typed Mix nodes driven by a float factor socket.
1025
+
1026
+ Access via ``FloatSocket.mix``. Each method creates a ``Mix`` node using
1027
+ this socket as the factor and returns the corresponding output socket.
1028
+ """
1029
+
1030
+ def __init__(self, socket: NodeSocket):
1031
+ self._socket = socket
1032
+ from ..nodes.geometry import Mix
1033
+
1034
+ self._mix = Mix
1035
+
1036
+ def float(self, a: InputFloat, b: InputFloat) -> "FloatSocket":
1037
+ "Mix two float values, returning a ``FloatSocket``."
1038
+ return self._mix.float(self._socket, a, b).o.result_float
1039
+
1040
+ def vector(self, a: InputVector, b: InputVector) -> "VectorSocket":
1041
+ "Mix two vectors, returning a ``VectorSocket``."
1042
+ return self._mix.vector(self._socket, a, b).o.result_vector
1043
+
1044
+ def color(self, a: InputColor, b: InputColor) -> "ColorSocket":
1045
+ "Mix two colors, returning a ``ColorSocket``."
1046
+ return self._mix.color(self._socket, a, b).o.result_color
1047
+
1048
+ def rotation(self, a: InputRotation, b: InputRotation) -> "RotationSocket":
1049
+ "Mix two rotations, returning a ``RotationSocket``."
1050
+ return self._mix.rotation(self._socket, a, b).o.result_rotation
764
1051
 
765
1052
 
766
1053
  class _FloatMixin(BaseSocket):
@@ -782,6 +1069,106 @@ class _FloatMixin(BaseSocket):
782
1069
 
783
1070
  return Math
784
1071
 
1072
+ @property
1073
+ def mix(self) -> _FloatMixDataTypeFactory:
1074
+ "Create a ``Mix`` node using this socket as the factor."
1075
+ return _FloatMixDataTypeFactory(self.socket)
1076
+
1077
+ @property
1078
+ def point(self) -> "_StatsDomain[FloatSocket]":
1079
+ return _StatsDomain(self.socket, "float", "point")
1080
+
1081
+ @property
1082
+ def edge(self) -> "_StatsDomain[FloatSocket]":
1083
+ return _StatsDomain(self.socket, "float", "edge")
1084
+
1085
+ @property
1086
+ def face(self) -> "_StatsDomain[FloatSocket]":
1087
+ return _StatsDomain(self.socket, "float", "face")
1088
+
1089
+ @property
1090
+ def corner(self) -> "_StatsDomain[FloatSocket]":
1091
+ return _StatsDomain(self.socket, "float", "corner")
1092
+
1093
+ @property
1094
+ def spline(self) -> "_StatsDomain[FloatSocket]":
1095
+ return _StatsDomain(self.socket, "float", "spline")
1096
+
1097
+ @property
1098
+ def instance(self) -> "_StatsDomain[FloatSocket]":
1099
+ return _StatsDomain(self.socket, "float", "instance")
1100
+
1101
+ @property
1102
+ def layer(self) -> "_StatsDomain[FloatSocket]":
1103
+ return _StatsDomain(self.socket, "float", "layer")
1104
+
1105
+ def map_range(
1106
+ self,
1107
+ from_min: InputFloat = 0.0,
1108
+ from_max: InputFloat = 1.0,
1109
+ to_min: InputFloat = 0.0,
1110
+ to_max: InputFloat = 1.0,
1111
+ *,
1112
+ clamp=True,
1113
+ interpolation_type: Literal[
1114
+ "LINEAR", "STEPPED", "SMOOTHSTEP", "SMOOTHERSTEP"
1115
+ ] = "LINEAR",
1116
+ steps: InputFloat = 4.0,
1117
+ ) -> "FloatSocket":
1118
+ """Remap the values on the float socket using the MapRange node."""
1119
+ from ..nodes.geometry import MapRange
1120
+
1121
+ node = MapRange.float(self.socket, from_min, from_max, to_min, to_max)
1122
+ node.clamp = clamp
1123
+ node.interpolation_type = interpolation_type
1124
+ if interpolation_type == "STEPPED":
1125
+ node._establish_links(steps=steps)
1126
+ return node.o.result
1127
+
1128
+ def clamp(self, min: InputFloat = 0.0, max: InputFloat = 1.0) -> "FloatSocket":
1129
+ """Clamp the value to *[min, max]*. Defaults to the unit interval ``[0, 1]``."""
1130
+ from ..nodes.geometry import Clamp
1131
+
1132
+ return Clamp.min_max(self.socket, min, max).o.result
1133
+
1134
+ def sqrt(self) -> "FloatSocket":
1135
+ """Return the square root of this value."""
1136
+ return self._math.square_root(self.socket).o.value
1137
+
1138
+ def power(self, exponent: InputFloat) -> "FloatSocket":
1139
+ """Raise this value to *exponent*."""
1140
+ return self._math.power(self.socket, exponent).o.value
1141
+
1142
+ def floor(self) -> "FloatSocket":
1143
+ """Round down to the nearest integer."""
1144
+ return self._math.floor(self.socket).o.value
1145
+
1146
+ def ceil(self) -> "FloatSocket":
1147
+ """Round up to the nearest integer."""
1148
+ return self._math.ceil(self.socket).o.value
1149
+
1150
+ def round(self) -> "FloatSocket":
1151
+ """Round to the nearest integer."""
1152
+ return self._math.round(self.socket).o.value
1153
+
1154
+ def modulo(self, divisor: InputFloat) -> "FloatSocket":
1155
+ """Floored modulo — remainder after dividing by *divisor*, always non-negative."""
1156
+ return self._math.floored_modulo(self.socket, divisor).o.value
1157
+
1158
+ def wrap(self, min: InputFloat, max: InputFloat) -> "FloatSocket":
1159
+ """Wrap the value into the *[min, max]* range, repeating cyclically."""
1160
+ # the wrap method has different order of arguments with max being first
1161
+ # compared to other nodes that are defined.
1162
+ return self._math.wrap(self.socket, value_001=max, value_002=min).o.value
1163
+
1164
+ def to_radians(self) -> "FloatSocket":
1165
+ """Convert degrees to radians."""
1166
+ return self._math.to_radians(self.socket).o.value
1167
+
1168
+ def to_degrees(self) -> "FloatSocket":
1169
+ """Convert radians to degrees."""
1170
+ return self._math.to_degrees(self.socket).o.value
1171
+
785
1172
  def sign(self) -> "FloatSocket":
786
1173
  "Return the sign of the FloatSocket, eithe `-1`, `0` or `1`."
787
1174
  return self._math.sign(self.socket).o.value
@@ -796,6 +1183,14 @@ class _FloatMixin(BaseSocket):
796
1183
 
797
1184
  return ValueToString.float(self.socket, decimals).o.string
798
1185
 
1186
+ def to_integer(
1187
+ self, rounding_mode: Literal["ROUND", "FLOOR", "CEILING", "TRUNCATE"] = "ROUND"
1188
+ ) -> "IntegerSocket":
1189
+ "Convert the `FloatSocket` to an `IntegerSocket` by truncating the decimal part."
1190
+ from ..nodes.geometry import FloatToInteger
1191
+
1192
+ return FloatToInteger(self.socket, rounding_mode=rounding_mode).o.integer
1193
+
799
1194
 
800
1195
  class _IntegerMixin(BaseSocket):
801
1196
  """Integer-specific dispatch — uses IntegerMath in geometry trees."""
@@ -812,6 +1207,34 @@ class _IntegerMixin(BaseSocket):
812
1207
  def default_value(self, value: int) -> None:
813
1208
  self.socket.default_value = value
814
1209
 
1210
+ @property
1211
+ def point(self) -> "_MinMaxDomain[IntegerSocket]":
1212
+ return _MinMaxDomain(self.socket, "integer", "point")
1213
+
1214
+ @property
1215
+ def edge(self) -> "_MinMaxDomain[IntegerSocket]":
1216
+ return _MinMaxDomain(self.socket, "integer", "edge")
1217
+
1218
+ @property
1219
+ def face(self) -> "_MinMaxDomain[IntegerSocket]":
1220
+ return _MinMaxDomain(self.socket, "integer", "face")
1221
+
1222
+ @property
1223
+ def corner(self) -> "_MinMaxDomain[IntegerSocket]":
1224
+ return _MinMaxDomain(self.socket, "integer", "corner")
1225
+
1226
+ @property
1227
+ def spline(self) -> "_MinMaxDomain[IntegerSocket]":
1228
+ return _MinMaxDomain(self.socket, "integer", "spline")
1229
+
1230
+ @property
1231
+ def instance(self) -> "_MinMaxDomain[IntegerSocket]":
1232
+ return _MinMaxDomain(self.socket, "integer", "instance")
1233
+
1234
+ @property
1235
+ def layer(self) -> "_MinMaxDomain[IntegerSocket]":
1236
+ return _MinMaxDomain(self.socket, "integer", "layer")
1237
+
815
1238
  @property
816
1239
  def _imath(self) -> "type[IntegerMath]":
817
1240
  from ..nodes.geometry import IntegerMath
@@ -824,6 +1247,16 @@ class _IntegerMixin(BaseSocket):
824
1247
 
825
1248
  return ValueToString.integer(self.socket).o.string
826
1249
 
1250
+ def clamp(self, min: InputInteger = 0, max: InputInteger = 1) -> "IntegerSocket":
1251
+ """Clamp the value to *[min, max]*."""
1252
+ return self._imath.minimum(
1253
+ self._imath.maximum(self.socket, min).o.value, max
1254
+ ).o.value
1255
+
1256
+ def modulo(self, divisor: InputInteger) -> "IntegerSocket":
1257
+ """Remainder after dividing by *divisor* (always non-negative)."""
1258
+ return self._imath.modulo(self.socket, divisor).o.value
1259
+
827
1260
  def sign(self) -> "IntegerSocket":
828
1261
  "Return the sign of the IntegerSocket, either `-1`, `0`, or `1`."
829
1262
  return self._imath.sign(self.socket).o.value
@@ -894,10 +1327,10 @@ class _IntegerMixin(BaseSocket):
894
1327
  def __rfloordiv__(self, other: Any) -> "IntegerSocket": ...
895
1328
  def __neg__(self) -> "IntegerSocket": ...
896
1329
  def __abs__(self) -> "IntegerSocket": ...
897
- def __lt__(self, other: Any) -> "Compare[NodeSocketInt]": ...
898
- def __gt__(self, other: Any) -> "Compare[NodeSocketInt]": ...
899
- def __le__(self, other: Any) -> "Compare[NodeSocketInt]": ...
900
- def __ge__(self, other: Any) -> "Compare[NodeSocketInt]": ...
1330
+ def __lt__(self, other: Any) -> "Compare[IntegerSocket]": ...
1331
+ def __gt__(self, other: Any) -> "Compare[IntegerSocket]": ...
1332
+ def __le__(self, other: Any) -> "Compare[IntegerSocket]": ...
1333
+ def __ge__(self, other: Any) -> "Compare[IntegerSocket]": ...
901
1334
 
902
1335
 
903
1336
  class _StringMixin(BaseSocket):
@@ -959,15 +1392,12 @@ class _StringMixin(BaseSocket):
959
1392
 
960
1393
  return StringLength(self.socket).o.length
961
1394
 
962
- def find(self, search: InputString) -> tuple["IntegerSocket", "IntegerSocket"]:
963
- """Find where in a string a pattern occurs.
964
-
965
- Returns a tuple(IntegerSocket, IntegerSocket), corresponding to (index_of_first_match, count_of_matches)."""
1395
+ def find(self, search: InputString) -> FindResult:
1396
+ "Find where in a string a pattern occurs. Returns `(first_found, count)`."
966
1397
  from ..nodes.geometry import FindInString
967
1398
 
968
- node = FindInString(self.socket, search)
969
-
970
- return (node.o.first_found, node.o.count)
1399
+ o = FindInString(self.socket, search).o
1400
+ return FindResult(o.first_found, o.count)
971
1401
 
972
1402
  def join(
973
1403
  self, strings: Iterable[str | "StringSocket" | NodeSocketString | BaseNode]
@@ -1035,11 +1465,50 @@ class _MatrixMixin(BaseSocket):
1035
1465
 
1036
1466
  return TransposeMatrix._find_or_create_linked(self.socket).o.matrix
1037
1467
 
1038
- def svd(self) -> tuple[MatrixSocket, VectorSocket, MatrixSocket]:
1039
- "Compute the 'Single Value Decomposition' and return output sockets of the MatrixSVD node, `tuple[u, s, v]`"
1468
+ def svd(self) -> SVDResult:
1469
+ "Decompose the matrix via SVD. Returns `(u, s, v)`."
1040
1470
  from ..nodes.geometry import MatrixSVD
1041
1471
 
1042
- return tuple(MatrixSVD(self.socket).o)
1472
+ o = MatrixSVD(self.socket).o
1473
+ return SVDResult(o.u, o.s, o.v)
1474
+
1475
+ def transform_direction(self, direction: InputVector) -> "VectorSocket":
1476
+ """Apply this matrix to *direction*, ignoring translation.
1477
+
1478
+ Use this instead of ``transform()`` when transforming a direction vector
1479
+ (e.g. a normal) where translation must not affect the result.
1480
+ """
1481
+ from ..nodes.geometry import TransformDirection
1482
+
1483
+ return TransformDirection(direction, self.socket).o.direction
1484
+
1485
+ @property
1486
+ def point(self) -> "_FieldDomain[MatrixSocket]":
1487
+ return _FieldDomain(self.socket, "matrix", "point")
1488
+
1489
+ @property
1490
+ def edge(self) -> "_FieldDomain[MatrixSocket]":
1491
+ return _FieldDomain(self.socket, "matrix", "edge")
1492
+
1493
+ @property
1494
+ def face(self) -> "_FieldDomain[MatrixSocket]":
1495
+ return _FieldDomain(self.socket, "matrix", "face")
1496
+
1497
+ @property
1498
+ def corner(self) -> "_FieldDomain[MatrixSocket]":
1499
+ return _FieldDomain(self.socket, "matrix", "corner")
1500
+
1501
+ @property
1502
+ def spline(self) -> "_FieldDomain[MatrixSocket]":
1503
+ return _FieldDomain(self.socket, "matrix", "spline")
1504
+
1505
+ @property
1506
+ def instance(self) -> "_FieldDomain[MatrixSocket]":
1507
+ return _FieldDomain(self.socket, "matrix", "instance")
1508
+
1509
+ @property
1510
+ def layer(self) -> "_FieldDomain[MatrixSocket]":
1511
+ return _FieldDomain(self.socket, "matrix", "layer")
1043
1512
 
1044
1513
  @overload
1045
1514
  def __getitem__(self, key: slice) -> "list[FloatSocket]": ...
@@ -1071,10 +1540,12 @@ class _MatrixMixin(BaseSocket):
1071
1540
 
1072
1541
  if self.socket.is_output:
1073
1542
  node = SeparateMatrix._find_or_create_linked(self.socket)
1074
- return iter(node.o)
1543
+ for i in node.o:
1544
+ yield cast(FloatSocket, i)
1075
1545
  else:
1076
1546
  node = CombineMatrix._find_or_create_linked(self.socket)
1077
- return iter(node.i)
1547
+ for i in node.i:
1548
+ yield cast(FloatSocket, i)
1078
1549
 
1079
1550
  def __len__(self) -> int:
1080
1551
  return 16
@@ -14,12 +14,10 @@ from ...builder import (
14
14
  from . import (
15
15
  AxesToRotation,
16
16
  CombineMatrix,
17
+ Compare,
17
18
  EdgesOfVertex,
18
19
  EdgeVertices,
19
- EvaluateAtIndex,
20
- FieldAverage,
21
20
  Frame,
22
- Switch,
23
21
  )
24
22
 
25
23
 
@@ -60,13 +58,15 @@ class OtherVertex(CustomGeometryGroup):
60
58
  vertex_index = tree.inputs.integer("Vertex Index", default_input="INDEX")
61
59
  edge_number = tree.inputs.integer("Edge Number")
62
60
 
63
- eov = EdgesOfVertex(vertex_index, sort_index=edge_number)
61
+ eov = EdgesOfVertex(vertex_index, sort_index=edge_number).o.edge_index
64
62
  ev = EdgeVertices()
65
- vert_1 = EvaluateAtIndex.edge.integer(ev.o.vertex_index_1, eov)
66
- vert_2 = EvaluateAtIndex.edge.integer(ev.o.vertex_index_2, eov)
67
- switch = (vert_1 != vertex_index) >> Switch.integer(..., vert_1, vert_2)
63
+ vert_1 = ev.o.vertex_index_1.edge.at(eov)
64
+ vert_2 = ev.o.vertex_index_2.edge.at(eov)
65
+ index = Compare.integer.not_equal(vert_1, vertex_index).o.result.switch.integer(
66
+ vert_1, vert_2
67
+ )
68
68
 
69
- _ = switch >> tree.outputs.integer("Other Vertex")
69
+ index >> tree.outputs.integer("Other Vertex")
70
70
 
71
71
 
72
72
  class OffsetVector(CustomGeometryGroup):
@@ -110,7 +110,7 @@ class OffsetVector(CustomGeometryGroup):
110
110
  vector = tree.inputs.vector("Vector", default_input="POSITION")
111
111
  offset = tree.inputs.integer("Offset")
112
112
 
113
- value = EvaluateAtIndex.point.vector(vector, index + offset)
113
+ value = vector.point.at(index + offset)
114
114
 
115
115
  _ = value >> tree.outputs.vector("Vector")
116
116
 
@@ -177,7 +177,7 @@ class PrincipalComponents(CustomGeometryGroup):
177
177
  out_short = tree.outputs.vector("Shortest Axis")
178
178
 
179
179
  with Frame("Centroid"):
180
- centroid = FieldAverage.point.vector(position, group_id)
180
+ centroid = position.point.mean(group_id)
181
181
  centroid >> out_centroid
182
182
 
183
183
  with Frame("Covariance Matrix"):
@@ -185,8 +185,8 @@ class PrincipalComponents(CustomGeometryGroup):
185
185
  matrix = CombineMatrix()
186
186
 
187
187
  for i, axis1 in enumerate(diff):
188
- mean = FieldAverage.point.vector(diff * axis1, group_id)
189
- for j, axis2 in enumerate(mean.o.mean):
188
+ mean = (diff * axis1).point.mean(group_id)
189
+ for j, axis2 in enumerate(mean):
190
190
  axis2 >> matrix.i[int(i * 4 + j)]
191
191
 
192
192
  with Frame("SVD"):
@@ -14,6 +14,7 @@ from bpy.types import (
14
14
  )
15
15
 
16
16
  from nodebpy.builder._registry import _get_socket_linker
17
+ from nodebpy.builder.socket import BaseSocket
17
18
 
18
19
  from ...builder import (
19
20
  BaseNode,
@@ -88,7 +89,7 @@ from .zone import (
88
89
  _sync_closure_items,
89
90
  )
90
91
 
91
- _T = TypeVar("_T")
92
+ _T = TypeVar("_T", bound=BaseSocket)
92
93
  _S = TypeVar("_S")
93
94
 
94
95
  __all__ = (
@@ -1441,7 +1442,7 @@ class JoinGeometry(BaseNode):
1441
1442
 
1442
1443
  def __init__(self, geometry: Iterable[InputGeometry] = ()):
1443
1444
  super().__init__()
1444
- for source in reversed(geometry):
1445
+ for source in reversed(list(geometry)):
1445
1446
  assert source
1446
1447
  self._link(*self._find_best_socket_pair(source, self))
1447
1448
 
@@ -2493,20 +2494,6 @@ class FieldAverage(BaseNode, Generic[_T]):
2493
2494
  instance = _FieldAverageDomainFactory("INSTANCE")
2494
2495
  layer = _FieldAverageDomainFactory("LAYER")
2495
2496
 
2496
- def __init__(
2497
- self,
2498
- value: InputFloat | InputVector = None,
2499
- group_index: InputFloat | InputVector = 0,
2500
- *,
2501
- data_type: Literal["FLOAT", "FLOAT_VECTOR"] = "FLOAT",
2502
- domain: _AttributeDomains = "POINT",
2503
- ):
2504
- super().__init__()
2505
- key_args = {"Value": value, "Group Index": group_index}
2506
- self.data_type = data_type
2507
- self.domain = domain
2508
- self._establish_links(**key_args)
2509
-
2510
2497
  class _Inputs(SocketAccessor, Generic[_S]):
2511
2498
  value: _S
2512
2499
  """The field value to average."""
@@ -2527,6 +2514,20 @@ class FieldAverage(BaseNode, Generic[_T]):
2527
2514
  @property
2528
2515
  def o(self) -> "_Outputs[_T]": ...
2529
2516
 
2517
+ def __init__(
2518
+ self,
2519
+ value: InputFloat | InputVector = None,
2520
+ group_index: InputFloat | InputVector = 0,
2521
+ *,
2522
+ data_type: Literal["FLOAT", "FLOAT_VECTOR"] = "FLOAT",
2523
+ domain: _AttributeDomains = "POINT",
2524
+ ):
2525
+ super().__init__()
2526
+ key_args = {"Value": value, "Group Index": group_index}
2527
+ self.data_type = data_type
2528
+ self.domain = domain
2529
+ self._establish_links(**key_args)
2530
+
2530
2531
  @property
2531
2532
  def data_type(self) -> Literal["FLOAT", "FLOAT_VECTOR"]:
2532
2533
  return self.node.data_type
@@ -2597,20 +2598,6 @@ class FieldMinAndMax(BaseNode, Generic[_T]):
2597
2598
  instance = _FieldMinAndMaxDomainFactory("INSTANCE")
2598
2599
  layer = _FieldMinAndMaxDomainFactory("LAYER")
2599
2600
 
2600
- def __init__(
2601
- self,
2602
- value: InputFloat | InputVector | InputInteger = 1.0,
2603
- group_index: InputInteger = 0,
2604
- *,
2605
- data_type: Literal["FLOAT", "INT", "FLOAT_VECTOR"] = "FLOAT",
2606
- domain: _AttributeDomains = "POINT",
2607
- ):
2608
- super().__init__()
2609
- key_args = {"Value": value, "Group Index": group_index}
2610
- self.data_type = data_type
2611
- self.domain = domain
2612
- self._establish_links(**key_args)
2613
-
2614
2601
  class _Inputs(SocketAccessor, Generic[_S]):
2615
2602
  value: _S
2616
2603
  """The field value to find the min/max of."""
@@ -2631,6 +2618,20 @@ class FieldMinAndMax(BaseNode, Generic[_T]):
2631
2618
  @property
2632
2619
  def o(self) -> "_Outputs[_T]": ...
2633
2620
 
2621
+ def __init__(
2622
+ self,
2623
+ value: InputFloat | InputVector | InputInteger = 1.0,
2624
+ group_index: InputInteger = 0,
2625
+ *,
2626
+ data_type: Literal["FLOAT", "INT", "FLOAT_VECTOR"] = "FLOAT",
2627
+ domain: _AttributeDomains = "POINT",
2628
+ ):
2629
+ super().__init__()
2630
+ key_args = {"Value": value, "Group Index": group_index}
2631
+ self.data_type = data_type
2632
+ self.domain = domain
2633
+ self._establish_links(**key_args)
2634
+
2634
2635
  @property
2635
2636
  def data_type(self) -> Literal["FLOAT", "INT", "FLOAT_VECTOR"]:
2636
2637
  return self.node.data_type
@@ -2681,7 +2682,7 @@ class EvaluateOnDomain(BaseNode, Generic[_T]):
2681
2682
  value, domain=self._domain, data_type="FLOAT_VECTOR"
2682
2683
  )
2683
2684
 
2684
- def rotation(
2685
+ def quaternion(
2685
2686
  self, value: InputRotation = None
2686
2687
  ) -> "EvaluateOnDomain[RotationSocket]":
2687
2688
  return EvaluateOnDomain(value, domain=self._domain, data_type="QUATERNION")
@@ -100,7 +100,7 @@ InputColor = typing.Union[
100
100
  InputLinkable,
101
101
  "ColorSocket",
102
102
  ]
103
- InputString = typing.Union[str, NodeSocketString, EllipsisType, "StringSocket"]
103
+ InputString = typing.Union[None, str, NodeSocketString, EllipsisType, "StringSocket"]
104
104
  InputGeometry = typing.Union[NodeSocketGeometry, InputLinkable, "GeometrySocket"]
105
105
  InputObject = typing.Union[NodeSocketObject, Object, InputLinkable, "ObjectSocket"]
106
106
  InputMaterial = typing.Union[
File without changes