nodeeditor-qtpy 0.8.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.
- nodeeditor/__init__.py +93 -0
- nodeeditor/node_colors_config.py +343 -0
- nodeeditor/node_content_widget.py +129 -0
- nodeeditor/node_decorators.py +65 -0
- nodeeditor/node_edge.py +518 -0
- nodeeditor/node_edge_dragging.py +185 -0
- nodeeditor/node_edge_intersect.py +219 -0
- nodeeditor/node_edge_rerouting.py +216 -0
- nodeeditor/node_edge_snapping.py +95 -0
- nodeeditor/node_edge_validators.py +82 -0
- nodeeditor/node_editor_widget.py +257 -0
- nodeeditor/node_editor_window.py +448 -0
- nodeeditor/node_graph.py +583 -0
- nodeeditor/node_graphics_cutline.py +57 -0
- nodeeditor/node_graphics_edge.py +375 -0
- nodeeditor/node_graphics_edge_path.py +226 -0
- nodeeditor/node_graphics_node.py +693 -0
- nodeeditor/node_graphics_scene.py +369 -0
- nodeeditor/node_graphics_socket.py +262 -0
- nodeeditor/node_graphics_view.py +907 -0
- nodeeditor/node_group.py +1286 -0
- nodeeditor/node_group_node.py +5 -0
- nodeeditor/node_icon_content_widget.py +176 -0
- nodeeditor/node_icon_graphics_node.py +524 -0
- nodeeditor/node_multi_input_node.py +450 -0
- nodeeditor/node_node.py +848 -0
- nodeeditor/node_scene.py +1106 -0
- nodeeditor/node_scene_clipboard.py +314 -0
- nodeeditor/node_scene_history.py +556 -0
- nodeeditor/node_serializable.py +43 -0
- nodeeditor/node_socket.py +445 -0
- nodeeditor/node_socket_shapes.py +261 -0
- nodeeditor/py.typed +0 -0
- nodeeditor/qss/nodestyle.qss +3 -0
- nodeeditor/utils.py +108 -0
- nodeeditor/utils_no_qt.py +21 -0
- nodeeditor_qtpy-0.8.1.dist-info/METADATA +165 -0
- nodeeditor_qtpy-0.8.1.dist-info/RECORD +40 -0
- nodeeditor_qtpy-0.8.1.dist-info/WHEEL +4 -0
- nodeeditor_qtpy-0.8.1.dist-info/licenses/LICENCE +23 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
A module containing the ``MultiInputNode`` class: a :class:`~nodeeditor.node_node.Node`
|
|
4
|
+
whose input :class:`~nodeeditor.node_socket.Socket` accepts any number of
|
|
5
|
+
:class:`~nodeeditor.node_edge.Edge` (s) and reads them in an explicit,
|
|
6
|
+
persisted order.
|
|
7
|
+
"""
|
|
8
|
+
from nodeeditor.node_node import Node
|
|
9
|
+
from nodeeditor.utils_no_qt import dumpException
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from typing import TYPE_CHECKING, Any, List, Optional, Tuple
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from nodeeditor.node_edge import Edge
|
|
17
|
+
from nodeeditor.node_scene import Scene
|
|
18
|
+
from nodeeditor.node_socket import Socket
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MultiInputNode(Node):
|
|
22
|
+
"""
|
|
23
|
+
Base class for nodes reading an arbitrary number of inputs **in order**.
|
|
24
|
+
|
|
25
|
+
Subclass it, declare the sockets you need and read them with
|
|
26
|
+
:meth:`getOrderedSources` / :meth:`getOrderedValues`::
|
|
27
|
+
|
|
28
|
+
class Sum(MultiInputNode, CalcNode):
|
|
29
|
+
def evalImplementation(self):
|
|
30
|
+
return [sum(self.getOrderedValues())]
|
|
31
|
+
|
|
32
|
+
By default *every* input socket accepts multiple `Edges`
|
|
33
|
+
(:attr:`multi_input_all_inputs`). Set :attr:`multi_input_socket` and
|
|
34
|
+
:attr:`multi_input_all_inputs = False` to make only one socket do so.
|
|
35
|
+
|
|
36
|
+
The order is, by default, the order the `Edges` were connected in. It can
|
|
37
|
+
be changed at any time from code (:meth:`setEdgeOrder`, :meth:`moveEdgeTo`,
|
|
38
|
+
:meth:`swapEdges`) and is saved to / loaded from the graph file.
|
|
39
|
+
|
|
40
|
+
This class deliberately does not define ``__init__``, so it can be mixed
|
|
41
|
+
into an existing node hierarchy without changing its constructor chain.
|
|
42
|
+
|
|
43
|
+
:Instance Attributes:
|
|
44
|
+
|
|
45
|
+
- **multi_input_socket** - index of the input `Socket` which accepts
|
|
46
|
+
many `Edges` when :attr:`multi_input_all_inputs` is ``False``
|
|
47
|
+
- **multi_input_all_inputs** - ``True`` to let every input `Socket`
|
|
48
|
+
accept multiple `Edges`
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
#: index of the input socket accepting many `Edges` (see multi_input_all_inputs)
|
|
52
|
+
multi_input_socket: int = 0
|
|
53
|
+
|
|
54
|
+
#: when ``True`` every input socket of this node accepts multiple ``Edges``
|
|
55
|
+
multi_input_all_inputs: bool = True
|
|
56
|
+
|
|
57
|
+
#: when ``True`` every `Edge` on the ordered input is labeled ``#1``,
|
|
58
|
+
#: ``#2``, ... (1-based display of its read position) and the labels are
|
|
59
|
+
#: kept in sync on reorder/removal. Labels are cleared when an `Edge`
|
|
60
|
+
#: detaches, but only when they still match the ``#N`` pattern, so a
|
|
61
|
+
#: custom label you set yourself is never overwritten on disconnect.
|
|
62
|
+
show_input_order_labels: bool = True
|
|
63
|
+
|
|
64
|
+
def initSettings(self) -> None:
|
|
65
|
+
"""Initialize properties and socket information"""
|
|
66
|
+
super().initSettings()
|
|
67
|
+
if self.multi_input_all_inputs:
|
|
68
|
+
self.input_multi_edged = True
|
|
69
|
+
|
|
70
|
+
def initSockets(self, inputs: list, outputs: list,
|
|
71
|
+
input_text: list = [], output_text: list = [],
|
|
72
|
+
reset: bool = True) -> None:
|
|
73
|
+
"""Create sockets for inputs and outputs, keeping only
|
|
74
|
+
:attr:`multi_input_socket` multi-edged if :attr:`multi_input_all_inputs`
|
|
75
|
+
is ``False``"""
|
|
76
|
+
super().initSockets(inputs, outputs, input_text, output_text, reset)
|
|
77
|
+
if self.multi_input_all_inputs:
|
|
78
|
+
return
|
|
79
|
+
for index, socket in enumerate(self.inputs):
|
|
80
|
+
socket.is_multi_edges = index == self.multi_input_socket
|
|
81
|
+
|
|
82
|
+
# socket access
|
|
83
|
+
|
|
84
|
+
def getMultiSocket(self, index: Optional[int] = None) -> Optional['Socket']:
|
|
85
|
+
"""
|
|
86
|
+
Get the input :class:`~nodeeditor.node_socket.Socket` which accepts many `Edges`.
|
|
87
|
+
|
|
88
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
89
|
+
:attr:`multi_input_socket`
|
|
90
|
+
:type index: ``int`` or ``None``
|
|
91
|
+
:return: the ordered input `Socket` or ``None`` if the node has no such socket
|
|
92
|
+
:rtype: :class:`~nodeeditor.node_socket.Socket` or ``None``
|
|
93
|
+
"""
|
|
94
|
+
if index is None:
|
|
95
|
+
index = self.multi_input_socket
|
|
96
|
+
try:
|
|
97
|
+
return self.inputs[index]
|
|
98
|
+
except IndexError:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
def compactEdgeOrder(self, index: Optional[int] = None) -> bool:
|
|
102
|
+
"""
|
|
103
|
+
Renumber the input `Edges` to a gap free ``0..n-1`` sequence.
|
|
104
|
+
|
|
105
|
+
Called automatically on every connection change, so it is only needed
|
|
106
|
+
after assigning :attr:`~nodeeditor.node_edge.Edge.input_index` directly.
|
|
107
|
+
|
|
108
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
109
|
+
:attr:`multi_input_socket`
|
|
110
|
+
:type index: ``int`` or ``None``
|
|
111
|
+
:return: ``True`` if anything actually changed
|
|
112
|
+
:rtype: ``bool``
|
|
113
|
+
"""
|
|
114
|
+
socket = self.getMultiSocket(index)
|
|
115
|
+
if socket is None:
|
|
116
|
+
return False
|
|
117
|
+
return socket.compactEdgeOrder()
|
|
118
|
+
|
|
119
|
+
# reading
|
|
120
|
+
|
|
121
|
+
def getOrderedEdges(self, index: Optional[int] = None) -> List['Edge']:
|
|
122
|
+
"""
|
|
123
|
+
Get the connected `Edges` of the ordered input `Socket` in read order.
|
|
124
|
+
|
|
125
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
126
|
+
:attr:`multi_input_socket`
|
|
127
|
+
:type index: ``int`` or ``None``
|
|
128
|
+
:return: `Edges` ordered by :attr:`~nodeeditor.node_edge.Edge.input_index`
|
|
129
|
+
:rtype: List[:class:`~nodeeditor.node_edge.Edge`]
|
|
130
|
+
"""
|
|
131
|
+
socket = self.getMultiSocket(index)
|
|
132
|
+
if socket is None:
|
|
133
|
+
return []
|
|
134
|
+
return socket.orderedEdges()
|
|
135
|
+
|
|
136
|
+
def getOrderedSources(self, index: Optional[int] = None) -> List[Tuple['Node', 'Socket']]:
|
|
137
|
+
"""
|
|
138
|
+
Get the ``(node, socket)`` pairs feeding the ordered input `Socket`.
|
|
139
|
+
|
|
140
|
+
One entry per connected `Edge`, in read order, so a source connected
|
|
141
|
+
twice appears twice.
|
|
142
|
+
|
|
143
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
144
|
+
:attr:`multi_input_socket`
|
|
145
|
+
:type index: ``int`` or ``None``
|
|
146
|
+
:return: source `Node` and its `Socket` per read position
|
|
147
|
+
:rtype: List[Tuple[:class:`~nodeeditor.node_node.Node`, :class:`~nodeeditor.node_socket.Socket`]]
|
|
148
|
+
"""
|
|
149
|
+
socket = self.getMultiSocket(index)
|
|
150
|
+
if socket is None:
|
|
151
|
+
return []
|
|
152
|
+
sources = []
|
|
153
|
+
for edge in socket.orderedEdges():
|
|
154
|
+
other_socket = edge.getOtherSocket(socket)
|
|
155
|
+
sources.append(
|
|
156
|
+
(other_socket.node, other_socket) if other_socket is not None else (None, None)
|
|
157
|
+
)
|
|
158
|
+
return sources
|
|
159
|
+
|
|
160
|
+
def getOrderedValues(self, index: Optional[int] = None) -> List[Any]:
|
|
161
|
+
"""
|
|
162
|
+
Evaluate every source of the ordered input `Socket` in read order.
|
|
163
|
+
|
|
164
|
+
The returned list always has one entry per connected `Edge`; a source
|
|
165
|
+
which fails to evaluate contributes ``None`` instead of raising, so
|
|
166
|
+
``len(getOrderedValues())`` always equals the number of connections.
|
|
167
|
+
|
|
168
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
169
|
+
:attr:`multi_input_socket`
|
|
170
|
+
:type index: ``int`` or ``None``
|
|
171
|
+
:return: evaluated value per read position
|
|
172
|
+
:rtype: ``list``
|
|
173
|
+
"""
|
|
174
|
+
values: List[Any] = []
|
|
175
|
+
for node, _socket in self.getOrderedSources(index):
|
|
176
|
+
if node is None:
|
|
177
|
+
values.append(None)
|
|
178
|
+
continue
|
|
179
|
+
try:
|
|
180
|
+
values.append(node.eval())
|
|
181
|
+
except Exception as e:
|
|
182
|
+
dumpException(e)
|
|
183
|
+
values.append(None)
|
|
184
|
+
return values
|
|
185
|
+
|
|
186
|
+
def getOrderedNodes(self, index: Optional[int] = None) -> List['Node']:
|
|
187
|
+
"""
|
|
188
|
+
Get the source :class:`~nodeeditor.node_node.Node` (s) of the ordered
|
|
189
|
+
input `Socket` in read order.
|
|
190
|
+
|
|
191
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
192
|
+
:attr:`multi_input_socket`
|
|
193
|
+
:type index: ``int`` or ``None``
|
|
194
|
+
:return: source `Nodes`, one per read position
|
|
195
|
+
:rtype: List[:class:`~nodeeditor.node_node.Node`]
|
|
196
|
+
"""
|
|
197
|
+
return [node for node, _socket in self.getOrderedSources(index)]
|
|
198
|
+
|
|
199
|
+
def describeInputOrder(self, index: Optional[int] = None) -> str:
|
|
200
|
+
"""
|
|
201
|
+
Human readable one-line-per-position description of the input order.
|
|
202
|
+
|
|
203
|
+
Handy for tooltips and for logging what the node currently sees::
|
|
204
|
+
|
|
205
|
+
0: Input
|
|
206
|
+
1: Add
|
|
207
|
+
|
|
208
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
209
|
+
:attr:`multi_input_socket`
|
|
210
|
+
:type index: ``int`` or ``None``
|
|
211
|
+
:return: description of the current read order
|
|
212
|
+
:rtype: ``str``
|
|
213
|
+
"""
|
|
214
|
+
lines = []
|
|
215
|
+
for position, (node, socket) in enumerate(self.getOrderedSources(index)):
|
|
216
|
+
title = node.title if node is not None else "?"
|
|
217
|
+
suffix = "" if socket is None else " [out %d]" % socket.index
|
|
218
|
+
lines.append("%d: %s%s" % (position, title, suffix))
|
|
219
|
+
return "\n".join(lines)
|
|
220
|
+
|
|
221
|
+
# edge order labels
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def orderLabel(position: int) -> str:
|
|
225
|
+
"""
|
|
226
|
+
Display label for read position ``position`` (0-based).
|
|
227
|
+
|
|
228
|
+
:param position: 0-based read position
|
|
229
|
+
:type position: ``int``
|
|
230
|
+
:return: ``"#1"`` for position 0, ``"#2"`` for position 1, and so on
|
|
231
|
+
:rtype: ``str``
|
|
232
|
+
"""
|
|
233
|
+
return "#%d" % (position + 1)
|
|
234
|
+
|
|
235
|
+
def updateInputOrderLabels(self, index: Optional[int] = None) -> None:
|
|
236
|
+
"""
|
|
237
|
+
Write :meth:`orderLabel` onto every `Edge` of the ordered input
|
|
238
|
+
`Socket`. No-op when :attr:`show_input_order_labels` is ``False``.
|
|
239
|
+
|
|
240
|
+
Called automatically after every connection change and reorder, so
|
|
241
|
+
you only need it when toggling :attr:`show_input_order_labels` on
|
|
242
|
+
for a node that already has connections.
|
|
243
|
+
|
|
244
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
245
|
+
:attr:`multi_input_socket`
|
|
246
|
+
:type index: ``int`` or ``None``
|
|
247
|
+
"""
|
|
248
|
+
if not self.show_input_order_labels:
|
|
249
|
+
return
|
|
250
|
+
socket = self.getMultiSocket(index)
|
|
251
|
+
if socket is None:
|
|
252
|
+
return
|
|
253
|
+
for position, edge in enumerate(socket.orderedEdges()):
|
|
254
|
+
try:
|
|
255
|
+
if edge.getLabel() != self.orderLabel(position):
|
|
256
|
+
edge.setLabel(self.orderLabel(position))
|
|
257
|
+
except Exception:
|
|
258
|
+
continue
|
|
259
|
+
|
|
260
|
+
def _clearDetachedOrderLabel(self, edge: 'Edge') -> None:
|
|
261
|
+
"""Clear a ``#N`` label from an `Edge` that just detached from us.
|
|
262
|
+
|
|
263
|
+
Only labels still matching the managed pattern are cleared, so a
|
|
264
|
+
custom label set from code is never destroyed on disconnect.
|
|
265
|
+
"""
|
|
266
|
+
if not self.show_input_order_labels or edge is None:
|
|
267
|
+
return
|
|
268
|
+
try:
|
|
269
|
+
label = edge.getLabel()
|
|
270
|
+
except Exception:
|
|
271
|
+
return
|
|
272
|
+
if not label:
|
|
273
|
+
return
|
|
274
|
+
if re.fullmatch(r"#\d+", label.strip()):
|
|
275
|
+
try:
|
|
276
|
+
edge.setLabel("")
|
|
277
|
+
except Exception:
|
|
278
|
+
pass
|
|
279
|
+
|
|
280
|
+
# manual ordering
|
|
281
|
+
|
|
282
|
+
def setEdgeOrder(self, edges: List['Edge'], index: Optional[int] = None) -> bool:
|
|
283
|
+
"""
|
|
284
|
+
Set the read order of the connected `Edges` of the ordered input `Socket`.
|
|
285
|
+
|
|
286
|
+
``edges`` may be a partial list; edges missing from it are appended
|
|
287
|
+
afterwards keeping their relative order.
|
|
288
|
+
|
|
289
|
+
:param edges: `Edges` of this node's ordered input `Socket`, first read
|
|
290
|
+
position first
|
|
291
|
+
:type edges: List[:class:`~nodeeditor.node_edge.Edge`]
|
|
292
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
293
|
+
:attr:`multi_input_socket`
|
|
294
|
+
:type index: ``int`` or ``None``
|
|
295
|
+
:return: ``True`` if the order changed
|
|
296
|
+
:rtype: ``bool``
|
|
297
|
+
"""
|
|
298
|
+
socket = self.getMultiSocket(index)
|
|
299
|
+
if socket is None:
|
|
300
|
+
return False
|
|
301
|
+
if not socket.setEdgeOrder(edges):
|
|
302
|
+
return False
|
|
303
|
+
self.updateInputOrderLabels(index)
|
|
304
|
+
self.markDirty()
|
|
305
|
+
self.markDescendantsDirty()
|
|
306
|
+
return True
|
|
307
|
+
|
|
308
|
+
def setEdgeOrderByNodes(self, nodes: List['Node'], index: Optional[int] = None) -> bool:
|
|
309
|
+
"""
|
|
310
|
+
Set the read order from a list of source :class:`~nodeeditor.node_node.Node` (s).
|
|
311
|
+
|
|
312
|
+
Each node moves to the first read position that is not already taken by
|
|
313
|
+
an earlier entry, so listing the same node twice moves it to position 0
|
|
314
|
+
and pushes the rest down.
|
|
315
|
+
|
|
316
|
+
:param nodes: source `Nodes`, first read position first
|
|
317
|
+
:type nodes: List[:class:`~nodeeditor.node_node.Node`]
|
|
318
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
319
|
+
:attr:`multi_input_socket`
|
|
320
|
+
:type index: ``int`` or ``None``
|
|
321
|
+
:return: ``True`` if the order changed
|
|
322
|
+
:rtype: ``bool``
|
|
323
|
+
"""
|
|
324
|
+
socket = self.getMultiSocket(index)
|
|
325
|
+
if socket is None:
|
|
326
|
+
return False
|
|
327
|
+
|
|
328
|
+
ordered = socket.orderedEdges()
|
|
329
|
+
taken: List['Edge'] = []
|
|
330
|
+
for node in nodes or []:
|
|
331
|
+
for edge in ordered:
|
|
332
|
+
if edge in taken:
|
|
333
|
+
continue
|
|
334
|
+
other_socket = edge.getOtherSocket(socket)
|
|
335
|
+
if other_socket is not None and other_socket.node is node:
|
|
336
|
+
taken.append(edge)
|
|
337
|
+
break
|
|
338
|
+
return self.setEdgeOrder(taken, index)
|
|
339
|
+
|
|
340
|
+
def moveEdgeTo(self, edge: 'Edge', position: int, index: Optional[int] = None) -> bool:
|
|
341
|
+
"""
|
|
342
|
+
Move a connected `Edge` to read position ``position``.
|
|
343
|
+
|
|
344
|
+
:param edge: :class:`~nodeeditor.node_edge.Edge` connected to this node
|
|
345
|
+
:type edge: :class:`~nodeeditor.node_edge.Edge`
|
|
346
|
+
:param position: target read position
|
|
347
|
+
:type position: ``int``
|
|
348
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
349
|
+
:attr:`multi_input_socket`
|
|
350
|
+
:type index: ``int`` or ``None``
|
|
351
|
+
:return: ``True`` if the order changed
|
|
352
|
+
:rtype: ``bool``
|
|
353
|
+
"""
|
|
354
|
+
socket = self.getMultiSocket(index)
|
|
355
|
+
if socket is None:
|
|
356
|
+
return False
|
|
357
|
+
if not socket.moveEdgeTo(edge, position):
|
|
358
|
+
return False
|
|
359
|
+
self.updateInputOrderLabels(index)
|
|
360
|
+
self.markDirty()
|
|
361
|
+
self.markDescendantsDirty()
|
|
362
|
+
return True
|
|
363
|
+
|
|
364
|
+
def moveEdgeBy(self, edge: 'Edge', offset: int, index: Optional[int] = None) -> bool:
|
|
365
|
+
"""
|
|
366
|
+
Shift a connected `Edge` by ``offset`` read positions.
|
|
367
|
+
|
|
368
|
+
Clamped at both ends, so ``offset=-1`` on the first input is a no-op.
|
|
369
|
+
|
|
370
|
+
:param edge: :class:`~nodeeditor.node_edge.Edge` connected to this node
|
|
371
|
+
:type edge: :class:`~nodeeditor.node_edge.Edge`
|
|
372
|
+
:param offset: how many positions to move, negative moves earlier
|
|
373
|
+
:type offset: ``int``
|
|
374
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
375
|
+
:attr:`multi_input_socket`
|
|
376
|
+
:type index: ``int`` or ``None``
|
|
377
|
+
:return: ``True`` if the order changed
|
|
378
|
+
:rtype: ``bool``
|
|
379
|
+
"""
|
|
380
|
+
socket = self.getMultiSocket(index)
|
|
381
|
+
if socket is None:
|
|
382
|
+
return False
|
|
383
|
+
return self.moveEdgeTo(edge, socket.edgeIndex(edge) + offset, index)
|
|
384
|
+
|
|
385
|
+
def swapEdges(self, edge_a: 'Edge', edge_b: 'Edge', index: Optional[int] = None) -> bool:
|
|
386
|
+
"""
|
|
387
|
+
Swap the read positions of two `Edges` connected to this node.
|
|
388
|
+
|
|
389
|
+
:param edge_a: first :class:`~nodeeditor.node_edge.Edge` to swap
|
|
390
|
+
:type edge_a: :class:`~nodeeditor.node_edge.Edge`
|
|
391
|
+
:param edge_b: second :class:`~nodeeditor.node_edge.Edge` to swap
|
|
392
|
+
:type edge_b: :class:`~nodeeditor.node_edge.Edge`
|
|
393
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
394
|
+
:attr:`multi_input_socket`
|
|
395
|
+
:type index: ``int`` or ``None``
|
|
396
|
+
:return: ``True`` if the order changed
|
|
397
|
+
:rtype: ``bool``
|
|
398
|
+
"""
|
|
399
|
+
socket = self.getMultiSocket(index)
|
|
400
|
+
if socket is None:
|
|
401
|
+
return False
|
|
402
|
+
if not socket.swapEdges(edge_a, edge_b):
|
|
403
|
+
return False
|
|
404
|
+
self.updateInputOrderLabels(index)
|
|
405
|
+
self.markDirty()
|
|
406
|
+
self.markDescendantsDirty()
|
|
407
|
+
return True
|
|
408
|
+
|
|
409
|
+
def reverseEdgeOrder(self, index: Optional[int] = None) -> bool:
|
|
410
|
+
"""
|
|
411
|
+
Reverse the read order of the connected `Edges`.
|
|
412
|
+
|
|
413
|
+
:param index: index of the input `Socket` or ``None`` to use
|
|
414
|
+
:attr:`multi_input_socket`
|
|
415
|
+
:type index: ``int`` or ``None``
|
|
416
|
+
:return: ``True`` if the order changed
|
|
417
|
+
:rtype: ``bool``
|
|
418
|
+
"""
|
|
419
|
+
socket = self.getMultiSocket(index)
|
|
420
|
+
if socket is None:
|
|
421
|
+
return False
|
|
422
|
+
return self.setEdgeOrder(list(reversed(socket.orderedEdges())), index)
|
|
423
|
+
|
|
424
|
+
# events
|
|
425
|
+
|
|
426
|
+
def onEdgeConnectionChanged(self, new_edge: 'Edge') -> None:
|
|
427
|
+
"""
|
|
428
|
+
Keep the read order gap free after any connection change.
|
|
429
|
+
|
|
430
|
+
This is the single hook every connection path already goes through
|
|
431
|
+
(``EdgeDragging``, ``EdgeRerouting``, ``Edge.remove``), so
|
|
432
|
+
:attr:`~nodeeditor.node_edge.Edge.input_index` stays a contiguous
|
|
433
|
+
``0..n-1`` sequence and the ``#N`` labels (see
|
|
434
|
+
:attr:`show_input_order_labels`) stay in sync. An `Edge` that just
|
|
435
|
+
detached from our ordered socket gets its managed ``#N`` label
|
|
436
|
+
cleared. ``onInputChanged`` is intentionally not overridden
|
|
437
|
+
so existing subclass chains keep working.
|
|
438
|
+
"""
|
|
439
|
+
self.compactEdgeOrder()
|
|
440
|
+
if (new_edge is not None
|
|
441
|
+
and new_edge.start_socket is None
|
|
442
|
+
and new_edge.end_socket is None):
|
|
443
|
+
# fully detached (Edge.remove): forget a managed #N label, but
|
|
444
|
+
# only when it still matches the pattern, so a custom label set
|
|
445
|
+
# from code is never destroyed. Edges that are still connected
|
|
446
|
+
# anywhere (e.g. our outputs, or rerouted elsewhere) are left
|
|
447
|
+
# alone; their current owner labels them through its own hook.
|
|
448
|
+
self._clearDetachedOrderLabel(new_edge)
|
|
449
|
+
self.updateInputOrderLabels()
|
|
450
|
+
super().onEdgeConnectionChanged(new_edge)
|