nodebpy 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.
@@ -0,0 +1,2051 @@
1
+ from typing import Any, Literal
2
+
3
+ import bpy
4
+
5
+ from ..builder import (
6
+ DynamicInputsMixin,
7
+ NodeBuilder,
8
+ NodeSocket,
9
+ SocketError,
10
+ SocketLinker,
11
+ )
12
+ from ..types import (
13
+ LINKABLE,
14
+ SOCKET_TYPES,
15
+ TYPE_INPUT_ALL,
16
+ TYPE_INPUT_BOOLEAN,
17
+ TYPE_INPUT_COLOR,
18
+ TYPE_INPUT_DATA,
19
+ TYPE_INPUT_GEOMETRY,
20
+ TYPE_INPUT_GRID,
21
+ TYPE_INPUT_INT,
22
+ TYPE_INPUT_MATRIX,
23
+ TYPE_INPUT_MENU,
24
+ TYPE_INPUT_ROTATION,
25
+ TYPE_INPUT_STRING,
26
+ TYPE_INPUT_VALUE,
27
+ TYPE_INPUT_VECTOR,
28
+ _AccumulateFieldDataTypes,
29
+ _AttributeDataTypes,
30
+ _AttributeDomains,
31
+ _BakeDataTypes,
32
+ _BakedDataTypeValues,
33
+ _EvaluateAtIndexDataTypes,
34
+ _GridDataTypes,
35
+ _is_default_value,
36
+ )
37
+ from .zone import (
38
+ RepeatInput,
39
+ RepeatOutput,
40
+ RepeatZone,
41
+ SimulationInput,
42
+ SimulationOutput,
43
+ SimulationZone,
44
+ )
45
+
46
+ __all__ = (
47
+ "RepeatInput",
48
+ "RepeatOutput",
49
+ "RepeatZone",
50
+ "SimulationInput",
51
+ "SimulationOutput",
52
+ "SimulationZone",
53
+ "GeometryToInstance",
54
+ "SDFGridBoolean",
55
+ #
56
+ "SetHandleType",
57
+ "HandleTypeSelection",
58
+ "IndexSwitch",
59
+ "MenuSwitch",
60
+ "CaptureAttribute",
61
+ "FieldToGrid",
62
+ "JoinGeometry",
63
+ "SDFGridBoolean",
64
+ "Bake",
65
+ "JoinStrings",
66
+ "GeometryToInstance",
67
+ "RepeatInput",
68
+ "RepeatOutput",
69
+ "RepeatZone",
70
+ "SimulationInput",
71
+ "SimulationOutput",
72
+ "SimulationZone",
73
+ "FormatString",
74
+ "Value",
75
+ "AccumulateField",
76
+ "EvaluateAtIndex",
77
+ "FieldAverage",
78
+ "FieldMinAndMax",
79
+ "EvaluateOnDomain",
80
+ "FieldVariance",
81
+ "Compare",
82
+ "AttributeStatistic",
83
+ )
84
+
85
+
86
+ class Bake(NodeBuilder, DynamicInputsMixin):
87
+ """Cache the incoming data so that it can be used without recomputation
88
+
89
+ TODO: properly handle Animation / Still bake opations and ability to bake to a file
90
+ """
91
+
92
+ _bl_idname = "GeometryNodeBake"
93
+ node: bpy.types.GeometryNodeBake
94
+ _socket_data_types = _BakedDataTypeValues
95
+
96
+ def __init__(self, *args, **kwargs):
97
+ super().__init__()
98
+ self._establish_links(**self._add_inputs(*args, **kwargs))
99
+
100
+ def _add_socket( # type: ignore
101
+ self, name: str, type: _BakeDataTypes, default_value: Any | None = None
102
+ ):
103
+ item = self.node.bake_items.new(socket_type=type, name=name)
104
+ return self.node.inputs[item.name]
105
+
106
+ @property
107
+ def outputs(self) -> dict[str, SocketLinker]:
108
+ return {
109
+ item.name: SocketLinker(self.node.outputs[item.name])
110
+ for item in self.node.bake_items
111
+ }
112
+
113
+ @property
114
+ def inputs(self) -> dict[str, SocketLinker]:
115
+ return {
116
+ item.name: SocketLinker(self.node.inputs[item.name])
117
+ for item in self.node.bake_items
118
+ }
119
+
120
+ @property
121
+ def i_input_socket(self) -> SocketLinker:
122
+ """Input socket:"""
123
+ return self._input("__extend__")
124
+
125
+ @property
126
+ def o_input_socket(self) -> SocketLinker:
127
+ """Output socket:"""
128
+ return self._output("__extend__")
129
+
130
+
131
+ class GeometryToInstance(NodeBuilder):
132
+ """Convert each input geometry into an instance, which can be much faster than the Join Geometry node when the inputs are large"""
133
+
134
+ _bl_idname = "GeometryNodeGeometryToInstance"
135
+ node: bpy.types.GeometryNodeGeometryToInstance
136
+
137
+ def __init__(self, *args: TYPE_INPUT_GEOMETRY):
138
+ super().__init__()
139
+ for arg in reversed(args):
140
+ self._link_from(arg, "Geometry")
141
+
142
+ @property
143
+ def i_geometry(self) -> SocketLinker:
144
+ """Input socket: Geometry"""
145
+ return self._input("Geometry")
146
+
147
+ @property
148
+ def o_instances(self) -> SocketLinker:
149
+ """Output socket: Instances"""
150
+ return self._output("Instances")
151
+
152
+
153
+ class Value(NodeBuilder):
154
+ """Input numerical values to other nodes in the tree"""
155
+
156
+ _bl_idname = "ShaderNodeValue"
157
+ node: bpy.types.ShaderNodeValue
158
+
159
+ def __init__(self, value: float = 0.0):
160
+ super().__init__()
161
+ self.value = value
162
+
163
+ @property
164
+ def value(self) -> float:
165
+ """Input socket: Value"""
166
+ # this node is a strange one because it doesn't have a value property,
167
+ # instead we directly access and change the sockets default output
168
+ return self.node.outputs[0].default_value # type: ignore
169
+
170
+ @value.setter
171
+ def value(self, value: float):
172
+ self.node.outputs[0].default_value = value # type: ignore
173
+
174
+ @property
175
+ def o_value(self) -> SocketLinker:
176
+ """Output socket: Value"""
177
+ return self._output("Value")
178
+
179
+
180
+ class FormatString(NodeBuilder, DynamicInputsMixin):
181
+ """Insert values into a string using a Python and path template compatible formatting syntax"""
182
+
183
+ _bl_idname = "FunctionNodeFormatString"
184
+ node: bpy.types.FunctionNodeFormatString
185
+ _socket_data_types = ("VALUE", "INT", "STRING")
186
+ _type_map = {
187
+ "VALUE": "FLOAT",
188
+ }
189
+
190
+ def __init__(
191
+ self,
192
+ *args,
193
+ format: TYPE_INPUT_STRING = "",
194
+ **kwargs,
195
+ ):
196
+ super().__init__()
197
+ key_args = {"Format": format}
198
+ key_args.update(self._add_inputs(*args, **kwargs)) # type: ignore
199
+ self._establish_links(**key_args)
200
+
201
+ def _add_socket( # type: ignore
202
+ self,
203
+ name: str,
204
+ type: Literal["FLOAT", "INT", "STRING"] = "FLOAT",
205
+ default_value: float | int | str | None = None,
206
+ ):
207
+ item = self.node.format_items.new(socket_type=type, name=name)
208
+ if default_value is not None:
209
+ try:
210
+ self.node.inputs[item.name].default_value = default_value # type: ignore
211
+ except TypeError as e:
212
+ raise ValueError(
213
+ f"Invalid default value for {type}: {default_value}"
214
+ ) from e
215
+ return self.node.inputs[item.name]
216
+
217
+ @property
218
+ def i_format(self) -> SocketLinker:
219
+ """Input socket: Format"""
220
+ return self._input("Format")
221
+
222
+ @property
223
+ def i_input_socket(self) -> SocketLinker:
224
+ """Input socket:"""
225
+ return self._input("__extend__")
226
+
227
+ @property
228
+ def items(self) -> dict[str, SocketLinker]:
229
+ """Input sockets:"""
230
+ return {socket.name: self._input(socket.name) for socket in self.node.inputs}
231
+
232
+ @property
233
+ def o_string(self) -> SocketLinker:
234
+ """Output socket: String"""
235
+ return self._output("String")
236
+
237
+
238
+ class JoinStrings(NodeBuilder):
239
+ """Combine any number of input strings"""
240
+
241
+ _bl_idname = "GeometryNodeStringJoin"
242
+ node: bpy.types.GeometryNodeStringJoin
243
+
244
+ def __init__(self, *args: LINKABLE, delimiter: TYPE_INPUT_STRING = ""):
245
+ super().__init__()
246
+
247
+ self._establish_links(Delimiter=delimiter)
248
+ for arg in args:
249
+ self._link_from(arg, "Strings")
250
+
251
+ @property
252
+ def i_delimiter(self) -> SocketLinker:
253
+ """Input socket: Delimiter"""
254
+ return self._input("Delimiter")
255
+
256
+ @property
257
+ def i_strings(self) -> SocketLinker:
258
+ """Input socket: Strings"""
259
+ return self._input("Strings")
260
+
261
+ @property
262
+ def o_string(self) -> SocketLinker:
263
+ """Output socket: String"""
264
+ return self._output("String")
265
+
266
+
267
+ class MeshBoolean(NodeBuilder):
268
+ """Cut, subtract, or join multiple mesh inputs"""
269
+
270
+ _bl_idname = "GeometryNodeMeshBoolean"
271
+ node: bpy.types.GeometryNodeMeshBoolean
272
+
273
+ def __init__(
274
+ self,
275
+ *args: TYPE_INPUT_GEOMETRY,
276
+ operation: Literal["INTERSECT", "UNION", "DIFFERENCE"] = "DIFFERENCE",
277
+ solver: Literal["EXACT", "FLOAT", "MANIFOLD"] = "FLOAT",
278
+ **kwargs,
279
+ ):
280
+ super().__init__()
281
+ key_args = {}
282
+ key_args.update(kwargs)
283
+ self.operation = operation
284
+ self.solver = solver
285
+ for arg in args:
286
+ self._link_from(arg, "Mesh 2")
287
+ self._establish_links(**key_args)
288
+
289
+ @classmethod
290
+ def intersect(
291
+ cls,
292
+ *args: TYPE_INPUT_GEOMETRY,
293
+ self_intersection: TYPE_INPUT_BOOLEAN = False,
294
+ hole_tolerant: TYPE_INPUT_BOOLEAN = False,
295
+ solver: Literal["EXACT", "FLOAT", "MANIFOLD"] = "FLOAT",
296
+ ):
297
+ key_args = {}
298
+ if solver == "EXACT":
299
+ key_args["Self Intersection"] = self_intersection
300
+ key_args["Hole Tolerant"] = hole_tolerant
301
+ return cls(
302
+ *args,
303
+ **key_args,
304
+ solver=solver,
305
+ operation="INTERSECT",
306
+ )
307
+
308
+ @classmethod
309
+ def m_union(
310
+ cls,
311
+ *args: TYPE_INPUT_GEOMETRY,
312
+ hole_tolerant: TYPE_INPUT_BOOLEAN = False,
313
+ self_intersection: TYPE_INPUT_BOOLEAN = False,
314
+ solver: Literal["EXACT", "FLOAT", "MANIFOLD"] = "FLOAT",
315
+ ):
316
+ key_args = {}
317
+ if solver == "EXACT":
318
+ key_args["Self Intersection"] = self_intersection
319
+ key_args["Hole Tolerant"] = hole_tolerant
320
+ return cls(
321
+ *args,
322
+ **key_args,
323
+ solver=solver,
324
+ operation="UNION",
325
+ )
326
+
327
+ @classmethod
328
+ def m_difference(
329
+ cls,
330
+ *args: TYPE_INPUT_GEOMETRY,
331
+ mesh_1: TYPE_INPUT_GEOMETRY = None,
332
+ hole_tolerant: TYPE_INPUT_BOOLEAN = False,
333
+ self_intersection: TYPE_INPUT_BOOLEAN = False,
334
+ solver: Literal["EXACT", "FLOAT", "MANIFOLD"] = "FLOAT",
335
+ ):
336
+ key_args = {}
337
+ key_args["Mesh 1"] = mesh_1
338
+ if solver == "EXACT":
339
+ key_args["Self Intersection"] = self_intersection
340
+ key_args["Hole Tolerant"] = hole_tolerant
341
+ return cls(
342
+ *args,
343
+ **key_args,
344
+ solver=solver,
345
+ operation="DIFFERENCE",
346
+ )
347
+
348
+ @classmethod
349
+ def union(cls, mesh_1: LINKABLE = None, mesh_2: LINKABLE = None) -> "MeshBoolean":
350
+ """Create Mesh Boolean with operation 'Union'."""
351
+ return cls(operation="UNION", mesh_1=mesh_1, mesh_2=mesh_2)
352
+
353
+ @classmethod
354
+ def difference(
355
+ cls, mesh_1: LINKABLE = None, mesh_2: LINKABLE = None
356
+ ) -> "MeshBoolean":
357
+ """Create Mesh Boolean with operation 'Difference'."""
358
+ return cls(operation="DIFFERENCE", mesh_1=mesh_1, mesh_2=mesh_2)
359
+
360
+ @property
361
+ def i_mesh_1(self) -> SocketLinker:
362
+ """Input socket: Mesh 1"""
363
+ return self._input("Mesh 1")
364
+
365
+ @property
366
+ def i_mesh_2(self) -> SocketLinker:
367
+ """Input socket: Mesh 2"""
368
+ return self._input("Mesh 2")
369
+
370
+ @property
371
+ def o_mesh(self) -> SocketLinker:
372
+ """Output socket: Mesh"""
373
+ return self._output("Mesh")
374
+
375
+ @property
376
+ def o_intersecting_edges(self) -> SocketLinker:
377
+ """Output socket: Mesh"""
378
+ if self.solver == "FLOAT":
379
+ raise ValueError("Intersecting Edges is not supported for FLOAT solver")
380
+ return self._output("Intersecting Edges")
381
+
382
+ @property
383
+ def operation(self) -> Literal["INTERSECT", "UNION", "DIFFERENCE"]:
384
+ return self.node.operation
385
+
386
+ @operation.setter
387
+ def operation(self, value: Literal["INTERSECT", "UNION", "DIFFERENCE"]):
388
+ self.node.operation = value
389
+
390
+ @property
391
+ def solver(self) -> Literal["EXACT", "FLOAT", "MANIFOLD"]:
392
+ return self.node.solver
393
+
394
+ @solver.setter
395
+ def solver(self, value: Literal["EXACT", "FLOAT", "MANIFOLD"]):
396
+ self.node.solver = value
397
+
398
+
399
+ class JoinGeometry(NodeBuilder):
400
+ """Merge separately generated geometries into a single one"""
401
+
402
+ _bl_idname = "GeometryNodeJoinGeometry"
403
+ node: bpy.types.GeometryNodeJoinGeometry
404
+
405
+ def __init__(self, *args: LINKABLE):
406
+ super().__init__()
407
+ for source in reversed(args):
408
+ try:
409
+ self._link(*self._find_best_socket_pair(source, self))
410
+ except SocketError:
411
+ self._link(*source._find_best_socket_pair(source, self))
412
+
413
+ @property
414
+ def i_geometry(self) -> SocketLinker:
415
+ """Input socket: Geometry"""
416
+ return self._input("Geometry")
417
+
418
+ @property
419
+ def o_geometry(self) -> SocketLinker:
420
+ """Output socket: Geometry"""
421
+ return self._output("Geometry")
422
+
423
+
424
+ class SetHandleType(NodeBuilder):
425
+ """Set the handle type for the control points of a Bézier curve"""
426
+
427
+ _bl_idname = "GeometryNodeCurveSetHandles"
428
+ node: bpy.types.GeometryNodeCurveSetHandles
429
+
430
+ def __init__(
431
+ self,
432
+ curve: TYPE_INPUT_GEOMETRY = None,
433
+ selection: TYPE_INPUT_BOOLEAN = True,
434
+ *,
435
+ left: bool = False,
436
+ right: bool = False,
437
+ handle_type: Literal["FREE", "AUTO", "VECTOR", "ALIGN"] = "AUTO",
438
+ ):
439
+ super().__init__()
440
+ key_args = {"Curve": curve, "Selection": selection}
441
+ self.handle_type = handle_type
442
+ self.left = left
443
+ self.right = right
444
+ self._establish_links(**key_args)
445
+
446
+ @property
447
+ def i_curve(self) -> SocketLinker:
448
+ """Input socket: Curve"""
449
+ return self._input("Curve")
450
+
451
+ @property
452
+ def i_selection(self) -> SocketLinker:
453
+ """Input socket: Selection"""
454
+ return self._input("Selection")
455
+
456
+ @property
457
+ def o_curve(self) -> SocketLinker:
458
+ """Output socket: Curve"""
459
+ return self._output("Curve")
460
+
461
+ @property
462
+ def handle_type(self) -> Literal["FREE", "AUTO", "VECTOR", "ALIGN"]:
463
+ return self.node.handle_type
464
+
465
+ @handle_type.setter
466
+ def handle_type(self, value: Literal["FREE", "AUTO", "VECTOR", "ALIGN"]):
467
+ self.node.handle_type = value
468
+
469
+ @property
470
+ def left(self) -> bool:
471
+ return "LEFT" in self.node.mode
472
+
473
+ @left.setter
474
+ def left(self, value: bool):
475
+ match value, self.right:
476
+ case True, True:
477
+ self.node.mode = {"LEFT", "RIGHT"}
478
+ case True, False:
479
+ self.node.mode = {"LEFT"}
480
+ case False, True:
481
+ self.node.mode = {"RIGHT"}
482
+ case False, False:
483
+ self.node.mode = set()
484
+
485
+ @property
486
+ def right(self) -> bool:
487
+ return "RIGHT" in self.node.mode
488
+
489
+ @right.setter
490
+ def right(self, value: bool):
491
+ match self.left, value:
492
+ case True, True:
493
+ self.node.mode = {"LEFT", "RIGHT"}
494
+ case True, False:
495
+ self.node.mode = {"LEFT"}
496
+ case False, True:
497
+ self.node.mode = {"RIGHT"}
498
+ case False, False:
499
+ self.node.mode = set()
500
+
501
+
502
+ class HandleTypeSelection(NodeBuilder):
503
+ """Provide a selection based on the handle types of Bézier control points"""
504
+
505
+ _bl_idname = "GeometryNodeCurveHandleTypeSelection"
506
+ node: bpy.types.GeometryNodeCurveHandleTypeSelection
507
+
508
+ def __init__(
509
+ self,
510
+ handle_type: Literal["FREE", "AUTO", "VECTOR", "ALIGN"] = "AUTO",
511
+ left: bool = True,
512
+ right: bool = True,
513
+ ):
514
+ super().__init__()
515
+ self.handle_type = handle_type
516
+ self.left = left
517
+ self.right = right
518
+
519
+ @property
520
+ def o_selection(self) -> SocketLinker:
521
+ """Output socket: Selection"""
522
+ return self._output("Selection")
523
+
524
+ @property
525
+ def handle_type(self) -> Literal["FREE", "AUTO", "VECTOR", "ALIGN"]:
526
+ return self.node.handle_type
527
+
528
+ @handle_type.setter
529
+ def handle_type(self, value: Literal["FREE", "AUTO", "VECTOR", "ALIGN"]):
530
+ self.node.handle_type = value
531
+
532
+ @property
533
+ def left(self) -> bool:
534
+ return "LEFT" in self.node.mode
535
+
536
+ @left.setter
537
+ def left(self, value: bool):
538
+ match value, self.right:
539
+ case True, True:
540
+ self.node.mode = {"LEFT", "RIGHT"}
541
+ case True, False:
542
+ self.node.mode = {"LEFT"}
543
+ case False, True:
544
+ self.node.mode = {"RIGHT"}
545
+ case False, False:
546
+ self.node.mode = set()
547
+
548
+ @property
549
+ def right(self) -> bool:
550
+ return "RIGHT" in self.node.mode
551
+
552
+ @right.setter
553
+ def right(self, value: bool):
554
+ match self.left, value:
555
+ case True, True:
556
+ self.node.mode = {"LEFT", "RIGHT"}
557
+ case True, False:
558
+ self.node.mode = {"LEFT"}
559
+ case False, True:
560
+ self.node.mode = {"RIGHT"}
561
+ case False, False:
562
+ self.node.mode = set()
563
+
564
+ @property
565
+ def mode(self) -> set[Literal["LEFT", "RIGHT"]]:
566
+ return self.node.mode
567
+
568
+ @mode.setter
569
+ def mode(self, value: set[Literal["LEFT", "RIGHT"]]):
570
+ self.node.mode = value
571
+
572
+
573
+ def _typed_index_switch(data_type: SOCKET_TYPES):
574
+ @classmethod
575
+ def method(cls, *args: TYPE_INPUT_ALL, index: TYPE_INPUT_INT = 0) -> "IndexSwitch":
576
+ """Create an IndexSwitch node with a pre-set data_type"""
577
+ return cls(*args, index=index, data_type=data_type)
578
+
579
+ return method
580
+
581
+
582
+ class IndexSwitch(NodeBuilder):
583
+ """Node builder for the Index Switch node"""
584
+
585
+ _bl_idname = "GeometryNodeIndexSwitch"
586
+ node: bpy.types.GeometryNodeIndexSwitch
587
+ float = _typed_index_switch("FLOAT")
588
+ integer = _typed_index_switch("INT")
589
+ boolean = _typed_index_switch("BOOLEAN")
590
+ vector = _typed_index_switch("VECTOR")
591
+ color = _typed_index_switch("RGBA")
592
+ rotation = _typed_index_switch("ROTATION")
593
+ matrix = _typed_index_switch("MATRIX")
594
+ string = _typed_index_switch("STRING")
595
+ menu = _typed_index_switch("MENU")
596
+ object = _typed_index_switch("OBJECT")
597
+ geometry = _typed_index_switch("GEOMETRY")
598
+ collection = _typed_index_switch("COLLECTION")
599
+ image = _typed_index_switch("IMAGE")
600
+ material = _typed_index_switch("MATERIAL")
601
+ bundle = _typed_index_switch("BUNDLE")
602
+ closure = _typed_index_switch("CLOSURE")
603
+
604
+ def __init__(
605
+ self,
606
+ *args: TYPE_INPUT_ALL,
607
+ index: TYPE_INPUT_INT = 0,
608
+ data_type: SOCKET_TYPES = "FLOAT",
609
+ ):
610
+ super().__init__()
611
+ self.data_type = data_type
612
+ key_args: dict[str, TYPE_INPUT_ALL] = {"Index": index}
613
+ self.node.index_switch_items.clear()
614
+ self._link_args(*args)
615
+ self._establish_links(**key_args)
616
+
617
+ def _create_socket(self) -> NodeSocket:
618
+ item = self.node.index_switch_items.new()
619
+ return self.node.inputs[item.identifier]
620
+
621
+ def _link_args(self, *args: TYPE_INPUT_ALL):
622
+ for arg in args:
623
+ if _is_default_value(arg):
624
+ socket = self._create_socket()
625
+ socket.default_value = arg
626
+ else:
627
+ source = self._source_socket(arg)
628
+ self.tree.link(source, self.node.inputs["__extend__"])
629
+
630
+ @property
631
+ def inputs(self) -> list[SocketLinker]:
632
+ """Input sockets"""
633
+ return [
634
+ SocketLinker(self.node.inputs[i + 1])
635
+ for i in range(len(self.node.index_switch_items))
636
+ ]
637
+
638
+ @property
639
+ def i_index(self) -> SocketLinker:
640
+ """Input socket: Index"""
641
+ return self._input("Index")
642
+
643
+ @property
644
+ def o_output(self) -> SocketLinker:
645
+ """Output socket: Output"""
646
+ return self._output("Output")
647
+
648
+ @property
649
+ def data_type(self) -> SOCKET_TYPES:
650
+ """Input socket: Data Type"""
651
+ return self.node.data_type # type: ignore
652
+
653
+ @data_type.setter
654
+ def data_type(self, value: SOCKET_TYPES):
655
+ """Input socket: Data Type"""
656
+ self.node.data_type = value
657
+
658
+
659
+ def _typed_menu_switch(data_type: SOCKET_TYPES):
660
+ @classmethod
661
+ def method(
662
+ cls,
663
+ *args: TYPE_INPUT_ALL,
664
+ menu: TYPE_INPUT_MENU = None,
665
+ data_type: SOCKET_TYPES = "FLOAT",
666
+ **kwargs: TYPE_INPUT_ALL,
667
+ ) -> "IndexSwitch":
668
+ """Create an IndexSwitch node with a pre-set data_type"""
669
+ return cls(*args, menu=menu, data_type=data_type, **kwargs)
670
+
671
+ return method
672
+
673
+
674
+ class MenuSwitch(NodeBuilder):
675
+ """Node builder for the Index Switch node"""
676
+
677
+ _bl_idname = "GeometryNodeMenuSwitch"
678
+ node: bpy.types.GeometryNodeMenuSwitch
679
+
680
+ float = _typed_menu_switch("FLOAT")
681
+ integer = _typed_menu_switch("INT")
682
+ boolean = _typed_menu_switch("BOOLEAN")
683
+ vector = _typed_menu_switch("VECTOR")
684
+ color = _typed_menu_switch("RGBA")
685
+ rotation = _typed_menu_switch("ROTATION")
686
+ matrix = _typed_menu_switch("MATRIX")
687
+ string = _typed_menu_switch("STRING")
688
+ menu = _typed_menu_switch("MENU")
689
+ object = _typed_menu_switch("OBJECT")
690
+ geometry = _typed_menu_switch("GEOMETRY")
691
+ collection = _typed_menu_switch("COLLECTION")
692
+ image = _typed_menu_switch("IMAGE")
693
+ material = _typed_menu_switch("MATERIAL")
694
+ bundle = _typed_menu_switch("BUNDLE")
695
+ closure = _typed_menu_switch("CLOSURE")
696
+
697
+ def __init__(
698
+ self,
699
+ *args: TYPE_INPUT_ALL,
700
+ menu: TYPE_INPUT_MENU = None,
701
+ data_type: SOCKET_TYPES = "FLOAT",
702
+ **kwargs: TYPE_INPUT_ALL,
703
+ ):
704
+ super().__init__()
705
+ self.data_type = data_type
706
+ self.node.enum_items.clear()
707
+ key_args = {"Menu": menu}
708
+ self._link_args(*args, **kwargs)
709
+ self._establish_links(**key_args)
710
+
711
+ def _link_args(self, *args: TYPE_INPUT_ALL, **kwargs: TYPE_INPUT_ALL):
712
+ for arg in args:
713
+ if _is_default_value(arg):
714
+ socket = self._create_socket(f"Item_{len(self.node.enum_items)}")
715
+ socket.default_value = arg
716
+ else:
717
+ source = self._source_socket(arg)
718
+ self.tree.link(source, self.node.inputs["__extend__"])
719
+
720
+ for key, value in kwargs.items():
721
+ if _is_default_value(value):
722
+ socket = self._create_socket(key)
723
+ socket.default_value = value
724
+ else:
725
+ source = self._source_socket(value) # type: ignore
726
+ self._link(source, self.node.inputs["__extend__"])
727
+ self.node.enum_items[-1].name = key
728
+
729
+ def _create_socket(self, name: str) -> bpy.types.NodeSocket:
730
+ item = self.node.enum_items.new(name)
731
+ return self.node.inputs[item.name]
732
+
733
+ @property
734
+ def inputs(self) -> dict[str, SocketLinker]:
735
+ """Input sockets"""
736
+ return {
737
+ item.name: SocketLinker(self.node.inputs[item.name])
738
+ for item in self.node.enum_items
739
+ }
740
+
741
+ @property
742
+ def outputs(self) -> dict[str, SocketLinker]:
743
+ """Input sockets"""
744
+ return {
745
+ item.name: SocketLinker(self.node.outputs[item.name])
746
+ for item in self.node.enum_items
747
+ }
748
+
749
+ @property
750
+ def i_menu(self) -> SocketLinker:
751
+ """Input socket: Menu"""
752
+ return self._input("Menu")
753
+
754
+ @property
755
+ def o_output(self) -> SocketLinker:
756
+ """Output socket: Output"""
757
+ return self._output("Output")
758
+
759
+ @property
760
+ def data_type(self) -> SOCKET_TYPES:
761
+ """Input socket: Data Type"""
762
+ return self.node.data_type # type: ignore
763
+
764
+ @data_type.setter
765
+ def data_type(self, value: SOCKET_TYPES):
766
+ """Input socket: Data Type"""
767
+ self.node.data_type = value
768
+
769
+
770
+ def _domain_capture_attribute(domain: _AttributeDomains):
771
+ @classmethod
772
+ def method(
773
+ cls,
774
+ *args: LINKABLE,
775
+ geometry: TYPE_INPUT_GEOMETRY = None,
776
+ **kwargs,
777
+ ) -> "CaptureAttribute":
778
+ """Create an IndexSwitch node with a pre-set domain"""
779
+ return cls(*args, geometry=geometry, domain=domain, **kwargs)
780
+
781
+ return method
782
+
783
+
784
+ class CaptureAttribute(NodeBuilder, DynamicInputsMixin):
785
+ """Store the result of a field on a geometry and output the data as a node socket. Allows remembering or interpolating data as the geometry changes, such as positions before deformation"""
786
+
787
+ _bl_idname = "GeometryNodeCaptureAttribute"
788
+ node: bpy.types.GeometryNodeCaptureAttribute
789
+ _socket_data_types = (
790
+ "VALUE",
791
+ "INT",
792
+ "BOOLEAN",
793
+ "VECTOR",
794
+ "RGBA",
795
+ "ROTATION",
796
+ "MATRIX",
797
+ )
798
+ _type_map = {
799
+ "VALUE": "FLOAT",
800
+ # "VECTOR": "FLOAT_VECTOR",
801
+ "RGBA": "FLOAT_COLOR",
802
+ "ROTATION": "QUATERNION",
803
+ "MATRIX": "FLOAT4X4",
804
+ }
805
+ point = _domain_capture_attribute("POINT")
806
+ edge = _domain_capture_attribute("EDGE")
807
+ face = _domain_capture_attribute("FACE")
808
+ corner = _domain_capture_attribute("CORNER")
809
+ curve = _domain_capture_attribute("CURVE")
810
+ instance = _domain_capture_attribute("INSTANCE")
811
+ layer = _domain_capture_attribute("LAYER")
812
+
813
+ def __init__(
814
+ self,
815
+ *args: LINKABLE,
816
+ geometry: TYPE_INPUT_GEOMETRY = None,
817
+ domain: _AttributeDomains = "POINT",
818
+ **kwargs,
819
+ ):
820
+ super().__init__()
821
+ key_args = {"Geometry": geometry}
822
+ self.domain = domain
823
+ key_args.update(self._add_inputs(*args, **kwargs)) # type: ignore
824
+ self._establish_links(**key_args)
825
+
826
+ def _add_socket(self, name: str, type: _AttributeDataTypes):
827
+ item = self.node.capture_items.new(socket_type=type, name=name)
828
+ return self.node.inputs[item.name]
829
+
830
+ def capture(self, value: LINKABLE) -> SocketLinker:
831
+ """Capture the value to store in the attribute
832
+
833
+ Return the SocketLinker for the output socket
834
+ """
835
+ # the _add_inputs returns a dictionary but we only want the first key
836
+ # because we are adding a single input
837
+ input_dict = self._add_inputs(value)
838
+ return SocketLinker(self.node.outputs[next(iter(input_dict))])
839
+
840
+ @property
841
+ def outputs(self) -> dict[str, SocketLinker]:
842
+ return {
843
+ item.name: SocketLinker(self.node.outputs[item.name])
844
+ for item in self.node.capture_items
845
+ }
846
+
847
+ @property
848
+ def inputs(self) -> dict[str, SocketLinker]:
849
+ return {
850
+ item.name: SocketLinker(self.node.inputs[item.name])
851
+ for item in self.node.capture_items
852
+ }
853
+
854
+ @property
855
+ def _items(self) -> bpy.types.NodeGeometryCaptureAttributeItems:
856
+ return self.node.capture_items
857
+
858
+ @property
859
+ def i_geometry(self) -> SocketLinker:
860
+ """Input socket: Geometry"""
861
+ return self._input("Geometry")
862
+
863
+ @property
864
+ def o_geometry(self) -> SocketLinker:
865
+ """Output socket: Geometry"""
866
+ return self._output("Geometry")
867
+
868
+ @property
869
+ def domain(
870
+ self,
871
+ ) -> _AttributeDomains:
872
+ return self.node.domain
873
+
874
+ @domain.setter
875
+ def domain(
876
+ self,
877
+ value: _AttributeDomains,
878
+ ):
879
+ self.node.domain = value
880
+
881
+
882
+ class FieldToGrid(DynamicInputsMixin, NodeBuilder):
883
+ """Create new grids by evaluating new values on an existing volume grid topology
884
+
885
+ New socket items for field evaluation are first created from *args then **kwargs to give specific names to the items.
886
+
887
+ Data types are inferred automatically from the closest compatible data type.
888
+
889
+ Inputs:
890
+ -------
891
+ topology: LINKABLE
892
+ The grid which contains the topology to evaluate the different fields on.
893
+ data_type: _GridDataTypes = "FLOAT"
894
+ The data type of the grid to evaluate on. Possible values are "FLOAT", "INT", "VECTOR", "BOOLEAN".
895
+ *args: TYPE_INPUT_VALUE | TYPE_INPUT_VECTOR | TYPE_INPUT_INT | TYPE_INPUT_BOOLEAN
896
+ The fields to evaluate on the grid.
897
+ **kwargs: dict[str, TYPE_INPUT_VALUE | TYPE_INPUT_VECTOR | TYPE_INPUT_INT | TYPE_INPUT_GEOMETRY]
898
+ The key-value pairs of the fields to evaluate on the grid. Keys will be used as the name of the socket.
899
+
900
+ """
901
+
902
+ _bl_idname = "GeometryNodeFieldToGrid"
903
+ node: bpy.types.GeometryNodeFieldToGrid
904
+ _socket_data_types = ("VALUE", "INT", "VECTOR", "BOOLEAN")
905
+ _type_map = {"VALUE": "FLOAT"}
906
+ _default_input_id = "Topology"
907
+
908
+ def __init__(
909
+ self,
910
+ *args: TYPE_INPUT_GRID,
911
+ topology: TYPE_INPUT_GRID = None,
912
+ data_type: _GridDataTypes = "FLOAT",
913
+ **kwargs: TYPE_INPUT_GRID,
914
+ ):
915
+ super().__init__()
916
+ self.data_type = data_type
917
+ key_args = {
918
+ "Topology": topology,
919
+ }
920
+ key_args.update(self._add_inputs(*args, **kwargs)) # type: ignore
921
+ self._establish_links(**key_args)
922
+
923
+ def _add_socket( # type: ignore
924
+ self,
925
+ name: str,
926
+ type: _GridDataTypes = "FLOAT",
927
+ default_value: float | int | str | None = None,
928
+ ):
929
+ item = self.node.grid_items.new(socket_type=type, name=name)
930
+ if default_value is not None:
931
+ try:
932
+ self.node.inputs[item.name].default_value = default_value # type: ignore
933
+ except TypeError as e:
934
+ raise ValueError(
935
+ f"Invalid default value for {type}: {default_value}"
936
+ ) from e
937
+ return self.node.inputs[item.name]
938
+
939
+ def capture(self, *args, **kwargs) -> list[SocketLinker]:
940
+ outputs = {
941
+ name: self.node.outputs[name] for name in self._add_inputs(*args, **kwargs)
942
+ }
943
+
944
+ return [SocketLinker(x) for x in outputs.values()]
945
+
946
+ @classmethod
947
+ def float(cls, *args: TYPE_INPUT_GRID, topology: TYPE_INPUT_GRID = None, **kwargs):
948
+ return cls(*args, data_type="FLOAT", topology=topology, **kwargs)
949
+
950
+ @classmethod
951
+ def integer(
952
+ cls, *args: TYPE_INPUT_GRID, topology: TYPE_INPUT_GRID = None, **kwargs
953
+ ):
954
+ return cls(*args, data_type="INT", topology=topology, **kwargs)
955
+
956
+ @classmethod
957
+ def vector(cls, *args: TYPE_INPUT_GRID, topology: TYPE_INPUT_GRID = None, **kwargs):
958
+ return cls(*args, data_type="VECTOR", topology=topology, **kwargs)
959
+
960
+ @classmethod
961
+ def boolean(
962
+ cls, *args: TYPE_INPUT_GRID, topology: TYPE_INPUT_GRID = None, **kwargs
963
+ ):
964
+ return cls(*args, data_type="BOOLEAN", topology=topology, **kwargs)
965
+
966
+ @property
967
+ def outputs(self) -> dict[str, SocketLinker]:
968
+ return {
969
+ item.name: SocketLinker(self.node.outputs[item.name])
970
+ for item in self.node.grid_items
971
+ }
972
+
973
+ @property
974
+ def inputs(self) -> dict[str, SocketLinker]:
975
+ return {
976
+ item.name: SocketLinker(self.node.inputs[item.name])
977
+ for item in self.node.grid_items
978
+ }
979
+
980
+ @property
981
+ def i_topology(self) -> SocketLinker:
982
+ """Input socket: Topology"""
983
+ return self._input("Topology")
984
+
985
+ @property
986
+ def data_type(
987
+ self,
988
+ ) -> _GridDataTypes:
989
+ return self.node.data_type # type: ignore
990
+
991
+ @data_type.setter
992
+ def data_type(
993
+ self,
994
+ value: _GridDataTypes,
995
+ ):
996
+ self.node.data_type = value
997
+
998
+
999
+ class SDFGridBoolean(NodeBuilder):
1000
+ """Cut, subtract, or join multiple SDF volume grid inputs"""
1001
+
1002
+ _bl_idname = "GeometryNodeSDFGridBoolean"
1003
+ node: bpy.types.GeometryNodeSDFGridBoolean
1004
+
1005
+ def __init__(
1006
+ self, *, operation: Literal["INTERSECT", "UNION", "DIFFERENCE"] = "DIFFERENCE"
1007
+ ):
1008
+ super().__init__()
1009
+ self.operation = operation
1010
+
1011
+ @classmethod
1012
+ def intersect(
1013
+ cls,
1014
+ *args: LINKABLE,
1015
+ ) -> "SDFGridBoolean":
1016
+ node = cls(operation="INTERSECT")
1017
+ for arg in args:
1018
+ if arg is None:
1019
+ continue
1020
+ node._link_from(*node._find_best_socket_pair(arg, node._input("Grid 2")))
1021
+ return node
1022
+
1023
+ @classmethod
1024
+ def union(
1025
+ cls,
1026
+ *args: LINKABLE,
1027
+ ) -> "SDFGridBoolean":
1028
+ node = cls(operation="UNION")
1029
+ for arg in args:
1030
+ if arg is None:
1031
+ continue
1032
+ node._link_from(*node._find_best_socket_pair(arg, node._input("Grid 2")))
1033
+ return node
1034
+
1035
+ @classmethod
1036
+ def difference(
1037
+ cls,
1038
+ *args: LINKABLE,
1039
+ grid_1: LINKABLE,
1040
+ ) -> "SDFGridBoolean":
1041
+ """Create SDF Grid Boolean with operation 'Difference'."""
1042
+ node = cls(operation="DIFFERENCE")
1043
+ node._link_from(*node._find_best_socket_pair(grid_1, node._input("Grid 1")))
1044
+ for arg in args:
1045
+ if arg is None:
1046
+ continue
1047
+ node._link_from(*node._find_best_socket_pair(arg, node._input("Grid 2")))
1048
+ return node
1049
+
1050
+ @property
1051
+ def i_grid_1(self) -> SocketLinker:
1052
+ """Input socket: Grid 1"""
1053
+ return self._input("Grid 1")
1054
+
1055
+ @property
1056
+ def i_grid_2(self) -> SocketLinker:
1057
+ """Input socket: Grid 2"""
1058
+ return self._input("Grid 2")
1059
+
1060
+ @property
1061
+ def o_grid(self) -> SocketLinker:
1062
+ """Output socket: Grid"""
1063
+ return self._output("Grid")
1064
+
1065
+ @property
1066
+ def operation(self) -> Literal["INTERSECT", "UNION", "DIFFERENCE"]:
1067
+ return self.node.operation
1068
+
1069
+ @operation.setter
1070
+ def operation(self, value: Literal["INTERSECT", "UNION", "DIFFERENCE"]):
1071
+ self.node.operation = value
1072
+
1073
+
1074
+ def _accumlate_field_factory(domain: _AttributeDomains):
1075
+ """Create a factory for AccumulateField with a specific data type"""
1076
+
1077
+ class EvaluateAtIndexDomainFactory:
1078
+ @staticmethod
1079
+ def float(
1080
+ value: TYPE_INPUT_VALUE = None, index: TYPE_INPUT_INT = 0
1081
+ ) -> "AccumulateField":
1082
+ return AccumulateField(value, index, domain=domain, data_type="FLOAT")
1083
+
1084
+ @staticmethod
1085
+ def integer(
1086
+ value: TYPE_INPUT_INT = None, index: TYPE_INPUT_INT = 0
1087
+ ) -> "AccumulateField":
1088
+ return AccumulateField(value, index, domain=domain, data_type="INT")
1089
+
1090
+ @staticmethod
1091
+ def vector(
1092
+ value: TYPE_INPUT_VECTOR = None, index: TYPE_INPUT_INT = 0
1093
+ ) -> "AccumulateField":
1094
+ return AccumulateField(
1095
+ value, index, domain=domain, data_type="FLOAT_VECTOR"
1096
+ )
1097
+
1098
+ @staticmethod
1099
+ def transform(
1100
+ value: TYPE_INPUT_MATRIX = None, index: TYPE_INPUT_INT = 0
1101
+ ) -> "AccumulateField":
1102
+ return AccumulateField(value, index, domain=domain, data_type="TRANSFORM")
1103
+
1104
+ return EvaluateAtIndexDomainFactory()
1105
+
1106
+
1107
+ class AccumulateField(NodeBuilder):
1108
+ """Add the values of an evaluated field together and output the running total for each element"""
1109
+
1110
+ _bl_idname = "GeometryNodeAccumulateField"
1111
+ node: bpy.types.GeometryNodeAccumulateField
1112
+
1113
+ point = _accumlate_field_factory("POINT")
1114
+ edge = _accumlate_field_factory("EDGE")
1115
+ face = _accumlate_field_factory("FACE")
1116
+ corner = _accumlate_field_factory("CORNER")
1117
+ spline = _accumlate_field_factory("CURVE")
1118
+ instance = _accumlate_field_factory("INSTANCE")
1119
+ layer = _accumlate_field_factory("LAYER")
1120
+
1121
+ def __init__(
1122
+ self,
1123
+ value: TYPE_INPUT_VALUE
1124
+ | TYPE_INPUT_INT
1125
+ | TYPE_INPUT_VECTOR
1126
+ | TYPE_INPUT_MATRIX = 1.0,
1127
+ group_index: TYPE_INPUT_INT = 0,
1128
+ *,
1129
+ data_type: _AccumulateFieldDataTypes = "FLOAT",
1130
+ domain: _AttributeDomains = "POINT",
1131
+ **kwargs,
1132
+ ):
1133
+ super().__init__()
1134
+ key_args = {"Value": value, "Group Index": group_index}
1135
+ key_args.update(kwargs)
1136
+ self.data_type = data_type
1137
+ self.domain = domain
1138
+ self._establish_links(**key_args)
1139
+
1140
+ @property
1141
+ def i_value(self) -> SocketLinker:
1142
+ """Input socket: Value"""
1143
+ return self._input("Value")
1144
+
1145
+ @property
1146
+ def i_group_id(self) -> SocketLinker:
1147
+ """Input socket: Group ID"""
1148
+ return self._input("Group Index")
1149
+
1150
+ @property
1151
+ def o_leading(self) -> SocketLinker:
1152
+ """Output socket: Leading"""
1153
+ return self._output("Leading")
1154
+
1155
+ @property
1156
+ def o_trailing(self) -> SocketLinker:
1157
+ """Output socket: Trailing"""
1158
+ return self._output("Trailing")
1159
+
1160
+ @property
1161
+ def o_total(self) -> SocketLinker:
1162
+ """Output socket: Total"""
1163
+ return self._output("Total")
1164
+
1165
+ @property
1166
+ def data_type(self) -> _AccumulateFieldDataTypes:
1167
+ return self.node.data_type
1168
+
1169
+ @data_type.setter
1170
+ def data_type(self, value: _AccumulateFieldDataTypes):
1171
+ self.node.data_type = value
1172
+
1173
+ @property
1174
+ def domain(
1175
+ self,
1176
+ ) -> _AttributeDomains:
1177
+ return self.node.domain
1178
+
1179
+ @domain.setter
1180
+ def domain(
1181
+ self,
1182
+ value: _AttributeDomains,
1183
+ ):
1184
+ self.node.domain = value
1185
+
1186
+
1187
+ def _evaluate_at_index_factory(domain: _AttributeDomains):
1188
+ """Create a factory for AccumulateField with a specific data type"""
1189
+
1190
+ class EvaluateAtIndexDomainFactory:
1191
+ @staticmethod
1192
+ def float(value: TYPE_INPUT_VALUE = None, index: TYPE_INPUT_INT = 0):
1193
+ return EvaluateAtIndex(value, index, domain=domain, data_type="FLOAT")
1194
+
1195
+ @staticmethod
1196
+ def integer(value: TYPE_INPUT_INT = None, index: TYPE_INPUT_INT = 0):
1197
+ return EvaluateAtIndex(value, index, domain=domain, data_type="INT")
1198
+
1199
+ @staticmethod
1200
+ def boolean(value: TYPE_INPUT_BOOLEAN = None, index: TYPE_INPUT_INT = 0):
1201
+ return EvaluateAtIndex(value, index, domain=domain, data_type="BOOLEAN")
1202
+
1203
+ @staticmethod
1204
+ def vector(value: TYPE_INPUT_VECTOR = None, index: TYPE_INPUT_INT = 0):
1205
+ return EvaluateAtIndex(
1206
+ value, index, domain=domain, data_type="FLOAT_VECTOR"
1207
+ )
1208
+
1209
+ @staticmethod
1210
+ def rotation(value: TYPE_INPUT_ROTATION = None, index: TYPE_INPUT_INT = 0):
1211
+ return EvaluateAtIndex(value, index, domain=domain, data_type="QUATERNION")
1212
+
1213
+ @staticmethod
1214
+ def transform(value: TYPE_INPUT_MATRIX = None, index: TYPE_INPUT_INT = 0):
1215
+ return EvaluateAtIndex(value, index, domain=domain, data_type="FLOAT4X4")
1216
+
1217
+ return EvaluateAtIndexDomainFactory()
1218
+
1219
+
1220
+ class EvaluateAtIndex(NodeBuilder):
1221
+ """Retrieve data of other elements in the context's geometry"""
1222
+
1223
+ _bl_idname = "GeometryNodeFieldAtIndex"
1224
+ node: bpy.types.GeometryNodeFieldAtIndex
1225
+
1226
+ point = _evaluate_at_index_factory("POINT")
1227
+ edge = _evaluate_at_index_factory("EDGE")
1228
+ face = _evaluate_at_index_factory("FACE")
1229
+ corner = _evaluate_at_index_factory("CORNER")
1230
+ spline = _evaluate_at_index_factory("CURVE")
1231
+ instance = _evaluate_at_index_factory("INSTANCE")
1232
+ layer = _evaluate_at_index_factory("LAYER")
1233
+
1234
+ def __init__(
1235
+ self,
1236
+ value: TYPE_INPUT_DATA = None,
1237
+ index: TYPE_INPUT_INT = 0,
1238
+ *,
1239
+ domain: _AttributeDomains = "POINT",
1240
+ data_type: _EvaluateAtIndexDataTypes = "FLOAT",
1241
+ **kwargs,
1242
+ ):
1243
+ super().__init__()
1244
+ key_args = {"Value": value, "Index": index}
1245
+ key_args.update(kwargs)
1246
+ self.domain = domain
1247
+ self.data_type = data_type
1248
+ self._establish_links(**key_args)
1249
+
1250
+ @property
1251
+ def i_value(self) -> SocketLinker:
1252
+ """Input socket: Value"""
1253
+ return self._input("Value")
1254
+
1255
+ @property
1256
+ def i_index(self) -> SocketLinker:
1257
+ """Input socket: Index"""
1258
+ return self._input("Index")
1259
+
1260
+ @property
1261
+ def o_value(self) -> SocketLinker:
1262
+ """Output socket: Value"""
1263
+ return self._output("Value")
1264
+
1265
+ @property
1266
+ def domain(
1267
+ self,
1268
+ ) -> _AttributeDomains:
1269
+ return self.node.domain
1270
+
1271
+ @domain.setter
1272
+ def domain(
1273
+ self,
1274
+ value: _AttributeDomains,
1275
+ ):
1276
+ self.node.domain = value
1277
+
1278
+ @property
1279
+ def data_type(
1280
+ self,
1281
+ ) -> _EvaluateAtIndexDataTypes:
1282
+ return self.node.data_type # type: ignore
1283
+
1284
+ @data_type.setter
1285
+ def data_type(
1286
+ self,
1287
+ value: _EvaluateAtIndexDataTypes,
1288
+ ):
1289
+ self.node.data_type = value
1290
+
1291
+
1292
+ def _field_average_factory(domain: _AttributeDomains):
1293
+ """Create a factory for FieldVariance with a specific data type"""
1294
+
1295
+ class FieldAverageDomainFactory:
1296
+ @staticmethod
1297
+ def float(
1298
+ value: TYPE_INPUT_VALUE = 1.0,
1299
+ group_index: TYPE_INPUT_INT = 0,
1300
+ ) -> "FieldAverage":
1301
+ """Create FieldAverage for the "FLOAT" data type"""
1302
+ return FieldAverage(value, group_index, data_type="FLOAT", domain=domain)
1303
+
1304
+ @staticmethod
1305
+ def vector(
1306
+ value: TYPE_INPUT_VECTOR = (1.0, 1.0, 1.0),
1307
+ group_index: TYPE_INPUT_INT = 0,
1308
+ ) -> "FieldAverage":
1309
+ """Create FieldAverage on for the "FLOAT_VECTOR" data type"""
1310
+ return FieldAverage(
1311
+ value, group_index, data_type="FLOAT_VECTOR", domain=domain
1312
+ )
1313
+
1314
+ return FieldAverageDomainFactory()
1315
+
1316
+
1317
+ class FieldAverage(NodeBuilder):
1318
+ """Calculate the mean and median of a given field"""
1319
+
1320
+ _bl_idname = "GeometryNodeFieldAverage"
1321
+ node: bpy.types.GeometryNodeFieldAverage
1322
+
1323
+ point = _field_average_factory("POINT")
1324
+ edge = _field_average_factory("EDGE")
1325
+ face = _field_average_factory("FACE")
1326
+ corner = _field_average_factory("CORNER")
1327
+ spline = _field_average_factory("CURVE")
1328
+ instance = _field_average_factory("INSTANCE")
1329
+ layer = _field_average_factory("LAYER")
1330
+
1331
+ def __init__(
1332
+ self,
1333
+ value: TYPE_INPUT_VALUE | TYPE_INPUT_VECTOR = None,
1334
+ group_index: TYPE_INPUT_VALUE | TYPE_INPUT_VECTOR = 0,
1335
+ *,
1336
+ data_type: Literal["FLOAT", "FLOAT_VECTOR"] = "FLOAT",
1337
+ domain: _AttributeDomains = "POINT",
1338
+ ):
1339
+ super().__init__()
1340
+ key_args = {"Value": value, "Group Index": group_index}
1341
+ self.data_type = data_type
1342
+ self.domain = domain
1343
+ self._establish_links(**key_args)
1344
+
1345
+ @property
1346
+ def i_value(self) -> SocketLinker:
1347
+ """Input socket: Value"""
1348
+ return self._input("Value")
1349
+
1350
+ @property
1351
+ def i_group_id(self) -> SocketLinker:
1352
+ """Input socket: Group ID"""
1353
+ return self._input("Group Index")
1354
+
1355
+ @property
1356
+ def o_mean(self) -> SocketLinker:
1357
+ """Output socket: Mean"""
1358
+ return self._output("Mean")
1359
+
1360
+ @property
1361
+ def o_median(self) -> SocketLinker:
1362
+ """Output socket: Median"""
1363
+ return self._output("Median")
1364
+
1365
+ @property
1366
+ def data_type(self) -> Literal["FLOAT", "FLOAT_VECTOR"]:
1367
+ return self.node.data_type
1368
+
1369
+ @data_type.setter
1370
+ def data_type(self, value: Literal["FLOAT", "FLOAT_VECTOR"]):
1371
+ self.node.data_type = value
1372
+
1373
+ @property
1374
+ def domain(
1375
+ self,
1376
+ ) -> _AttributeDomains:
1377
+ return self.node.domain
1378
+
1379
+ @domain.setter
1380
+ def domain(
1381
+ self,
1382
+ value: _AttributeDomains,
1383
+ ):
1384
+ self.node.domain = value
1385
+
1386
+
1387
+ def _field_min_and_max_factory(domain: _AttributeDomains):
1388
+ """Create a factory for AccumulateField with a specific data type"""
1389
+
1390
+ class FieldMinMaxDataTypeFactory:
1391
+ @staticmethod
1392
+ def float(
1393
+ value: TYPE_INPUT_VALUE = 1.0,
1394
+ group_index: TYPE_INPUT_INT = 0,
1395
+ ) -> "FieldMinAndMax":
1396
+ """Create FieldMinMax for the "FLOAT" data type"""
1397
+ return FieldMinAndMax(value, group_index, data_type="FLOAT", domain=domain)
1398
+
1399
+ @staticmethod
1400
+ def integer(
1401
+ value: TYPE_INPUT_INT = 1,
1402
+ group_index: TYPE_INPUT_INT = 0,
1403
+ ) -> "FieldMinAndMax":
1404
+ """Create FieldMinMax for the "INT" data type"""
1405
+ return FieldMinAndMax(value, group_index, data_type="INT", domain=domain)
1406
+
1407
+ @staticmethod
1408
+ def vector(
1409
+ value: TYPE_INPUT_VECTOR = (1.0, 1.0, 1.0),
1410
+ group_index: TYPE_INPUT_INT = 0,
1411
+ ) -> "FieldMinAndMax":
1412
+ """Create FieldMinMax on for the "FLOAT_VECTOR" data type"""
1413
+ return FieldMinAndMax(
1414
+ value, group_index, data_type="FLOAT_VECTOR", domain=domain
1415
+ )
1416
+
1417
+ return FieldMinMaxDataTypeFactory()
1418
+
1419
+
1420
+ class FieldMinAndMax(NodeBuilder):
1421
+ """Calculate the minimum and maximum of a given field"""
1422
+
1423
+ _bl_idname = "GeometryNodeFieldMinAndMax"
1424
+ node: bpy.types.GeometryNodeFieldMinAndMax
1425
+
1426
+ point = _field_min_and_max_factory("POINT")
1427
+ edge = _field_min_and_max_factory("EDGE")
1428
+ face = _field_min_and_max_factory("FACE")
1429
+ corner = _field_min_and_max_factory("CORNER")
1430
+ spline = _field_min_and_max_factory("CURVE")
1431
+ instance = _field_min_and_max_factory("INSTANCE")
1432
+ layer = _field_min_and_max_factory("LAYER")
1433
+
1434
+ def __init__(
1435
+ self,
1436
+ value: TYPE_INPUT_VALUE | TYPE_INPUT_VECTOR | TYPE_INPUT_INT = 1.0,
1437
+ group_index: TYPE_INPUT_INT = 0,
1438
+ *,
1439
+ data_type: Literal["FLOAT", "INT", "FLOAT_VECTOR"] = "FLOAT",
1440
+ domain: _AttributeDomains = "POINT",
1441
+ ):
1442
+ super().__init__()
1443
+ key_args = {"Value": value, "Group Index": group_index}
1444
+ self.data_type = data_type
1445
+ self.domain = domain
1446
+ self._establish_links(**key_args)
1447
+
1448
+ @property
1449
+ def i_value(self) -> SocketLinker:
1450
+ """Input socket: Value"""
1451
+ return self._input("Value")
1452
+
1453
+ @property
1454
+ def i_group_id(self) -> SocketLinker:
1455
+ """Input socket: Group ID"""
1456
+ return self._input("Group Index")
1457
+
1458
+ @property
1459
+ def o_min(self) -> SocketLinker:
1460
+ """Output socket: Min"""
1461
+ return self._output("Min")
1462
+
1463
+ @property
1464
+ def o_max(self) -> SocketLinker:
1465
+ """Output socket: Max"""
1466
+ return self._output("Max")
1467
+
1468
+ @property
1469
+ def data_type(self) -> Literal["FLOAT", "INT", "FLOAT_VECTOR"]:
1470
+ return self.node.data_type
1471
+
1472
+ @data_type.setter
1473
+ def data_type(self, value: Literal["FLOAT", "INT", "FLOAT_VECTOR"]):
1474
+ self.node.data_type = value
1475
+
1476
+ @property
1477
+ def domain(
1478
+ self,
1479
+ ) -> _AttributeDomains:
1480
+ return self.node.domain
1481
+
1482
+ @domain.setter
1483
+ def domain(
1484
+ self,
1485
+ value: _AttributeDomains,
1486
+ ):
1487
+ self.node.domain = value
1488
+
1489
+
1490
+ def _evaluate_on_domain_factory(domain: _AttributeDomains):
1491
+ """Create a factory for AccumulateField with a specific data type"""
1492
+
1493
+ class EvaluateOnDomainDomainFactory:
1494
+ @staticmethod
1495
+ def float(value: TYPE_INPUT_VALUE = None):
1496
+ return EvaluateOnDomain(value, domain=domain, data_type="FLOAT")
1497
+
1498
+ @staticmethod
1499
+ def integer(value: TYPE_INPUT_INT = None):
1500
+ return EvaluateOnDomain(value, domain=domain, data_type="INT")
1501
+
1502
+ @staticmethod
1503
+ def boolean(value: TYPE_INPUT_BOOLEAN = None, index: TYPE_INPUT_INT = 0):
1504
+ return EvaluateOnDomain(value, domain=domain, data_type="BOOLEAN")
1505
+
1506
+ @staticmethod
1507
+ def vector(value: TYPE_INPUT_VECTOR = None):
1508
+ return EvaluateOnDomain(value, domain=domain, data_type="FLOAT_VECTOR")
1509
+
1510
+ @staticmethod
1511
+ def rotation(value: TYPE_INPUT_ROTATION = None):
1512
+ return EvaluateOnDomain(value, domain=domain, data_type="QUATERNION")
1513
+
1514
+ @staticmethod
1515
+ def transform(value: TYPE_INPUT_MATRIX = None):
1516
+ return EvaluateOnDomain(value, domain=domain, data_type="FLOAT4X4")
1517
+
1518
+ return EvaluateOnDomainDomainFactory()
1519
+
1520
+
1521
+ class EvaluateOnDomain(NodeBuilder):
1522
+ """Retrieve values from a field on a different domain besides the domain from the context"""
1523
+
1524
+ _bl_idname = "GeometryNodeFieldOnDomain"
1525
+ node: bpy.types.GeometryNodeFieldOnDomain
1526
+
1527
+ point = _field_min_and_max_factory("POINT")
1528
+ edge = _field_min_and_max_factory("EDGE")
1529
+ face = _field_min_and_max_factory("FACE")
1530
+ corner = _field_min_and_max_factory("CORNER")
1531
+ spline = _field_min_and_max_factory("CURVE")
1532
+ instance = _field_min_and_max_factory("INSTANCE")
1533
+ layer = _field_min_and_max_factory("LAYER")
1534
+
1535
+ def __init__(
1536
+ self,
1537
+ value: TYPE_INPUT_DATA = None,
1538
+ *,
1539
+ domain: _AttributeDomains = "POINT",
1540
+ data_type: _EvaluateAtIndexDataTypes = "FLOAT",
1541
+ ):
1542
+ super().__init__()
1543
+ key_args = {"Value": value}
1544
+ self.domain = domain
1545
+ self.data_type = data_type
1546
+ self._establish_links(**key_args)
1547
+
1548
+ @property
1549
+ def i_value(self) -> SocketLinker:
1550
+ """Input socket: Value"""
1551
+ return self._input("Value")
1552
+
1553
+ @property
1554
+ def o_value(self) -> SocketLinker:
1555
+ """Output socket: Value"""
1556
+ return self._output("Value")
1557
+
1558
+ @property
1559
+ def domain(
1560
+ self,
1561
+ ) -> _AttributeDomains:
1562
+ return self.node.domain
1563
+
1564
+ @domain.setter
1565
+ def domain(
1566
+ self,
1567
+ value: _AttributeDomains,
1568
+ ):
1569
+ self.node.domain = value
1570
+
1571
+ @property
1572
+ def data_type(
1573
+ self,
1574
+ ) -> _EvaluateAtIndexDataTypes:
1575
+ return self.node.data_type # type: ignore
1576
+
1577
+ @data_type.setter
1578
+ def data_type(
1579
+ self,
1580
+ value: _EvaluateAtIndexDataTypes,
1581
+ ):
1582
+ self.node.data_type = value
1583
+
1584
+
1585
+ def _field_variance_factory(domain: _AttributeDomains):
1586
+ """Create a factory for FieldVariance with a specific data type"""
1587
+
1588
+ class FieldVarianceDomainFactory:
1589
+ @staticmethod
1590
+ def float(
1591
+ value: TYPE_INPUT_VALUE = 1.0,
1592
+ group_index: TYPE_INPUT_INT = 0,
1593
+ ) -> "FieldVariance":
1594
+ """Create FieldVariance for the "FLOAT" data type"""
1595
+ return FieldVariance(value, group_index, data_type="FLOAT", domain=domain)
1596
+
1597
+ @staticmethod
1598
+ def vector(
1599
+ value: TYPE_INPUT_VECTOR = (1.0, 1.0, 1.0),
1600
+ group_index: TYPE_INPUT_INT = 0,
1601
+ ) -> "FieldVariance":
1602
+ """Create FieldVariance on for the "FLOAT_VECTOR" data type"""
1603
+ return FieldVariance(
1604
+ value, group_index, data_type="FLOAT_VECTOR", domain=domain
1605
+ )
1606
+
1607
+ return FieldVarianceDomainFactory()
1608
+
1609
+
1610
+ class FieldVariance(NodeBuilder):
1611
+ """Calculate the standard deviation and variance of a given field"""
1612
+
1613
+ _bl_idname = "GeometryNodeFieldVariance"
1614
+ node: bpy.types.GeometryNodeFieldVariance
1615
+
1616
+ point = _field_variance_factory("POINT")
1617
+ edge = _field_variance_factory("EDGE")
1618
+ face = _field_variance_factory("FACE")
1619
+ corner = _field_variance_factory("CORNER")
1620
+ spline = _field_variance_factory("CURVE")
1621
+ instance = _field_variance_factory("INSTANCE")
1622
+ layer = _field_variance_factory("LAYER")
1623
+
1624
+ def __init__(
1625
+ self,
1626
+ value: TYPE_INPUT_VALUE | TYPE_INPUT_VECTOR = None,
1627
+ group_index: TYPE_INPUT_INT = None,
1628
+ *,
1629
+ data_type: Literal["FLOAT", "FLOAT_VECTOR"] = "FLOAT",
1630
+ domain: _AttributeDomains = "POINT",
1631
+ ):
1632
+ super().__init__()
1633
+ key_args = {"Value": value, "Group Index": group_index}
1634
+ self.data_type = data_type
1635
+ self.domain = domain
1636
+ self._establish_links(**key_args)
1637
+
1638
+ @property
1639
+ def i_value(self) -> SocketLinker:
1640
+ """Input socket: Value"""
1641
+ return self._input("Value")
1642
+
1643
+ @property
1644
+ def i_group_id(self) -> SocketLinker:
1645
+ """Input socket: Group ID"""
1646
+ return self._input("Group Index")
1647
+
1648
+ @property
1649
+ def o_standard_deviation(self) -> SocketLinker:
1650
+ """Output socket: Standard Deviation"""
1651
+ return self._output("Standard Deviation")
1652
+
1653
+ @property
1654
+ def o_variance(self) -> SocketLinker:
1655
+ """Output socket: Variance"""
1656
+ return self._output("Variance")
1657
+
1658
+ @property
1659
+ def data_type(self) -> Literal["FLOAT", "FLOAT_VECTOR"]:
1660
+ return self.node.data_type
1661
+
1662
+ @data_type.setter
1663
+ def data_type(self, value: Literal["FLOAT", "FLOAT_VECTOR"]):
1664
+ self.node.data_type = value
1665
+
1666
+ @property
1667
+ def domain(
1668
+ self,
1669
+ ) -> _AttributeDomains:
1670
+ return self.node.domain
1671
+
1672
+ @domain.setter
1673
+ def domain(
1674
+ self,
1675
+ value: _AttributeDomains,
1676
+ ):
1677
+ self.node.domain = value
1678
+
1679
+
1680
+ _CompareOperations = Literal[
1681
+ "LESS_THAN",
1682
+ "LESS_EQUAL",
1683
+ "GREATER_THAN",
1684
+ "GREATER_EQUAL",
1685
+ "EQUAL",
1686
+ "NOT_EQUAL",
1687
+ "BRIGHTER",
1688
+ "DARKER",
1689
+ ]
1690
+
1691
+ _CompareDataTypes = Literal[
1692
+ "FLOAT",
1693
+ "INT",
1694
+ "VECTOR",
1695
+ "RGBA",
1696
+ "ROTATION",
1697
+ "STRING",
1698
+ ]
1699
+
1700
+ _CompareVectorModes = Literal[
1701
+ "ELEMENT", "LENGTH", "AVERAGE", "DOT_PRODUCT", "DIRECTION"
1702
+ ]
1703
+
1704
+
1705
+ def _compare_operation_method(operation: _CompareOperations):
1706
+ """Create a factory for Compare with a specific operation"""
1707
+
1708
+ class CompareOperationFactory:
1709
+ @staticmethod
1710
+ def float(
1711
+ a: TYPE_INPUT_VALUE = 0.0,
1712
+ b: TYPE_INPUT_VALUE = 0.0,
1713
+ *,
1714
+ epsilon: TYPE_INPUT_VALUE = 0.0001,
1715
+ ) -> "Compare":
1716
+ return Compare.float(operation=operation, a=a, b=b, epsilon=epsilon)
1717
+
1718
+ @staticmethod
1719
+ def integer(a: TYPE_INPUT_INT = 0, b: TYPE_INPUT_INT = 0) -> "Compare":
1720
+ return Compare.integer(operation=operation, a=a, b=b)
1721
+
1722
+ @staticmethod
1723
+ def vector(
1724
+ a: TYPE_INPUT_VECTOR = (0.0, 0.0, 0.0),
1725
+ b: TYPE_INPUT_VECTOR = (0.0, 0.0, 0.0),
1726
+ *,
1727
+ mode: _CompareVectorModes = "ELEMENT",
1728
+ c: TYPE_INPUT_VALUE = None,
1729
+ angle: TYPE_INPUT_VALUE = None,
1730
+ epsilon: TYPE_INPUT_VALUE = None,
1731
+ ) -> "Compare":
1732
+ return Compare.vector(
1733
+ operation=operation,
1734
+ a=a,
1735
+ b=b,
1736
+ mode=mode,
1737
+ c=c,
1738
+ angle=angle,
1739
+ epsilon=epsilon,
1740
+ )
1741
+
1742
+ @staticmethod
1743
+ def color(
1744
+ a: TYPE_INPUT_COLOR = None,
1745
+ b: TYPE_INPUT_COLOR = None,
1746
+ epsilon: TYPE_INPUT_VALUE = None,
1747
+ ) -> "Compare":
1748
+ return Compare.color(operation=operation, a=a, b=b, epsilon=epsilon)
1749
+
1750
+ @staticmethod
1751
+ def string(a: TYPE_INPUT_STRING = "", b: TYPE_INPUT_STRING = "") -> "Compare":
1752
+ return Compare.string(operation=operation, a=a, b=b)
1753
+
1754
+ return CompareOperationFactory()
1755
+
1756
+
1757
+ class Compare(NodeBuilder):
1758
+ """Perform a comparison operation on the two given inputs"""
1759
+
1760
+ _bl_idname = "FunctionNodeCompare"
1761
+ node: bpy.types.FunctionNodeCompare
1762
+
1763
+ less_than = _compare_operation_method("LESS_THAN")
1764
+ less_equal = _compare_operation_method("LESS_EQUAL")
1765
+ greater_than = _compare_operation_method("GREATER_THAN")
1766
+ greater_equal = _compare_operation_method("GREATER_EQUAL")
1767
+ equal = _compare_operation_method("EQUAL")
1768
+ not_equal = _compare_operation_method("NOT_EQUAL")
1769
+ brighter = _compare_operation_method("BRIGHTER")
1770
+ darker = _compare_operation_method("DARKER")
1771
+
1772
+ def __init__(
1773
+ self,
1774
+ operation: _CompareOperations = "GREATER_THAN",
1775
+ data_type: _CompareDataTypes = "FLOAT",
1776
+ **kwargs,
1777
+ ):
1778
+ super().__init__()
1779
+ self.operation = operation
1780
+ self.data_type = data_type
1781
+ if self.data_type == "VECTOR":
1782
+ self.mode = kwargs["mode"]
1783
+ self._establish_links(**kwargs)
1784
+
1785
+ @classmethod
1786
+ def float(
1787
+ cls,
1788
+ a: TYPE_INPUT_VALUE = 0.0,
1789
+ b: TYPE_INPUT_VALUE = 0.0,
1790
+ operation: _CompareOperations = "LESS_THAN",
1791
+ *,
1792
+ epsilon: TYPE_INPUT_VALUE = 0.0001,
1793
+ ):
1794
+ kwargs = {"operation": operation, "data_type": "FLOAT", "A": a, "B": b}
1795
+ if operation in ("EQUAL", "NOT_EQUAL"):
1796
+ kwargs["Epsilon"] = epsilon
1797
+ return cls(**kwargs)
1798
+
1799
+ @classmethod
1800
+ def integer(
1801
+ cls,
1802
+ a: TYPE_INPUT_INT = 0,
1803
+ b: TYPE_INPUT_INT = 0,
1804
+ operation: _CompareOperations = "LESS_THAN",
1805
+ ) -> "Compare":
1806
+ return cls(operation=operation, data_type="INT", A_INT=a, B_INT=b)
1807
+
1808
+ @classmethod
1809
+ def vector(
1810
+ cls,
1811
+ a: TYPE_INPUT_VECTOR = (0.0, 0.0, 0.0),
1812
+ b: TYPE_INPUT_VECTOR = (0.0, 0.0, 0.0),
1813
+ operation: _CompareOperations = "LESS_THAN",
1814
+ *,
1815
+ mode: _CompareVectorModes = "ELEMENT",
1816
+ c: TYPE_INPUT_VALUE = None,
1817
+ angle: TYPE_INPUT_VALUE = None,
1818
+ epsilon: TYPE_INPUT_VALUE = None,
1819
+ ) -> "Compare":
1820
+ kwargs = {
1821
+ "operation": operation,
1822
+ "data_type": "VECTOR",
1823
+ "mode": mode,
1824
+ "A_VEC3": a,
1825
+ "B_VEC3": b,
1826
+ }
1827
+ if operation in ("EQUAL", "NOT_EQUAL"):
1828
+ kwargs["Epsilon"] = epsilon
1829
+
1830
+ match mode:
1831
+ case "DIRECTION":
1832
+ kwargs["Angle"] = angle
1833
+ case "DOT_PRODUCT":
1834
+ kwargs["C"] = c
1835
+ case _:
1836
+ pass
1837
+
1838
+ return cls(**kwargs)
1839
+
1840
+ @classmethod
1841
+ def color(
1842
+ cls,
1843
+ a: TYPE_INPUT_COLOR = None,
1844
+ b: TYPE_INPUT_COLOR = None,
1845
+ operation: _CompareOperations = "EQUAL",
1846
+ *,
1847
+ epsilon: TYPE_INPUT_VALUE = None,
1848
+ ) -> "Compare":
1849
+ """Create Compare with operation 'Color'."""
1850
+ kwargs = {
1851
+ "operation": operation,
1852
+ "data_type": "RGBA",
1853
+ "A_COL": a,
1854
+ "B_COL": b,
1855
+ }
1856
+ if operation in ("EQUAL", "NOT_EQUAL"):
1857
+ kwargs["Epsilon"] = epsilon
1858
+ return cls(**kwargs)
1859
+
1860
+ @classmethod
1861
+ def string(
1862
+ cls,
1863
+ a,
1864
+ b,
1865
+ ) -> "Compare":
1866
+ """Create Compare with operation 'String'."""
1867
+ return cls(mode="STRING", A_STR=a, B_STR=b)
1868
+
1869
+ def _suffix(self) -> str:
1870
+ suffix_lookup = {
1871
+ "FLOAT": "",
1872
+ "INT": "_INT",
1873
+ "VECTOR": "_VEC",
1874
+ "RGBA": "_COL",
1875
+ "STRING": "_STR",
1876
+ }
1877
+ return suffix_lookup[self.data_type]
1878
+
1879
+ @property
1880
+ def i_a(self) -> SocketLinker:
1881
+ """Input socket: A"""
1882
+ return self._input(f"A{self._suffix()}")
1883
+
1884
+ @property
1885
+ def i_b(self) -> SocketLinker:
1886
+ """Input socket: B"""
1887
+ return self._input(f"B{self._suffix()}")
1888
+
1889
+ @property
1890
+ def o_result(self) -> SocketLinker:
1891
+ """Output socket: Result"""
1892
+ return self._output("Result")
1893
+
1894
+ @property
1895
+ def operation(
1896
+ self,
1897
+ ) -> _CompareOperations:
1898
+ return self.node.operation
1899
+
1900
+ @operation.setter
1901
+ def operation(
1902
+ self,
1903
+ value: _CompareOperations,
1904
+ ):
1905
+ self.node.operation = value
1906
+
1907
+ @property
1908
+ def data_type(
1909
+ self,
1910
+ ) -> _CompareDataTypes:
1911
+ return self.node.data_type # type: ignore
1912
+
1913
+ @data_type.setter
1914
+ def data_type(
1915
+ self,
1916
+ value: _CompareDataTypes,
1917
+ ):
1918
+ self.node.data_type = value
1919
+
1920
+ @property
1921
+ def mode(
1922
+ self,
1923
+ ) -> _CompareVectorModes:
1924
+ return self.node.mode
1925
+
1926
+ @mode.setter
1927
+ def mode(
1928
+ self,
1929
+ value: _CompareVectorModes,
1930
+ ):
1931
+ self.node.mode = value
1932
+
1933
+
1934
+ class AttributeStatistic(NodeBuilder):
1935
+ """Calculate statistics about a data set from a field evaluated on a geometry"""
1936
+
1937
+ _bl_idname = "GeometryNodeAttributeStatistic"
1938
+ node: bpy.types.GeometryNodeAttributeStatistic
1939
+
1940
+ def __init__(
1941
+ self,
1942
+ geometry: TYPE_INPUT_GEOMETRY = None,
1943
+ selection: TYPE_INPUT_BOOLEAN = True,
1944
+ attribute: LINKABLE = None,
1945
+ *,
1946
+ data_type: Literal[
1947
+ "FLOAT",
1948
+ "FLOAT_VECTOR",
1949
+ ] = "FLOAT",
1950
+ domain: Literal[
1951
+ "POINT", "EDGE", "FACE", "CORNER", "CURVE", "INSTANCE", "LAYER"
1952
+ ] = "POINT",
1953
+ **kwargs,
1954
+ ):
1955
+ super().__init__()
1956
+ key_args = {
1957
+ "Geometry": geometry,
1958
+ "Selection": selection,
1959
+ "Attribute": attribute,
1960
+ }
1961
+ key_args.update(kwargs)
1962
+ self.data_type = data_type
1963
+ self.domain = domain
1964
+ self._establish_links(**key_args)
1965
+
1966
+ @property
1967
+ def i_geometry(self) -> SocketLinker:
1968
+ """Input socket: Geometry"""
1969
+ return self._input("Geometry")
1970
+
1971
+ @property
1972
+ def i_selection(self) -> SocketLinker:
1973
+ """Input socket: Selection"""
1974
+ return self._input("Selection")
1975
+
1976
+ @property
1977
+ def i_attribute(self) -> SocketLinker:
1978
+ """Input socket: Attribute"""
1979
+ return self._input("Attribute")
1980
+
1981
+ @property
1982
+ def o_mean(self) -> SocketLinker:
1983
+ """Output socket: Mean"""
1984
+ return self._output("Mean")
1985
+
1986
+ @property
1987
+ def o_median(self) -> SocketLinker:
1988
+ """Output socket: Median"""
1989
+ return self._output("Median")
1990
+
1991
+ @property
1992
+ def o_sum(self) -> SocketLinker:
1993
+ """Output socket: Sum"""
1994
+ return self._output("Sum")
1995
+
1996
+ @property
1997
+ def o_min(self) -> SocketLinker:
1998
+ """Output socket: Min"""
1999
+ return self._output("Min")
2000
+
2001
+ @property
2002
+ def o_max(self) -> SocketLinker:
2003
+ """Output socket: Max"""
2004
+ return self._output("Max")
2005
+
2006
+ @property
2007
+ def o_range(self) -> SocketLinker:
2008
+ """Output socket: Range"""
2009
+ return self._output("Range")
2010
+
2011
+ @property
2012
+ def o_standard_deviation(self) -> SocketLinker:
2013
+ """Output socket: Standard Deviation"""
2014
+ return self._output("Standard Deviation")
2015
+
2016
+ @property
2017
+ def o_variance(self) -> SocketLinker:
2018
+ """Output socket: Variance"""
2019
+ return self._output("Variance")
2020
+
2021
+ @property
2022
+ def data_type(
2023
+ self,
2024
+ ) -> Literal[
2025
+ "FLOAT",
2026
+ "FLOAT_VECTOR",
2027
+ ]:
2028
+ return self.node.data_type # type: ignore
2029
+
2030
+ @data_type.setter
2031
+ def data_type(
2032
+ self,
2033
+ value: Literal[
2034
+ "FLOAT",
2035
+ "FLOAT_VECTOR",
2036
+ ],
2037
+ ):
2038
+ self.node.data_type = value
2039
+
2040
+ @property
2041
+ def domain(
2042
+ self,
2043
+ ) -> _AttributeDomains:
2044
+ return self.node.domain
2045
+
2046
+ @domain.setter
2047
+ def domain(
2048
+ self,
2049
+ value: _AttributeDomains,
2050
+ ):
2051
+ self.node.domain = value