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