nodebpy 0.4.1__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nodebpy-0.4.1 → nodebpy-0.5.0}/PKG-INFO +4 -4
- {nodebpy-0.4.1 → nodebpy-0.5.0}/README.md +1 -1
- {nodebpy-0.4.1 → nodebpy-0.5.0}/pyproject.toml +8 -3
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/__init__.py +5 -1
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/arrange.py +2 -1
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/builder.py +294 -13
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +1 -1
- nodebpy-0.5.0/src/nodebpy/nodes/__init__.py +3 -0
- nodebpy-0.5.0/src/nodebpy/nodes/compositor/__init__.py +245 -0
- nodebpy-0.5.0/src/nodebpy/nodes/compositor/color.py +923 -0
- nodebpy-0.5.0/src/nodebpy/nodes/compositor/converter.py +1215 -0
- nodebpy-0.5.0/src/nodebpy/nodes/compositor/distort.py +844 -0
- nodebpy-0.5.0/src/nodebpy/nodes/compositor/filter.py +1102 -0
- nodebpy-0.5.0/src/nodebpy/nodes/compositor/group.py +21 -0
- nodebpy-0.5.0/src/nodebpy/nodes/compositor/input.py +496 -0
- nodebpy-0.5.0/src/nodebpy/nodes/compositor/interface.py +117 -0
- nodebpy-0.5.0/src/nodebpy/nodes/compositor/matte.py +873 -0
- nodebpy-0.5.0/src/nodebpy/nodes/compositor/output.py +99 -0
- nodebpy-0.5.0/src/nodebpy/nodes/compositor/texture.py +886 -0
- nodebpy-0.5.0/src/nodebpy/nodes/compositor/vector.py +35 -0
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/__init__.py +18 -10
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/attribute.py +16 -13
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/color.py +7 -3
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/converter.py +20 -15
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/geometry.py +112 -55
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/grid.py +67 -25
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/group.py +4 -1
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/input.py +9 -6
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/interface.py +81 -36
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/manual.py +6 -6
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/output.py +3 -1
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/texture.py +6 -3
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/vector.py +5 -2
- {nodebpy-0.4.1/src/nodebpy/nodes → nodebpy-0.5.0/src/nodebpy/nodes/geometry}/zone.py +1 -1
- nodebpy-0.5.0/src/nodebpy/nodes/shader/__init__.py +251 -0
- nodebpy-0.5.0/src/nodebpy/nodes/shader/color.py +187 -0
- nodebpy-0.5.0/src/nodebpy/nodes/shader/converter.py +731 -0
- nodebpy-0.5.0/src/nodebpy/nodes/shader/grid.py +406 -0
- nodebpy-0.5.0/src/nodebpy/nodes/shader/group.py +21 -0
- nodebpy-0.5.0/src/nodebpy/nodes/shader/input.py +918 -0
- nodebpy-0.5.0/src/nodebpy/nodes/shader/interface.py +73 -0
- nodebpy-0.5.0/src/nodebpy/nodes/shader/output.py +373 -0
- nodebpy-0.5.0/src/nodebpy/nodes/shader/script.py +74 -0
- nodebpy-0.5.0/src/nodebpy/nodes/shader/shader.py +1557 -0
- nodebpy-0.5.0/src/nodebpy/nodes/shader/texture.py +356 -0
- nodebpy-0.5.0/src/nodebpy/nodes/shader/vector.py +402 -0
- nodebpy-0.5.0/src/nodebpy/nodes/shader/zone.py +58 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/screenshot.py +1 -1
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/sockets.py +2 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/types.py +3 -7
- nodebpy-0.4.1/src/nodebpy/nodes/experimental.py +0 -318
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +0 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +0 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/lib/nodearrange/config.py +0 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
- {nodebpy-0.4.1 → nodebpy-0.5.0}/src/nodebpy/screenshot_subprocess.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: nodebpy
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Build nodes in Blender with code
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Build nodes trees in Blender more elegantly with code
|
|
5
5
|
Author: Brady Johnston
|
|
6
6
|
Author-email: Brady Johnston <brady.johnston@me.com>
|
|
7
7
|
Requires-Dist: networkx>=3.6.1
|
|
8
|
-
Requires-Dist: bpy>=5.0.
|
|
8
|
+
Requires-Dist: bpy>=5.0.1 ; extra == 'bpy'
|
|
9
9
|
Requires-Dist: fake-bpy-module>=20260113 ; extra == 'dev'
|
|
10
10
|
Requires-Dist: jsondiff>=2.2.1 ; extra == 'dev'
|
|
11
11
|
Requires-Dist: pytest>=9.0.2 ; extra == 'dev'
|
|
@@ -165,5 +165,5 @@ Most node classes are generated automatically with this. The nodes in
|
|
|
165
165
|
complexities of particular nodes (usually lergacy).
|
|
166
166
|
|
|
167
167
|
``` bash
|
|
168
|
-
uv run generate.py && ruff format && ruff check --fix
|
|
168
|
+
uv run generate.py && ruff format && ruff check --fix
|
|
169
169
|
```
|
|
@@ -141,5 +141,5 @@ Most node classes are generated automatically with this. The nodes in
|
|
|
141
141
|
complexities of particular nodes (usually lergacy).
|
|
142
142
|
|
|
143
143
|
``` bash
|
|
144
|
-
uv run generate.py && ruff format && ruff check --fix
|
|
144
|
+
uv run generate.py && ruff format && ruff check --fix
|
|
145
145
|
```
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nodebpy"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "Build nodes in Blender with code"
|
|
3
|
+
version = "0.5.0"
|
|
4
|
+
description = "Build nodes trees in Blender more elegantly with code"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
7
7
|
{ name = "Brady Johnston", email = "brady.johnston@me.com" }
|
|
@@ -16,7 +16,7 @@ nodebpy = "nodebpy:main"
|
|
|
16
16
|
|
|
17
17
|
[project.optional-dependencies]
|
|
18
18
|
bpy = [
|
|
19
|
-
"bpy>=5.0.
|
|
19
|
+
"bpy>=5.0.1",
|
|
20
20
|
]
|
|
21
21
|
jupyter = [
|
|
22
22
|
"ipython>=8.0.0",
|
|
@@ -36,3 +36,8 @@ dev = [
|
|
|
36
36
|
[build-system]
|
|
37
37
|
requires = ["uv_build>=0.8.15,<0.9.0"]
|
|
38
38
|
build-backend = "uv_build"
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
dev = [
|
|
42
|
+
"numpy<2.0",
|
|
43
|
+
]
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
from . import nodes,
|
|
1
|
+
from . import nodes, screenshot, sockets
|
|
2
2
|
from .builder import TreeBuilder
|
|
3
|
+
from .nodes import compositor, geometry, shader
|
|
3
4
|
from .screenshot import generate_mermaid_diagram, save_mermaid_diagram
|
|
4
5
|
|
|
5
6
|
__all__ = [
|
|
6
7
|
"nodes",
|
|
8
|
+
"compositor",
|
|
9
|
+
"geometry",
|
|
10
|
+
"shader",
|
|
7
11
|
"sockets",
|
|
8
12
|
"screenshot",
|
|
9
13
|
"TreeBuilder",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import typing
|
|
2
2
|
from collections import Counter, deque
|
|
3
|
+
|
|
3
4
|
import bpy
|
|
4
5
|
from mathutils import Vector
|
|
5
6
|
|
|
@@ -245,7 +246,7 @@ def position_nodes_in_columns(
|
|
|
245
246
|
spacing : tuple of float, optional
|
|
246
247
|
Tuple of (horizontal, vertical) spacing between nodes, by default (50, 25)
|
|
247
248
|
"""
|
|
248
|
-
interface_scale =
|
|
249
|
+
interface_scale = 1.0
|
|
249
250
|
non_geo_offset = 20 + 28 * 2 # header + 2 socket heights
|
|
250
251
|
|
|
251
252
|
# position nodes column by column
|
|
@@ -3,14 +3,19 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any, ClassVar, Iterable, Literal
|
|
4
4
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
|
-
from .nodes import Math, VectorMath
|
|
6
|
+
from .nodes.geometry import IntegerMath, Math, VectorMath
|
|
7
|
+
from .nodes.geometry.converter import BooleanMath, MultiplyMatrices
|
|
8
|
+
from .nodes.geometry.manual import Compare
|
|
7
9
|
|
|
8
10
|
import bpy
|
|
9
11
|
from bpy.types import (
|
|
12
|
+
CompositorNodeTree,
|
|
10
13
|
GeometryNodeTree,
|
|
11
14
|
Node,
|
|
12
15
|
Nodes,
|
|
13
16
|
NodeSocket,
|
|
17
|
+
NodeTree,
|
|
18
|
+
ShaderNodeTree,
|
|
14
19
|
)
|
|
15
20
|
|
|
16
21
|
from .lib.nodearrange import arrange
|
|
@@ -64,9 +69,9 @@ class SocketContext:
|
|
|
64
69
|
self.builder = tree_builder
|
|
65
70
|
|
|
66
71
|
@property
|
|
67
|
-
def tree(self) ->
|
|
72
|
+
def tree(self) -> NodeTree:
|
|
68
73
|
tree = self.builder.tree
|
|
69
|
-
assert tree is not None
|
|
74
|
+
assert tree is not None
|
|
70
75
|
return tree
|
|
71
76
|
|
|
72
77
|
@property
|
|
@@ -114,7 +119,10 @@ class OutputInterfaceContext(DirectionalContext):
|
|
|
114
119
|
|
|
115
120
|
|
|
116
121
|
class TreeBuilder:
|
|
117
|
-
"""Builder for creating Blender
|
|
122
|
+
"""Builder for creating Blender node trees with a clean Python API.
|
|
123
|
+
|
|
124
|
+
Supports geometry, shader, and compositor node trees.
|
|
125
|
+
"""
|
|
118
126
|
|
|
119
127
|
_tree_contexts: ClassVar["list[TreeBuilder]"] = []
|
|
120
128
|
just_added: "Node | None" = None
|
|
@@ -122,15 +130,18 @@ class TreeBuilder:
|
|
|
122
130
|
|
|
123
131
|
def __init__(
|
|
124
132
|
self,
|
|
125
|
-
tree:
|
|
133
|
+
tree: NodeTree | str = "Geometry Nodes",
|
|
126
134
|
*,
|
|
135
|
+
tree_type: Literal[
|
|
136
|
+
"GeometryNodeTree", "ShaderNodeTree", "CompositorNodeTree"
|
|
137
|
+
] = "GeometryNodeTree",
|
|
127
138
|
collapse: bool = False,
|
|
128
139
|
arrange: bool = True,
|
|
140
|
+
fake_user: bool = False,
|
|
129
141
|
):
|
|
130
142
|
if isinstance(tree, str):
|
|
131
|
-
self.tree = bpy.data.node_groups.new(tree,
|
|
143
|
+
self.tree = bpy.data.node_groups.new(tree, tree_type)
|
|
132
144
|
else:
|
|
133
|
-
assert isinstance(tree, GeometryNodeTree)
|
|
134
145
|
self.tree = tree
|
|
135
146
|
|
|
136
147
|
# Create socket accessors for named access
|
|
@@ -138,11 +149,74 @@ class TreeBuilder:
|
|
|
138
149
|
self.outputs = OutputInterfaceContext(self)
|
|
139
150
|
self._arrange = arrange
|
|
140
151
|
self.collapse = collapse
|
|
152
|
+
self.fake_user = fake_user
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def geometry(
|
|
156
|
+
cls,
|
|
157
|
+
name: GeometryNodeTree | str = "Geometry Nodes",
|
|
158
|
+
*,
|
|
159
|
+
collapse: bool = False,
|
|
160
|
+
arrange: bool = True,
|
|
161
|
+
fake_user: bool = False,
|
|
162
|
+
) -> "TreeBuilder":
|
|
163
|
+
"""Create a geometry node tree."""
|
|
164
|
+
return cls(
|
|
165
|
+
name,
|
|
166
|
+
tree_type="GeometryNodeTree",
|
|
167
|
+
collapse=collapse,
|
|
168
|
+
arrange=arrange,
|
|
169
|
+
fake_user=fake_user,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
@classmethod
|
|
173
|
+
def shader(
|
|
174
|
+
cls,
|
|
175
|
+
name: ShaderNodeTree | str = "Shader Nodes",
|
|
176
|
+
*,
|
|
177
|
+
collapse: bool = False,
|
|
178
|
+
arrange: bool = True,
|
|
179
|
+
fake_user: bool = False,
|
|
180
|
+
) -> "TreeBuilder":
|
|
181
|
+
"""Create a shader node tree."""
|
|
182
|
+
return cls(
|
|
183
|
+
name,
|
|
184
|
+
tree_type="ShaderNodeTree",
|
|
185
|
+
collapse=collapse,
|
|
186
|
+
arrange=arrange,
|
|
187
|
+
fake_user=fake_user,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def compositor(
|
|
192
|
+
cls,
|
|
193
|
+
name: CompositorNodeTree | str = "Compositor Nodes",
|
|
194
|
+
*,
|
|
195
|
+
collapse: bool = False,
|
|
196
|
+
arrange: bool = True,
|
|
197
|
+
fake_user: bool = False,
|
|
198
|
+
) -> "TreeBuilder":
|
|
199
|
+
"""Create a compositor node tree."""
|
|
200
|
+
return cls(
|
|
201
|
+
name,
|
|
202
|
+
tree_type="CompositorNodeTree",
|
|
203
|
+
collapse=collapse,
|
|
204
|
+
arrange=arrange,
|
|
205
|
+
fake_user=fake_user,
|
|
206
|
+
)
|
|
141
207
|
|
|
142
208
|
@property
|
|
143
209
|
def nodes(self) -> Nodes:
|
|
144
210
|
return self.tree.nodes
|
|
145
211
|
|
|
212
|
+
@property
|
|
213
|
+
def fake_user(self) -> bool:
|
|
214
|
+
return self.tree.use_fake_user
|
|
215
|
+
|
|
216
|
+
@fake_user.setter
|
|
217
|
+
def fake_user(self, value: bool) -> None:
|
|
218
|
+
self.tree.use_extra_user = value
|
|
219
|
+
|
|
146
220
|
def activate_tree(self) -> None:
|
|
147
221
|
"""Make this tree the active tree for all new node creation."""
|
|
148
222
|
TreeBuilder._tree_contexts.append(self)
|
|
@@ -247,6 +321,7 @@ class NodeBuilder:
|
|
|
247
321
|
_from_socket: NodeSocket | None = None
|
|
248
322
|
_default_input_id: str | None = None
|
|
249
323
|
_default_output_id: str | None = None
|
|
324
|
+
__array_ufunc__ = None
|
|
250
325
|
|
|
251
326
|
def __init__(self):
|
|
252
327
|
# Get active tree from context manager
|
|
@@ -458,7 +533,13 @@ class NodeBuilder:
|
|
|
458
533
|
def _establish_links(self, **kwargs: TYPE_INPUT_ALL):
|
|
459
534
|
input_ids = [input.identifier for input in self.node.inputs]
|
|
460
535
|
for name, value in kwargs.items():
|
|
461
|
-
if value is None
|
|
536
|
+
if value is None or (
|
|
537
|
+
# TODO: this is an ugly single-node exception for this particular case. I'd
|
|
538
|
+
# like to fine a cleaner way to handle this automatically instead.
|
|
539
|
+
"GridPrune" in self._bl_idname
|
|
540
|
+
and name == "Threshold"
|
|
541
|
+
and self.node.data_type == "BOOLEAN"
|
|
542
|
+
):
|
|
462
543
|
continue
|
|
463
544
|
if isinstance(value, Node):
|
|
464
545
|
node = NodeBuilder()
|
|
@@ -522,7 +603,7 @@ class NodeBuilder:
|
|
|
522
603
|
self, other: Any, operation: str, reverse: bool = False
|
|
523
604
|
) -> "VectorMath | Math":
|
|
524
605
|
"""Apply a math operation with appropriate Math/VectorMath node."""
|
|
525
|
-
from .nodes import VectorMath
|
|
606
|
+
from .nodes.geometry import VectorMath
|
|
526
607
|
|
|
527
608
|
values = (
|
|
528
609
|
(self._default_output_socket, other)
|
|
@@ -556,9 +637,9 @@ class NodeBuilder:
|
|
|
556
637
|
if isinstance(other, (int, float)):
|
|
557
638
|
scalar_vector = (other, other, other)
|
|
558
639
|
return (
|
|
559
|
-
vector_method(
|
|
640
|
+
vector_method(self._default_output_socket, scalar_vector)
|
|
560
641
|
if not reverse
|
|
561
|
-
else vector_method(self._default_output_socket
|
|
642
|
+
else vector_method(scalar_vector, self._default_output_socket)
|
|
562
643
|
)
|
|
563
644
|
elif (
|
|
564
645
|
isinstance(other, (list, tuple)) and len(other) == 3
|
|
@@ -570,12 +651,16 @@ class NodeBuilder:
|
|
|
570
651
|
f"Unsupported type for {operation} with VECTOR operand: {type(other)}"
|
|
571
652
|
)
|
|
572
653
|
else: # Both operands are scalar types, use regular Math
|
|
573
|
-
from .nodes.converter import IntegerMath, Math
|
|
654
|
+
from .nodes.geometry.converter import IntegerMath, Math
|
|
574
655
|
|
|
575
656
|
if isinstance(other, int) and self._default_output_socket.type == "INT":
|
|
576
657
|
return getattr(IntegerMath, operation)(*values)
|
|
577
658
|
else:
|
|
578
|
-
|
|
659
|
+
# Math node uses 'floored_modulo' instead of 'modulo'
|
|
660
|
+
math_operation = (
|
|
661
|
+
"floored_modulo" if operation == "modulo" else operation
|
|
662
|
+
)
|
|
663
|
+
return getattr(Math, math_operation)(*values)
|
|
579
664
|
|
|
580
665
|
def __mul__(self, other: Any) -> "VectorMath | Math":
|
|
581
666
|
return self._apply_math_operation(other, "multiply")
|
|
@@ -601,6 +686,179 @@ class NodeBuilder:
|
|
|
601
686
|
def __rsub__(self, other: Any) -> "VectorMath | Math":
|
|
602
687
|
return self._apply_math_operation(other, "subtract", reverse=True)
|
|
603
688
|
|
|
689
|
+
def __pow__(self, other: Any) -> "VectorMath | Math":
|
|
690
|
+
return self._apply_math_operation(other, "power")
|
|
691
|
+
|
|
692
|
+
def __rpow__(self, other: Any) -> "VectorMath | Math":
|
|
693
|
+
return self._apply_math_operation(other, "power", reverse=True)
|
|
694
|
+
|
|
695
|
+
def __mod__(self, other: Any) -> "VectorMath | Math":
|
|
696
|
+
return self._apply_math_operation(other, "modulo")
|
|
697
|
+
|
|
698
|
+
def __rmod__(self, other: Any) -> "VectorMath | Math":
|
|
699
|
+
return self._apply_math_operation(other, "modulo", reverse=True)
|
|
700
|
+
|
|
701
|
+
def __floordiv__(self, other: Any) -> "VectorMath | Math | IntegerMath":
|
|
702
|
+
return self._apply_floordiv_operation(other)
|
|
703
|
+
|
|
704
|
+
def __rfloordiv__(self, other: Any) -> "VectorMath | Math | IntegerMath":
|
|
705
|
+
return self._apply_floordiv_operation(other, reverse=True)
|
|
706
|
+
|
|
707
|
+
def __neg__(self) -> "VectorMath | Math | IntegerMath":
|
|
708
|
+
from .nodes.geometry import VectorMath
|
|
709
|
+
from .nodes.geometry.converter import IntegerMath, Math
|
|
710
|
+
|
|
711
|
+
socket = self._default_output_socket
|
|
712
|
+
if socket.type == "VECTOR":
|
|
713
|
+
return VectorMath.scale(socket, -1)
|
|
714
|
+
elif socket.type == "INT":
|
|
715
|
+
return IntegerMath.negate(socket)
|
|
716
|
+
else:
|
|
717
|
+
return Math.multiply(socket, -1)
|
|
718
|
+
|
|
719
|
+
def __abs__(self) -> "VectorMath | Math | IntegerMath":
|
|
720
|
+
from .nodes.geometry import VectorMath
|
|
721
|
+
from .nodes.geometry.converter import IntegerMath, Math
|
|
722
|
+
|
|
723
|
+
socket = self._default_output_socket
|
|
724
|
+
if socket.type == "VECTOR":
|
|
725
|
+
return VectorMath.absolute(socket)
|
|
726
|
+
elif socket.type == "INT":
|
|
727
|
+
return IntegerMath.absolute(socket)
|
|
728
|
+
else:
|
|
729
|
+
return Math.absolute(socket)
|
|
730
|
+
|
|
731
|
+
def _apply_floordiv_operation(
|
|
732
|
+
self, other: Any, reverse: bool = False
|
|
733
|
+
) -> "VectorMath | Math | IntegerMath":
|
|
734
|
+
"""Apply floor division: divide then floor."""
|
|
735
|
+
from .nodes.geometry import VectorMath
|
|
736
|
+
from .nodes.geometry.converter import IntegerMath, Math
|
|
737
|
+
|
|
738
|
+
socket = self._default_output_socket
|
|
739
|
+
component_is_vector = (
|
|
740
|
+
socket.type == "VECTOR" or getattr(other, "type", None) == "VECTOR"
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
if not component_is_vector and isinstance(other, int) and socket.type == "INT":
|
|
744
|
+
values = (socket, other) if not reverse else (other, socket)
|
|
745
|
+
return IntegerMath.divide_floor(*values)
|
|
746
|
+
|
|
747
|
+
divided = self._apply_math_operation(other, "divide", reverse=reverse)
|
|
748
|
+
if component_is_vector:
|
|
749
|
+
return VectorMath.floor(divided)
|
|
750
|
+
else:
|
|
751
|
+
return Math.floor(divided)
|
|
752
|
+
|
|
753
|
+
def _apply_compare_operation(self, other: Any, operation: str) -> "Compare | Math":
|
|
754
|
+
"""Apply a comparison operation.
|
|
755
|
+
|
|
756
|
+
Uses the Compare node in geometry trees (supports float, int, vector and
|
|
757
|
+
outputs a boolean). Falls back to Math.less_than / Math.greater_than in
|
|
758
|
+
compositor and shader trees which lack a Compare node. For <= and >= in
|
|
759
|
+
non-geometry trees, we swap the operands (a <= b == b >= a == !(a > b)
|
|
760
|
+
is equivalent to less_than(b, a) when treating the output as boolean).
|
|
761
|
+
"""
|
|
762
|
+
if isinstance(self._tree.tree, GeometryNodeTree):
|
|
763
|
+
from .nodes.geometry.manual import Compare
|
|
764
|
+
|
|
765
|
+
socket = self._default_output_socket
|
|
766
|
+
values = (socket, other)
|
|
767
|
+
|
|
768
|
+
if socket.type == "VECTOR":
|
|
769
|
+
return getattr(Compare, operation).vector(*values)
|
|
770
|
+
elif socket.type == "INT":
|
|
771
|
+
return getattr(Compare, operation).integer(*values)
|
|
772
|
+
else:
|
|
773
|
+
return getattr(Compare, operation).float(*values)
|
|
774
|
+
else:
|
|
775
|
+
# Compositor / Shader trees only have Math.less_than and
|
|
776
|
+
# Math.greater_than (float output, no boolean). Map <= and >= by
|
|
777
|
+
# swapping operands: a <= b ≡ less_than(b, a) is wrong —
|
|
778
|
+
# but greater_than(b, a) gives 1 when b>a i.e. a<b.
|
|
779
|
+
# So: a <= b → 1 - greater_than(a, b) — needs two nodes.
|
|
780
|
+
# Simpler: a >= b ≡ !(a < b) ≡ 1 - less_than(a, b)
|
|
781
|
+
from .nodes.geometry.converter import Math
|
|
782
|
+
|
|
783
|
+
socket = self._default_output_socket
|
|
784
|
+
_MATH_COMPARE_MAP = {
|
|
785
|
+
"less_than": ("less_than", False),
|
|
786
|
+
"greater_than": ("greater_than", False),
|
|
787
|
+
"less_equal": ("greater_than", True), # a<=b → !(a>b) → 1-gt(a,b)
|
|
788
|
+
"greater_equal": ("less_than", True), # a>=b → !(a<b) → 1-lt(a,b)
|
|
789
|
+
}
|
|
790
|
+
math_op, negate = _MATH_COMPARE_MAP[operation]
|
|
791
|
+
result = getattr(Math, math_op)(socket, other)
|
|
792
|
+
if negate:
|
|
793
|
+
result = Math.subtract(1.0, result._default_output_socket)
|
|
794
|
+
return result
|
|
795
|
+
|
|
796
|
+
def __lt__(self, other: Any) -> "Compare":
|
|
797
|
+
return self._apply_compare_operation(other, "less_than")
|
|
798
|
+
|
|
799
|
+
def __gt__(self, other: Any) -> "Compare":
|
|
800
|
+
return self._apply_compare_operation(other, "greater_than")
|
|
801
|
+
|
|
802
|
+
def __le__(self, other: Any) -> "Compare":
|
|
803
|
+
return self._apply_compare_operation(other, "less_equal")
|
|
804
|
+
|
|
805
|
+
def __ge__(self, other: Any) -> "Compare":
|
|
806
|
+
return self._apply_compare_operation(other, "greater_equal")
|
|
807
|
+
|
|
808
|
+
def _apply_boolean_operation(self, other: Any, operation: str) -> "BooleanMath":
|
|
809
|
+
"""Apply a boolean operation using the BooleanMath node."""
|
|
810
|
+
from .nodes.geometry.converter import BooleanMath
|
|
811
|
+
|
|
812
|
+
return getattr(BooleanMath, operation)(self, other)
|
|
813
|
+
|
|
814
|
+
def __and__(self, other: Any) -> "BooleanMath":
|
|
815
|
+
return self._apply_boolean_operation(other, "l_and")
|
|
816
|
+
|
|
817
|
+
def __rand__(self, other: Any) -> "BooleanMath":
|
|
818
|
+
from .nodes.geometry.converter import BooleanMath
|
|
819
|
+
|
|
820
|
+
return BooleanMath.l_and(other, self)
|
|
821
|
+
|
|
822
|
+
def __or__(self, other: Any) -> "BooleanMath":
|
|
823
|
+
return self._apply_boolean_operation(other, "l_or")
|
|
824
|
+
|
|
825
|
+
def __ror__(self, other: Any) -> "BooleanMath":
|
|
826
|
+
from .nodes.geometry.converter import BooleanMath
|
|
827
|
+
|
|
828
|
+
return BooleanMath.l_or(other, self)
|
|
829
|
+
|
|
830
|
+
def __xor__(self, other: Any) -> "BooleanMath":
|
|
831
|
+
return self._apply_boolean_operation(other, "not_equal")
|
|
832
|
+
|
|
833
|
+
def __rxor__(self, other: Any) -> "BooleanMath":
|
|
834
|
+
from .nodes.geometry.converter import BooleanMath
|
|
835
|
+
|
|
836
|
+
return BooleanMath.not_equal(other, self)
|
|
837
|
+
|
|
838
|
+
def __invert__(self) -> "BooleanMath":
|
|
839
|
+
from .nodes.geometry.converter import BooleanMath
|
|
840
|
+
|
|
841
|
+
return BooleanMath.l_not(self)
|
|
842
|
+
|
|
843
|
+
@staticmethod
|
|
844
|
+
def _cast_to_matrix(value):
|
|
845
|
+
from .nodes.geometry.converter import CombineMatrix
|
|
846
|
+
|
|
847
|
+
if hasattr(value, "shape") and value.shape == (4, 4):
|
|
848
|
+
return CombineMatrix(*value.ravel())
|
|
849
|
+
else:
|
|
850
|
+
return value
|
|
851
|
+
|
|
852
|
+
def __matmul__(self, other: Any) -> "MultiplyMatrices":
|
|
853
|
+
from .nodes.geometry.converter import MultiplyMatrices
|
|
854
|
+
|
|
855
|
+
return MultiplyMatrices(self, self._cast_to_matrix(other))
|
|
856
|
+
|
|
857
|
+
def __rmatmul__(self, other: Any) -> "MultiplyMatrices":
|
|
858
|
+
from .nodes.geometry.converter import MultiplyMatrices
|
|
859
|
+
|
|
860
|
+
return MultiplyMatrices(self._cast_to_matrix(other), self)
|
|
861
|
+
|
|
604
862
|
|
|
605
863
|
class DynamicInputsMixin:
|
|
606
864
|
_socket_data_types: tuple[str]
|
|
@@ -1213,3 +1471,26 @@ class SocketClosure(SocketBase):
|
|
|
1213
1471
|
hide_value=hide_value,
|
|
1214
1472
|
hide_in_modifier=hide_in_modifier,
|
|
1215
1473
|
)
|
|
1474
|
+
|
|
1475
|
+
|
|
1476
|
+
class SocketShader(SocketBase):
|
|
1477
|
+
"""Shader that is the final output for a material"""
|
|
1478
|
+
|
|
1479
|
+
_bl_socket_type: str = "NodeSocketShader"
|
|
1480
|
+
socket: bpy.types.NodeTreeInterfaceSocketShader
|
|
1481
|
+
|
|
1482
|
+
def __init__(
|
|
1483
|
+
self,
|
|
1484
|
+
name: str = "Shader",
|
|
1485
|
+
description: str = "",
|
|
1486
|
+
*,
|
|
1487
|
+
optional_label: bool = False,
|
|
1488
|
+
hide_value: bool = False,
|
|
1489
|
+
hide_in_modifier: bool = False,
|
|
1490
|
+
):
|
|
1491
|
+
super().__init__(name, description)
|
|
1492
|
+
self._set_values(
|
|
1493
|
+
optional_label=optional_label,
|
|
1494
|
+
hide_value=hide_value,
|
|
1495
|
+
hide_in_modifier=hide_in_modifier,
|
|
1496
|
+
)
|
|
@@ -482,7 +482,7 @@ class ClusterGraph:
|
|
|
482
482
|
def get_socket_y(socket: NodeSocket) -> float:
|
|
483
483
|
b_socket = bNodeSocket.from_address(socket.as_pointer())
|
|
484
484
|
ui_scale = 1.0 # type: ignore
|
|
485
|
-
return
|
|
485
|
+
return 1.0
|
|
486
486
|
|
|
487
487
|
|
|
488
488
|
@dataclass(frozen=True)
|