nodebpy 0.16.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.
Files changed (69) hide show
  1. {nodebpy-0.16.0 → nodebpy-0.18.0}/PKG-INFO +2 -2
  2. {nodebpy-0.16.0 → nodebpy-0.18.0}/README.md +1 -1
  3. {nodebpy-0.16.0 → nodebpy-0.18.0}/pyproject.toml +4 -3
  4. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/arrange.py +5 -1
  5. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/builder/_utils.py +1 -1
  6. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/builder/accessor.py +4 -2
  7. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/builder/mixins.py +74 -44
  8. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/builder/node.py +3 -3
  9. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/builder/socket.py +674 -59
  10. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/builder/tree.py +10 -7
  11. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/diagram.py +4 -2
  12. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +3 -3
  13. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +5 -5
  14. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +2 -2
  15. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +2 -2
  16. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +3 -3
  17. nodebpy-0.18.0/src/nodebpy/lib/nodearrange/arrange/structs.py +179 -0
  18. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +2 -1
  19. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +3 -3
  20. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +1 -1
  21. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/color.py +6 -4
  22. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/converter.py +16 -238
  23. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/input.py +1 -1
  24. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/interface.py +9 -3
  25. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/manual.py +113 -5
  26. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/output.py +0 -10
  27. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/attribute.py +2 -3
  28. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/converter.py +34 -12
  29. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/geometry.py +25 -7
  30. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/grid.py +31 -31
  31. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/groups.py +53 -14
  32. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/input.py +7 -5
  33. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/interface.py +21 -5
  34. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/manual.py +63 -44
  35. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/utilities.py +1 -1
  36. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/zone.py +8 -5
  37. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/converter.py +1 -1
  38. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/shader.py +2 -2
  39. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/texture.py +4 -2
  40. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/types.py +1 -1
  41. nodebpy-0.16.0/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -139
  42. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/__init__.py +0 -0
  43. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/builder/__init__.py +0 -0
  44. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/builder/_registry.py +0 -0
  45. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
  46. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/config.py +0 -0
  47. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
  48. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/__init__.py +0 -0
  49. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/__init__.py +1 -1
  50. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/distort.py +0 -0
  51. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/filter.py +0 -0
  52. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/group.py +0 -0
  53. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/matte.py +0 -0
  54. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
  55. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/__init__.py +0 -0
  56. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/color.py +0 -0
  57. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/group.py +0 -0
  58. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/output.py +0 -0
  59. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/texture.py +0 -0
  60. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/geometry/vector.py +0 -0
  61. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/__init__.py +0 -0
  62. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/color.py +0 -0
  63. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/grid.py +0 -0
  64. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/group.py +0 -0
  65. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/input.py +0 -0
  66. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/manual.py +0 -0
  67. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/output.py +0 -0
  68. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/script.py +0 -0
  69. {nodebpy-0.16.0 → nodebpy-0.18.0}/src/nodebpy/nodes/shader/vector.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nodebpy
3
- Version: 0.16.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.16.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>=20260113",
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.bl_rna.properties
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) # type: ignore[arg-type]
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
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Literal, overload
3
+ from typing import TYPE_CHECKING, Iterator, Literal, overload
4
4
 
5
5
  import bpy
6
6
  from bpy.types import NodeSocket
@@ -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
 
@@ -200,7 +202,7 @@ class SocketAccessor:
200
202
  def __len__(self) -> int:
201
203
  return len(self._items())
202
204
 
203
- def __iter__(self):
205
+ def __iter__(self) -> Iterator["Socket"]:
204
206
  return iter(self._values())
205
207
 
206
208
  def __getattr__(self, name: str) -> "Socket":
@@ -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, Math, MultiplyMatrices, TransformPoint
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, # type: ignore[attr-defined]
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, # type: ignore[attr-defined]
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, # type: ignore[attr-defined]
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( # type: ignore[attr-defined]
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( # type: ignore[attr-defined]
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, # type: ignore[attr-defined]
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) -> "Compare":
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) -> "Compare":
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) -> "Compare":
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) -> "Compare": # type: ignore[override]
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) -> "Compare": # type: ignore[override]
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
- else:
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 {source.node.name} to any input of {target.node.name}. " # type: ignore[union-attr]
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
- name = other._placeholder_inputs.pop(0)
359
+ node_other = cast("BaseNode", other)
360
+ name = node_other._placeholder_inputs.pop(0)
337
361
  try:
338
- target = other.node.inputs[name]
362
+ target = node_other.node.inputs[name]
339
363
  except KeyError:
340
- target = other.node.inputs[other.i._index(name)]
341
- source = self.o._best_match(target.type) if hasattr(self, "o") else self
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 = other._find_best_socket_pair(self, other)
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)]