nodebpy 0.1.1__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nodebpy/builder.py +786 -316
- nodebpy/nodes/__init__.py +641 -10
- nodebpy/nodes/attribute.py +345 -389
- nodebpy/nodes/color.py +72 -0
- nodebpy/nodes/converter.py +3527 -0
- nodebpy/nodes/experimental.py +312 -0
- nodebpy/nodes/geometry.py +3677 -4717
- nodebpy/nodes/grid.py +1713 -0
- nodebpy/nodes/group.py +17 -0
- nodebpy/nodes/input.py +1821 -316
- nodebpy/nodes/interface.py +519 -0
- nodebpy/nodes/manual.py +2022 -0
- nodebpy/nodes/output.py +85 -0
- nodebpy/nodes/texture.py +930 -0
- nodebpy/nodes/vector.py +528 -0
- nodebpy/nodes/zone.py +442 -0
- nodebpy/screenshot.py +2 -1
- nodebpy/sockets.py +12 -12
- nodebpy/types.py +445 -0
- {nodebpy-0.1.1.dist-info → nodebpy-0.2.1.dist-info}/METADATA +5 -5
- nodebpy-0.2.1.dist-info/RECORD +26 -0
- nodebpy/nodes/curve.py +0 -2006
- nodebpy/nodes/manually_specified.py +0 -1382
- nodebpy/nodes/mesh.py +0 -1408
- nodebpy/nodes/types.py +0 -119
- nodebpy/nodes/utilities.py +0 -2344
- nodebpy-0.1.1.dist-info/RECORD +0 -19
- {nodebpy-0.1.1.dist-info → nodebpy-0.2.1.dist-info}/WHEEL +0 -0
- {nodebpy-0.1.1.dist-info → nodebpy-0.2.1.dist-info}/entry_points.txt +0 -0
nodebpy/builder.py
CHANGED
|
@@ -2,6 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any, ClassVar, Literal
|
|
4
4
|
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .nodes import Math, VectorMath
|
|
7
|
+
|
|
5
8
|
import arrangebpy
|
|
6
9
|
import bpy
|
|
7
10
|
from bpy.types import (
|
|
@@ -11,13 +14,19 @@ from bpy.types import (
|
|
|
11
14
|
NodeSocket,
|
|
12
15
|
)
|
|
13
16
|
|
|
14
|
-
from .
|
|
17
|
+
from .types import (
|
|
18
|
+
LINKABLE,
|
|
19
|
+
SOCKET_COMPATIBILITY,
|
|
20
|
+
SOCKET_TYPES,
|
|
21
|
+
TYPE_INPUT_ALL,
|
|
15
22
|
FloatInterfaceSubtypes,
|
|
16
23
|
IntegerInterfaceSubtypes,
|
|
17
24
|
StringInterfaceSubtypes,
|
|
18
25
|
VectorInterfaceSubtypes,
|
|
19
26
|
_AttributeDomains,
|
|
27
|
+
_SocketShapeStructureType,
|
|
20
28
|
)
|
|
29
|
+
|
|
21
30
|
# from .arrange import arrange_tree
|
|
22
31
|
|
|
23
32
|
GEO_NODE_NAMES = (
|
|
@@ -33,13 +42,6 @@ GEO_NODE_NAMES = (
|
|
|
33
42
|
)
|
|
34
43
|
|
|
35
44
|
|
|
36
|
-
# POSSIBLE_NODE_NAMES = "GeometryNode"
|
|
37
|
-
LINKABLE = "Node | NodeSocket | NodeBuilder"
|
|
38
|
-
TYPE_INPUT_VECTOR = "NodeSocketVector | Vector | NodeBuilder | list[float] | tuple[float, float, float] | None"
|
|
39
|
-
TYPE_INPUT_ROTATION = "NodeSocketRotation | Quaternion | NodeBuilder | list[float] | tuple[float, float, float, float] | None"
|
|
40
|
-
TYPE_INPUT_BOOLEAN = "NodeSocketBool | bool | NodeBuilder | None"
|
|
41
|
-
|
|
42
|
-
|
|
43
45
|
def normalize_name(name: str) -> str:
|
|
44
46
|
"""Convert 'Geometry' or 'My Socket' to 'geometry' or 'my_socket'."""
|
|
45
47
|
return name.lower().replace(" ", "_")
|
|
@@ -50,28 +52,61 @@ def denormalize_name(attr_name: str) -> str:
|
|
|
50
52
|
return attr_name.replace("_", " ").title()
|
|
51
53
|
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
class SocketContext:
|
|
56
|
+
_direction: Literal["INPUT", "OUTPUT"] | None
|
|
57
|
+
_active_context: SocketContext | None = None
|
|
58
|
+
|
|
59
|
+
def __init__(self, tree_builder: TreeBuilder):
|
|
60
|
+
self.builder = tree_builder
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def tree(self) -> GeometryNodeTree:
|
|
64
|
+
tree = self.builder.tree
|
|
65
|
+
assert tree is not None and isinstance(tree, GeometryNodeTree)
|
|
66
|
+
return tree
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def interface(self) -> bpy.types.NodeTreeInterface:
|
|
70
|
+
interface = self.tree.interface
|
|
71
|
+
assert interface is not None
|
|
72
|
+
return interface
|
|
73
|
+
|
|
74
|
+
def _create_socket(
|
|
75
|
+
self, socket_def: SocketBase, name: str
|
|
76
|
+
) -> bpy.types.NodeTreeInterfaceSocket:
|
|
77
|
+
"""Create a socket from a socket definition."""
|
|
78
|
+
socket = self.interface.new_socket(
|
|
79
|
+
name=name,
|
|
80
|
+
in_out=self._direction,
|
|
81
|
+
socket_type=socket_def._bl_socket_type,
|
|
82
|
+
)
|
|
83
|
+
socket.description = socket_def.description
|
|
84
|
+
return socket
|
|
85
|
+
|
|
86
|
+
def __enter__(self):
|
|
87
|
+
SocketContext._direction = self._direction
|
|
88
|
+
SocketContext._active_context = self
|
|
89
|
+
return self
|
|
63
90
|
|
|
91
|
+
def __exit__(self, *args):
|
|
92
|
+
SocketContext._direction = None
|
|
93
|
+
SocketContext._active_context = None
|
|
94
|
+
pass
|
|
64
95
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
96
|
+
|
|
97
|
+
class DirectionalContext(SocketContext):
|
|
98
|
+
"""Base class for directional socket contexts"""
|
|
99
|
+
|
|
100
|
+
_direction: Literal["INPUT", "OUTPUT"] = "INPUT"
|
|
101
|
+
_active_context = None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class InputInterfaceContext(DirectionalContext):
|
|
105
|
+
_direction = "INPUT"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class OutputInterfaceContext(DirectionalContext):
|
|
109
|
+
_direction = "OUTPUT"
|
|
75
110
|
|
|
76
111
|
|
|
77
112
|
class TreeBuilder:
|
|
@@ -82,12 +117,10 @@ class TreeBuilder:
|
|
|
82
117
|
just_added: "Node | None" = None
|
|
83
118
|
|
|
84
119
|
def __init__(
|
|
85
|
-
self, tree:
|
|
120
|
+
self, tree: GeometryNodeTree | str = "Geometry Nodes", arrange: bool = True
|
|
86
121
|
):
|
|
87
122
|
if isinstance(tree, str):
|
|
88
123
|
self.tree = bpy.data.node_groups.new(tree, "GeometryNodeTree")
|
|
89
|
-
elif tree is None:
|
|
90
|
-
self.tree = bpy.data.node_groups.new("GeometryNodeTree", "GeometryNodeTree")
|
|
91
124
|
else:
|
|
92
125
|
assert isinstance(tree, GeometryNodeTree)
|
|
93
126
|
self.tree = tree
|
|
@@ -112,6 +145,9 @@ class TreeBuilder:
|
|
|
112
145
|
def nodes(self) -> Nodes:
|
|
113
146
|
return self.tree.nodes
|
|
114
147
|
|
|
148
|
+
def __len__(self) -> int:
|
|
149
|
+
return len(self.nodes)
|
|
150
|
+
|
|
115
151
|
def arrange(self):
|
|
116
152
|
settings = arrangebpy.LayoutSettings(
|
|
117
153
|
horizontal_spacing=200, vertical_spacing=200, align_top_layer=True
|
|
@@ -148,92 +184,47 @@ class TreeBuilder:
|
|
|
148
184
|
except KeyError:
|
|
149
185
|
return self.tree.nodes.new("NodeGroupOutput") # type: ignore
|
|
150
186
|
|
|
151
|
-
def link(self, socket1: NodeSocket, socket2: NodeSocket):
|
|
187
|
+
def link(self, socket1: NodeSocket, socket2: NodeSocket) -> bpy.types.NodeLink:
|
|
152
188
|
if isinstance(socket1, SocketLinker):
|
|
153
189
|
socket1 = socket1.socket
|
|
154
190
|
if isinstance(socket2, SocketLinker):
|
|
155
191
|
socket2 = socket2.socket
|
|
156
192
|
|
|
157
|
-
self.tree.links.new(socket1, socket2)
|
|
193
|
+
link = self.tree.links.new(socket1, socket2, handle_dynamic_sockets=True)
|
|
158
194
|
|
|
159
195
|
if any(socket.is_inactive for socket in [socket1, socket2]):
|
|
196
|
+
assert socket1.node
|
|
197
|
+
assert socket2.node
|
|
160
198
|
# the warning message should report which sockets from which nodes were linked and which were innactive
|
|
161
199
|
for socket in [socket1, socket2]:
|
|
162
|
-
if socket
|
|
163
|
-
|
|
200
|
+
# we want to be loud about it if we end up linking an inactive socket to a node that is not a switch
|
|
201
|
+
if socket.is_inactive and socket.node.bl_idname not in ( # type: ignore
|
|
202
|
+
"GeometryNodeIndexSwitch",
|
|
203
|
+
"GeometryNodeMenuSwitch",
|
|
204
|
+
):
|
|
205
|
+
message = f"Socket {socket.name} from node {socket.node.name} is inactive." # type: ignore
|
|
164
206
|
message += f" It is linked to socket {socket2.name} from node {socket2.node.name}."
|
|
165
207
|
message += " This link will be created by Blender but ignored when evaluated."
|
|
166
208
|
message += f"Socket type: {socket.bl_idname}"
|
|
167
209
|
raise RuntimeError(message)
|
|
168
210
|
|
|
169
|
-
|
|
170
|
-
self.just_added = self.tree.nodes.new(name) # type: ignore
|
|
171
|
-
assert self.just_added is not None
|
|
172
|
-
return self.just_added
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
class SocketContext:
|
|
176
|
-
_direction: Literal["INPUT", "OUTPUT"] | None
|
|
177
|
-
_active_context: SocketContext | None = None
|
|
211
|
+
return link
|
|
178
212
|
|
|
179
|
-
def
|
|
180
|
-
self.
|
|
181
|
-
|
|
182
|
-
@property
|
|
183
|
-
def tree(self) -> GeometryNodeTree:
|
|
184
|
-
tree = self.builder.tree
|
|
185
|
-
assert tree is not None and isinstance(tree, GeometryNodeTree)
|
|
186
|
-
return tree
|
|
187
|
-
|
|
188
|
-
@property
|
|
189
|
-
def interface(self) -> bpy.types.NodeTreeInterface:
|
|
190
|
-
interface = self.tree.interface
|
|
191
|
-
assert interface is not None
|
|
192
|
-
return interface
|
|
193
|
-
|
|
194
|
-
def _create_socket(
|
|
195
|
-
self, socket_def: SocketBase
|
|
196
|
-
) -> bpy.types.NodeTreeInterfaceSocket:
|
|
197
|
-
"""Create a socket from a socket definition."""
|
|
198
|
-
socket = self.interface.new_socket(
|
|
199
|
-
name=socket_def.name,
|
|
200
|
-
in_out=self._direction,
|
|
201
|
-
socket_type=socket_def._bl_socket_type,
|
|
202
|
-
)
|
|
203
|
-
socket.description = socket_def.description
|
|
204
|
-
return socket
|
|
205
|
-
|
|
206
|
-
def __enter__(self):
|
|
207
|
-
SocketContext._direction = self._direction
|
|
208
|
-
SocketContext._active_context = self
|
|
209
|
-
return self
|
|
210
|
-
|
|
211
|
-
def __exit__(self, *args):
|
|
212
|
-
SocketContext._direction = None
|
|
213
|
-
SocketContext._active_context = None
|
|
214
|
-
pass
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
class InputInterfaceContext(SocketContext):
|
|
218
|
-
_direction = "INPUT"
|
|
219
|
-
_active_context = None
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
class OutputInterfaceContext(SocketContext):
|
|
223
|
-
_direction = "OUTPUT"
|
|
224
|
-
_active_context = None
|
|
213
|
+
def add(self, name: str) -> Node:
|
|
214
|
+
return self.tree.nodes.new(name)
|
|
225
215
|
|
|
226
216
|
|
|
227
217
|
class NodeBuilder:
|
|
228
218
|
"""Base class for all geometry node wrappers."""
|
|
229
219
|
|
|
230
|
-
node:
|
|
231
|
-
|
|
220
|
+
node: Any
|
|
221
|
+
_bl_idname: str
|
|
232
222
|
_tree: "TreeBuilder"
|
|
233
|
-
_link_target: str | None = None
|
|
223
|
+
_link_target: str | None = None
|
|
234
224
|
_from_socket: NodeSocket | None = None
|
|
235
225
|
_default_input_id: str | None = None
|
|
236
226
|
_default_output_id: str | None = None
|
|
227
|
+
_socket_data_types = tuple(SOCKET_COMPATIBILITY.keys())
|
|
237
228
|
|
|
238
229
|
def __init__(self):
|
|
239
230
|
# Get active tree from context manager
|
|
@@ -246,13 +237,10 @@ class NodeBuilder:
|
|
|
246
237
|
f" node = {self.__class__.__name__}()\n"
|
|
247
238
|
)
|
|
248
239
|
|
|
249
|
-
self.inputs = InputInterfaceContext(tree)
|
|
250
|
-
self.outputs = OutputInterfaceContext(tree)
|
|
251
|
-
|
|
252
240
|
self._tree = tree
|
|
253
241
|
self._link_target = None
|
|
254
242
|
if self.__class__.name is not None:
|
|
255
|
-
self.node = self._tree.add(self.__class__.
|
|
243
|
+
self.node = self._tree.add(self.__class__._bl_idname)
|
|
256
244
|
else:
|
|
257
245
|
raise ValueError(
|
|
258
246
|
f"Class {self.__class__.__name__} must define a 'name' attribute"
|
|
@@ -266,6 +254,14 @@ class NodeBuilder:
|
|
|
266
254
|
def tree(self, value: "TreeBuilder"):
|
|
267
255
|
self._tree = value
|
|
268
256
|
|
|
257
|
+
@property
|
|
258
|
+
def type(self) -> SOCKET_TYPES:
|
|
259
|
+
return self._default_output_socket.type # type: ignore
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def name(self) -> str:
|
|
263
|
+
return str(self.node.name)
|
|
264
|
+
|
|
269
265
|
@property
|
|
270
266
|
def _default_input_socket(self) -> NodeSocket:
|
|
271
267
|
if self._default_input_id is not None:
|
|
@@ -276,7 +272,317 @@ class NodeBuilder:
|
|
|
276
272
|
def _default_output_socket(self) -> NodeSocket:
|
|
277
273
|
if self._default_output_id is not None:
|
|
278
274
|
return self.node.outputs[self._output_idx(self._default_output_id)]
|
|
279
|
-
|
|
275
|
+
|
|
276
|
+
counter = 0
|
|
277
|
+
socket = self.node.outputs[counter]
|
|
278
|
+
while not socket.is_icon_visible:
|
|
279
|
+
print(f"skipping inactive socket {socket.name}")
|
|
280
|
+
counter += 1
|
|
281
|
+
socket = self.node.outputs[counter]
|
|
282
|
+
return socket
|
|
283
|
+
|
|
284
|
+
def _source_socket(self, node: LINKABLE | SocketLinker | NodeSocket) -> NodeSocket:
|
|
285
|
+
assert node
|
|
286
|
+
if isinstance(node, NodeSocket):
|
|
287
|
+
return node
|
|
288
|
+
elif isinstance(node, Node):
|
|
289
|
+
return node.outputs[0]
|
|
290
|
+
elif hasattr(node, "_default_output_socket"):
|
|
291
|
+
return node._default_output_socket
|
|
292
|
+
else:
|
|
293
|
+
raise TypeError(f"Unsupported type: {type(node)}")
|
|
294
|
+
|
|
295
|
+
def _target_socket(self, node: LINKABLE | SocketLinker | NodeSocket) -> NodeSocket:
|
|
296
|
+
assert node
|
|
297
|
+
if isinstance(node, NodeSocket):
|
|
298
|
+
return node
|
|
299
|
+
elif isinstance(node, Node):
|
|
300
|
+
return node.inputs[0]
|
|
301
|
+
elif hasattr(node, "_default_input_socket"):
|
|
302
|
+
return node._default_input_socket
|
|
303
|
+
else:
|
|
304
|
+
raise TypeError(f"Unsupported type: {type(node)}")
|
|
305
|
+
|
|
306
|
+
def _find_compatible_output_socket(self, linkable: "NodeBuilder") -> NodeSocket:
|
|
307
|
+
"""Find a compatible output socket from the linkable node that matches our accepted socket types."""
|
|
308
|
+
# First try the default output socket
|
|
309
|
+
default_output = linkable._default_output_socket
|
|
310
|
+
for comp in SOCKET_COMPATIBILITY[default_output.type]:
|
|
311
|
+
if comp in self._socket_data_types:
|
|
312
|
+
return default_output
|
|
313
|
+
|
|
314
|
+
# If default doesn't work, try all other output sockets
|
|
315
|
+
for output_socket in linkable.node.outputs:
|
|
316
|
+
for comp in SOCKET_COMPATIBILITY[output_socket.type]:
|
|
317
|
+
if comp in self._socket_data_types:
|
|
318
|
+
return output_socket
|
|
319
|
+
|
|
320
|
+
# No compatible socket found
|
|
321
|
+
raise ValueError(
|
|
322
|
+
f"No compatible output socket found on {linkable.node.name} for {self.__class__.__name__}. "
|
|
323
|
+
f"Available output types: {[s.type for s in linkable.node.outputs]}, "
|
|
324
|
+
f"Accepted input types: {self._socket_data_types}"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
def _find_compatible_source_socket(
|
|
328
|
+
self, source_node: "NodeBuilder", target_socket: NodeSocket
|
|
329
|
+
) -> NodeSocket:
|
|
330
|
+
"""Find a compatible output socket from source node that can link to the target input socket."""
|
|
331
|
+
target_type = target_socket.type
|
|
332
|
+
compatible_types = SOCKET_COMPATIBILITY.get(target_type, ())
|
|
333
|
+
|
|
334
|
+
# Collect all compatible sockets with their compatibility priority
|
|
335
|
+
compatible_sockets = []
|
|
336
|
+
for output_socket in source_node.node.outputs:
|
|
337
|
+
if output_socket.type in compatible_types:
|
|
338
|
+
# Priority is the index in the compatibility list (0 = highest priority)
|
|
339
|
+
priority = compatible_types.index(output_socket.type)
|
|
340
|
+
compatible_sockets.append((priority, output_socket))
|
|
341
|
+
|
|
342
|
+
if not compatible_sockets:
|
|
343
|
+
# No compatible socket found
|
|
344
|
+
raise ValueError(
|
|
345
|
+
f"No compatible output socket found on {source_node.node.name} for target socket {target_socket.name} of type {target_type}. "
|
|
346
|
+
f"Available output types: {[s.type for s in source_node.node.outputs]}, "
|
|
347
|
+
f"Compatible types: {compatible_types}"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# Sort by priority (lowest number = highest priority) and return the best match
|
|
351
|
+
compatible_sockets.sort(key=lambda x: x[0])
|
|
352
|
+
return compatible_sockets[0][1]
|
|
353
|
+
|
|
354
|
+
def _find_best_socket_pair(
|
|
355
|
+
self, target_node: "NodeBuilder"
|
|
356
|
+
) -> tuple[NodeSocket, NodeSocket]:
|
|
357
|
+
"""Find the best compatible socket pair between this node (source) and target node."""
|
|
358
|
+
# First try to connect default output to default input
|
|
359
|
+
default_output = self._default_output_socket
|
|
360
|
+
default_input = target_node._default_input_socket
|
|
361
|
+
|
|
362
|
+
# Handle zone outputs that don't have inputs yet
|
|
363
|
+
if default_input is None and hasattr(target_node, "_add_socket"):
|
|
364
|
+
# Target is a zone without inputs - create compatible socket
|
|
365
|
+
source_type = default_output.type
|
|
366
|
+
compatible_types = SOCKET_COMPATIBILITY.get(source_type, [source_type])
|
|
367
|
+
best_type = compatible_types[0] if compatible_types else source_type
|
|
368
|
+
|
|
369
|
+
# Create socket on target zone
|
|
370
|
+
default_input = target_node._add_socket(
|
|
371
|
+
name=best_type.title(), type=best_type
|
|
372
|
+
)
|
|
373
|
+
return default_output, default_input
|
|
374
|
+
|
|
375
|
+
# Check if default sockets are compatible
|
|
376
|
+
if default_input is not None:
|
|
377
|
+
output_compatibles = SOCKET_COMPATIBILITY.get(default_output.type, ())
|
|
378
|
+
if default_input.type in output_compatibles and (
|
|
379
|
+
not default_input.links or default_input.is_multi_input
|
|
380
|
+
):
|
|
381
|
+
return default_output, default_input
|
|
382
|
+
|
|
383
|
+
# If defaults don't work, try all combinations with priority-based matching
|
|
384
|
+
best_match = None
|
|
385
|
+
best_priority = float("inf")
|
|
386
|
+
|
|
387
|
+
for output_socket in self.node.outputs:
|
|
388
|
+
output_compatibles = SOCKET_COMPATIBILITY.get(output_socket.type, ())
|
|
389
|
+
for input_socket in target_node.node.inputs:
|
|
390
|
+
# Skip if socket already has a link and isn't multi-input
|
|
391
|
+
if input_socket.links and not input_socket.is_multi_input:
|
|
392
|
+
continue
|
|
393
|
+
|
|
394
|
+
if input_socket.type in output_compatibles:
|
|
395
|
+
# Calculate priority as the index in the compatibility list
|
|
396
|
+
priority = output_compatibles.index(input_socket.type)
|
|
397
|
+
if priority < best_priority:
|
|
398
|
+
best_priority = priority
|
|
399
|
+
best_match = (output_socket, input_socket)
|
|
400
|
+
|
|
401
|
+
if best_match:
|
|
402
|
+
return best_match
|
|
403
|
+
|
|
404
|
+
# No compatible pair found
|
|
405
|
+
available_outputs = [s.type for s in self.node.outputs]
|
|
406
|
+
available_inputs = [
|
|
407
|
+
s.type for s in target_node.node.inputs if not s.links or s.is_multi_input
|
|
408
|
+
]
|
|
409
|
+
raise RuntimeError(
|
|
410
|
+
f"Cannot link any output from {self.node.name} to any input of {target_node.node.name}. "
|
|
411
|
+
f"Available output types: {available_outputs}, "
|
|
412
|
+
f"Available input types: {available_inputs}"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
def _socket_type_from_linkable(self, linkable: LINKABLE):
|
|
416
|
+
assert linkable, "Linkable cannot be None"
|
|
417
|
+
# If it's a NodeBuilder, try to find a compatible output socket
|
|
418
|
+
if hasattr(linkable, "node") and hasattr(linkable, "_default_output_socket"):
|
|
419
|
+
compatible_socket = self._find_compatible_output_socket(linkable)
|
|
420
|
+
socket_type = compatible_socket.type
|
|
421
|
+
for comp in SOCKET_COMPATIBILITY[socket_type]:
|
|
422
|
+
if comp in self._socket_data_types:
|
|
423
|
+
return comp if comp != "VALUE" else "FLOAT"
|
|
424
|
+
|
|
425
|
+
# Fallback to original logic for other types
|
|
426
|
+
for comp in SOCKET_COMPATIBILITY[linkable.type]:
|
|
427
|
+
if comp in self._socket_data_types:
|
|
428
|
+
return comp if comp != "VALUE" else "FLOAT"
|
|
429
|
+
raise ValueError(
|
|
430
|
+
f"Unsupported socket type for linking: {linkable}, type: {linkable.type=}"
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
def _add_inputs(self, *args, **kwargs) -> dict[str, LINKABLE]:
|
|
434
|
+
"""Dictionary with {new_socket.name: from_linkable} for link creation"""
|
|
435
|
+
new_sockets = {}
|
|
436
|
+
items = {}
|
|
437
|
+
for arg in args:
|
|
438
|
+
if isinstance(arg, bpy.types.NodeSocket):
|
|
439
|
+
name = arg.name
|
|
440
|
+
items[name] = arg
|
|
441
|
+
else:
|
|
442
|
+
items[arg._default_output_socket.name] = arg
|
|
443
|
+
items.update(kwargs)
|
|
444
|
+
for key, value in items.items():
|
|
445
|
+
# For NodeBuilder objects, find the best compatible output socket
|
|
446
|
+
if hasattr(value, "node") and hasattr(value, "_default_output_socket"):
|
|
447
|
+
compatible_socket = self._find_compatible_output_socket(value)
|
|
448
|
+
# Create a SocketLinker to represent the specific socket we want to link from
|
|
449
|
+
# from . import SocketLinker
|
|
450
|
+
socket_linker = SocketLinker(compatible_socket)
|
|
451
|
+
type = self._socket_type_from_linkable(value)
|
|
452
|
+
socket = self._add_socket(name=key, type=type)
|
|
453
|
+
new_sockets[socket.name] = socket_linker
|
|
454
|
+
else:
|
|
455
|
+
type = self._socket_type_from_linkable(value)
|
|
456
|
+
socket = self._add_socket(name=key, type=type)
|
|
457
|
+
new_sockets[socket.name] = value
|
|
458
|
+
|
|
459
|
+
return new_sockets
|
|
460
|
+
|
|
461
|
+
def _add_socket(
|
|
462
|
+
self, name: str, type: str, default_value: Any | None = None
|
|
463
|
+
) -> NodeSocket:
|
|
464
|
+
raise NotImplementedError
|
|
465
|
+
|
|
466
|
+
def _find_or_create_compatible_output_socket(
|
|
467
|
+
self, target_type: str
|
|
468
|
+
) -> NodeSocket | None:
|
|
469
|
+
"""Find an existing compatible output socket or create a new one if this node supports it.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
target_type: The socket type needed for compatibility
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
Compatible output socket if found/created, None if not possible
|
|
476
|
+
"""
|
|
477
|
+
if not hasattr(self, "_add_socket"):
|
|
478
|
+
return None
|
|
479
|
+
|
|
480
|
+
# Check if we already have a compatible output socket
|
|
481
|
+
if hasattr(self, "outputs"):
|
|
482
|
+
for name, socket_linker in self.outputs.items():
|
|
483
|
+
socket_compatibles = SOCKET_COMPATIBILITY.get(
|
|
484
|
+
socket_linker.socket.type, []
|
|
485
|
+
)
|
|
486
|
+
if target_type in socket_compatibles:
|
|
487
|
+
return socket_linker.socket
|
|
488
|
+
|
|
489
|
+
# No existing compatible socket found, try to create one
|
|
490
|
+
try:
|
|
491
|
+
# Check if this node type supports the target socket type
|
|
492
|
+
# by examining the type signature of _add_socket
|
|
493
|
+
import inspect
|
|
494
|
+
|
|
495
|
+
sig = inspect.signature(self._add_socket)
|
|
496
|
+
type_param = sig.parameters.get("type")
|
|
497
|
+
|
|
498
|
+
# If there's a type annotation that limits the allowed types, check it
|
|
499
|
+
if type_param and hasattr(type_param.annotation, "__args__"):
|
|
500
|
+
# This is a Literal type with specific allowed values
|
|
501
|
+
allowed_types = list(type_param.annotation.__args__)
|
|
502
|
+
if target_type not in allowed_types:
|
|
503
|
+
# Try to find a compatible type that this node can create
|
|
504
|
+
for allowed_type in allowed_types:
|
|
505
|
+
if target_type in SOCKET_COMPATIBILITY.get(allowed_type, []):
|
|
506
|
+
# Create the allowed type instead
|
|
507
|
+
self._add_socket(
|
|
508
|
+
name=allowed_type.title(), type=allowed_type
|
|
509
|
+
)
|
|
510
|
+
break
|
|
511
|
+
else:
|
|
512
|
+
# No compatible type found
|
|
513
|
+
return None
|
|
514
|
+
else:
|
|
515
|
+
# Target type is directly supported
|
|
516
|
+
self._add_socket(name=target_type.title(), type=target_type)
|
|
517
|
+
else:
|
|
518
|
+
# No type restrictions, try to create the target type
|
|
519
|
+
self._add_socket(name=target_type.title(), type=target_type)
|
|
520
|
+
|
|
521
|
+
# Find the newly created output socket
|
|
522
|
+
if hasattr(self, "outputs"):
|
|
523
|
+
for name, socket_linker in self.outputs.items():
|
|
524
|
+
socket_compatibles = SOCKET_COMPATIBILITY.get(
|
|
525
|
+
socket_linker.socket.type, []
|
|
526
|
+
)
|
|
527
|
+
if target_type in socket_compatibles:
|
|
528
|
+
return socket_linker.socket
|
|
529
|
+
|
|
530
|
+
# Fallback: try to get the socket directly from the node
|
|
531
|
+
if hasattr(self.node, "outputs"):
|
|
532
|
+
for output_socket in self.node.outputs:
|
|
533
|
+
socket_compatibles = SOCKET_COMPATIBILITY.get(
|
|
534
|
+
output_socket.type, []
|
|
535
|
+
)
|
|
536
|
+
if target_type in socket_compatibles:
|
|
537
|
+
return output_socket
|
|
538
|
+
except (NotImplementedError, AttributeError, RuntimeError):
|
|
539
|
+
# Node doesn't support dynamic socket creation or the type is not supported
|
|
540
|
+
pass
|
|
541
|
+
|
|
542
|
+
return None
|
|
543
|
+
|
|
544
|
+
def _smart_link_to(self, target_node: "NodeBuilder") -> "NodeBuilder":
|
|
545
|
+
"""Smart linking that creates compatible sockets when needed.
|
|
546
|
+
|
|
547
|
+
This method checks if we have a compatible output socket for the target node's input,
|
|
548
|
+
and creates one if this node supports dynamic socket creation.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
target_node: The node to link to
|
|
552
|
+
|
|
553
|
+
Returns:
|
|
554
|
+
The target node (for chaining)
|
|
555
|
+
"""
|
|
556
|
+
if not hasattr(target_node, "_default_input_socket"):
|
|
557
|
+
# Fall back to regular linking
|
|
558
|
+
return target_node
|
|
559
|
+
|
|
560
|
+
target_socket = target_node._default_input_socket
|
|
561
|
+
if not target_socket:
|
|
562
|
+
# Target has no input socket - can't link
|
|
563
|
+
return target_node
|
|
564
|
+
|
|
565
|
+
# Check if our default output is compatible
|
|
566
|
+
source_socket = self._default_output_socket
|
|
567
|
+
if source_socket:
|
|
568
|
+
source_compatibles = SOCKET_COMPATIBILITY.get(source_socket.type, [])
|
|
569
|
+
if target_socket.type in source_compatibles:
|
|
570
|
+
# Compatible - use normal linking
|
|
571
|
+
self.tree.link(source_socket, target_socket)
|
|
572
|
+
return target_node
|
|
573
|
+
|
|
574
|
+
# Not compatible - try to find/create a compatible output socket
|
|
575
|
+
compatible_socket = self._find_or_create_compatible_output_socket(
|
|
576
|
+
target_socket.type
|
|
577
|
+
)
|
|
578
|
+
if compatible_socket:
|
|
579
|
+
self.tree.link(compatible_socket, target_socket)
|
|
580
|
+
return target_node
|
|
581
|
+
|
|
582
|
+
# Fall back to regular linking (may create reroute nodes)
|
|
583
|
+
if source_socket:
|
|
584
|
+
self.tree.link(source_socket, target_socket)
|
|
585
|
+
return target_node
|
|
280
586
|
|
|
281
587
|
def _input_idx(self, identifier: str) -> int:
|
|
282
588
|
# currently there is a Blender bug that is preventing the lookup of sockets from identifiers on some
|
|
@@ -312,22 +618,120 @@ class NodeBuilder:
|
|
|
312
618
|
"""Output socket: Vector"""
|
|
313
619
|
return SocketLinker(self.node.outputs[self._output_idx(identifier)])
|
|
314
620
|
|
|
315
|
-
def
|
|
316
|
-
self
|
|
621
|
+
def _link(
|
|
622
|
+
self, source: LINKABLE | SocketLinker | NodeSocket, target: LINKABLE
|
|
623
|
+
) -> bpy.types.NodeLink:
|
|
624
|
+
return self.tree.link(self._source_socket(source), self._target_socket(target))
|
|
317
625
|
|
|
318
|
-
def
|
|
319
|
-
self.tree.link(self._default_output_socket,
|
|
626
|
+
def _link_to(self, target: LINKABLE) -> bpy.types.NodeLink:
|
|
627
|
+
return self.tree.link(self._default_output_socket, self._target_socket(target))
|
|
628
|
+
|
|
629
|
+
def _link_from(
|
|
630
|
+
self,
|
|
631
|
+
source: LINKABLE,
|
|
632
|
+
input: LINKABLE | str,
|
|
633
|
+
):
|
|
634
|
+
# Special handling for dynamic socket nodes (zones, bake, capture attribute, etc.)
|
|
635
|
+
# These nodes have an 'outputs' property that returns a dict based on their items
|
|
636
|
+
if (
|
|
637
|
+
hasattr(source, "_add_socket")
|
|
638
|
+
and hasattr(source, "_smart_link_to")
|
|
639
|
+
and hasattr(source.__class__, "outputs")
|
|
640
|
+
and isinstance(getattr(source.__class__, "outputs"), property)
|
|
641
|
+
and not isinstance(input, str)
|
|
642
|
+
):
|
|
643
|
+
# Use smart linking that can create compatible sockets
|
|
644
|
+
return source._smart_link_to(input)
|
|
320
645
|
|
|
321
|
-
def link_from(self, source: LINKABLE, input: "LINKABLE | str"):
|
|
322
646
|
if isinstance(input, str):
|
|
323
647
|
try:
|
|
324
|
-
self.
|
|
648
|
+
self._link(source, self.node.inputs[input])
|
|
325
649
|
except KeyError:
|
|
326
|
-
self.
|
|
650
|
+
self._link(source, self.node.inputs[self._input_idx(input)])
|
|
327
651
|
else:
|
|
328
|
-
self.
|
|
652
|
+
self._link(source, input)
|
|
329
653
|
|
|
330
|
-
def
|
|
654
|
+
def _smart_link_from(
|
|
655
|
+
self,
|
|
656
|
+
source: LINKABLE,
|
|
657
|
+
input_name: str,
|
|
658
|
+
):
|
|
659
|
+
"""Smart linking that finds compatible sockets when default fails."""
|
|
660
|
+
# Get the target input socket
|
|
661
|
+
try:
|
|
662
|
+
target_socket = self.node.inputs[input_name]
|
|
663
|
+
except KeyError:
|
|
664
|
+
target_socket = self.node.inputs[self._input_idx(input_name)]
|
|
665
|
+
|
|
666
|
+
# If source is a NodeBuilder, find the best compatible output socket
|
|
667
|
+
if isinstance(source, NodeBuilder):
|
|
668
|
+
# Search for compatible output sockets - don't try default first as it might be wrong type
|
|
669
|
+
try:
|
|
670
|
+
compatible_output = self._find_compatible_source_socket(
|
|
671
|
+
source, target_socket
|
|
672
|
+
)
|
|
673
|
+
self._link(compatible_output, target_socket)
|
|
674
|
+
return
|
|
675
|
+
except ValueError:
|
|
676
|
+
# No compatible socket found - this is an error, don't fall back
|
|
677
|
+
raise ValueError(
|
|
678
|
+
f"Cannot link {source.node.name} to {self.node.name}.{input_name}: "
|
|
679
|
+
f"No compatible sockets. Available output types: {[s.type for s in source.node.outputs]}, "
|
|
680
|
+
f"Target socket type: {target_socket.type}, "
|
|
681
|
+
f"Compatible types: {SOCKET_COMPATIBILITY.get(target_socket.type, ())}"
|
|
682
|
+
)
|
|
683
|
+
else:
|
|
684
|
+
# For other types, use original link_from behavior
|
|
685
|
+
self._link_from(source, input_name)
|
|
686
|
+
|
|
687
|
+
def _set_input_default_value(self, input, value):
|
|
688
|
+
"""Set the default value for an input socket, handling type conversions."""
|
|
689
|
+
if (
|
|
690
|
+
hasattr(input, "type")
|
|
691
|
+
and input.type == "VECTOR"
|
|
692
|
+
and isinstance(value, (int, float))
|
|
693
|
+
):
|
|
694
|
+
input.default_value = [value] * len(input.default_value)
|
|
695
|
+
else:
|
|
696
|
+
input.default_value = value
|
|
697
|
+
|
|
698
|
+
def _find_best_compatible_socket(
|
|
699
|
+
self, target_node: "NodeBuilder", output_socket: NodeSocket
|
|
700
|
+
) -> NodeSocket:
|
|
701
|
+
"""Find the best compatible input socket on target node for the given output socket."""
|
|
702
|
+
output_type = output_socket.type
|
|
703
|
+
compatible_types = SOCKET_COMPATIBILITY.get(output_type, ())
|
|
704
|
+
|
|
705
|
+
# Collect all compatible input sockets with their priority
|
|
706
|
+
compatible_inputs = []
|
|
707
|
+
for input_socket in target_node.node.inputs:
|
|
708
|
+
# Skip if socket already has a link and isn't multi-input
|
|
709
|
+
if input_socket.links and not input_socket.is_multi_input:
|
|
710
|
+
continue
|
|
711
|
+
|
|
712
|
+
if input_socket.type in compatible_types:
|
|
713
|
+
# Priority is the index in the compatibility list (0 = highest priority)
|
|
714
|
+
priority = compatible_types.index(input_socket.type)
|
|
715
|
+
compatible_inputs.append((priority, input_socket))
|
|
716
|
+
|
|
717
|
+
if not compatible_inputs:
|
|
718
|
+
# No compatible socket found
|
|
719
|
+
available_types = [
|
|
720
|
+
socket.type
|
|
721
|
+
for socket in target_node.node.inputs
|
|
722
|
+
if not socket.links or socket.is_multi_input
|
|
723
|
+
]
|
|
724
|
+
raise RuntimeError(
|
|
725
|
+
f"Cannot link {output_type} output to {target_node.node.name}. "
|
|
726
|
+
f"Compatible types: {compatible_types}, "
|
|
727
|
+
f"Available input types: {available_types}"
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
# Sort by priority (lowest number = highest priority) and return the best match
|
|
731
|
+
compatible_inputs.sort(key=lambda x: x[0])
|
|
732
|
+
return compatible_inputs[0][1]
|
|
733
|
+
|
|
734
|
+
def _establish_links(self, **kwargs: TYPE_INPUT_ALL):
|
|
331
735
|
input_ids = [input.identifier for input in self.node.inputs]
|
|
332
736
|
for name, value in kwargs.items():
|
|
333
737
|
if value is None:
|
|
@@ -337,21 +741,23 @@ class NodeBuilder:
|
|
|
337
741
|
# Ellipsis indicates this input should receive links from >> operator
|
|
338
742
|
# which can potentially target multiple inputs on the new node
|
|
339
743
|
if self._from_socket is not None:
|
|
340
|
-
self.
|
|
744
|
+
self._link(
|
|
341
745
|
self._from_socket, self.node.inputs[self._input_idx(name)]
|
|
342
746
|
)
|
|
343
747
|
|
|
748
|
+
elif isinstance(value, SocketLinker):
|
|
749
|
+
self._link(value, self.node.inputs[self._input_idx(name)])
|
|
344
750
|
# we can also provide just a default value for the socket to take if we aren't
|
|
345
751
|
# providing a socket to link with
|
|
346
|
-
elif isinstance(value, (NodeBuilder,
|
|
347
|
-
self.
|
|
752
|
+
elif isinstance(value, (NodeBuilder, NodeSocket, Node)):
|
|
753
|
+
self._smart_link_from(value, name)
|
|
348
754
|
else:
|
|
349
755
|
if name in input_ids:
|
|
350
756
|
input = self.node.inputs[input_ids.index(name)]
|
|
351
|
-
input
|
|
757
|
+
self._set_input_default_value(input, value)
|
|
352
758
|
else:
|
|
353
759
|
input = self.node.inputs[name.replace("_", "").capitalize()]
|
|
354
|
-
input
|
|
760
|
+
self._set_input_default_value(input, value)
|
|
355
761
|
|
|
356
762
|
def __rshift__(self, other: "NodeBuilder | SocketLinker") -> "NodeBuilder":
|
|
357
763
|
"""Chain nodes using >> operator. Links output to input.
|
|
@@ -361,32 +767,34 @@ class NodeBuilder:
|
|
|
361
767
|
tree.inputs.value >> Math.add(..., 0.1) >> tree.outputs.result
|
|
362
768
|
|
|
363
769
|
If the target node has an ellipsis placeholder (...), links to that specific input.
|
|
364
|
-
Otherwise,
|
|
770
|
+
Otherwise, finds the best compatible socket pair based on type compatibility.
|
|
365
771
|
|
|
366
772
|
Returns the right-hand node to enable continued chaining.
|
|
367
773
|
"""
|
|
368
|
-
# Get source socket - prefer Geometry, fall back to default
|
|
369
|
-
socket_out = self.node.outputs.get("Geometry") or self._default_output_socket
|
|
370
|
-
other._from_socket = socket_out
|
|
371
|
-
|
|
372
774
|
if isinstance(other, SocketLinker):
|
|
775
|
+
# Direct socket linking - use default output
|
|
776
|
+
socket_out = self._default_output_socket
|
|
373
777
|
socket_in = other.socket
|
|
778
|
+
other._from_socket = socket_out
|
|
374
779
|
else:
|
|
375
|
-
#
|
|
780
|
+
# Standard NodeBuilder linking - need to find compatible sockets
|
|
376
781
|
if other._link_target is not None:
|
|
377
|
-
#
|
|
782
|
+
# Target socket is specified
|
|
378
783
|
socket_in = self._get_input_socket_by_name(other, other._link_target)
|
|
379
|
-
|
|
380
|
-
# Default behavior - prefer Geometry, fall back to default
|
|
381
|
-
socket_in = (
|
|
382
|
-
other.node.inputs.get("Geometry") or other._default_input_socket
|
|
383
|
-
)
|
|
784
|
+
socket_out = self._default_output_socket
|
|
384
785
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
786
|
+
# Try to find a better source socket if default doesn't work
|
|
787
|
+
try:
|
|
788
|
+
socket_out = self._find_compatible_source_socket(self, socket_in)
|
|
789
|
+
except ValueError:
|
|
790
|
+
# If no compatible socket found, use default and let the link fail with a clear error
|
|
791
|
+
pass
|
|
792
|
+
|
|
793
|
+
other._from_socket = socket_out
|
|
794
|
+
else:
|
|
795
|
+
# No target specified - find best compatible socket pair
|
|
796
|
+
socket_out, socket_in = self._find_best_socket_pair(other)
|
|
797
|
+
other._from_socket = socket_out
|
|
390
798
|
|
|
391
799
|
self.tree.link(socket_out, socket_in)
|
|
392
800
|
return other
|
|
@@ -396,116 +804,92 @@ class NodeBuilder:
|
|
|
396
804
|
try:
|
|
397
805
|
return node.node.inputs[name]
|
|
398
806
|
except KeyError:
|
|
399
|
-
# Try with title case if direct access fails
|
|
400
807
|
title_name = name.replace("_", " ").title()
|
|
401
808
|
return node.node.inputs[title_name]
|
|
402
809
|
|
|
403
|
-
def
|
|
404
|
-
self,
|
|
405
|
-
) ->
|
|
406
|
-
"""
|
|
407
|
-
|
|
408
|
-
inputs = socket.node.inputs
|
|
409
|
-
current_idx = inputs.find(socket.identifier)
|
|
410
|
-
if current_idx >= 0 and current_idx + 1 < len(inputs):
|
|
411
|
-
if socket_out.type == "GEOMETRY":
|
|
412
|
-
# Prefer Geometry sockets
|
|
413
|
-
for idx in range(current_idx + 1, len(inputs)):
|
|
414
|
-
if inputs[idx].type == "GEOMETRY" and not inputs[idx].links:
|
|
415
|
-
return inputs[idx]
|
|
416
|
-
raise RuntimeError("No available Geometry input sockets found.")
|
|
417
|
-
return inputs[current_idx + 1]
|
|
418
|
-
except (KeyError, IndexError, AttributeError):
|
|
419
|
-
pass
|
|
420
|
-
return None
|
|
810
|
+
def _apply_math_operation(
|
|
811
|
+
self, other: Any, operation: str, reverse: bool = False
|
|
812
|
+
) -> "VectorMath | Math":
|
|
813
|
+
"""Apply a math operation with appropriate Math/VectorMath node."""
|
|
814
|
+
from .nodes import VectorMath
|
|
421
815
|
|
|
422
|
-
|
|
423
|
-
|
|
816
|
+
values = (
|
|
817
|
+
(self._default_output_socket, other)
|
|
818
|
+
if not reverse
|
|
819
|
+
else (other, self._default_output_socket)
|
|
820
|
+
)
|
|
424
821
|
|
|
425
|
-
|
|
426
|
-
|
|
822
|
+
# Determine if either operand is a vector type
|
|
823
|
+
self_is_vector = self._default_output_socket.type == "VECTOR"
|
|
824
|
+
other_is_vector = False
|
|
825
|
+
if isinstance(other, NodeBuilder):
|
|
826
|
+
other_is_vector = other._default_output_socket.type == "VECTOR"
|
|
827
|
+
|
|
828
|
+
# Use VectorMath if either operand is a vector
|
|
829
|
+
if self_is_vector or other_is_vector:
|
|
830
|
+
if operation == "multiply":
|
|
831
|
+
# Handle special cases for vector multiplication where we might scale instead
|
|
832
|
+
# of using the multiply method
|
|
427
833
|
if isinstance(other, (int, float)):
|
|
428
834
|
return VectorMath.scale(self._default_output_socket, other)
|
|
429
835
|
elif isinstance(other, (list, tuple)) and len(other) == 3:
|
|
430
|
-
return VectorMath.multiply(
|
|
836
|
+
return VectorMath.multiply(*values)
|
|
837
|
+
elif isinstance(other, NodeBuilder):
|
|
838
|
+
return VectorMath.multiply(*values)
|
|
431
839
|
else:
|
|
432
840
|
raise TypeError(
|
|
433
|
-
f"Unsupported type for
|
|
841
|
+
f"Unsupported type for {operation} with VECTOR socket: {type(other)}"
|
|
434
842
|
)
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
case _:
|
|
438
|
-
raise TypeError(
|
|
439
|
-
f"Unsupported socket type for multiplication: {self._default_output_socket.type}"
|
|
440
|
-
)
|
|
441
|
-
|
|
442
|
-
def __rmul__(self, other: Any) -> "VectorMath | Math":
|
|
443
|
-
from .nodes import Math, VectorMath
|
|
444
|
-
|
|
445
|
-
match self._default_output_socket.type:
|
|
446
|
-
case "VECTOR":
|
|
843
|
+
else:
|
|
844
|
+
vector_method = getattr(VectorMath, operation)
|
|
447
845
|
if isinstance(other, (int, float)):
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
846
|
+
scalar_vector = (other, other, other)
|
|
847
|
+
return (
|
|
848
|
+
vector_method(scalar_vector, self._default_output_socket)
|
|
849
|
+
if not reverse
|
|
850
|
+
else vector_method(self._default_output_socket, scalar_vector)
|
|
851
|
+
)
|
|
852
|
+
elif (
|
|
853
|
+
isinstance(other, (list, tuple)) and len(other) == 3
|
|
854
|
+
) or isinstance(other, NodeBuilder):
|
|
855
|
+
return vector_method(*values)
|
|
856
|
+
|
|
451
857
|
else:
|
|
452
858
|
raise TypeError(
|
|
453
|
-
f"Unsupported type for
|
|
859
|
+
f"Unsupported type for {operation} with VECTOR operand: {type(other)}"
|
|
454
860
|
)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
raise TypeError(
|
|
459
|
-
f"Unsupported socket type for multiplication: {self._default_output_socket.type}"
|
|
460
|
-
)
|
|
861
|
+
else:
|
|
862
|
+
# Both operands are scalar types, use regular Math
|
|
863
|
+
from .nodes.converter import IntegerMath, Math
|
|
461
864
|
|
|
462
|
-
|
|
463
|
-
|
|
865
|
+
if isinstance(other, int):
|
|
866
|
+
return getattr(IntegerMath, operation)(*values)
|
|
867
|
+
else:
|
|
868
|
+
return getattr(Math, operation)(*values)
|
|
464
869
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
return VectorMath.divide(self._default_output_socket, other)
|
|
468
|
-
case _:
|
|
469
|
-
raise TypeError(
|
|
470
|
-
f"Unsupported socket type for division: {self._default_output_socket.type}"
|
|
471
|
-
)
|
|
870
|
+
def __mul__(self, other: Any) -> "VectorMath | Math":
|
|
871
|
+
return self._apply_math_operation(other, "multiply")
|
|
472
872
|
|
|
473
|
-
def
|
|
474
|
-
|
|
873
|
+
def __rmul__(self, other: Any) -> "VectorMath | Math":
|
|
874
|
+
return self._apply_math_operation(other, "multiply", reverse=True)
|
|
475
875
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
f"Unsupported socket type for division: {self._default_output_socket.type}"
|
|
482
|
-
)
|
|
876
|
+
def __truediv__(self, other: Any) -> "VectorMath | Math":
|
|
877
|
+
return self._apply_math_operation(other, "divide")
|
|
878
|
+
|
|
879
|
+
def __rtruediv__(self, other: Any) -> "VectorMath | Math":
|
|
880
|
+
return self._apply_math_operation(other, "divide", reverse=True)
|
|
483
881
|
|
|
484
882
|
def __add__(self, other: Any) -> "VectorMath | Math":
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
match self._default_output_socket.type:
|
|
488
|
-
case "VECTOR":
|
|
489
|
-
return VectorMath.add(self._default_output_socket, other)
|
|
490
|
-
case "VALUE":
|
|
491
|
-
return Math.add(self._default_output_socket, other)
|
|
492
|
-
case _:
|
|
493
|
-
raise TypeError(
|
|
494
|
-
f"Unsupported socket type for addition: {self._default_output_socket.type}"
|
|
495
|
-
)
|
|
883
|
+
return self._apply_math_operation(other, "add")
|
|
496
884
|
|
|
497
885
|
def __radd__(self, other: Any) -> "VectorMath | Math":
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
case _:
|
|
506
|
-
raise TypeError(
|
|
507
|
-
f"Unsupported socket type for addition: {self._default_output_socket.type}"
|
|
508
|
-
)
|
|
886
|
+
return self._apply_math_operation(other, "add", reverse=True)
|
|
887
|
+
|
|
888
|
+
def __sub__(self, other: Any) -> "VectorMath | Math":
|
|
889
|
+
return self._apply_math_operation(other, "subtract")
|
|
890
|
+
|
|
891
|
+
def __rsub__(self, other: Any) -> "VectorMath | Math":
|
|
892
|
+
return self._apply_math_operation(other, "subtract", reverse=True)
|
|
509
893
|
|
|
510
894
|
|
|
511
895
|
class SocketLinker(NodeBuilder):
|
|
@@ -517,39 +901,16 @@ class SocketLinker(NodeBuilder):
|
|
|
517
901
|
self._tree = TreeBuilder(socket.node.id_data) # type: ignore
|
|
518
902
|
|
|
519
903
|
@property
|
|
520
|
-
def type(self) ->
|
|
521
|
-
return self.socket.type
|
|
904
|
+
def type(self) -> SOCKET_TYPES:
|
|
905
|
+
return self.socket.type # type: ignore
|
|
522
906
|
|
|
523
907
|
@property
|
|
524
908
|
def socket_name(self) -> str:
|
|
525
909
|
return self.socket.name
|
|
526
910
|
|
|
527
|
-
|
|
528
|
-
class SocketNodeBuilder(NodeBuilder):
|
|
529
|
-
"""Special NodeBuilder for accessing specific sockets on input/output nodes."""
|
|
530
|
-
|
|
531
|
-
def __init__(self, node: Node, socket_name: str, direction: str):
|
|
532
|
-
# Don't call super().__init__ - we already have a node
|
|
533
|
-
self.node = node
|
|
534
|
-
self._tree = TreeBuilder(node.id_data) # type: ignore
|
|
535
|
-
self._socket_name = socket_name
|
|
536
|
-
self._direction = direction
|
|
537
|
-
|
|
538
|
-
@property
|
|
539
|
-
def _default_output_socket(self) -> NodeSocket:
|
|
540
|
-
"""Return the specific named output socket."""
|
|
541
|
-
if self._direction == "INPUT":
|
|
542
|
-
return self.node.outputs[self._socket_name]
|
|
543
|
-
else:
|
|
544
|
-
raise ValueError("Output nodes don't have outputs")
|
|
545
|
-
|
|
546
911
|
@property
|
|
547
|
-
def
|
|
548
|
-
|
|
549
|
-
if self._direction == "OUTPUT":
|
|
550
|
-
return self.node.inputs[self._socket_name]
|
|
551
|
-
else:
|
|
552
|
-
raise ValueError("Input nodes don't have inputs")
|
|
912
|
+
def name(self) -> str:
|
|
913
|
+
return str(self.socket.name)
|
|
553
914
|
|
|
554
915
|
|
|
555
916
|
class SocketBase(SocketLinker):
|
|
@@ -558,11 +919,10 @@ class SocketBase(SocketLinker):
|
|
|
558
919
|
_bl_socket_type: str = ""
|
|
559
920
|
|
|
560
921
|
def __init__(self, name: str, description: str = ""):
|
|
561
|
-
self.name = name
|
|
562
922
|
self.description = description
|
|
563
923
|
|
|
564
924
|
self._socket_context: SocketContext = SocketContext._active_context
|
|
565
|
-
self.interface_socket = self._socket_context._create_socket(self)
|
|
925
|
+
self.interface_socket = self._socket_context._create_socket(self, name)
|
|
566
926
|
self._tree = self._socket_context.builder
|
|
567
927
|
if self._socket_context._direction == "INPUT":
|
|
568
928
|
socket = self.tree._input_node().outputs[self.interface_socket.identifier]
|
|
@@ -576,58 +936,78 @@ class SocketBase(SocketLinker):
|
|
|
576
936
|
continue
|
|
577
937
|
setattr(self.interface_socket, key, value)
|
|
578
938
|
|
|
939
|
+
@property
|
|
940
|
+
def default_value(self):
|
|
941
|
+
if not hasattr(self.interface_socket, "default_value"):
|
|
942
|
+
raise AttributeError(
|
|
943
|
+
f"'{self.__class__.__name__}' object has no attribute 'default_value'"
|
|
944
|
+
)
|
|
945
|
+
return self.interface_socket.default_value
|
|
579
946
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
super().__init__(name, description)
|
|
947
|
+
@default_value.setter
|
|
948
|
+
def default_value(self, value):
|
|
949
|
+
if not hasattr(self.interface_socket, "default_value"):
|
|
950
|
+
raise AttributeError(
|
|
951
|
+
f"'{self.__class__.__name__}' object has no attribute 'default_value'"
|
|
952
|
+
)
|
|
953
|
+
self.interface_socket.default_value = value
|
|
588
954
|
|
|
589
955
|
|
|
590
|
-
class
|
|
591
|
-
"""
|
|
956
|
+
class SocketFloat(SocketBase):
|
|
957
|
+
"""Float socket"""
|
|
592
958
|
|
|
593
|
-
_bl_socket_type: str = "
|
|
594
|
-
socket: bpy.types.
|
|
959
|
+
_bl_socket_type: str = "NodeSocketFloat"
|
|
960
|
+
socket: bpy.types.NodeTreeInterfaceSocketFloat
|
|
595
961
|
|
|
596
962
|
def __init__(
|
|
597
963
|
self,
|
|
598
|
-
name: str = "
|
|
599
|
-
default_value:
|
|
600
|
-
*,
|
|
964
|
+
name: str = "Value",
|
|
965
|
+
default_value: float = 0.0,
|
|
601
966
|
description: str = "",
|
|
967
|
+
*,
|
|
968
|
+
min_value: float | None = None,
|
|
969
|
+
max_value: float | None = None,
|
|
970
|
+
optional_label: bool = False,
|
|
602
971
|
hide_value: bool = False,
|
|
972
|
+
hide_in_modifier: bool = False,
|
|
973
|
+
structure_type: _SocketShapeStructureType = "AUTO",
|
|
974
|
+
subtype: FloatInterfaceSubtypes = "NONE",
|
|
603
975
|
attribute_domain: _AttributeDomains = "POINT",
|
|
604
976
|
default_attribute: str | None = None,
|
|
605
977
|
):
|
|
606
978
|
super().__init__(name, description)
|
|
607
979
|
self._set_values(
|
|
608
980
|
default_value=default_value,
|
|
981
|
+
min_value=min_value,
|
|
982
|
+
max_value=max_value,
|
|
983
|
+
optional_label=optional_label,
|
|
609
984
|
hide_value=hide_value,
|
|
985
|
+
hide_in_modifier=hide_in_modifier,
|
|
986
|
+
structure_type=structure_type,
|
|
987
|
+
subtype=subtype,
|
|
610
988
|
attribute_domain=attribute_domain,
|
|
611
989
|
default_attribute=default_attribute,
|
|
612
990
|
)
|
|
613
991
|
|
|
614
992
|
|
|
615
|
-
class
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
_bl_socket_type: str = "NodeSocketFloat"
|
|
619
|
-
socket: bpy.types.NodeTreeInterfaceSocketFloat
|
|
993
|
+
class SocketInt(SocketBase):
|
|
994
|
+
_bl_socket_type: str = "NodeSocketInt"
|
|
995
|
+
socket: bpy.types.NodeTreeInterfaceSocketInt
|
|
620
996
|
|
|
621
997
|
def __init__(
|
|
622
998
|
self,
|
|
623
|
-
name: str = "
|
|
624
|
-
default_value:
|
|
625
|
-
*,
|
|
999
|
+
name: str = "Integer",
|
|
1000
|
+
default_value: int = 0,
|
|
626
1001
|
description: str = "",
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
1002
|
+
*,
|
|
1003
|
+
min_value: int = -2147483648,
|
|
1004
|
+
max_value: int = 2147483647,
|
|
1005
|
+
optional_label: bool = False,
|
|
630
1006
|
hide_value: bool = False,
|
|
1007
|
+
hide_in_modifier: bool = False,
|
|
1008
|
+
structure_type: _SocketShapeStructureType = "AUTO",
|
|
1009
|
+
default_input: Literal["INDEX", "VALUE", "ID_OR_INDEX"] = "VALUE",
|
|
1010
|
+
subtype: IntegerInterfaceSubtypes = "NONE",
|
|
631
1011
|
attribute_domain: _AttributeDomains = "POINT",
|
|
632
1012
|
default_attribute: str | None = None,
|
|
633
1013
|
):
|
|
@@ -636,73 +1016,87 @@ class SocketFloat(SocketBase):
|
|
|
636
1016
|
default_value=default_value,
|
|
637
1017
|
min_value=min_value,
|
|
638
1018
|
max_value=max_value,
|
|
639
|
-
|
|
1019
|
+
optional_label=optional_label,
|
|
640
1020
|
hide_value=hide_value,
|
|
1021
|
+
hide_in_modifier=hide_in_modifier,
|
|
1022
|
+
structure_type=structure_type,
|
|
1023
|
+
default_input=default_input,
|
|
1024
|
+
subtype=subtype,
|
|
641
1025
|
attribute_domain=attribute_domain,
|
|
642
1026
|
default_attribute=default_attribute,
|
|
643
1027
|
)
|
|
644
1028
|
|
|
645
1029
|
|
|
646
|
-
class
|
|
647
|
-
|
|
648
|
-
|
|
1030
|
+
class SocketBoolean(SocketBase):
|
|
1031
|
+
"""Boolean socket - true/false value."""
|
|
1032
|
+
|
|
1033
|
+
_bl_socket_type: str = "NodeSocketBool"
|
|
1034
|
+
socket: bpy.types.NodeTreeInterfaceSocketBool
|
|
649
1035
|
|
|
650
1036
|
def __init__(
|
|
651
1037
|
self,
|
|
652
|
-
name: str = "
|
|
653
|
-
default_value:
|
|
654
|
-
*,
|
|
1038
|
+
name: str = "Boolean",
|
|
1039
|
+
default_value: bool = False,
|
|
655
1040
|
description: str = "",
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
max_value: float | None = None,
|
|
1041
|
+
*,
|
|
1042
|
+
optional_label: bool = False,
|
|
659
1043
|
hide_value: bool = False,
|
|
660
|
-
|
|
661
|
-
|
|
1044
|
+
hide_in_modifier: bool = False,
|
|
1045
|
+
structure_type: _SocketShapeStructureType = "AUTO",
|
|
1046
|
+
layer_selection_field: bool = False,
|
|
662
1047
|
attribute_domain: _AttributeDomains = "POINT",
|
|
1048
|
+
default_attribute: str | None = None,
|
|
663
1049
|
):
|
|
664
1050
|
super().__init__(name, description)
|
|
665
|
-
assert len(default_value) == dimensions, (
|
|
666
|
-
"Default value length must match dimensions"
|
|
667
|
-
)
|
|
668
1051
|
self._set_values(
|
|
669
|
-
dimensions=dimensions,
|
|
670
1052
|
default_value=default_value,
|
|
671
|
-
|
|
672
|
-
max_value=max_value,
|
|
1053
|
+
optional_label=optional_label,
|
|
673
1054
|
hide_value=hide_value,
|
|
674
|
-
|
|
675
|
-
|
|
1055
|
+
layer_selection_field=layer_selection_field,
|
|
1056
|
+
hide_in_modifier=hide_in_modifier,
|
|
1057
|
+
structure_type=structure_type,
|
|
676
1058
|
attribute_domain=attribute_domain,
|
|
1059
|
+
default_attribute=default_attribute,
|
|
677
1060
|
)
|
|
678
1061
|
|
|
679
1062
|
|
|
680
|
-
class
|
|
681
|
-
_bl_socket_type: str = "
|
|
682
|
-
socket: bpy.types.
|
|
1063
|
+
class SocketVector(SocketBase):
|
|
1064
|
+
_bl_socket_type: str = "NodeSocketVector"
|
|
1065
|
+
socket: bpy.types.NodeTreeInterfaceSocketVector
|
|
683
1066
|
|
|
684
1067
|
def __init__(
|
|
685
1068
|
self,
|
|
686
|
-
name: str = "
|
|
687
|
-
default_value:
|
|
688
|
-
*,
|
|
1069
|
+
name: str = "Vector",
|
|
1070
|
+
default_value: tuple[float, float, float] = (0.0, 0.0, 0.0),
|
|
689
1071
|
description: str = "",
|
|
690
|
-
|
|
691
|
-
|
|
1072
|
+
*,
|
|
1073
|
+
dimensions: int = 3,
|
|
1074
|
+
min_value: float | None = None,
|
|
1075
|
+
max_value: float | None = None,
|
|
1076
|
+
optional_label: bool = False,
|
|
692
1077
|
hide_value: bool = False,
|
|
693
|
-
|
|
694
|
-
|
|
1078
|
+
hide_in_modifier: bool = False,
|
|
1079
|
+
structure_type: _SocketShapeStructureType = "AUTO",
|
|
1080
|
+
subtype: VectorInterfaceSubtypes = "NONE",
|
|
695
1081
|
default_attribute: str | None = None,
|
|
1082
|
+
attribute_domain: _AttributeDomains = "POINT",
|
|
696
1083
|
):
|
|
1084
|
+
assert len(default_value) == dimensions, (
|
|
1085
|
+
"Default value length must match dimensions"
|
|
1086
|
+
)
|
|
697
1087
|
super().__init__(name, description)
|
|
698
1088
|
self._set_values(
|
|
1089
|
+
dimensions=dimensions,
|
|
699
1090
|
default_value=default_value,
|
|
700
1091
|
min_value=min_value,
|
|
701
1092
|
max_value=max_value,
|
|
1093
|
+
optional_label=optional_label,
|
|
702
1094
|
hide_value=hide_value,
|
|
1095
|
+
hide_in_modifier=hide_in_modifier,
|
|
1096
|
+
structure_type=structure_type,
|
|
703
1097
|
subtype=subtype,
|
|
704
|
-
attribute_domain=attribute_domain,
|
|
705
1098
|
default_attribute=default_attribute,
|
|
1099
|
+
attribute_domain=attribute_domain,
|
|
706
1100
|
)
|
|
707
1101
|
|
|
708
1102
|
|
|
@@ -716,17 +1110,23 @@ class SocketColor(SocketBase):
|
|
|
716
1110
|
self,
|
|
717
1111
|
name: str = "Color",
|
|
718
1112
|
default_value: tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0),
|
|
719
|
-
*,
|
|
720
1113
|
description: str = "",
|
|
1114
|
+
*,
|
|
1115
|
+
optional_label: bool = False,
|
|
721
1116
|
hide_value: bool = False,
|
|
1117
|
+
hide_in_modifier: bool = False,
|
|
1118
|
+
structure_type: _SocketShapeStructureType = "AUTO",
|
|
722
1119
|
attribute_domain: _AttributeDomains = "POINT",
|
|
723
1120
|
default_attribute: str | None = None,
|
|
724
1121
|
):
|
|
725
|
-
super().__init__(name, description)
|
|
726
1122
|
assert len(default_value) == 4, "Default color must be RGBA tuple"
|
|
1123
|
+
super().__init__(name, description)
|
|
727
1124
|
self._set_values(
|
|
728
1125
|
default_value=default_value,
|
|
1126
|
+
optional_label=optional_label,
|
|
729
1127
|
hide_value=hide_value,
|
|
1128
|
+
hide_in_modifier=hide_in_modifier,
|
|
1129
|
+
structure_type=structure_type,
|
|
730
1130
|
attribute_domain=attribute_domain,
|
|
731
1131
|
default_attribute=default_attribute,
|
|
732
1132
|
)
|
|
@@ -742,17 +1142,22 @@ class SocketRotation(SocketBase):
|
|
|
742
1142
|
self,
|
|
743
1143
|
name: str = "Rotation",
|
|
744
1144
|
default_value: tuple[float, float, float] = (1.0, 0.0, 0.0),
|
|
745
|
-
*,
|
|
746
1145
|
description: str = "",
|
|
1146
|
+
*,
|
|
1147
|
+
optional_label: bool = False,
|
|
747
1148
|
hide_value: bool = False,
|
|
1149
|
+
hide_in_modifier: bool = False,
|
|
1150
|
+
structure_type: _SocketShapeStructureType = "AUTO",
|
|
748
1151
|
attribute_domain: _AttributeDomains = "POINT",
|
|
749
1152
|
default_attribute: str | None = None,
|
|
750
1153
|
):
|
|
751
1154
|
super().__init__(name, description)
|
|
752
|
-
assert len(default_value) == 4, "Default rotation must be quaternion tuple"
|
|
753
1155
|
self._set_values(
|
|
754
1156
|
default_value=default_value,
|
|
1157
|
+
optional_label=optional_label,
|
|
755
1158
|
hide_value=hide_value,
|
|
1159
|
+
hide_in_modifier=hide_in_modifier,
|
|
1160
|
+
structure_type=structure_type,
|
|
756
1161
|
attribute_domain=attribute_domain,
|
|
757
1162
|
default_attribute=default_attribute,
|
|
758
1163
|
)
|
|
@@ -767,15 +1172,23 @@ class SocketMatrix(SocketBase):
|
|
|
767
1172
|
def __init__(
|
|
768
1173
|
self,
|
|
769
1174
|
name: str = "Matrix",
|
|
770
|
-
*,
|
|
771
1175
|
description: str = "",
|
|
1176
|
+
*,
|
|
1177
|
+
optional_label: bool = False,
|
|
772
1178
|
hide_value: bool = False,
|
|
1179
|
+
hide_in_modifier: bool = False,
|
|
1180
|
+
structure_type: _SocketShapeStructureType = "AUTO",
|
|
1181
|
+
default_input: Literal["VALUE", "INSTANCE_TRANSFORM"] = "VALUE",
|
|
773
1182
|
attribute_domain: _AttributeDomains = "POINT",
|
|
774
1183
|
default_attribute: str | None = None,
|
|
775
1184
|
):
|
|
776
1185
|
super().__init__(name, description)
|
|
777
1186
|
self._set_values(
|
|
1187
|
+
optional_label=optional_label,
|
|
778
1188
|
hide_value=hide_value,
|
|
1189
|
+
hide_in_modifier=hide_in_modifier,
|
|
1190
|
+
structure_type=structure_type,
|
|
1191
|
+
default_input=default_input,
|
|
779
1192
|
attribute_domain=attribute_domain,
|
|
780
1193
|
default_attribute=default_attribute,
|
|
781
1194
|
)
|
|
@@ -789,20 +1202,24 @@ class SocketString(SocketBase):
|
|
|
789
1202
|
self,
|
|
790
1203
|
name: str = "String",
|
|
791
1204
|
default_value: str = "",
|
|
792
|
-
*,
|
|
793
1205
|
description: str = "",
|
|
1206
|
+
*,
|
|
1207
|
+
optional_label: bool = False,
|
|
794
1208
|
hide_value: bool = False,
|
|
1209
|
+
hide_in_modifier: bool = False,
|
|
795
1210
|
subtype: StringInterfaceSubtypes = "NONE",
|
|
796
1211
|
):
|
|
797
1212
|
super().__init__(name, description)
|
|
798
1213
|
self._set_values(
|
|
799
1214
|
default_value=default_value,
|
|
1215
|
+
optional_label=optional_label,
|
|
800
1216
|
hide_value=hide_value,
|
|
1217
|
+
hide_in_modifier=hide_in_modifier,
|
|
801
1218
|
subtype=subtype,
|
|
802
1219
|
)
|
|
803
1220
|
|
|
804
1221
|
|
|
805
|
-
class
|
|
1222
|
+
class SocketMenu(SocketBase):
|
|
806
1223
|
"""Menu socket - holds a selection from predefined items."""
|
|
807
1224
|
|
|
808
1225
|
_bl_socket_type: str = "NodeSocketMenu"
|
|
@@ -812,16 +1229,22 @@ class MenuSocket(SocketBase):
|
|
|
812
1229
|
self,
|
|
813
1230
|
name: str = "Menu",
|
|
814
1231
|
default_value: str | None = None,
|
|
815
|
-
*,
|
|
816
1232
|
description: str = "",
|
|
1233
|
+
*,
|
|
817
1234
|
expanded: bool = False,
|
|
1235
|
+
optional_label: bool = False,
|
|
818
1236
|
hide_value: bool = False,
|
|
1237
|
+
hide_in_modifier: bool = False,
|
|
1238
|
+
structure_type: _SocketShapeStructureType = "AUTO",
|
|
819
1239
|
):
|
|
820
1240
|
super().__init__(name, description)
|
|
821
1241
|
self._set_values(
|
|
822
1242
|
default_value=default_value,
|
|
823
1243
|
menu_expanded=expanded,
|
|
1244
|
+
optional_label=optional_label,
|
|
824
1245
|
hide_value=hide_value,
|
|
1246
|
+
hide_in_modifier=hide_in_modifier,
|
|
1247
|
+
structure_type=structure_type,
|
|
825
1248
|
)
|
|
826
1249
|
|
|
827
1250
|
|
|
@@ -835,14 +1258,41 @@ class SocketObject(SocketBase):
|
|
|
835
1258
|
self,
|
|
836
1259
|
name: str = "Object",
|
|
837
1260
|
default_value: bpy.types.Object | None = None,
|
|
838
|
-
*,
|
|
839
1261
|
description: str = "",
|
|
1262
|
+
*,
|
|
1263
|
+
optional_label: bool = False,
|
|
840
1264
|
hide_value: bool = False,
|
|
1265
|
+
hide_in_modifier: bool = False,
|
|
841
1266
|
):
|
|
842
1267
|
super().__init__(name, description)
|
|
843
1268
|
self._set_values(
|
|
844
1269
|
default_value=default_value,
|
|
1270
|
+
optional_label=optional_label,
|
|
845
1271
|
hide_value=hide_value,
|
|
1272
|
+
hide_in_modifier=hide_in_modifier,
|
|
1273
|
+
)
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
class SocketGeometry(SocketBase):
|
|
1277
|
+
"""Geometry socket - holds mesh, curve, point cloud, or volume data."""
|
|
1278
|
+
|
|
1279
|
+
_bl_socket_type: str = "NodeSocketGeometry"
|
|
1280
|
+
socket: bpy.types.NodeTreeInterfaceSocketGeometry
|
|
1281
|
+
|
|
1282
|
+
def __init__(
|
|
1283
|
+
self,
|
|
1284
|
+
name: str = "Geometry",
|
|
1285
|
+
description: str = "",
|
|
1286
|
+
*,
|
|
1287
|
+
optional_label: bool = False,
|
|
1288
|
+
hide_value: bool = False,
|
|
1289
|
+
hide_in_modifier: bool = False,
|
|
1290
|
+
):
|
|
1291
|
+
super().__init__(name, description)
|
|
1292
|
+
self._set_values(
|
|
1293
|
+
optional_label=optional_label,
|
|
1294
|
+
hide_value=hide_value,
|
|
1295
|
+
hide_in_modifier=hide_in_modifier,
|
|
846
1296
|
)
|
|
847
1297
|
|
|
848
1298
|
|
|
@@ -856,14 +1306,18 @@ class SocketCollection(SocketBase):
|
|
|
856
1306
|
self,
|
|
857
1307
|
name: str = "Collection",
|
|
858
1308
|
default_value: bpy.types.Collection | None = None,
|
|
859
|
-
*,
|
|
860
1309
|
description: str = "",
|
|
1310
|
+
*,
|
|
1311
|
+
optional_label: bool = False,
|
|
861
1312
|
hide_value: bool = False,
|
|
1313
|
+
hide_in_modifier: bool = False,
|
|
862
1314
|
):
|
|
863
1315
|
super().__init__(name, description)
|
|
864
1316
|
self._set_values(
|
|
865
1317
|
default_value=default_value,
|
|
1318
|
+
optional_label=optional_label,
|
|
866
1319
|
hide_value=hide_value,
|
|
1320
|
+
hide_in_modifier=hide_in_modifier,
|
|
867
1321
|
)
|
|
868
1322
|
|
|
869
1323
|
|
|
@@ -877,14 +1331,18 @@ class SocketImage(SocketBase):
|
|
|
877
1331
|
self,
|
|
878
1332
|
name: str = "Image",
|
|
879
1333
|
default_value: bpy.types.Image | None = None,
|
|
880
|
-
*,
|
|
881
1334
|
description: str = "",
|
|
1335
|
+
*,
|
|
1336
|
+
optional_label: bool = False,
|
|
882
1337
|
hide_value: bool = False,
|
|
1338
|
+
hide_in_modifier: bool = False,
|
|
883
1339
|
):
|
|
884
1340
|
super().__init__(name, description)
|
|
885
1341
|
self._set_values(
|
|
886
1342
|
default_value=default_value,
|
|
1343
|
+
optional_label=optional_label,
|
|
887
1344
|
hide_value=hide_value,
|
|
1345
|
+
hide_in_modifier=hide_in_modifier,
|
|
888
1346
|
)
|
|
889
1347
|
|
|
890
1348
|
|
|
@@ -898,14 +1356,18 @@ class SocketMaterial(SocketBase):
|
|
|
898
1356
|
self,
|
|
899
1357
|
name: str = "Material",
|
|
900
1358
|
default_value: bpy.types.Material | None = None,
|
|
901
|
-
*,
|
|
902
1359
|
description: str = "",
|
|
1360
|
+
*,
|
|
1361
|
+
optional_label: bool = False,
|
|
903
1362
|
hide_value: bool = False,
|
|
1363
|
+
hide_in_modifier: bool = False,
|
|
904
1364
|
):
|
|
905
1365
|
super().__init__(name, description)
|
|
906
1366
|
self._set_values(
|
|
907
1367
|
default_value=default_value,
|
|
1368
|
+
optional_label=optional_label,
|
|
908
1369
|
hide_value=hide_value,
|
|
1370
|
+
hide_in_modifier=hide_in_modifier,
|
|
909
1371
|
)
|
|
910
1372
|
|
|
911
1373
|
|
|
@@ -918,13 +1380,17 @@ class SocketBundle(SocketBase):
|
|
|
918
1380
|
def __init__(
|
|
919
1381
|
self,
|
|
920
1382
|
name: str = "Bundle",
|
|
921
|
-
*,
|
|
922
1383
|
description: str = "",
|
|
1384
|
+
*,
|
|
1385
|
+
optional_label: bool = False,
|
|
923
1386
|
hide_value: bool = False,
|
|
1387
|
+
hide_in_modifier: bool = False,
|
|
924
1388
|
):
|
|
925
1389
|
super().__init__(name, description)
|
|
926
1390
|
self._set_values(
|
|
1391
|
+
optional_label=optional_label,
|
|
927
1392
|
hide_value=hide_value,
|
|
1393
|
+
hide_in_modifier=hide_in_modifier,
|
|
928
1394
|
)
|
|
929
1395
|
|
|
930
1396
|
|
|
@@ -937,11 +1403,15 @@ class SocketClosure(SocketBase):
|
|
|
937
1403
|
def __init__(
|
|
938
1404
|
self,
|
|
939
1405
|
name: str = "Closure",
|
|
940
|
-
*,
|
|
941
1406
|
description: str = "",
|
|
1407
|
+
*,
|
|
1408
|
+
optional_label: bool = False,
|
|
942
1409
|
hide_value: bool = False,
|
|
1410
|
+
hide_in_modifier: bool = False,
|
|
943
1411
|
):
|
|
944
1412
|
super().__init__(name, description)
|
|
945
1413
|
self._set_values(
|
|
1414
|
+
optional_label=optional_label,
|
|
946
1415
|
hide_value=hide_value,
|
|
1416
|
+
hide_in_modifier=hide_in_modifier,
|
|
947
1417
|
)
|