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.
Files changed (40) hide show
  1. nodeeditor/__init__.py +93 -0
  2. nodeeditor/node_colors_config.py +343 -0
  3. nodeeditor/node_content_widget.py +129 -0
  4. nodeeditor/node_decorators.py +65 -0
  5. nodeeditor/node_edge.py +518 -0
  6. nodeeditor/node_edge_dragging.py +185 -0
  7. nodeeditor/node_edge_intersect.py +219 -0
  8. nodeeditor/node_edge_rerouting.py +216 -0
  9. nodeeditor/node_edge_snapping.py +95 -0
  10. nodeeditor/node_edge_validators.py +82 -0
  11. nodeeditor/node_editor_widget.py +257 -0
  12. nodeeditor/node_editor_window.py +448 -0
  13. nodeeditor/node_graph.py +583 -0
  14. nodeeditor/node_graphics_cutline.py +57 -0
  15. nodeeditor/node_graphics_edge.py +375 -0
  16. nodeeditor/node_graphics_edge_path.py +226 -0
  17. nodeeditor/node_graphics_node.py +693 -0
  18. nodeeditor/node_graphics_scene.py +369 -0
  19. nodeeditor/node_graphics_socket.py +262 -0
  20. nodeeditor/node_graphics_view.py +907 -0
  21. nodeeditor/node_group.py +1286 -0
  22. nodeeditor/node_group_node.py +5 -0
  23. nodeeditor/node_icon_content_widget.py +176 -0
  24. nodeeditor/node_icon_graphics_node.py +524 -0
  25. nodeeditor/node_multi_input_node.py +450 -0
  26. nodeeditor/node_node.py +848 -0
  27. nodeeditor/node_scene.py +1106 -0
  28. nodeeditor/node_scene_clipboard.py +314 -0
  29. nodeeditor/node_scene_history.py +556 -0
  30. nodeeditor/node_serializable.py +43 -0
  31. nodeeditor/node_socket.py +445 -0
  32. nodeeditor/node_socket_shapes.py +261 -0
  33. nodeeditor/py.typed +0 -0
  34. nodeeditor/qss/nodestyle.qss +3 -0
  35. nodeeditor/utils.py +108 -0
  36. nodeeditor/utils_no_qt.py +21 -0
  37. nodeeditor_qtpy-0.8.1.dist-info/METADATA +165 -0
  38. nodeeditor_qtpy-0.8.1.dist-info/RECORD +40 -0
  39. nodeeditor_qtpy-0.8.1.dist-info/WHEEL +4 -0
  40. nodeeditor_qtpy-0.8.1.dist-info/licenses/LICENCE +23 -0
@@ -0,0 +1,219 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ A module containing the intersecting nodes functionality. If a node gets dragged and dropped on an existing edge
4
+ it will intersect that edge.
5
+ """
6
+ from qtpy.QtWidgets import QGraphicsView, QGraphicsItem
7
+ from qtpy.QtCore import QRectF, QPointF
8
+ from nodeeditor.node_edge import Edge
9
+ from nodeeditor.node_graphics_edge import QDMGraphicsEdge
10
+
11
+ from typing import TYPE_CHECKING, Union, List
12
+
13
+ from nodeeditor.node_socket import Socket
14
+
15
+
16
+ if TYPE_CHECKING:
17
+ from nodeeditor.node_graphics_view import QDMGraphicsView
18
+ from nodeeditor.node_node import Node
19
+ from nodeeditor.node_graphics_scene import QDMGraphicsScene
20
+
21
+
22
+ class EdgeIntersect:
23
+ """Class handling the intersection of a dragged node with an existing edge"""
24
+
25
+ def __init__(self, grView: 'QDMGraphicsView') -> None:
26
+ """
27
+ :param grView: reference to the :class:`~nodeeditor.node_graphics_view.QDMGraphicsView`
28
+ :type grView: :class:`~nodeeditor.node_graphics_view.QDMGraphicsView`
29
+
30
+ :Instance Attributes:
31
+
32
+ - **grView** - reference to the :class:`~nodeeditor.node_graphics_view.QDMGraphicsView`
33
+ - **grScene** - reference to the :class:`~nodeeditor.node_graphics_scene.QDMGraphicsScene`
34
+ - **draggedNode** - the :class:`~nodeeditor.node_node.Node` currently being dragged
35
+ - **hoveredList** - graphics items hovered by the dragged node
36
+ """
37
+ self.grView = grView
38
+ self.grScene: 'QDMGraphicsScene' = grView.grScene
39
+ self.draggedNode: Node | None = None
40
+ self.hoveredList: List[Union['QDMGraphicsEdge',
41
+ 'QGraphicsItem', 'QGraphicsView', 'Socket', 'Node']] = []
42
+
43
+ def enterState(self, node: 'Node') -> None:
44
+ """
45
+ Initialize when we enter the state
46
+
47
+ :param node: :class:`~nodeeditor.node_node.Node` which we started to drag
48
+ :type node: :class:`~nodeeditor.node_node.Node`
49
+ """
50
+ self.hoveredList = []
51
+ self.draggedNode = node
52
+
53
+ def leaveState(self, scene_pos_x: float, scene_pos_y: float) -> None:
54
+ """
55
+ Deinit when we leave this state
56
+
57
+ :param scene_pos_x: scene position x
58
+ :type scene_pos_x: `float`
59
+ :param scene_pos_y: scene position y
60
+ :type scene_pos_y: `float`
61
+ """
62
+ if self.draggedNode is None:
63
+ print(">>> Warning: self.draggedNode is None")
64
+ return
65
+
66
+ self.dropNode(self.draggedNode, scene_pos_x, scene_pos_y)
67
+ self.draggedNode = None
68
+ self.hoveredList = []
69
+
70
+ def dropNode(self, node: "Node", scene_pos_x: float, scene_pos_y: float) -> None:
71
+ """
72
+ Code handling the dropping of a node on an existing edge.
73
+
74
+ :param scene_pos_x: scene position x
75
+ :type scene_pos_x: `float`
76
+ :param scene_pos_y: scene position y
77
+ :type scene_pos_y: `float`
78
+ """
79
+
80
+ node_box = self.hotZoneRect(node)
81
+
82
+ # check if the node is dropped on an existing edge
83
+ edge = self.intersect(node_box)
84
+ if edge is None:
85
+ return
86
+
87
+ if self.isConnected(node):
88
+ return
89
+
90
+ # determine the order of start and end
91
+ if edge.start_socket.is_output:
92
+ socket_start = edge.start_socket
93
+ socket_end = edge.end_socket
94
+ else:
95
+ socket_start = edge.end_socket
96
+ socket_end = edge.start_socket
97
+
98
+ new_node_socket_in = node.inputs[0]
99
+ new_node_socket_out = node.outputs[0]
100
+
101
+ # Validate like manual drag does before touching the original edge.
102
+ if not Edge.validateEdge(socket_start, new_node_socket_in):
103
+ return
104
+ if not Edge.validateEdge(new_node_socket_out, socket_end):
105
+ return
106
+
107
+ # Use the Scene's Edge class so subclasses survive splits
108
+ # (hardcoded Edge() would silently downgrade them).
109
+ edge_class = self.grScene.scene.getEdgeClass()
110
+
111
+ # The new edges will have the same edge_type as the intersected edge
112
+ edge_type = edge.edge_type
113
+ edge.remove()
114
+ self.grView.grScene.scene.history.storeHistory(
115
+ 'Delete existing edge', setModified=True)
116
+
117
+ e1 = edge_class(self.grScene.scene, socket_start,
118
+ new_node_socket_in, edge_type=edge_type)
119
+ e2 = edge_class(self.grScene.scene, new_node_socket_out,
120
+ socket_end, edge_type=edge_type)
121
+
122
+ # Notify exactly like manual drag (EdgeDragging.edgeDragEnd) so nodes
123
+ # relying on onEdgeConnectionChanged/onInputChanged (dirty marking,
124
+ # re-eval, cached input data) behave identically for auto-splits.
125
+ for socket, new_edge in ((socket_start, e1), (new_node_socket_in, e1),
126
+ (new_node_socket_out, e2), (socket_end, e2)):
127
+ try:
128
+ socket.node.onEdgeConnectionChanged(new_edge)
129
+ if socket.is_input:
130
+ socket.node.onInputChanged(socket)
131
+ except Exception:
132
+ continue
133
+
134
+ self.grView.grScene.scene.history.storeHistory(
135
+ 'Created new edges by dropping node', setModified=True)
136
+
137
+ def hotZoneRect(self, node: 'Node') -> 'QRectF':
138
+ """
139
+ Returns A QRectF of creating a box around a node
140
+
141
+ :param node: :class:`~nodeeditor.node_node.Node` for which we want to get `QRectF` describing its position and area
142
+ :type node: :class:`~nodeeditor.node_node.Node`
143
+ :return: `QRectF` describing node's position and area
144
+ :rtype: `QRectF`
145
+ """
146
+ if node.grNode is None:
147
+ print(">>> Warning: node is None")
148
+ return QRectF()
149
+
150
+ nodePos: QPointF = node.grNode.scenePos()
151
+ x = nodePos.x()
152
+ y = nodePos.y()
153
+ w = node.grNode.width
154
+ h = node.grNode.height
155
+ return QRectF(x, y, w, h)
156
+
157
+ def update(self, scene_pos_x: float, scene_pos_y: float) -> None:
158
+ """
159
+ Updating during mouse move when grView is in this state
160
+
161
+ :param scene_pos_x: scene position x
162
+ :type scene_pos_x: `float`
163
+ :param scene_pos_y: scene position y
164
+ :type scene_pos_y: `float`
165
+ """
166
+ if self.draggedNode is None:
167
+ print(">>> Warning: self.draggedNode is None")
168
+ return
169
+
170
+ rect = self.hotZoneRect(self.draggedNode)
171
+ grItems = self.grScene.items(rect)
172
+
173
+ for grEdge in self.hoveredList:
174
+ if isinstance(grEdge, QDMGraphicsEdge):
175
+ grEdge.hovered = False
176
+
177
+ self.hoveredList = []
178
+
179
+ for grItem in grItems:
180
+ if hasattr(grItem, 'edge') and not self.draggedNode.hasConnectedEdge(grItem.edge):
181
+ self.hoveredList.append(grItem)
182
+ if hasattr(grItem, 'hovered'):
183
+ grItem.hovered = True
184
+
185
+ def intersect(self, node_box: 'QRectF') -> Union['Edge', None]:
186
+ """
187
+ Checking for intersection of a rectangle (usually a `Node`) with edges in the scene
188
+
189
+ :param node_box: `QRectF` for which we want find intersecting `Edges`
190
+ :type node_box: `QRectF`
191
+ :return: :class:`~nodeeditor.node_edge.Edge` or `None` if the node is being cut by an `Edge`
192
+ :rtype: :class:`~nodeeditor.node_edge.Edge`
193
+ """
194
+
195
+ if self.draggedNode is None:
196
+ print(">>> Warning: self.draggedNode is None")
197
+ return None
198
+
199
+ # returns the first edge that intersects with the dropped node, ignores the rest
200
+ grItems = self.grScene.items(node_box)
201
+ for grItem in grItems:
202
+ if hasattr(grItem, 'edge') and not self.draggedNode.hasConnectedEdge(grItem.edge):
203
+ return grItem.edge
204
+ return None
205
+
206
+ def isConnected(self, node: 'Node'):
207
+ """
208
+ Return ``True`` if node got any connections
209
+
210
+ :param node: :class:`~nodeeditor.node_node.Node` which connections to check
211
+ :type node: :class:`~nodeeditor.node_node.Node`
212
+ :return:
213
+ """
214
+ # Nodes with only inputs or outputs are excluded
215
+ if node.inputs == [] or node.outputs == []:
216
+ return True
217
+
218
+ # Check if the node has edges connected
219
+ return node.getInput() or node.getOutputs()
@@ -0,0 +1,216 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ A module containing the Edge Rerouting functionality
4
+ """
5
+
6
+
7
+ from typing import TYPE_CHECKING, List, Optional, Tuple, Any
8
+
9
+
10
+ if TYPE_CHECKING:
11
+ from nodeeditor.node_graphics_view import QDMGraphicsView
12
+ from nodeeditor.node_edge import Edge
13
+ from nodeeditor.node_socket import Socket
14
+
15
+
16
+ DEBUG_REROUTING = True
17
+
18
+
19
+ class EdgeRerouting:
20
+ """Class handling the re-routing of existing edges to another socket"""
21
+
22
+ def __init__(self, grView: 'QDMGraphicsView') -> None:
23
+ """
24
+ :param grView: reference to the :class:`~nodeeditor.node_graphics_view.QDMGraphicsView`
25
+ :type grView: :class:`~nodeeditor.node_graphics_view.QDMGraphicsView`
26
+
27
+ :Instance Attributes:
28
+
29
+ - **grView** - reference to the :class:`~nodeeditor.node_graphics_view.QDMGraphicsView`
30
+ - **start_socket** - the :class:`~nodeeditor.node_socket.Socket` where the re-routing started
31
+ - **rerouting_edges** - dashed edges representing the re-routing in progress
32
+ - **is_rerouting** - are we currently re-routing?
33
+ - **first_mb_release** - flag for detecting if we already clicked with the rerouting LMB release
34
+ """
35
+ self.grView = grView
36
+ # store where we started re-routing the edges
37
+ self.start_socket: Optional['Socket'] = None
38
+ # edges representing the re-routing (dashed edges)
39
+ self.rerouting_edges: List['Edge'] = []
40
+ self.is_rerouting: bool = False # are we currently re-routing?
41
+ # flag for detecting if we already clicked with the rerouting LMB release
42
+ self.first_mb_release: bool = False
43
+
44
+ def print(self, *args) -> None:
45
+ """Helper function to better control debug printing to console for this feature"""
46
+ if DEBUG_REROUTING:
47
+ print("REROUTING:", *args)
48
+
49
+ def getEdgeClass(self):
50
+ """Helper function to get the Edge class. Using what the Scene class provides"""
51
+ return self.grView.grScene.scene.getEdgeClass()
52
+
53
+ def getAffectedEdges(self) -> list:
54
+ """
55
+ Get a list of all edges connected to the `self.start_socket` where we started the re-routing
56
+
57
+ :return: List of all edges affected by the rerouting started from this `self.start_socket` :class:`~nodeeditor.node_socket.Socket`
58
+ :rtype: ``list``
59
+ """
60
+ if self.start_socket is None:
61
+ return [] # no starting socket assigned, so no edges for us
62
+ # return edges connected to the socket
63
+ return self.start_socket.edges.copy()
64
+
65
+ def setAffectedEdgesVisible(self, visibility: bool = True) -> None:
66
+ """
67
+ Show/Hide all edges connected to the `self.start_socket` where we started the re-routing
68
+
69
+ :param visibility: ``True`` if all the affected :class:`~nodeeditor.node_edge.Edge` (s) should be shown or hidden
70
+ :type visibility: ``bool``
71
+ """
72
+ for edge in self.getAffectedEdges():
73
+ if visibility:
74
+ edge.grEdge.show()
75
+ else:
76
+ edge.grEdge.hide()
77
+
78
+ def resetRerouting(self) -> None:
79
+ """Reset to default state. Init this feature internal variables"""
80
+ self.is_rerouting = False
81
+ self.start_socket = None
82
+ self.first_mb_release = False
83
+ # holding all rerouting edges should be empty at this point...
84
+ # self.rerouting_edges = []
85
+
86
+ def clearReroutingEdges(self) -> None:
87
+ """Remove the helping dashed edges from the :class:`~nodeeditor.node_scene.Scene`"""
88
+ self.print("clean called")
89
+ while self.rerouting_edges != []:
90
+ edge = self.rerouting_edges.pop()
91
+ self.print("\twant to clean:", edge)
92
+ edge.remove()
93
+
94
+ def updateScenePos(self, x: float, y: float) -> None:
95
+ """
96
+ Update position of all the rerouting edges (dashed ones). Called from mouseMove event to update to new mouse position
97
+
98
+ :param x: new X position
99
+ :type x: ``float``
100
+ :param y: new Y position
101
+ :type y: ``float``
102
+ """
103
+ if self.is_rerouting:
104
+ for edge in self.rerouting_edges:
105
+ if edge and edge.grEdge:
106
+ edge.grEdge.setDestination(x, y)
107
+ edge.grEdge.update()
108
+
109
+ def startRerouting(self, socket: 'Socket') -> None:
110
+ """
111
+ Method to start the re-routing. Called from the grView's state machine.
112
+
113
+ :param socket: :class:`~nodeeditor.node_socket.Socket` where we started the re-routing
114
+ :type socket: :class:`~nodeeditor.node_socket.Socket`
115
+ """
116
+ self.print("startRerouting", socket)
117
+ self.is_rerouting = True
118
+ self.start_socket = socket
119
+
120
+ self.print("numEdges:", len(self.getAffectedEdges()))
121
+ self.setAffectedEdgesVisible(visibility=False)
122
+
123
+ start_position = self.start_socket.node.getSocketScenePosition(
124
+ self.start_socket)
125
+
126
+ for edge in self.getAffectedEdges():
127
+ other_socket = edge.getOtherSocket(self.start_socket)
128
+
129
+ new_edge = self.getEdgeClass()(self.start_socket.node.scene,
130
+ edge_type=edge.edge_type)
131
+ new_edge.start_socket = other_socket
132
+ new_edge.grEdge.setSource(
133
+ *other_socket.node.getSocketScenePosition(other_socket))
134
+ new_edge.grEdge.setDestination(*start_position)
135
+ new_edge.grEdge.update()
136
+ self.rerouting_edges.append(new_edge)
137
+
138
+ def stopRerouting(self, target: 'Socket' = None) -> None:
139
+ """
140
+ Method for stopping the re-routing
141
+
142
+ :param target: Target where we ended the rerouting (usually released mouse button). Provide ``Socket`` or ``None`` to cancel
143
+ :type target: :class:`~nodeeditor.node_socket.Socket` or ``None``
144
+ """
145
+ self.print("stopRerouting on:", target,
146
+ "no change" if target == self.start_socket else "")
147
+
148
+ if self.start_socket is None:
149
+ return
150
+
151
+ # reset start socket highlight
152
+ self.start_socket.grSocket.isHighlighted = False
153
+
154
+ # collect all affected (node, edge) tuples in the meantime.. if necessary
155
+ affected_nodes = []
156
+
157
+ if target is None or target == self.start_socket:
158
+ # canceling -> no change
159
+ self.setAffectedEdgesVisible(visibility=True)
160
+
161
+ else:
162
+ # validate edges before doing anything else
163
+ valid_edges, invalid_edges = self.getAffectedEdges(), []
164
+ for edge in self.getAffectedEdges():
165
+ start_sock = edge.getOtherSocket(self.start_socket)
166
+ if not edge.validateEdge(start_sock, target):
167
+ # not valid edge
168
+ self.print("This edge rerouting is not valid!", edge)
169
+ invalid_edges.append(edge)
170
+
171
+ # remove the invalidated edges from the list
172
+ for invalid_edge in invalid_edges:
173
+ valid_edges.remove(invalid_edge)
174
+
175
+ # reconnect to new socket
176
+ self.print("should reconnect from:",
177
+ self.start_socket, "-->", target)
178
+
179
+ self.setAffectedEdgesVisible(visibility=True)
180
+
181
+ for edge in valid_edges:
182
+ for node in [edge.start_socket.node, edge.end_socket.node]:
183
+ if node not in affected_nodes:
184
+ affected_nodes.append((node, edge))
185
+
186
+ if target.is_input and not target.is_multi_edges:
187
+ # single-edged input: the new edge replaces the old one.
188
+ # Multi-edged inputs keep every other edge, and each moved
189
+ # edge lands at the read position it had on the old socket.
190
+ target.removeAllEdges(silent=True)
191
+
192
+ if edge.end_socket == self.start_socket:
193
+ edge.end_socket = target
194
+ else:
195
+ edge.start_socket = target
196
+
197
+ edge.updatePositions()
198
+
199
+ # hide rerouting edges
200
+ self.clearReroutingEdges()
201
+
202
+ # Send notifications for all affected nodes
203
+ for affected_node, edge in affected_nodes:
204
+ affected_node.onEdgeConnectionChanged(edge)
205
+ if edge.start_socket in affected_node.inputs:
206
+ affected_node.onInputChanged(edge.start_socket)
207
+ if edge.end_socket in affected_node.inputs:
208
+ affected_node.onInputChanged(edge.end_socket)
209
+
210
+ # store history stamp only if we have a valid start socket
211
+ if self.start_socket and self.start_socket.node:
212
+ self.start_socket.node.scene.history.storeHistory(
213
+ "Rerouted edges", setModified=True)
214
+
215
+ # reset variables of this rerouting state
216
+ self.resetRerouting()
@@ -0,0 +1,95 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ A module containing the Edge Snapping functions which are used in :class:`~nodeeditor.node_graphics_view.QDMGraphicsView` class.
4
+ """
5
+
6
+
7
+ from qtpy.QtCore import QPointF, QRectF
8
+ from qtpy.QtGui import QMouseEvent
9
+
10
+ from nodeeditor.node_graphics_socket import QDMGraphicsSocket
11
+
12
+ from typing import TYPE_CHECKING, List, Optional, Tuple, Any
13
+
14
+
15
+ if TYPE_CHECKING:
16
+ from nodeeditor.node_graphics_view import QDMGraphicsView
17
+ from nodeeditor.node_edge import Edge
18
+ from nodeeditor.node_socket import Socket
19
+
20
+
21
+ class EdgeSnapping():
22
+ """Class handling the snapping of a dragged edge to a nearby socket"""
23
+
24
+ def __init__(self, grView: 'QDMGraphicsView', snapping_radius: float = 24) -> None:
25
+ """
26
+ :param grView: reference to the :class:`~nodeeditor.node_graphics_view.QDMGraphicsView`
27
+ :type grView: :class:`~nodeeditor.node_graphics_view.QDMGraphicsView`
28
+ :param snapping_radius: radius in scene units within which sockets are considered for snapping
29
+ :type snapping_radius: ``float``
30
+
31
+ :Instance Attributes:
32
+
33
+ - **grView** - reference to the :class:`~nodeeditor.node_graphics_view.QDMGraphicsView`
34
+ - **grScene** - reference to the :class:`~nodeeditor.node_graphics_scene.QDMGraphicsScene`
35
+ - **edge_snapping_radius** - radius in scene units within which sockets are considered for snapping
36
+ """
37
+ self.grView = grView
38
+ self.grScene = self.grView.grScene
39
+ self.edge_snapping_radius = snapping_radius
40
+
41
+ def getSnappedSocketItem(self, event: 'QMouseEvent') -> Optional['QDMGraphicsSocket']:
42
+ """Returns :class:`~nodeeditor.node_graphics_socket.QDMGraphicsSocket` which we should snap to"""
43
+ scenepos = self.grView.mapToScene(event.pos())
44
+ grSocket, pos = self.getSnappedToSocketPosition(scenepos)
45
+
46
+ if grSocket is None:
47
+ return None
48
+
49
+ return grSocket
50
+
51
+ def getSnappedToSocketPosition(self, scenepos: QPointF) -> Tuple[Optional['QDMGraphicsSocket'], QPointF]:
52
+ """
53
+ Returns grSocket and Scene position to nearest Socket or original position if no nearby Socket found
54
+
55
+ :param scenepos: From which point should I snap?
56
+ :type scenepos: ``QPointF``
57
+ :return: grSocket and Scene postion to nearest socket
58
+ """
59
+ scanrect = QRectF(
60
+ scenepos.x() - self.edge_snapping_radius,
61
+ scenepos.y() - self.edge_snapping_radius,
62
+ self.edge_snapping_radius * 2,
63
+ self.edge_snapping_radius * 2
64
+ )
65
+ items = self.grScene.items(scanrect)
66
+ socket_items = list(
67
+ filter(lambda x: isinstance(x, QDMGraphicsSocket), items))
68
+
69
+ if not socket_items:
70
+ return None, scenepos
71
+
72
+ selected_item = socket_items[0]
73
+ if len(socket_items) > 1:
74
+ # calculate the nearest socket
75
+ nearest = float('inf')
76
+ for grsock in socket_items:
77
+ if not isinstance(grsock, QDMGraphicsSocket) or not grsock.socket or not grsock.socket.node:
78
+ continue
79
+
80
+ grsock_scenepos = grsock.socket.node.getSocketScenePosition(
81
+ grsock.socket)
82
+ qpdist = QPointF(*grsock_scenepos) - scenepos
83
+ dist = qpdist.x() * qpdist.x() + qpdist.y() * qpdist.y()
84
+ if dist < nearest:
85
+ nearest, selected_item = dist, grsock
86
+
87
+ if not isinstance(selected_item, QDMGraphicsSocket) or not selected_item.socket or not selected_item.socket.node:
88
+ return None, scenepos
89
+
90
+ selected_item.isHighlighted = True
91
+
92
+ calcpos = selected_item.socket.node.getSocketScenePosition(
93
+ selected_item.socket)
94
+
95
+ return selected_item, QPointF(*calcpos)
@@ -0,0 +1,82 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ A module containing the Edge Validator functions which can be registered as callbacks to
4
+ :class:`~nodeeditor.node_edge.Edge` class.
5
+
6
+ Example of registering Edge Validator callbacks:
7
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8
+
9
+ You can register validation callbacks once for example on the bottom of node_edge.py file or on the
10
+ application start with calling this:
11
+
12
+ .. code-block:: python
13
+
14
+ from nodeeditor.node_edge_validators import *
15
+
16
+ Edge.registerEdgeValidator(edge_validator_debug)
17
+ Edge.registerEdgeValidator(edge_cannot_connect_two_outputs_or_two_inputs)
18
+ Edge.registerEdgeValidator(edge_cannot_connect_input_and_output_of_same_node)
19
+ Edge.registerEdgeValidator(edge_cannot_connect_input_and_output_of_different_type)
20
+
21
+
22
+ """
23
+
24
+ from typing import TYPE_CHECKING
25
+
26
+
27
+ if TYPE_CHECKING:
28
+ from nodeeditor.node_socket import Socket
29
+
30
+
31
+ DEBUG = False
32
+
33
+
34
+ def print_error(*args) -> None:
35
+ """Helper method which prints to console if `DEBUG` is set to `True`"""
36
+ if DEBUG:
37
+ print("Edge Validation Error:", *args)
38
+
39
+
40
+ def edge_validator_debug(input: 'Socket', output: 'Socket') -> bool:
41
+ """This will consider edge always valid, however writes bunch of debug stuff into console"""
42
+ print("VALIDATING:")
43
+ print(input, "input" if input.is_input else "output", "of node", input.node)
44
+ for s in input.node.inputs+input.node.outputs:
45
+ print("\t", s, "input" if s.is_input else "output")
46
+ print(output, "input" if input.is_input else "output", "of node", output.node)
47
+ for s in output.node.inputs+output.node.outputs:
48
+ print("\t", s, "input" if s.is_input else "output")
49
+
50
+ return True
51
+
52
+
53
+ def edge_cannot_connect_two_outputs_or_two_inputs(input: 'Socket', output: 'Socket') -> bool:
54
+ """Edge is invalid if it connects 2 output sockets or 2 input sockets"""
55
+ if input.is_output and output.is_output:
56
+ print_error("Connecting 2 outputs")
57
+ return False
58
+
59
+ if input.is_input and output.is_input:
60
+ print_error("Connecting 2 inputs")
61
+ return False
62
+
63
+ return True
64
+
65
+
66
+ def edge_cannot_connect_input_and_output_of_same_node(input: 'Socket', output: 'Socket') -> bool:
67
+ """Edge is invalid if it connects the same node"""
68
+ if input.node == output.node:
69
+ print_error("Connecting the same node")
70
+ return False
71
+
72
+ return True
73
+
74
+
75
+ def edge_cannot_connect_input_and_output_of_different_type(input: 'Socket', output: 'Socket') -> bool:
76
+ """Edge is invalid if it connects sockets with different colors"""
77
+
78
+ if input.socket_type != output.socket_type:
79
+ print_error("Connecting sockets with different colors")
80
+ return False
81
+
82
+ return True