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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
nodebpy/nodes/grid.py ADDED
@@ -0,0 +1,1228 @@
1
+ import bpy
2
+ from typing_extensions import Literal
3
+
4
+ from nodebpy.builder import NodeBuilder, SocketLinker
5
+
6
+ from .types import (
7
+ LINKABLE,
8
+ TYPE_INPUT_BOOLEAN,
9
+ TYPE_INPUT_GEOMETRY,
10
+ TYPE_INPUT_GRID,
11
+ TYPE_INPUT_INT,
12
+ TYPE_INPUT_STRING,
13
+ TYPE_INPUT_VALUE,
14
+ TYPE_INPUT_VECTOR,
15
+ _AdvectGridIntegration,
16
+ _GridDataTypes,
17
+ )
18
+
19
+
20
+ class DistributePointsInGrid(NodeBuilder):
21
+ """Generate points inside a volume grid"""
22
+
23
+ name = "GeometryNodeDistributePointsInGrid"
24
+ node: bpy.types.GeometryNodeDistributePointsInGrid
25
+
26
+ def __init__(
27
+ self,
28
+ grid: LINKABLE = None,
29
+ mode: Literal["DENSITY_RANDOM", "DENSITY_GRID"] = "DENSITY_RANDOM",
30
+ **kwargs,
31
+ ):
32
+ super().__init__()
33
+ key_args = {
34
+ "Grid": grid,
35
+ }
36
+ self.mode = mode
37
+ key_args.update(kwargs)
38
+ self._establish_links(**key_args)
39
+
40
+ @classmethod
41
+ def grid(
42
+ cls,
43
+ grid: LINKABLE,
44
+ spacing: TYPE_INPUT_VECTOR = (0.3, 0.3, 0.3),
45
+ threshold: TYPE_INPUT_VALUE = 0.1,
46
+ ) -> "DistributePointsInGrid":
47
+ return cls(grid=grid, Spacing=spacing, Threshold=threshold, mode="DENSITY_GRID")
48
+
49
+ @classmethod
50
+ def random(
51
+ cls,
52
+ grid: LINKABLE,
53
+ density: TYPE_INPUT_VALUE = 1.0,
54
+ seed: TYPE_INPUT_INT = 0,
55
+ ) -> "DistributePointsInGrid":
56
+ return cls(grid=grid, Density=density, Seed=seed, mode="DENSITY_RANDOM")
57
+
58
+ @property
59
+ def i_grid(self) -> SocketLinker:
60
+ """Input socket: Grid"""
61
+ return self._input("Grid")
62
+
63
+ @property
64
+ def i_density(self) -> SocketLinker:
65
+ """Input socket: Density"""
66
+ return self._input("Density")
67
+
68
+ @property
69
+ def i_seed(self) -> SocketLinker:
70
+ """Input socket: Seed"""
71
+ return self._input("Seed")
72
+
73
+ @property
74
+ def o_points(self) -> SocketLinker:
75
+ """Output socket: Points"""
76
+ return self._output("Points")
77
+
78
+ @property
79
+ def mode(self) -> Literal["DENSITY_RANDOM", "DENSITY_GRID"]:
80
+ return self.node.mode
81
+
82
+ @mode.setter
83
+ def mode(self, value: Literal["DENSITY_RANDOM", "DENSITY_GRID"]):
84
+ self.node.mode = value
85
+
86
+
87
+ class DistributePointsInVolume(NodeBuilder):
88
+ """Generate points inside a volume"""
89
+
90
+ name = "GeometryNodeDistributePointsInVolume"
91
+ node: bpy.types.GeometryNodeDistributePointsInVolume
92
+
93
+ def __init__(
94
+ self,
95
+ volume: TYPE_INPUT_GEOMETRY = None,
96
+ mode: Literal["Random", "Grid"] = "Random",
97
+ **kwargs,
98
+ ):
99
+ super().__init__()
100
+ key_args = {
101
+ "Volume": volume,
102
+ "Mode": mode,
103
+ }
104
+ key_args.update(kwargs)
105
+ self._establish_links(**key_args)
106
+
107
+ @classmethod
108
+ def grid(
109
+ cls,
110
+ volume: TYPE_INPUT_GEOMETRY = None,
111
+ density: TYPE_INPUT_VALUE = 1.0,
112
+ seed: TYPE_INPUT_INT = 0,
113
+ ):
114
+ return cls(volume=volume, Density=density, Seed=seed, mode="Grid")
115
+
116
+ @classmethod
117
+ def random(
118
+ cls,
119
+ volume: TYPE_INPUT_GEOMETRY = None,
120
+ spacing: TYPE_INPUT_VECTOR = (0.3, 0.3, 0.3),
121
+ threshold: TYPE_INPUT_VALUE = 0.1,
122
+ ):
123
+ return cls(volume=volume, Space=spacing, Threshold=threshold, mode="Random")
124
+
125
+ @property
126
+ def i_density(self) -> SocketLinker:
127
+ """Input socket: Density"""
128
+ return self._input("Density")
129
+
130
+ @property
131
+ def i_seed(self) -> SocketLinker:
132
+ """Input socket: Seed"""
133
+ return self._input("Seed")
134
+
135
+ @property
136
+ def i_spacing(self) -> SocketLinker:
137
+ """Input socket: Spacing"""
138
+ return self._input("Spacing")
139
+
140
+ @property
141
+ def i_threshold(self) -> SocketLinker:
142
+ """Input socket: Threshold"""
143
+ return self._input("Threshold")
144
+
145
+ @property
146
+ def o_points(self) -> SocketLinker:
147
+ """Output socket: Points"""
148
+ return self._output("Points")
149
+
150
+
151
+ class FieldToGrid(NodeBuilder):
152
+ """Create new grids by evaluating new values on an existing volume grid topology
153
+
154
+ New socket items for field evaluation are first created from *args then **kwargs to give specific names to the items.
155
+
156
+ Data types are inferred automatically from the closest compatible data type.
157
+
158
+ Inputs:
159
+ -------
160
+ topology: LINKABLE
161
+ The grid which contains the topology to evaluate the different fields on.
162
+ data_type: _GridDataTypes = "FLOAT"
163
+ The data type of the grid to evaluate on. Possible values are "FLOAT", "INT", "VECTOR", "BOOLEAN".
164
+ *args: TYPE_INPUT_VALUE | TYPE_INPUT_VECTOR | TYPE_INPUT_INT | TYPE_INPUT_BOOLEAN
165
+ The fields to evaluate on the grid.
166
+ **kwargs: dict[str, TYPE_INPUT_VALUE | TYPE_INPUT_VECTOR | TYPE_INPUT_INT | TYPE_INPUT_GEOMETRY]
167
+ The key-value pairs of the fields to evaluate on the grid. Keys will be used as the name of the socket.
168
+
169
+ """
170
+
171
+ name = "GeometryNodeFieldToGrid"
172
+ node: bpy.types.GeometryNodeFieldToGrid
173
+ _socket_data_types = ("FLOAT", "VALUE", "INT", "VECTOR", "BOOLEAN")
174
+ _default_input_id = "Topology"
175
+
176
+ def __init__(
177
+ self,
178
+ *args: TYPE_INPUT_GRID,
179
+ topology: TYPE_INPUT_GRID = None,
180
+ data_type: _GridDataTypes = "FLOAT",
181
+ **kwargs: TYPE_INPUT_GRID,
182
+ ):
183
+ super().__init__()
184
+ self.data_type = data_type
185
+ key_args = {
186
+ "Topology": topology,
187
+ }
188
+ key_args.update(self._add_inputs(*args, **kwargs)) # type: ignore
189
+ self._establish_links(**key_args)
190
+
191
+ def _add_socket( # type: ignore
192
+ self,
193
+ name: str,
194
+ type: _GridDataTypes = "FLOAT",
195
+ default_value: float | int | str | None = None,
196
+ ):
197
+ item = self.node.grid_items.new(socket_type=type, name=name)
198
+ if default_value is not None:
199
+ try:
200
+ self.node.inputs[item.name].default_value = default_value # type: ignore
201
+ except TypeError as e:
202
+ raise ValueError(
203
+ f"Invalid default value for {type}: {default_value}"
204
+ ) from e
205
+ return self.node.inputs[item.name]
206
+
207
+ def capture(self, *args, **kwargs) -> list[SocketLinker]:
208
+ outputs = {
209
+ name: self.node.outputs[name] for name in self._add_inputs(*args, **kwargs)
210
+ }
211
+
212
+ return [SocketLinker(x) for x in outputs.values()]
213
+
214
+ @property
215
+ def outputs(self) -> dict[str, SocketLinker]:
216
+ return {
217
+ item.name: SocketLinker(self.node.outputs[item.name])
218
+ for item in self.node.grid_items
219
+ }
220
+
221
+ @property
222
+ def inputs(self) -> dict[str, SocketLinker]:
223
+ return {
224
+ item.name: SocketLinker(self.node.inputs[item.name])
225
+ for item in self.node.grid_items
226
+ }
227
+
228
+ @property
229
+ def i_topology(self) -> SocketLinker:
230
+ """Input socket: Topology"""
231
+ return self._input("Topology")
232
+
233
+ @property
234
+ def data_type(
235
+ self,
236
+ ) -> _GridDataTypes:
237
+ return self.node.data_type # type: ignore
238
+
239
+ @data_type.setter
240
+ def data_type(
241
+ self,
242
+ value: _GridDataTypes,
243
+ ):
244
+ self.node.data_type = value
245
+
246
+
247
+ class GetNamedGrid(NodeBuilder):
248
+ """Get volume grid from a volume geometry with the specified name"""
249
+
250
+ name = "GeometryNodeGetNamedGrid"
251
+ node: bpy.types.GeometryNodeGetNamedGrid
252
+ _default_input_id = "Volume"
253
+
254
+ def __init__(
255
+ self,
256
+ volume: TYPE_INPUT_GEOMETRY = None,
257
+ name: TYPE_INPUT_STRING = "",
258
+ remove: TYPE_INPUT_BOOLEAN = True,
259
+ data_type: _GridDataTypes = "FLOAT",
260
+ ):
261
+ super().__init__()
262
+ key_args = {"Volume": volume, "Name": name, "Remove": remove}
263
+ self.data_type = data_type
264
+ self._establish_links(**key_args)
265
+
266
+ @property
267
+ def i_volume(self) -> SocketLinker:
268
+ """Input socket: Volume"""
269
+ return self._input("Volume")
270
+
271
+ @property
272
+ def i_name(self) -> SocketLinker:
273
+ """Input socket: Name"""
274
+ return self._input("Name")
275
+
276
+ @property
277
+ def i_remove(self) -> SocketLinker:
278
+ """Input socket: Remove"""
279
+ return self._input("Remove")
280
+
281
+ @property
282
+ def o_volume(self) -> SocketLinker:
283
+ """Output socket: Volume"""
284
+ return self._output("Volume")
285
+
286
+ @property
287
+ def o_grid(self) -> SocketLinker:
288
+ """Output socket: Grid"""
289
+ return self._output("Grid")
290
+
291
+ @property
292
+ def data_type(
293
+ self,
294
+ ) -> _GridDataTypes:
295
+ return self.node.data_type # type: ignore
296
+
297
+ @data_type.setter
298
+ def data_type(
299
+ self,
300
+ value: _GridDataTypes,
301
+ ):
302
+ self.node.data_type = value
303
+
304
+
305
+ class AdvectGrid(NodeBuilder):
306
+ """Move grid values through a velocity field using numerical integration. Supports multiple integration schemes for different accuracy and performance trade-offs"""
307
+
308
+ name = "GeometryNodeGridAdvect"
309
+ node: bpy.types.GeometryNodeGridAdvect
310
+ _socket_data_types = ["FLOAT", "VALUE", "INT", "VECTOR"]
311
+
312
+ def __init__(
313
+ self,
314
+ grid: TYPE_INPUT_VALUE = None,
315
+ velocity: TYPE_INPUT_VECTOR = None,
316
+ time_step: TYPE_INPUT_VALUE = 1.0,
317
+ *,
318
+ integration_scheme: _AdvectGridIntegration = "Runge-Kutta 3",
319
+ limiter: Literal["None", "Clamp", "Revert"] = "Clamp",
320
+ data_type: Literal["FLOAT", "INT", "VECTOR"] = "FLOAT",
321
+ ):
322
+ super().__init__()
323
+ key_args = {
324
+ "Grid": grid,
325
+ "Velocity": velocity,
326
+ "Time Step": time_step,
327
+ "Integration Scheme": integration_scheme,
328
+ "Limiter": limiter,
329
+ }
330
+ self.data_type = data_type
331
+ self._establish_links(**key_args)
332
+
333
+ @property
334
+ def i_grid(self) -> SocketLinker:
335
+ """Input socket: Grid"""
336
+ return self._input("Grid")
337
+
338
+ @property
339
+ def i_velocity(self) -> SocketLinker:
340
+ """Input socket: Velocity"""
341
+ return self._input("Velocity")
342
+
343
+ @property
344
+ def i_time_step(self) -> SocketLinker:
345
+ """Input socket: Time Step"""
346
+ return self._input("Time Step")
347
+
348
+ @property
349
+ def i_integration_scheme(self) -> SocketLinker:
350
+ """Input socket: Integration Scheme"""
351
+ return self._input("Integration Scheme")
352
+
353
+ @property
354
+ def i_limiter(self) -> SocketLinker:
355
+ """Input socket: Limiter"""
356
+ return self._input("Limiter")
357
+
358
+ @property
359
+ def o_grid(self) -> SocketLinker:
360
+ """Output socket: Grid"""
361
+ return self._output("Grid")
362
+
363
+ @property
364
+ def data_type(
365
+ self,
366
+ ) -> Literal["FLOAT", "INT", "VECTOR"]:
367
+ return self.node.data_type # type: ignore
368
+
369
+ @data_type.setter
370
+ def data_type(
371
+ self,
372
+ value: Literal["FLOAT", "INT", "VECTOR"],
373
+ ):
374
+ self.node.data_type = value
375
+
376
+
377
+ class GridCurl(NodeBuilder):
378
+ """Calculate the magnitude and direction of circulation of a directional vector grid"""
379
+
380
+ name = "GeometryNodeGridCurl"
381
+ node: bpy.types.GeometryNodeGridCurl
382
+
383
+ def __init__(self, grid: TYPE_INPUT_VECTOR = None):
384
+ super().__init__()
385
+ key_args = {"Grid": grid}
386
+ self._establish_links(**key_args)
387
+
388
+ @property
389
+ def i_grid(self) -> SocketLinker:
390
+ """Input socket: Grid"""
391
+ return self._input("Grid")
392
+
393
+ @property
394
+ def o_curl(self) -> SocketLinker:
395
+ """Output socket: Curl"""
396
+ return self._output("Curl")
397
+
398
+
399
+ class GridDivergence(NodeBuilder):
400
+ """Calculate the flow into and out of each point of a directional vector grid"""
401
+
402
+ name = "GeometryNodeGridDivergence"
403
+ node: bpy.types.GeometryNodeGridDivergence
404
+
405
+ def __init__(self, grid: TYPE_INPUT_VECTOR = None):
406
+ super().__init__()
407
+ key_args = {"Grid": grid}
408
+ self._establish_links(**key_args)
409
+
410
+ @property
411
+ def i_grid(self) -> SocketLinker:
412
+ """Input socket: Grid"""
413
+ return self._input("Grid")
414
+
415
+ @property
416
+ def o_divergence(self) -> SocketLinker:
417
+ """Output socket: Divergence"""
418
+ return self._output("Divergence")
419
+
420
+
421
+ class GridGradient(NodeBuilder):
422
+ """Calculate the direction and magnitude of the change in values of a scalar grid"""
423
+
424
+ name = "GeometryNodeGridGradient"
425
+ node: bpy.types.GeometryNodeGridGradient
426
+
427
+ def __init__(self, grid: TYPE_INPUT_VALUE = None):
428
+ super().__init__()
429
+ key_args = {"Grid": grid}
430
+ self._establish_links(**key_args)
431
+
432
+ @property
433
+ def i_grid(self) -> SocketLinker:
434
+ """Input socket: Grid"""
435
+ return self._input("Grid")
436
+
437
+ @property
438
+ def o_gradient(self) -> SocketLinker:
439
+ """Output socket: Gradient"""
440
+ return self._output("Gradient")
441
+
442
+
443
+ class GridInfo(NodeBuilder):
444
+ """Retrieve information about a volume grid"""
445
+
446
+ name = "GeometryNodeGridInfo"
447
+ node: bpy.types.GeometryNodeGridInfo
448
+
449
+ def __init__(
450
+ self, grid: TYPE_INPUT_GRID = None, data_type: _GridDataTypes = "FLOAT"
451
+ ):
452
+ super().__init__()
453
+ key_args = {"Grid": grid}
454
+ self.data_type = data_type
455
+ self._establish_links(**key_args)
456
+
457
+ @property
458
+ def i_grid(self) -> SocketLinker:
459
+ """Input socket: Grid"""
460
+ return self._input("Grid")
461
+
462
+ @property
463
+ def o_transform(self) -> SocketLinker:
464
+ """Output socket: Transform"""
465
+ return self._output("Transform")
466
+
467
+ @property
468
+ def o_background_value(self) -> SocketLinker:
469
+ """Output socket: Background Value"""
470
+ return self._output("Background Value")
471
+
472
+ @property
473
+ def data_type(
474
+ self,
475
+ ) -> _GridDataTypes:
476
+ return self.node.data_type # type: ignore
477
+
478
+ @data_type.setter
479
+ def data_type(
480
+ self,
481
+ value: _GridDataTypes,
482
+ ):
483
+ self.node.data_type = value
484
+
485
+
486
+ class GridLaplacian(NodeBuilder):
487
+ """Compute the divergence of the gradient of the input grid"""
488
+
489
+ name = "GeometryNodeGridLaplacian"
490
+ node: bpy.types.GeometryNodeGridLaplacian
491
+
492
+ def __init__(self, grid: TYPE_INPUT_VALUE = None):
493
+ super().__init__()
494
+ key_args = {"Grid": grid}
495
+ self._establish_links(**key_args)
496
+
497
+ @property
498
+ def i_grid(self) -> SocketLinker:
499
+ """Input socket: Grid"""
500
+ return self._input("Grid")
501
+
502
+ @property
503
+ def o_laplacian(self) -> SocketLinker:
504
+ """Output socket: Laplacian"""
505
+ return self._output("Laplacian")
506
+
507
+
508
+ class PruneGrid(NodeBuilder):
509
+ """Make the storage of a volume grid more efficient by collapsing data into tiles or inner nodes"""
510
+
511
+ name = "GeometryNodeGridPrune"
512
+ node: bpy.types.GeometryNodeGridPrune
513
+
514
+ def __init__(
515
+ self,
516
+ grid: TYPE_INPUT_GRID = None,
517
+ *,
518
+ mode: Literal["Inactive", "Threshold", "SDF"] = "Threshold",
519
+ threshold: TYPE_INPUT_VALUE = 0.01,
520
+ data_type: _GridDataTypes = "FLOAT",
521
+ ):
522
+ super().__init__()
523
+ key_args = {"Grid": grid, "Mode": mode, "Threshold": threshold}
524
+ self.data_type = data_type
525
+ self._establish_links(**key_args)
526
+
527
+ @property
528
+ def i_grid(self) -> SocketLinker:
529
+ """Input socket: Grid"""
530
+ return self._input("Grid")
531
+
532
+ @property
533
+ def i_mode(self) -> SocketLinker:
534
+ """Input socket: Mode"""
535
+ return self._input("Mode")
536
+
537
+ @property
538
+ def i_threshold(self) -> SocketLinker:
539
+ """Input socket: Threshold"""
540
+ return self._input("Threshold")
541
+
542
+ @property
543
+ def o_grid(self) -> SocketLinker:
544
+ """Output socket: Grid"""
545
+ return self._output("Grid")
546
+
547
+ @property
548
+ def data_type(
549
+ self,
550
+ ) -> _GridDataTypes:
551
+ return self.node.data_type # type: ignore
552
+
553
+ @data_type.setter
554
+ def data_type(
555
+ self,
556
+ value: _GridDataTypes,
557
+ ):
558
+ self.node.data_type = value
559
+
560
+
561
+ class VoxelizeGrid(NodeBuilder):
562
+ """Remove sparseness from a volume grid by making the active tiles into voxels"""
563
+
564
+ name = "GeometryNodeGridVoxelize"
565
+ node: bpy.types.GeometryNodeGridVoxelize
566
+
567
+ def __init__(
568
+ self, grid: TYPE_INPUT_GRID = None, data_type: _GridDataTypes = "FLOAT"
569
+ ):
570
+ super().__init__()
571
+ key_args = {"Grid": grid}
572
+ self.data_type = data_type
573
+ self._establish_links(**key_args)
574
+
575
+ @property
576
+ def i_grid(self) -> SocketLinker:
577
+ """Input socket: Grid"""
578
+ return self._input("Grid")
579
+
580
+ @property
581
+ def o_grid(self) -> SocketLinker:
582
+ """Output socket: Grid"""
583
+ return self._output("Grid")
584
+
585
+ @property
586
+ def data_type(
587
+ self,
588
+ ) -> _GridDataTypes:
589
+ return self.node.data_type # type: ignore
590
+
591
+ @data_type.setter
592
+ def data_type(
593
+ self,
594
+ value: _GridDataTypes,
595
+ ):
596
+ self.node.data_type = value
597
+
598
+
599
+ class VolumeCube(NodeBuilder):
600
+ """Generate a dense volume with a field that controls the density at each grid voxel based on its position"""
601
+
602
+ name = "GeometryNodeVolumeCube"
603
+ node: bpy.types.GeometryNodeVolumeCube
604
+
605
+ def __init__(
606
+ self,
607
+ density: TYPE_INPUT_VALUE = 1.0,
608
+ background: TYPE_INPUT_VALUE = 0.0,
609
+ min: TYPE_INPUT_VECTOR = (-1.0, -1.0, -1.0),
610
+ max: TYPE_INPUT_VECTOR = (1.0, 1.0, 1.0),
611
+ resolution_x: TYPE_INPUT_INT = 32,
612
+ resolution_y: TYPE_INPUT_INT = 32,
613
+ resolution_z: TYPE_INPUT_INT = 32,
614
+ ):
615
+ super().__init__()
616
+ key_args = {
617
+ "Density": density,
618
+ "Background": background,
619
+ "Min": min,
620
+ "Max": max,
621
+ "Resolution X": resolution_x,
622
+ "Resolution Y": resolution_y,
623
+ "Resolution Z": resolution_z,
624
+ }
625
+ self._establish_links(**key_args)
626
+
627
+ @property
628
+ def i_density(self) -> SocketLinker:
629
+ """Input socket: Density"""
630
+ return self._input("Density")
631
+
632
+ @property
633
+ def i_background(self) -> SocketLinker:
634
+ """Input socket: Background"""
635
+ return self._input("Background")
636
+
637
+ @property
638
+ def i_min(self) -> SocketLinker:
639
+ """Input socket: Min"""
640
+ return self._input("Min")
641
+
642
+ @property
643
+ def i_max(self) -> SocketLinker:
644
+ """Input socket: Max"""
645
+ return self._input("Max")
646
+
647
+ @property
648
+ def i_resolution_x(self) -> SocketLinker:
649
+ """Input socket: Resolution X"""
650
+ return self._input("Resolution X")
651
+
652
+ @property
653
+ def i_resolution_y(self) -> SocketLinker:
654
+ """Input socket: Resolution Y"""
655
+ return self._input("Resolution Y")
656
+
657
+ @property
658
+ def i_resolution_z(self) -> SocketLinker:
659
+ """Input socket: Resolution Z"""
660
+ return self._input("Resolution Z")
661
+
662
+ @property
663
+ def o_volume(self) -> SocketLinker:
664
+ """Output socket: Volume"""
665
+ return self._output("Volume")
666
+
667
+
668
+ class SDFGridBoolean(NodeBuilder):
669
+ """Cut, subtract, or join multiple SDF volume grid inputs"""
670
+
671
+ name = "GeometryNodeSDFGridBoolean"
672
+ node: bpy.types.GeometryNodeSDFGridBoolean
673
+
674
+ def __init__(
675
+ self, *, operation: Literal["INTERSECT", "UNION", "DIFFERENCE"] = "DIFFERENCE"
676
+ ):
677
+ super().__init__()
678
+ self.operation = operation
679
+
680
+ @classmethod
681
+ def intersect(
682
+ cls,
683
+ *args: LINKABLE,
684
+ ) -> "SDFGridBoolean":
685
+ node = cls(operation="INTERSECT")
686
+ for arg in args:
687
+ if arg is None:
688
+ continue
689
+ node._link_from(arg, "Grid 2")
690
+ return node
691
+
692
+ @classmethod
693
+ def union(
694
+ cls,
695
+ *args: LINKABLE,
696
+ ) -> "SDFGridBoolean":
697
+ node = cls(operation="UNION")
698
+ for arg in args:
699
+ if arg is None:
700
+ continue
701
+ node._link_from(arg, "Grid 2")
702
+ return node
703
+
704
+ @classmethod
705
+ def difference(
706
+ cls,
707
+ *args: LINKABLE,
708
+ grid_1: LINKABLE,
709
+ ) -> "SDFGridBoolean":
710
+ """Create SDF Grid Boolean with operation 'Difference'."""
711
+ node = cls(operation="DIFFERENCE")
712
+ node._link_from(grid_1, "Grid 1")
713
+ for arg in args:
714
+ if arg is None:
715
+ continue
716
+ node._link_from(arg, "Grid 2")
717
+ return node
718
+
719
+ @property
720
+ def i_grid_1(self) -> SocketLinker:
721
+ """Input socket: Grid 1"""
722
+ return self._input("Grid 1")
723
+
724
+ @property
725
+ def i_grid_2(self) -> SocketLinker:
726
+ """Input socket: Grid 2"""
727
+ return self._input("Grid 2")
728
+
729
+ @property
730
+ def o_grid(self) -> SocketLinker:
731
+ """Output socket: Grid"""
732
+ return self._output("Grid")
733
+
734
+ @property
735
+ def operation(self) -> Literal["INTERSECT", "UNION", "DIFFERENCE"]:
736
+ return self.node.operation
737
+
738
+ @operation.setter
739
+ def operation(self, value: Literal["INTERSECT", "UNION", "DIFFERENCE"]):
740
+ self.node.operation = value
741
+
742
+
743
+ class SDFGridFillet(NodeBuilder):
744
+ """Round off concave internal corners in a signed distance field. Only affects areas with negative principal curvature, creating smoother transitions between surfaces"""
745
+
746
+ name = "GeometryNodeSDFGridFillet"
747
+ node: bpy.types.GeometryNodeSDFGridFillet
748
+
749
+ def __init__(self, grid: TYPE_INPUT_VALUE = None, iterations: TYPE_INPUT_INT = 1):
750
+ super().__init__()
751
+ key_args = {"Grid": grid, "Iterations": iterations}
752
+ self._establish_links(**key_args)
753
+
754
+ @property
755
+ def i_grid(self) -> SocketLinker:
756
+ """Input socket: Grid"""
757
+ return self._input("Grid")
758
+
759
+ @property
760
+ def i_iterations(self) -> SocketLinker:
761
+ """Input socket: Iterations"""
762
+ return self._input("Iterations")
763
+
764
+ @property
765
+ def o_grid(self) -> SocketLinker:
766
+ """Output socket: Grid"""
767
+ return self._output("Grid")
768
+
769
+
770
+ class SDFGridLaplacian(NodeBuilder):
771
+ """Apply Laplacian flow smoothing to a signed distance field. Computationally efficient alternative to mean curvature flow, ideal when combined with SDF normalization"""
772
+
773
+ name = "GeometryNodeSDFGridLaplacian"
774
+ node: bpy.types.GeometryNodeSDFGridLaplacian
775
+
776
+ def __init__(self, grid: TYPE_INPUT_VALUE = None, iterations: TYPE_INPUT_INT = 1):
777
+ super().__init__()
778
+ key_args = {"Grid": grid, "Iterations": iterations}
779
+ self._establish_links(**key_args)
780
+
781
+ @property
782
+ def i_grid(self) -> SocketLinker:
783
+ """Input socket: Grid"""
784
+ return self._input("Grid")
785
+
786
+ @property
787
+ def i_iterations(self) -> SocketLinker:
788
+ """Input socket: Iterations"""
789
+ return self._input("Iterations")
790
+
791
+ @property
792
+ def o_grid(self) -> SocketLinker:
793
+ """Output socket: Grid"""
794
+ return self._output("Grid")
795
+
796
+
797
+ class SDFGridMean(NodeBuilder):
798
+ """Apply mean (box) filter smoothing to a signed distance field. Fast separable averaging filter for general smoothing of the distance field"""
799
+
800
+ name = "GeometryNodeSDFGridMean"
801
+ node: bpy.types.GeometryNodeSDFGridMean
802
+
803
+ def __init__(
804
+ self,
805
+ grid: TYPE_INPUT_VALUE = None,
806
+ width: TYPE_INPUT_INT = 1,
807
+ iterations: TYPE_INPUT_INT = 1,
808
+ ):
809
+ super().__init__()
810
+ key_args = {"Grid": grid, "Width": width, "Iterations": iterations}
811
+ self._establish_links(**key_args)
812
+
813
+ @property
814
+ def i_grid(self) -> SocketLinker:
815
+ """Input socket: Grid"""
816
+ return self._input("Grid")
817
+
818
+ @property
819
+ def i_width(self) -> SocketLinker:
820
+ """Input socket: Width"""
821
+ return self._input("Width")
822
+
823
+ @property
824
+ def i_iterations(self) -> SocketLinker:
825
+ """Input socket: Iterations"""
826
+ return self._input("Iterations")
827
+
828
+ @property
829
+ def o_grid(self) -> SocketLinker:
830
+ """Output socket: Grid"""
831
+ return self._output("Grid")
832
+
833
+
834
+ class SDFGridMeanCurvature(NodeBuilder):
835
+ """Apply mean curvature flow smoothing to a signed distance field. Evolves the surface based on its mean curvature, naturally smoothing high-curvature regions more than flat areas"""
836
+
837
+ name = "GeometryNodeSDFGridMeanCurvature"
838
+ node: bpy.types.GeometryNodeSDFGridMeanCurvature
839
+
840
+ def __init__(
841
+ self,
842
+ grid: TYPE_INPUT_VALUE = None,
843
+ iterations: TYPE_INPUT_INT = 1,
844
+ ):
845
+ super().__init__()
846
+ key_args = {"Grid": grid, "Iterations": iterations}
847
+ self._establish_links(**key_args)
848
+
849
+ @property
850
+ def i_grid(self) -> SocketLinker:
851
+ """Input socket: Grid"""
852
+ return self._input("Grid")
853
+
854
+ @property
855
+ def i_iterations(self) -> SocketLinker:
856
+ """Input socket: Iterations"""
857
+ return self._input("Iterations")
858
+
859
+ @property
860
+ def o_grid(self) -> SocketLinker:
861
+ """Output socket: Grid"""
862
+ return self._output("Grid")
863
+
864
+
865
+ class SDFGridMedian(NodeBuilder):
866
+ """Apply median filter to a signed distance field. Reduces noise while preserving sharp features and edges in the distance field"""
867
+
868
+ name = "GeometryNodeSDFGridMedian"
869
+ node: bpy.types.GeometryNodeSDFGridMedian
870
+
871
+ def __init__(
872
+ self,
873
+ grid: TYPE_INPUT_VALUE = None,
874
+ width: TYPE_INPUT_INT = 1,
875
+ iterations: TYPE_INPUT_INT = 1,
876
+ ):
877
+ super().__init__()
878
+ key_args = {"Grid": grid, "Width": width, "Iterations": iterations}
879
+ self._establish_links(**key_args)
880
+
881
+ @property
882
+ def i_grid(self) -> SocketLinker:
883
+ """Input socket: Grid"""
884
+ return self._input("Grid")
885
+
886
+ @property
887
+ def i_width(self) -> SocketLinker:
888
+ """Input socket: Width"""
889
+ return self._input("Width")
890
+
891
+ @property
892
+ def i_iterations(self) -> SocketLinker:
893
+ """Input socket: Iterations"""
894
+ return self._input("Iterations")
895
+
896
+ @property
897
+ def o_grid(self) -> SocketLinker:
898
+ """Output socket: Grid"""
899
+ return self._output("Grid")
900
+
901
+
902
+ class SDFGridOffset(NodeBuilder):
903
+ """Offset a signed distance field surface by a world-space distance. Dilates (positive) or erodes (negative) while maintaining the signed distance property"""
904
+
905
+ name = "GeometryNodeSDFGridOffset"
906
+ node: bpy.types.GeometryNodeSDFGridOffset
907
+
908
+ def __init__(self, grid: TYPE_INPUT_VALUE = 0.0, distance: TYPE_INPUT_VALUE = 0.1):
909
+ super().__init__()
910
+ key_args = {"Grid": grid, "Distance": distance}
911
+ self._establish_links(**key_args)
912
+
913
+ @property
914
+ def i_grid(self) -> SocketLinker:
915
+ """Input socket: Grid"""
916
+ return self._input("Grid")
917
+
918
+ @property
919
+ def i_distance(self) -> SocketLinker:
920
+ """Input socket: Distance"""
921
+ return self._input("Distance")
922
+
923
+ @property
924
+ def o_grid(self) -> SocketLinker:
925
+ """Output socket: Grid"""
926
+ return self._output("Grid")
927
+
928
+
929
+ class SampleGrid(NodeBuilder):
930
+ """Retrieve values from the specified volume grid"""
931
+
932
+ name = "GeometryNodeSampleGrid"
933
+ node: bpy.types.GeometryNodeSampleGrid
934
+
935
+ def __init__(
936
+ self,
937
+ grid: TYPE_INPUT_VALUE = None,
938
+ position: TYPE_INPUT_VECTOR = None,
939
+ interpolation: Literal["Trilinear", "Triquadratic", "Nearest Neighbor"]
940
+ | bpy.types.NodeSocketMenu = "Trilinear",
941
+ *,
942
+ data_type: _GridDataTypes = "FLOAT",
943
+ ):
944
+ super().__init__()
945
+ key_args = {"Grid": grid, "Position": position, "Interpolation": interpolation}
946
+ self.data_type = data_type
947
+ self._establish_links(**key_args)
948
+
949
+ @property
950
+ def i_grid(self) -> SocketLinker:
951
+ """Input socket: Grid"""
952
+ return self._input("Grid")
953
+
954
+ @property
955
+ def i_position(self) -> SocketLinker:
956
+ """Input socket: Position"""
957
+ return self._input("Position")
958
+
959
+ @property
960
+ def i_interpolation(self) -> SocketLinker:
961
+ """Input socket: Interpolation"""
962
+ return self._input("Interpolation")
963
+
964
+ @property
965
+ def o_value(self) -> SocketLinker:
966
+ """Output socket: Value"""
967
+ return self._output("Value")
968
+
969
+ @property
970
+ def data_type(
971
+ self,
972
+ ) -> _GridDataTypes:
973
+ return self.node.data_type # type: ignore
974
+
975
+ @data_type.setter
976
+ def data_type(
977
+ self,
978
+ value: _GridDataTypes,
979
+ ):
980
+ self.node.data_type = value
981
+
982
+
983
+ class SampleGridIndex(NodeBuilder):
984
+ """Retrieve volume grid values at specific voxels"""
985
+
986
+ name = "GeometryNodeSampleGridIndex"
987
+ node: bpy.types.GeometryNodeSampleGridIndex
988
+
989
+ def __init__(
990
+ self,
991
+ grid: TYPE_INPUT_VALUE = None,
992
+ x: TYPE_INPUT_INT = 0,
993
+ y: TYPE_INPUT_INT = 0,
994
+ z: TYPE_INPUT_INT = 0,
995
+ *,
996
+ data_type: _GridDataTypes = "FLOAT",
997
+ ):
998
+ super().__init__()
999
+ key_args = {"Grid": grid, "X": x, "Y": y, "Z": z}
1000
+ self.data_type = data_type
1001
+ self._establish_links(**key_args)
1002
+
1003
+ @property
1004
+ def i_grid(self) -> SocketLinker:
1005
+ """Input socket: Grid"""
1006
+ return self._input("Grid")
1007
+
1008
+ @property
1009
+ def i_x(self) -> SocketLinker:
1010
+ """Input socket: X"""
1011
+ return self._input("X")
1012
+
1013
+ @property
1014
+ def i_y(self) -> SocketLinker:
1015
+ """Input socket: Y"""
1016
+ return self._input("Y")
1017
+
1018
+ @property
1019
+ def i_z(self) -> SocketLinker:
1020
+ """Input socket: Z"""
1021
+ return self._input("Z")
1022
+
1023
+ @property
1024
+ def o_value(self) -> SocketLinker:
1025
+ """Output socket: Value"""
1026
+ return self._output("Value")
1027
+
1028
+ @property
1029
+ def data_type(
1030
+ self,
1031
+ ) -> _GridDataTypes:
1032
+ return self.node.data_type # type: ignore
1033
+
1034
+ @data_type.setter
1035
+ def data_type(
1036
+ self,
1037
+ value: _GridDataTypes,
1038
+ ):
1039
+ self.node.data_type = value
1040
+
1041
+
1042
+ class SetGridBackground(NodeBuilder):
1043
+ """Set the background value used for inactive voxels and tiles"""
1044
+
1045
+ name = "GeometryNodeSetGridBackground"
1046
+ node: bpy.types.GeometryNodeSetGridBackground
1047
+
1048
+ def __init__(
1049
+ self,
1050
+ grid: TYPE_INPUT_VALUE = 0.0,
1051
+ background: TYPE_INPUT_VALUE = 0.0,
1052
+ *,
1053
+ data_type: _GridDataTypes = "FLOAT",
1054
+ ):
1055
+ super().__init__()
1056
+ key_args = {"Grid": grid, "Background": background}
1057
+ self.data_type = data_type
1058
+ self._establish_links(**key_args)
1059
+
1060
+ @property
1061
+ def i_grid(self) -> SocketLinker:
1062
+ """Input socket: Grid"""
1063
+ return self._input("Grid")
1064
+
1065
+ @property
1066
+ def i_background(self) -> SocketLinker:
1067
+ """Input socket: Background"""
1068
+ return self._input("Background")
1069
+
1070
+ @property
1071
+ def o_grid(self) -> SocketLinker:
1072
+ """Output socket: Grid"""
1073
+ return self._output("Grid")
1074
+
1075
+ @property
1076
+ def data_type(
1077
+ self,
1078
+ ) -> _GridDataTypes:
1079
+ return self.node.data_type # type: ignore
1080
+
1081
+ @data_type.setter
1082
+ def data_type(
1083
+ self,
1084
+ value: _GridDataTypes,
1085
+ ):
1086
+ self.node.data_type = value
1087
+
1088
+
1089
+ class SetGridTransform(NodeBuilder):
1090
+ """Set the transform for the grid from index space into object space."""
1091
+
1092
+ name = "GeometryNodeSetGridTransform"
1093
+ node: bpy.types.GeometryNodeSetGridTransform
1094
+
1095
+ def __init__(
1096
+ self,
1097
+ grid: TYPE_INPUT_VALUE = 0.0,
1098
+ transform: LINKABLE | None = None,
1099
+ *,
1100
+ data_type: _GridDataTypes = "FLOAT",
1101
+ ):
1102
+ super().__init__()
1103
+ key_args = {"Grid": grid, "Transform": transform}
1104
+ self.data_type = data_type
1105
+ self._establish_links(**key_args)
1106
+
1107
+ @property
1108
+ def i_grid(self) -> SocketLinker:
1109
+ """Input socket: Grid"""
1110
+ return self._input("Grid")
1111
+
1112
+ @property
1113
+ def i_transform(self) -> SocketLinker:
1114
+ """Input socket: Transform"""
1115
+ return self._input("Transform")
1116
+
1117
+ @property
1118
+ def o_is_valid(self) -> SocketLinker:
1119
+ """Output socket: Is Valid"""
1120
+ return self._output("Is Valid")
1121
+
1122
+ @property
1123
+ def o_grid(self) -> SocketLinker:
1124
+ """Output socket: Grid"""
1125
+ return self._output("Grid")
1126
+
1127
+ @property
1128
+ def data_type(
1129
+ self,
1130
+ ) -> _GridDataTypes:
1131
+ return self.node.data_type # type: ignore
1132
+
1133
+ @data_type.setter
1134
+ def data_type(
1135
+ self,
1136
+ value: _GridDataTypes,
1137
+ ):
1138
+ self.node.data_type = value
1139
+
1140
+
1141
+ class StoreNamedGrid(NodeBuilder):
1142
+ """Store grid data in a volume geometry with the specified name"""
1143
+
1144
+ name = "GeometryNodeStoreNamedGrid"
1145
+ node: bpy.types.GeometryNodeStoreNamedGrid
1146
+
1147
+ def __init__(
1148
+ self,
1149
+ volume: TYPE_INPUT_GEOMETRY = None,
1150
+ name: TYPE_INPUT_STRING = "",
1151
+ grid: TYPE_INPUT_VALUE = 0.0,
1152
+ *,
1153
+ data_type: Literal["FLOAT", "VECTOR_FLOAT", "INT", "BOOLEAN"] = "FLOAT",
1154
+ ):
1155
+ super().__init__()
1156
+ key_args = {"Volume": volume, "Name": name, "Grid": grid}
1157
+ self.data_type = data_type
1158
+ self._establish_links(**key_args)
1159
+
1160
+ @property
1161
+ def i_volume(self) -> SocketLinker:
1162
+ """Input socket: Volume"""
1163
+ return self._input("Volume")
1164
+
1165
+ @property
1166
+ def i_name(self) -> SocketLinker:
1167
+ """Input socket: Name"""
1168
+ return self._input("Name")
1169
+
1170
+ @property
1171
+ def i_grid(self) -> SocketLinker:
1172
+ """Input socket: Grid"""
1173
+ return self._input("Grid")
1174
+
1175
+ @property
1176
+ def o_volume(self) -> SocketLinker:
1177
+ """Output socket: Volume"""
1178
+ return self._output("Volume")
1179
+
1180
+ @property
1181
+ def data_type(
1182
+ self,
1183
+ ) -> Literal["FLOAT", "VECTOR_FLOAT", "INT", "BOOLEAN"]:
1184
+ return self.node.data_type # type: ignore
1185
+
1186
+ @data_type.setter
1187
+ def data_type(
1188
+ self,
1189
+ value: Literal["FLOAT", "VECTOR_FLOAT", "INT", "BOOLEAN"],
1190
+ ):
1191
+ self.node.data_type = value
1192
+
1193
+
1194
+ class GridToMesh(NodeBuilder):
1195
+ """Generate a mesh on the "surface" of a volume grid"""
1196
+
1197
+ name = "GeometryNodeGridToMesh"
1198
+ node: bpy.types.GeometryNodeGridToMesh
1199
+
1200
+ def __init__(
1201
+ self,
1202
+ grid: TYPE_INPUT_VALUE = 0.0,
1203
+ threshold: TYPE_INPUT_VALUE = 0.1,
1204
+ adaptivity: TYPE_INPUT_VALUE = 0.0,
1205
+ ):
1206
+ super().__init__()
1207
+ key_args = {"Grid": grid, "Threshold": threshold, "Adaptivity": adaptivity}
1208
+ self._establish_links(**key_args)
1209
+
1210
+ @property
1211
+ def i_grid(self) -> SocketLinker:
1212
+ """Input socket: Grid"""
1213
+ return self._input("Grid")
1214
+
1215
+ @property
1216
+ def i_threshold(self) -> SocketLinker:
1217
+ """Input socket: Threshold"""
1218
+ return self._input("Threshold")
1219
+
1220
+ @property
1221
+ def i_adaptivity(self) -> SocketLinker:
1222
+ """Input socket: Adaptivity"""
1223
+ return self._input("Adaptivity")
1224
+
1225
+ @property
1226
+ def o_mesh(self) -> SocketLinker:
1227
+ """Output socket: Mesh"""
1228
+ return self._output("Mesh")