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.
- nodebpy/builder.py +19 -13
- nodebpy/nodes/__init__.py +352 -335
- nodebpy/nodes/attribute.py +362 -307
- nodebpy/nodes/color.py +30 -34
- nodebpy/nodes/converter.py +1987 -2978
- nodebpy/nodes/experimental.py +201 -203
- nodebpy/nodes/geometry.py +4189 -3644
- nodebpy/nodes/grid.py +932 -447
- nodebpy/nodes/group.py +7 -10
- nodebpy/nodes/input.py +1496 -1308
- nodebpy/nodes/interface.py +236 -117
- nodebpy/nodes/manual.py +2022 -0
- nodebpy/nodes/output.py +85 -0
- nodebpy/nodes/texture.py +867 -7
- nodebpy/nodes/vector.py +528 -0
- nodebpy/nodes/zone.py +7 -7
- nodebpy/{nodes/types.py → types.py} +14 -1
- {nodebpy-0.2.0.dist-info → nodebpy-0.2.1.dist-info}/METADATA +2 -2
- nodebpy-0.2.1.dist-info/RECORD +26 -0
- nodebpy/nodes/mesh.py +0 -17
- nodebpy-0.2.0.dist-info/RECORD +0 -25
- {nodebpy-0.2.0.dist-info → nodebpy-0.2.1.dist-info}/WHEEL +0 -0
- {nodebpy-0.2.0.dist-info → nodebpy-0.2.1.dist-info}/entry_points.txt +0 -0
nodebpy/nodes/manual.py
ADDED
|
@@ -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
|