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