nodebpy 0.17.0__tar.gz → 0.18.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.17.0 → nodebpy-0.18.0}/PKG-INFO +2 -2
- {nodebpy-0.17.0 → nodebpy-0.18.0}/README.md +1 -1
- {nodebpy-0.17.0 → nodebpy-0.18.0}/pyproject.toml +4 -3
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/arrange.py +5 -1
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/builder/_utils.py +1 -1
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/builder/accessor.py +2 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/builder/mixins.py +74 -44
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/builder/node.py +3 -3
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/builder/socket.py +157 -13
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/builder/tree.py +10 -7
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/diagram.py +4 -2
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +3 -3
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +5 -5
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +2 -2
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +2 -2
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +3 -3
- nodebpy-0.18.0/src/nodebpy/lib/nodearrange/arrange/structs.py +179 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +2 -1
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +3 -3
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +1 -1
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/color.py +6 -4
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/converter.py +16 -238
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/input.py +1 -1
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/interface.py +9 -3
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/manual.py +113 -5
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/output.py +0 -10
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/attribute.py +2 -3
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/converter.py +34 -12
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/geometry.py +25 -7
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/grid.py +31 -31
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/groups.py +49 -10
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/input.py +7 -5
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/interface.py +21 -5
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/manual.py +31 -13
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/utilities.py +1 -1
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/zone.py +8 -5
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/converter.py +1 -1
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/shader.py +2 -2
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/texture.py +4 -2
- nodebpy-0.17.0/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -139
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/__init__.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/builder/__init__.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/builder/_registry.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/config.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/__init__.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/__init__.py +1 -1
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/distort.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/filter.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/group.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/matte.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/__init__.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/color.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/group.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/output.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/texture.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/vector.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/__init__.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/color.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/grid.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/group.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/input.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/manual.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/output.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/script.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/vector.py +0 -0
- {nodebpy-0.17.0 → nodebpy-0.18.0}/src/nodebpy/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: nodebpy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
4
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>
|
|
@@ -130,7 +130,7 @@ Some nodes are manually specified in the `src/nodebpy/nodes/geometry/manual.py`
|
|
|
130
130
|
Run the build & format script as such:
|
|
131
131
|
|
|
132
132
|
```bash
|
|
133
|
-
uv run generate.py && uvx ruff format && uvx ruff check --fix
|
|
133
|
+
uv run generate.py && uvx ruff format && uvx ruff check --fix && uvx ty check --fix src
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
## Other Projects
|
|
@@ -116,7 +116,7 @@ Some nodes are manually specified in the `src/nodebpy/nodes/geometry/manual.py`
|
|
|
116
116
|
Run the build & format script as such:
|
|
117
117
|
|
|
118
118
|
```bash
|
|
119
|
-
uv run generate.py && uvx ruff format && uvx ruff check --fix
|
|
119
|
+
uv run generate.py && uvx ruff format && uvx ruff check --fix && uvx ty check --fix src
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
## Other Projects
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nodebpy"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.18.0"
|
|
4
4
|
description = "Build nodes trees in Blender more elegantly with code"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -28,7 +28,7 @@ build-backend = "uv_build"
|
|
|
28
28
|
dev = [
|
|
29
29
|
"ipython>=8.0.0",
|
|
30
30
|
"networkx>=3.6.1",
|
|
31
|
-
"fake-bpy-module>=
|
|
31
|
+
"fake-bpy-module>=20260501",
|
|
32
32
|
"jsondiff>=2.2.1",
|
|
33
33
|
"pytest>=9.0.2",
|
|
34
34
|
"pytest-cov>=7.0.0",
|
|
@@ -38,5 +38,6 @@ dev = [
|
|
|
38
38
|
"syrupy>=5.0.0",
|
|
39
39
|
"tree-clipper>=0.1.1",
|
|
40
40
|
"pytest-xdist>=3.8.0",
|
|
41
|
-
"griffe<2.0.0"
|
|
41
|
+
"griffe<2.0.0",
|
|
42
|
+
"ty>=0.0.35",
|
|
42
43
|
]
|
|
@@ -117,7 +117,7 @@ def calculate_node_dimensions(
|
|
|
117
117
|
inherited_ids = {
|
|
118
118
|
prop.identifier
|
|
119
119
|
for base in type(node).__bases__
|
|
120
|
-
for prop in base
|
|
120
|
+
for prop in getattr(base, "bl_rna").properties
|
|
121
121
|
}
|
|
122
122
|
node_property_count = sum(
|
|
123
123
|
1 for prop in node.bl_rna.properties if prop.identifier not in inherited_ids
|
|
@@ -143,6 +143,7 @@ def calculate_node_dimensions(
|
|
|
143
143
|
|
|
144
144
|
def _socket_index(socket: bpy.types.NodeSocket) -> int:
|
|
145
145
|
"""Return the index of a socket among its node's enabled sockets."""
|
|
146
|
+
assert socket.node is not None
|
|
146
147
|
collection = socket.node.inputs if not socket.is_output else socket.node.outputs
|
|
147
148
|
idx = 0
|
|
148
149
|
for s in collection:
|
|
@@ -208,6 +209,7 @@ def _reduce_crossings(
|
|
|
208
209
|
# on the target node (input side).
|
|
209
210
|
out_count = max(1, sum(1 for s in src.outputs if s.enabled))
|
|
210
211
|
in_count = max(1, sum(1 for s in dst.inputs if s.enabled))
|
|
212
|
+
assert link.from_socket is not None and link.to_socket is not None
|
|
211
213
|
src_frac = _socket_index(link.from_socket) / out_count
|
|
212
214
|
dst_frac = _socket_index(link.to_socket) / in_count
|
|
213
215
|
|
|
@@ -299,8 +301,10 @@ def position_reroutes(tree: bpy.types.NodeTree) -> None:
|
|
|
299
301
|
targets: list[bpy.types.Node] = []
|
|
300
302
|
for link in tree.links:
|
|
301
303
|
if link.to_node == node:
|
|
304
|
+
assert link.from_node is not None
|
|
302
305
|
sources.append(link.from_node)
|
|
303
306
|
if link.from_node == node:
|
|
307
|
+
assert link.to_node is not None
|
|
304
308
|
targets.append(link.to_node)
|
|
305
309
|
|
|
306
310
|
neighbours = sources + targets
|
|
@@ -63,7 +63,7 @@ def _resolve_promotion(
|
|
|
63
63
|
"""
|
|
64
64
|
other_type = getattr(other, "type", None)
|
|
65
65
|
self_prec = _TYPE_PRECEDENCE.get(self_socket.type, 1)
|
|
66
|
-
other_prec = _TYPE_PRECEDENCE.get(other_type, -1)
|
|
66
|
+
other_prec = _TYPE_PRECEDENCE.get(other_type, -1) if other_type is not None else -1
|
|
67
67
|
|
|
68
68
|
if other_prec > self_prec:
|
|
69
69
|
# Other side is dominant — swap so the linker wraps the vector/higher socket
|
|
@@ -103,10 +103,12 @@ class SocketAccessor:
|
|
|
103
103
|
def _node(self) -> bpy.types.Node:
|
|
104
104
|
"""The node this accessor is associated with."""
|
|
105
105
|
if isinstance(self._collection, list):
|
|
106
|
+
assert self._collection[0].node is not None
|
|
106
107
|
return self._collection[0].node
|
|
107
108
|
# bpy NodeInputs/NodeOutputs.id_data returns the NodeTree (top-level ID),
|
|
108
109
|
# not the Node. Retrieve the node via the first socket instead.
|
|
109
110
|
for s in self._collection:
|
|
111
|
+
assert s.node is not None
|
|
110
112
|
return s.node
|
|
111
113
|
return self._collection.data # empty collection fallback
|
|
112
114
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from types import EllipsisType
|
|
4
|
-
from typing import TYPE_CHECKING, Any, TypeVar, overload
|
|
4
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast, overload
|
|
5
5
|
|
|
6
6
|
from bpy.types import NodeLink, NodeSocket
|
|
7
7
|
|
|
@@ -11,8 +11,8 @@ from ._utils import SocketError, _resolve_promotion, _SocketLike
|
|
|
11
11
|
_RShiftT = TypeVar("_RShiftT")
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
|
-
from ..nodes.geometry import Compare
|
|
15
14
|
from ..types import InputLinkable
|
|
15
|
+
from .accessor import SocketAccessor
|
|
16
16
|
from .node import BaseNode
|
|
17
17
|
from .socket import (
|
|
18
18
|
BooleanSocket,
|
|
@@ -35,75 +35,78 @@ class OperatorMixin:
|
|
|
35
35
|
|
|
36
36
|
__array_ufunc__ = None
|
|
37
37
|
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def _default_output_socket(self) -> "NodeSocket": ...
|
|
42
|
+
|
|
38
43
|
def _apply_math_operation(
|
|
39
44
|
self, other: Any, operation: str, reverse: bool = False
|
|
40
45
|
) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
41
46
|
socket, other, reverse = _resolve_promotion(
|
|
42
47
|
self._default_output_socket,
|
|
43
48
|
other,
|
|
44
|
-
reverse,
|
|
49
|
+
reverse,
|
|
45
50
|
)
|
|
46
51
|
return _get_socket_linker(socket)._dispatch_math(other, operation, reverse)
|
|
47
52
|
|
|
48
|
-
def __mul__(self, other: Any) -> "FloatSocket":
|
|
53
|
+
def __mul__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
49
54
|
return self._apply_math_operation(other, "multiply")
|
|
50
55
|
|
|
51
|
-
def __rmul__(self, other: Any) -> "FloatSocket":
|
|
56
|
+
def __rmul__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
52
57
|
return self._apply_math_operation(other, "multiply", reverse=True)
|
|
53
58
|
|
|
54
|
-
def __truediv__(self, other: Any) -> "FloatSocket":
|
|
59
|
+
def __truediv__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
55
60
|
return self._apply_math_operation(other, "divide")
|
|
56
61
|
|
|
57
|
-
def __rtruediv__(self, other: Any) -> "FloatSocket":
|
|
62
|
+
def __rtruediv__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
58
63
|
return self._apply_math_operation(other, "divide", reverse=True)
|
|
59
64
|
|
|
60
|
-
def __add__(self, other: Any) -> "FloatSocket":
|
|
65
|
+
def __add__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
61
66
|
return self._apply_math_operation(other, "add")
|
|
62
67
|
|
|
63
|
-
def __radd__(self, other: Any) -> "FloatSocket":
|
|
68
|
+
def __radd__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
64
69
|
return self._apply_math_operation(other, "add", reverse=True)
|
|
65
70
|
|
|
66
|
-
def __sub__(self, other: Any) -> "FloatSocket":
|
|
71
|
+
def __sub__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
67
72
|
return self._apply_math_operation(other, "subtract")
|
|
68
73
|
|
|
69
|
-
def __rsub__(self, other: Any) -> "FloatSocket":
|
|
74
|
+
def __rsub__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
70
75
|
return self._apply_math_operation(other, "subtract", reverse=True)
|
|
71
76
|
|
|
72
|
-
def __pow__(self, other: Any) -> "FloatSocket":
|
|
77
|
+
def __pow__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
73
78
|
return self._apply_math_operation(other, "power")
|
|
74
79
|
|
|
75
|
-
def __rpow__(self, other: Any) -> "FloatSocket":
|
|
80
|
+
def __rpow__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
76
81
|
return self._apply_math_operation(other, "power", reverse=True)
|
|
77
82
|
|
|
78
|
-
def __mod__(self, other: Any) -> "FloatSocket":
|
|
83
|
+
def __mod__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
79
84
|
return self._apply_math_operation(other, "modulo")
|
|
80
85
|
|
|
81
|
-
def __rmod__(self, other: Any) -> "FloatSocket":
|
|
86
|
+
def __rmod__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
82
87
|
return self._apply_math_operation(other, "modulo", reverse=True)
|
|
83
88
|
|
|
84
|
-
def __floordiv__(self, other: Any) -> "FloatSocket":
|
|
89
|
+
def __floordiv__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
85
90
|
socket, other, reverse = _resolve_promotion(
|
|
86
91
|
self._default_output_socket,
|
|
87
92
|
other,
|
|
88
|
-
False,
|
|
93
|
+
False,
|
|
89
94
|
)
|
|
90
95
|
return _get_socket_linker(socket)._dispatch_floordiv(other, reverse)
|
|
91
96
|
|
|
92
|
-
def __rfloordiv__(self, other: Any) -> "FloatSocket":
|
|
97
|
+
def __rfloordiv__(self, other: Any) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
93
98
|
socket, other, reverse = _resolve_promotion(
|
|
94
99
|
self._default_output_socket,
|
|
95
100
|
other,
|
|
96
|
-
True,
|
|
101
|
+
True,
|
|
97
102
|
)
|
|
98
103
|
return _get_socket_linker(socket)._dispatch_floordiv(other, reverse)
|
|
99
104
|
|
|
100
|
-
def __neg__(self) -> "FloatSocket":
|
|
101
|
-
return _get_socket_linker(self._default_output_socket)._dispatch_unary(
|
|
102
|
-
"negate"
|
|
103
|
-
)
|
|
105
|
+
def __neg__(self) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
106
|
+
return _get_socket_linker(self._default_output_socket)._dispatch_unary("negate")
|
|
104
107
|
|
|
105
|
-
def __abs__(self) -> "FloatSocket":
|
|
106
|
-
return _get_socket_linker(self._default_output_socket)._dispatch_unary(
|
|
108
|
+
def __abs__(self) -> "FloatSocket | VectorSocket | IntegerSocket":
|
|
109
|
+
return _get_socket_linker(self._default_output_socket)._dispatch_unary(
|
|
107
110
|
"absolute"
|
|
108
111
|
)
|
|
109
112
|
|
|
@@ -111,28 +114,28 @@ class OperatorMixin:
|
|
|
111
114
|
self, other: Any, operation: str
|
|
112
115
|
) -> "FloatSocket | BooleanSocket":
|
|
113
116
|
socket, other, _ = _resolve_promotion(
|
|
114
|
-
self._default_output_socket,
|
|
117
|
+
self._default_output_socket,
|
|
115
118
|
other,
|
|
116
119
|
False,
|
|
117
120
|
)
|
|
118
121
|
return _get_socket_linker(socket)._dispatch_compare(other, operation)
|
|
119
122
|
|
|
120
|
-
def __lt__(self, other: Any) -> "BooleanSocket":
|
|
123
|
+
def __lt__(self, other: Any) -> "FloatSocket | BooleanSocket":
|
|
121
124
|
return self._apply_compare_operation(other, "less_than")
|
|
122
125
|
|
|
123
|
-
def __gt__(self, other: Any) -> "
|
|
126
|
+
def __gt__(self, other: Any) -> "FloatSocket | BooleanSocket":
|
|
124
127
|
return self._apply_compare_operation(other, "greater_than")
|
|
125
128
|
|
|
126
|
-
def __le__(self, other: Any) -> "
|
|
129
|
+
def __le__(self, other: Any) -> "FloatSocket | BooleanSocket":
|
|
127
130
|
return self._apply_compare_operation(other, "less_equal")
|
|
128
131
|
|
|
129
|
-
def __ge__(self, other: Any) -> "
|
|
132
|
+
def __ge__(self, other: Any) -> "FloatSocket | BooleanSocket":
|
|
130
133
|
return self._apply_compare_operation(other, "greater_equal")
|
|
131
134
|
|
|
132
|
-
def __eq__(self, other: Any) -> "
|
|
135
|
+
def __eq__(self, other: Any) -> "FloatSocket | BooleanSocket": # type: ignore
|
|
133
136
|
return self._apply_compare_operation(other, "equal")
|
|
134
137
|
|
|
135
|
-
def __ne__(self, other: Any) -> "
|
|
138
|
+
def __ne__(self, other: Any) -> "FloatSocket | BooleanSocket": # type: ignore
|
|
136
139
|
return self._apply_compare_operation(other, "not_equal")
|
|
137
140
|
|
|
138
141
|
def _apply_boolean_operation(self, other: Any, operation: str):
|
|
@@ -146,7 +149,7 @@ class OperatorMixin:
|
|
|
146
149
|
def __rand__(self, other: Any):
|
|
147
150
|
from ..nodes.geometry.converter import BooleanMath
|
|
148
151
|
|
|
149
|
-
return BooleanMath.l_and(other, self)
|
|
152
|
+
return BooleanMath.l_and(other, cast(Any, self))
|
|
150
153
|
|
|
151
154
|
def __or__(self, other: Any):
|
|
152
155
|
return self._apply_boolean_operation(other, "l_or")
|
|
@@ -154,7 +157,7 @@ class OperatorMixin:
|
|
|
154
157
|
def __ror__(self, other: Any):
|
|
155
158
|
from ..nodes.geometry.converter import BooleanMath
|
|
156
159
|
|
|
157
|
-
return BooleanMath.l_or(other, self)
|
|
160
|
+
return BooleanMath.l_or(other, cast(Any, self))
|
|
158
161
|
|
|
159
162
|
def __xor__(self, other: Any):
|
|
160
163
|
return self._apply_boolean_operation(other, "not_equal")
|
|
@@ -162,12 +165,12 @@ class OperatorMixin:
|
|
|
162
165
|
def __rxor__(self, other: Any):
|
|
163
166
|
from ..nodes.geometry.converter import BooleanMath
|
|
164
167
|
|
|
165
|
-
return BooleanMath.not_equal(other, self)
|
|
168
|
+
return BooleanMath.not_equal(other, cast(Any, self))
|
|
166
169
|
|
|
167
170
|
def __invert__(self):
|
|
168
171
|
from ..nodes.geometry.converter import BooleanMath
|
|
169
172
|
|
|
170
|
-
return BooleanMath.l_not(self)
|
|
173
|
+
return BooleanMath.l_not(cast(Any, self))
|
|
171
174
|
|
|
172
175
|
@staticmethod
|
|
173
176
|
def _cast_to_matrix(value) -> MatrixSocket:
|
|
@@ -214,6 +217,20 @@ class LinkingMixin:
|
|
|
214
217
|
|
|
215
218
|
tree: "TreeBuilder"
|
|
216
219
|
|
|
220
|
+
if TYPE_CHECKING:
|
|
221
|
+
import bpy
|
|
222
|
+
|
|
223
|
+
node: bpy.types.Node
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def i(self) -> "SocketAccessor": ...
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def o(self) -> "SocketAccessor": ...
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def _default_output_socket(self) -> "NodeSocket": ...
|
|
233
|
+
|
|
217
234
|
def _source_socket(self, node: "InputLinkable | Socket | NodeSocket") -> NodeSocket:
|
|
218
235
|
assert node is not None
|
|
219
236
|
if isinstance(node, NodeSocket):
|
|
@@ -254,8 +271,12 @@ class LinkingMixin:
|
|
|
254
271
|
|
|
255
272
|
if isinstance(target, BaseNode):
|
|
256
273
|
inputs = target.i._available
|
|
257
|
-
|
|
274
|
+
elif isinstance(target, Socket):
|
|
275
|
+
inputs = [target.socket]
|
|
276
|
+
elif isinstance(target, NodeSocket):
|
|
258
277
|
inputs = [target]
|
|
278
|
+
else:
|
|
279
|
+
raise TypeError(f"Cannot get inputs from {type(target)}")
|
|
259
280
|
|
|
260
281
|
# NodeReroute adapts its type to whatever is linked — skip type matching
|
|
261
282
|
if getattr(getattr(target, "node", None), "bl_idname", None) == "NodeReroute":
|
|
@@ -291,8 +312,10 @@ class LinkingMixin:
|
|
|
291
312
|
if possible_combos:
|
|
292
313
|
return sorted(possible_combos, key=lambda x: x[0])[0][1]
|
|
293
314
|
|
|
315
|
+
src_name = getattr(getattr(source, "node", None), "name", repr(source))
|
|
316
|
+
tgt_name = getattr(getattr(target, "node", None), "name", repr(target))
|
|
294
317
|
raise SocketError(
|
|
295
|
-
f"Cannot link any output from {
|
|
318
|
+
f"Cannot link any output from {src_name} to any input of {tgt_name}. "
|
|
296
319
|
f"Available output types: {[f'{o.name}:{o.type}' for o in outputs]}, "
|
|
297
320
|
f"Available input types: {[f'{i.name}:{i.type}' for i in inputs]}"
|
|
298
321
|
)
|
|
@@ -333,17 +356,24 @@ class LinkingMixin:
|
|
|
333
356
|
source = self._default_output_socket
|
|
334
357
|
target = other.socket
|
|
335
358
|
elif getattr(other, "_placeholder_inputs", None):
|
|
336
|
-
|
|
359
|
+
node_other = cast("BaseNode", other)
|
|
360
|
+
name = node_other._placeholder_inputs.pop(0)
|
|
337
361
|
try:
|
|
338
|
-
target =
|
|
362
|
+
target = node_other.node.inputs[name]
|
|
339
363
|
except KeyError:
|
|
340
|
-
target =
|
|
341
|
-
source =
|
|
364
|
+
target = node_other.node.inputs[node_other.i._index(name)]
|
|
365
|
+
source = (
|
|
366
|
+
self.o._best_match(target.type)
|
|
367
|
+
if hasattr(self, "o")
|
|
368
|
+
else self._default_output_socket
|
|
369
|
+
)
|
|
342
370
|
else:
|
|
343
371
|
try:
|
|
344
|
-
source, target = self._find_best_socket_pair(self, other)
|
|
372
|
+
source, target = self._find_best_socket_pair(self, cast(Any, other))
|
|
345
373
|
except SocketError:
|
|
346
|
-
source, target =
|
|
374
|
+
source, target = cast("LinkingMixin", other)._find_best_socket_pair(
|
|
375
|
+
self, cast(Any, other)
|
|
376
|
+
)
|
|
347
377
|
|
|
348
378
|
self.tree.link(source, target)
|
|
349
379
|
return other
|
|
@@ -138,9 +138,9 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
138
138
|
and input.type == "VECTOR"
|
|
139
139
|
and isinstance(value, (int, float))
|
|
140
140
|
):
|
|
141
|
-
input.default_value = [value] * len(input.default_value)
|
|
141
|
+
input.default_value = [value] * len(input.default_value) # type: ignore
|
|
142
142
|
else:
|
|
143
|
-
input.default_value = value
|
|
143
|
+
input.default_value = value # type: ignore
|
|
144
144
|
|
|
145
145
|
def _establish_links(self, **kwargs: InputAny):
|
|
146
146
|
input_ids = [input.identifier for input in self.node.inputs]
|
|
@@ -165,7 +165,7 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
165
165
|
elif isinstance(value, NodeSocket):
|
|
166
166
|
self._link_from(value, name)
|
|
167
167
|
elif isinstance(value, _NodeLike):
|
|
168
|
-
self._link_from(value.o._best_match(self.i._get(name).type), name)
|
|
168
|
+
self._link_from(value.o._best_match(self.i._get(name).type), name) # type: ignore
|
|
169
169
|
else:
|
|
170
170
|
if name in input_ids:
|
|
171
171
|
input = self.node.inputs[input_ids.index(name)]
|
|
@@ -68,11 +68,7 @@ from .mixins import LinkingMixin, OperatorMixin
|
|
|
68
68
|
|
|
69
69
|
if TYPE_CHECKING:
|
|
70
70
|
from ..nodes import compositor, geometry, shader
|
|
71
|
-
from ..nodes.geometry import
|
|
72
|
-
IntegerMath,
|
|
73
|
-
MatchString,
|
|
74
|
-
Math,
|
|
75
|
-
)
|
|
71
|
+
from ..nodes.geometry import IntegerMath, MatchString, Math, ObjectInfo
|
|
76
72
|
from ..nodes.geometry.manual import Compare
|
|
77
73
|
from ..nodes.geometry.vector import VectorMath
|
|
78
74
|
from .node import BaseNode
|
|
@@ -269,8 +265,8 @@ class Socket(BaseSocket, _SocketLike, OperatorMixin, LinkingMixin):
|
|
|
269
265
|
def __gt__(self, other: Any) -> "BooleanSocket": ...
|
|
270
266
|
def __le__(self, other: Any) -> "BooleanSocket": ...
|
|
271
267
|
def __ge__(self, other: Any) -> "BooleanSocket": ...
|
|
272
|
-
def __eq__(self, other: Any) -> "
|
|
273
|
-
def __ne__(self, other: Any) -> "
|
|
268
|
+
def __eq__(self, other: Any) -> "BooleanSocket": ...
|
|
269
|
+
def __ne__(self, other: Any) -> "BooleanSocket": ...
|
|
274
270
|
|
|
275
271
|
|
|
276
272
|
# ---------------------------------------------------------------------------
|
|
@@ -1553,13 +1549,10 @@ class _MatrixMixin(BaseSocket):
|
|
|
1553
1549
|
if TYPE_CHECKING:
|
|
1554
1550
|
|
|
1555
1551
|
@overload
|
|
1556
|
-
def __matmul__(
|
|
1557
|
-
self, other: "VectorSocket | NodeSocketVector"
|
|
1558
|
-
) -> "VectorSocket": ...
|
|
1552
|
+
def __matmul__(self, other: "VectorSocket") -> "VectorSocket": ...
|
|
1559
1553
|
@overload
|
|
1560
|
-
def __matmul__(self, other:
|
|
1561
|
-
|
|
1562
|
-
def __rmatmul__(self, other: Any) -> "MatrixSocket": ...
|
|
1554
|
+
def __matmul__(self, other: "MatrixSocket") -> "MatrixSocket": ...
|
|
1555
|
+
def __rmatmul__(self, other: "MatrixSocket") -> "MatrixSocket": ...
|
|
1563
1556
|
|
|
1564
1557
|
|
|
1565
1558
|
# ---------------------------------------------------------------------------
|
|
@@ -1621,6 +1614,21 @@ class GeometrySocket(Socket):
|
|
|
1621
1614
|
|
|
1622
1615
|
socket: NodeSocketGeometry
|
|
1623
1616
|
|
|
1617
|
+
def realize_instances(
|
|
1618
|
+
self,
|
|
1619
|
+
selection: InputBoolean = True,
|
|
1620
|
+
realize_all: InputBoolean = False,
|
|
1621
|
+
depth: InputInteger = 0,
|
|
1622
|
+
) -> "GeometrySocket":
|
|
1623
|
+
from ..nodes.geometry import RealizeInstances
|
|
1624
|
+
|
|
1625
|
+
return RealizeInstances(
|
|
1626
|
+
self.socket,
|
|
1627
|
+
selection=selection,
|
|
1628
|
+
realize_all=realize_all,
|
|
1629
|
+
depth=depth,
|
|
1630
|
+
).o.geometry
|
|
1631
|
+
|
|
1624
1632
|
|
|
1625
1633
|
class ObjectSocket(Socket):
|
|
1626
1634
|
"""Runtime object socket wrapper."""
|
|
@@ -1635,6 +1643,110 @@ class ObjectSocket(Socket):
|
|
|
1635
1643
|
def default_value(self, value: bpy.types.Object) -> None:
|
|
1636
1644
|
self.socket.default_value = value
|
|
1637
1645
|
|
|
1646
|
+
@property
|
|
1647
|
+
def _info(self) -> "type[ObjectInfo]":
|
|
1648
|
+
from ..nodes.geometry import ObjectInfo
|
|
1649
|
+
|
|
1650
|
+
return ObjectInfo
|
|
1651
|
+
|
|
1652
|
+
def transform(
|
|
1653
|
+
self, transform_space: Literal["ORIGINAL", "RELATIVE"] = "ORIGINAL"
|
|
1654
|
+
) -> "MatrixSocket":
|
|
1655
|
+
"""The Object's transform matrix, optionally in relative space.
|
|
1656
|
+
|
|
1657
|
+
Adds [`ObjectInfo`](~nodebpy.nodes.geometry.ObjectInfo) to the node tree and returns.
|
|
1658
|
+
|
|
1659
|
+
Parameters
|
|
1660
|
+
----------
|
|
1661
|
+
transform_space : Literal["ORIGINAL", "RELATIVE"]
|
|
1662
|
+
The space in which to return the transform matrix.
|
|
1663
|
+
|
|
1664
|
+
Returns
|
|
1665
|
+
-------
|
|
1666
|
+
MatrixSocket
|
|
1667
|
+
The output 'Transform' `MatrixSocket`.
|
|
1668
|
+
"""
|
|
1669
|
+
return self._info(self.socket, transform_space=transform_space).o.transform
|
|
1670
|
+
|
|
1671
|
+
def location(
|
|
1672
|
+
self, transform_space: Literal["ORIGINAL", "RELATIVE"] = "ORIGINAL"
|
|
1673
|
+
) -> "VectorSocket":
|
|
1674
|
+
"""
|
|
1675
|
+
The object's location, optionally in relative space, via [`ObjectInfo`](~nodebpy.nodes.geometry.ObjectInfo).
|
|
1676
|
+
|
|
1677
|
+
Parameters
|
|
1678
|
+
----------
|
|
1679
|
+
transform_space : Literal["ORIGINAL", "RELATIVE"]
|
|
1680
|
+
The space in which to return the location.
|
|
1681
|
+
|
|
1682
|
+
Returns
|
|
1683
|
+
-------
|
|
1684
|
+
VectorSocket
|
|
1685
|
+
The output 'Location' `VectorSocket`.
|
|
1686
|
+
|
|
1687
|
+
"""
|
|
1688
|
+
return self._info(self.socket, transform_space=transform_space).o.location
|
|
1689
|
+
|
|
1690
|
+
def rotation(
|
|
1691
|
+
self, transform_space: Literal["ORIGINAL", "RELATIVE"] = "ORIGINAL"
|
|
1692
|
+
) -> "RotationSocket":
|
|
1693
|
+
"""
|
|
1694
|
+
The object's rotation, optionally in relative space, via [`ObjectInfo`](~nodebpy.nodes.geometry.ObjectInfo).
|
|
1695
|
+
|
|
1696
|
+
Parameters
|
|
1697
|
+
----------
|
|
1698
|
+
transform_space : Literal["ORIGINAL", "RELATIVE"]
|
|
1699
|
+
The space in which to return the rotation.
|
|
1700
|
+
|
|
1701
|
+
Returns
|
|
1702
|
+
-------
|
|
1703
|
+
RotationSocket
|
|
1704
|
+
The output 'Rotation' `RotationSocket`.
|
|
1705
|
+
"""
|
|
1706
|
+
return self._info(self.socket, transform_space=transform_space).o.rotation
|
|
1707
|
+
|
|
1708
|
+
def scale(
|
|
1709
|
+
self, transform_space: Literal["ORIGINAL", "RELATIVE"] = "ORIGINAL"
|
|
1710
|
+
) -> "VectorSocket":
|
|
1711
|
+
"""
|
|
1712
|
+
The object's scale, optionally in relative space, via [`ObjectInfo`](~nodebpy.nodes.geometry.ObjectInfo).
|
|
1713
|
+
|
|
1714
|
+
Parameters
|
|
1715
|
+
----------
|
|
1716
|
+
transform_space : Literal["ORIGINAL", "RELATIVE"]
|
|
1717
|
+
The space in which to return the scale.
|
|
1718
|
+
|
|
1719
|
+
Returns
|
|
1720
|
+
-------
|
|
1721
|
+
VectorSocket
|
|
1722
|
+
The output 'Scale' `VectorSocket`.
|
|
1723
|
+
"""
|
|
1724
|
+
return self._info(self.socket, transform_space=transform_space).o.scale
|
|
1725
|
+
|
|
1726
|
+
def geometry(
|
|
1727
|
+
self,
|
|
1728
|
+
transform_space: Literal["ORIGINAL", "RELATIVE"] = "ORIGINAL",
|
|
1729
|
+
as_instance: InputBoolean = False,
|
|
1730
|
+
) -> "GeometrySocket":
|
|
1731
|
+
"""
|
|
1732
|
+
The object's geometry, optionally in relative space, via [`ObjectInfo`](~nodebpy.nodes.geometry.ObjectInfo).
|
|
1733
|
+
|
|
1734
|
+
Parameters
|
|
1735
|
+
----------
|
|
1736
|
+
transform_space : Literal["ORIGINAL", "RELATIVE"]
|
|
1737
|
+
The space in which to return the geometry.
|
|
1738
|
+
as_instance : InputBoolean
|
|
1739
|
+
Whether to return the geometry as an instance.
|
|
1740
|
+
|
|
1741
|
+
Returns
|
|
1742
|
+
-------
|
|
1743
|
+
GeometrySocket
|
|
1744
|
+
The output 'Geometry' `GeometrySocket`.
|
|
1745
|
+
"""
|
|
1746
|
+
return self._info(
|
|
1747
|
+
self.socket, as_instance=as_instance, transform_space=transform_space
|
|
1748
|
+
).o.geometry
|
|
1749
|
+
|
|
1638
1750
|
|
|
1639
1751
|
class MaterialSocket(Socket):
|
|
1640
1752
|
"""Runtime material socket wrapper."""
|
|
@@ -1677,6 +1789,38 @@ class CollectionSocket(Socket):
|
|
|
1677
1789
|
def default_value(self, value: bpy.types.Collection) -> None:
|
|
1678
1790
|
self.socket.default_value = value
|
|
1679
1791
|
|
|
1792
|
+
def instances(
|
|
1793
|
+
self,
|
|
1794
|
+
transform_space: Literal["ORIGINAL", "RELATIVE"] = "ORIGINAL",
|
|
1795
|
+
separate_children: InputBoolean = False,
|
|
1796
|
+
reset_children: InputBoolean = False,
|
|
1797
|
+
) -> "GeometrySocket":
|
|
1798
|
+
"""Import objects from the collection as instances.
|
|
1799
|
+
|
|
1800
|
+
Parameters
|
|
1801
|
+
----------
|
|
1802
|
+
transform_space : Literal["ORIGINAL", "RELATIVE"]
|
|
1803
|
+
The transform space to use for the instances.
|
|
1804
|
+
separate_children : bool
|
|
1805
|
+
Whether to separate objects as their own instances.
|
|
1806
|
+
reset_children : bool
|
|
1807
|
+
Whether to reset children of the collection to world origin.
|
|
1808
|
+
|
|
1809
|
+
Returns
|
|
1810
|
+
-------
|
|
1811
|
+
GeometrySocket
|
|
1812
|
+
The output 'Instances' `GeometrySocket`. Will be a single instance or multiple instances if `separate_children` is `True`.
|
|
1813
|
+
|
|
1814
|
+
"""
|
|
1815
|
+
from ..nodes.geometry import CollectionInfo
|
|
1816
|
+
|
|
1817
|
+
return CollectionInfo(
|
|
1818
|
+
self.socket,
|
|
1819
|
+
separate_children,
|
|
1820
|
+
reset_children,
|
|
1821
|
+
transform_space=transform_space,
|
|
1822
|
+
).o.instances
|
|
1823
|
+
|
|
1680
1824
|
|
|
1681
1825
|
class BundleSocket(Socket):
|
|
1682
1826
|
"""Runtime bundle socket wrapper."""
|
|
@@ -559,11 +559,13 @@ class SocketContext:
|
|
|
559
559
|
return self._wrap(ShaderSocket, iface)
|
|
560
560
|
|
|
561
561
|
def __len__(self) -> int:
|
|
562
|
+
assert self.tree.interface is not None
|
|
562
563
|
return len(
|
|
563
564
|
list(
|
|
564
565
|
item
|
|
565
566
|
for item in self.tree.interface.items_tree
|
|
566
|
-
if item
|
|
567
|
+
if isinstance(item, bpy.types.NodeTreeInterfaceSocket)
|
|
568
|
+
and item.in_out == self._direction
|
|
567
569
|
)
|
|
568
570
|
)
|
|
569
571
|
|
|
@@ -610,7 +612,7 @@ class TreeBuilder(Generic[_TreeT]):
|
|
|
610
612
|
if isinstance(tree, str):
|
|
611
613
|
self.tree = bpy.data.node_groups.new(tree, tree_type) # type: ignore[assignment]
|
|
612
614
|
else:
|
|
613
|
-
self.tree = tree # type: ignore
|
|
615
|
+
self.tree = tree # type: ignore
|
|
614
616
|
|
|
615
617
|
self._menu_defaults: dict[str, str] = {}
|
|
616
618
|
self.inputs = InputInterfaceContext(self)
|
|
@@ -720,7 +722,7 @@ class TreeBuilder(Generic[_TreeT]):
|
|
|
720
722
|
continue
|
|
721
723
|
if item.identifier == key:
|
|
722
724
|
if hasattr(item, "default_value"):
|
|
723
|
-
item.default_value = value
|
|
725
|
+
item.default_value = value # type: ignore
|
|
724
726
|
|
|
725
727
|
def __len__(self) -> int:
|
|
726
728
|
return len(self.nodes)
|
|
@@ -728,10 +730,10 @@ class TreeBuilder(Generic[_TreeT]):
|
|
|
728
730
|
def arrange(self):
|
|
729
731
|
if self._arrange == "sugiyama":
|
|
730
732
|
try:
|
|
731
|
-
from ..lib.nodearrange import
|
|
733
|
+
from ..lib.nodearrange.arrange import sugiyama
|
|
732
734
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
+
sugiyama.sugiyama_layout(self.tree)
|
|
736
|
+
sugiyama.config.reset()
|
|
735
737
|
except ImportError as e:
|
|
736
738
|
if "networkx" not in str(e):
|
|
737
739
|
raise
|
|
@@ -805,8 +807,9 @@ class TreeBuilder(Generic[_TreeT]):
|
|
|
805
807
|
assert socket1.node
|
|
806
808
|
assert socket2.node
|
|
807
809
|
for socket in [socket1, socket2]:
|
|
810
|
+
assert socket.node is not None
|
|
808
811
|
if socket.is_inactive and not _allow_innactive_sockets(socket.node):
|
|
809
|
-
message = f"Socket {socket.name} from node {socket.node.name} is inactive."
|
|
812
|
+
message = f"Socket {socket.name} from node {socket.node.name} is inactive."
|
|
810
813
|
message += f" It is linked to socket {socket2.name} from node {socket2.node.name}."
|
|
811
814
|
message += " This link will be created by Blender but ignored when evaluated."
|
|
812
815
|
message += f"Socket type: {socket.bl_idname}"
|