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,375 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ A module containing the Graphics representation of an Edge
4
+ """
5
+ from qtpy.QtWidgets import QGraphicsPathItem, QWidget, QGraphicsItem, QGraphicsSceneHoverEvent, QGraphicsSimpleTextItem
6
+ from qtpy.QtGui import QColor, QPen, QBrush, QPainterPath, QFont
7
+ from qtpy.QtCore import Qt, QRectF, QPointF
8
+
9
+ from nodeeditor.node_graphics_edge_path import GraphicsEdgePathBezier, GraphicsEdgePathDirect, GraphicsEdgePathSquare, GraphicsEdgePathImprovedSharp, GraphicsEdgePathImprovedBezier
10
+ from nodeeditor.node_colors_config import get_color_scheme
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 QDMGraphicsEdge(QGraphicsPathItem):
22
+ """Base class for Graphics Edge"""
23
+
24
+ def __init__(self, edge: 'Edge', parent: QGraphicsPathItem = None) -> None:
25
+ """
26
+ :param edge: reference to :class:`~nodeeditor.node_edge.Edge`
27
+ :type edge: :class:`~nodeeditor.node_edge.Edge`
28
+ :param parent: parent widget
29
+ :type parent: ``QWidget``
30
+
31
+ :Instance attributes:
32
+
33
+ - **edge** - reference to :class:`~nodeeditor.node_edge.Edge`
34
+ - **posSource** - ``[x, y]`` source position in the `Scene`
35
+ - **posDestination** - ``[x, y]`` destination position in the `Scene`
36
+ """
37
+ super().__init__(parent)
38
+
39
+ self.edge = edge
40
+
41
+ # create instance of our path class
42
+ self.pathCalculator = self.determineEdgePathClass()(self)
43
+
44
+ # init our flags
45
+ self._last_selected_state = False
46
+ self.hovered = False
47
+
48
+ # init our variables
49
+ self.posSource: List[float] = [0, 0]
50
+ self.posDestination: List[float] = [200, 100]
51
+
52
+ # code-settable, user-read-only label drawn on top of the edge path.
53
+ # Never editable/selectable/movable: users cannot change it, code can
54
+ # via QDMGraphicsEdge.setLabel() / Edge.label.
55
+ self._label_offset = QPointF(0, -18)
56
+ self.labelItem = QGraphicsSimpleTextItem(self)
57
+ self.labelItem.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
58
+ self.labelItem.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
59
+ self.labelItem.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsFocusable, False)
60
+ self.labelItem.setAcceptedMouseButtons(Qt.MouseButton.NoButton)
61
+ self.labelItem.setAcceptHoverEvents(False)
62
+ # QGraphicsSimpleTextItem has no text-interaction/editing API at all,
63
+ # which is exactly what we want: code-only, never user-editable.
64
+ self.labelItem.setBrush(QBrush(QColor("#ffffff")))
65
+ self.labelItem.setZValue(1)
66
+ self.labelItem.setVisible(False)
67
+
68
+ self.initAssets()
69
+ self.initUI()
70
+
71
+ def initUI(self) -> None:
72
+ """Set up this ``QGraphicsPathItem``"""
73
+ self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
74
+ self.setAcceptHoverEvents(True)
75
+ self.setZValue(-1)
76
+
77
+ def initAssets(self) -> None:
78
+ """Initialize ``QObjects`` like ``QColor``, ``QPen`` and ``QBrush``"""
79
+ # Get colors from global color scheme
80
+ scheme = get_color_scheme()
81
+ self._color = self._default_color = scheme.edges.default
82
+ self._color_selected = scheme.edges.selected
83
+ self._color_hovered = scheme.edges.hovered
84
+ self._pen = QPen(self._color)
85
+ self._pen_selected = QPen(self._color_selected)
86
+ self._pen_dragging = QPen(scheme.edges.dragging)
87
+ self._pen_hovered = QPen(self._color_hovered)
88
+ self._pen_dragging.setStyle(Qt.PenStyle.DashLine)
89
+ self._pen.setWidthF(3.0)
90
+ self._pen_selected.setWidthF(3.0)
91
+ self._pen_dragging.setWidthF(3.0)
92
+ self._pen_hovered.setWidthF(5.0)
93
+
94
+ def createEdgePathCalculator(self):
95
+ """Create instance of :class:`~nodeeditor.node_graphics_edge_path.GraphicsEdgePathBase`"""
96
+ self.pathCalculator = self.determineEdgePathClass()(self)
97
+ return self.pathCalculator
98
+
99
+ def determineEdgePathClass(self):
100
+ """Decide which GraphicsEdgePath class should be used to calculate path according to edge.edge_type value"""
101
+ from nodeeditor.node_edge import EDGE_TYPE_BEZIER, EDGE_TYPE_DIRECT, EDGE_TYPE_SQUARE, EDGE_TYPE_IMPROVED_SHARP, EDGE_TYPE_IMPROVED_BEZIER
102
+ if self.edge.edge_type == EDGE_TYPE_BEZIER:
103
+ return GraphicsEdgePathBezier
104
+ if self.edge.edge_type == EDGE_TYPE_DIRECT:
105
+ return GraphicsEdgePathDirect
106
+ if self.edge.edge_type == EDGE_TYPE_SQUARE:
107
+ return GraphicsEdgePathSquare
108
+ if self.edge.edge_type == EDGE_TYPE_IMPROVED_SHARP:
109
+ return GraphicsEdgePathImprovedSharp
110
+ if self.edge.edge_type == EDGE_TYPE_IMPROVED_BEZIER:
111
+ return GraphicsEdgePathImprovedBezier
112
+
113
+ else:
114
+ return GraphicsEdgePathImprovedBezier
115
+
116
+ def makeUnselectable(self) -> None:
117
+ """Used for drag edge to disable click detection over this graphics item"""
118
+ self.setFlag(QGraphicsItem.ItemIsSelectable, False)
119
+ self.setAcceptHoverEvents(False)
120
+
121
+ def changeColor(self, color) -> None:
122
+ """Change color of the edge from string hex value '#00ff00'"""
123
+ # print("^Called change color to:", color.red(), color.green(), color.blue(), "on edge:", self.edge)
124
+ self._color = QColor(color) if type(color) == str else color
125
+ self._pen = QPen(self._color)
126
+ self._pen.setWidthF(3.0)
127
+
128
+ def setColorFromSockets(self) -> bool:
129
+ """Change color according to connected sockets. Returns ``True`` if color can be determined"""
130
+ socket_type_start = self.edge.start_socket.socket_type
131
+ socket_type_end = self.edge.end_socket.socket_type
132
+ if socket_type_start != socket_type_end:
133
+ return False
134
+ self.changeColor(
135
+ self.edge.start_socket.grSocket.getSocketColor(socket_type_start))
136
+
137
+ return True
138
+
139
+ def label(self) -> str:
140
+ """Return the current code-set edge label (``""`` when unset)."""
141
+ return self.labelItem.text()
142
+
143
+ def setLabel(self, text: str) -> None:
144
+ """Set the text drawn on top of the edge.
145
+
146
+ Code-only API: the label item is not selectable, movable, focusable
147
+ and accepts no mouse buttons, so users cannot edit it directly.
148
+
149
+ :param text: label text, ``""``/``None`` hides the label
150
+ """
151
+ self.labelItem.setText(text or "")
152
+ self.labelItem.setVisible(bool(text))
153
+ self.updateLabelPosition()
154
+ self.update()
155
+
156
+ def setLabelColor(self, color) -> None:
157
+ """Set label text color from ``QColor`` or hex string."""
158
+ self.labelItem.setBrush(QBrush(QColor(color) if isinstance(color, str) else color))
159
+ self.update()
160
+
161
+ def setLabelFont(self, font: QFont) -> None:
162
+ """Set label font, then recenter it on the edge path."""
163
+ self.labelItem.setFont(font)
164
+ self.updateLabelPosition()
165
+ self.update()
166
+
167
+ def setLabelOffset(self, x: float, y: float) -> None:
168
+ """Offset (in px) applied to the centered label position."""
169
+ self._label_offset = QPointF(x, y)
170
+ self.updateLabelPosition()
171
+ self.update()
172
+
173
+ def updateLabelPosition(self) -> None:
174
+ """Center the label on top of the current edge path."""
175
+ if not self.labelItem.isVisible():
176
+ return
177
+ try:
178
+ path = self.path()
179
+ mid = path.pointAtPercent(0.5) if path.length() > 0 else QPointF(
180
+ (self.posSource[0] + self.posDestination[0]) / 2.0,
181
+ (self.posSource[1] + self.posDestination[1]) / 2.0,
182
+ )
183
+ except Exception:
184
+ mid = QPointF(
185
+ (self.posSource[0] + self.posDestination[0]) / 2.0,
186
+ (self.posSource[1] + self.posDestination[1]) / 2.0,
187
+ )
188
+ rect = self.labelItem.boundingRect()
189
+ self.labelItem.setPos(mid + self._label_offset - QPointF(rect.width() / 2.0, rect.height() / 2.0))
190
+
191
+ def onSelected(self) -> None:
192
+ """Our event handling when the edge was selected"""
193
+ self.edge.scene.grScene.itemSelected.emit()
194
+
195
+ def doSelect(self, new_state: bool = True) -> None:
196
+ """Safe version of selecting the `Graphics Node`. Takes care about the selection state flag used internally
197
+
198
+ :param new_state: ``True`` to select, ``False`` to deselect
199
+ :type new_state: ``bool``
200
+ """
201
+ self.setSelected(new_state)
202
+ self._last_selected_state = new_state
203
+ if new_state:
204
+ self.onSelected()
205
+
206
+ def mouseReleaseEvent(self, event) -> None:
207
+ """Overridden Qt's method to handle selecting and deselecting this `Graphics Edge`"""
208
+ super().mouseReleaseEvent(event)
209
+ if self._last_selected_state != self.isSelected():
210
+ self.edge.scene.resetLastSelectedStates()
211
+ self._last_selected_state = self.isSelected()
212
+ self.onSelected()
213
+
214
+ def hoverEnterEvent(self, event: Optional['QGraphicsSceneHoverEvent']) -> None:
215
+ """Handle hover effect
216
+
217
+ :param event: The hover event
218
+ :type event: Optional[QGraphicsSceneHoverEvent]
219
+ """
220
+ self.hovered = True
221
+ self.update()
222
+
223
+ def hoverLeaveEvent(self, event: Optional['QGraphicsSceneHoverEvent']) -> None:
224
+ """Handle hover effect
225
+
226
+ :param event: The hover event
227
+ :type event: Optional[QGraphicsSceneHoverEvent]
228
+ """
229
+ self.hovered = False
230
+ self.update()
231
+
232
+ def setSource(self, x: float, y: float) -> None:
233
+ """ Set source point
234
+
235
+ :param x: x position
236
+ :type x: ``float``
237
+ :param y: y position
238
+ :type y: ``float``
239
+ """
240
+ self.setPositions((x, y), self.posDestination)
241
+
242
+ def setDestination(self, x: float, y: float) -> None:
243
+ """ Set destination point
244
+
245
+ :param x: x position
246
+ :type x: ``float``
247
+ :param y: y position
248
+ :type y: ``float``
249
+ """
250
+ self.setPositions(self.posSource, (x, y))
251
+
252
+ def setPositions(self, source, destination) -> None:
253
+ """Set both ends and adopt the resulting path once.
254
+
255
+ A node move changes both ends. Adopting inside each setter would run the
256
+ route calculation twice per edge per move, and that calculation is the
257
+ expensive part of moving a node.
258
+
259
+ :param source: new source point
260
+ :param destination: new destination point
261
+ """
262
+ self.posSource = [source[0], source[1]]
263
+ self.posDestination = [destination[0], destination[1]]
264
+ self.adoptGeometry()
265
+
266
+ def adoptGeometry(self) -> None:
267
+ """Recompute the drawn path right away, so Qt invalidates the true rect.
268
+
269
+ The setters run before the caller repaints, and a path picked up only
270
+ while painting invalidates the *previous* geometry (see
271
+ :meth:`refreshPath`).
272
+ """
273
+ if getattr(self, "pathCalculator", None) is None:
274
+ return
275
+ if getattr(self, "labelItem", None) is None:
276
+ return
277
+ self.refreshPath()
278
+
279
+ def strokeMargin(self) -> float:
280
+ """Half the widest pen this edge can paint with, plus an antialias pixel.
281
+
282
+ ``paint`` strokes the path, and a stroke reaches half its width past the
283
+ path on both sides. A ``boundingRect`` of the bare path is therefore
284
+ smaller than what the item draws, and every repaint leaves the outer band
285
+ of the *previous* stroke on screen. That is not a one-pixel cosmetic
286
+ miss: while a node is dragged, each frame adds a band along the path's
287
+ old position, and dozens of frames accumulate into the smeared arcs and
288
+ loops the canvas was showing.
289
+ """
290
+ widest = 0.0
291
+ for name in ("_pen", "_pen_selected", "_pen_dragging", "_pen_hovered"):
292
+ pen = getattr(self, name, None)
293
+ if pen is not None:
294
+ widest = max(widest, pen.widthF())
295
+ return widest / 2.0 + 1.0
296
+
297
+ def boundingRect(self) -> QRectF:
298
+ """Defining Qt' bounding rectangle (path united with label rect)."""
299
+ margin = self.strokeMargin()
300
+ base = self.shape().boundingRect().adjusted(-margin, -margin, margin, margin)
301
+ try:
302
+ if self.labelItem.isVisible():
303
+ label_rect = self.labelItem.boundingRect().translated(self.labelItem.pos())
304
+ return base.united(label_rect)
305
+ except Exception:
306
+ pass
307
+ return base
308
+
309
+ def refreshPath(self) -> None:
310
+ """Adopt the current path and label position now.
311
+
312
+ Qt invalidates an item by its *declared* geometry, so an item that only
313
+ adopts a new path while painting invalidates the previous one and draws
314
+ the new one: whatever the old path covered outside both rectangles stays
315
+ on screen. Callers that know the edge moved (``updatePositions``) call
316
+ this first, which makes ``setPath`` repaint the union of old and new.
317
+ """
318
+ self.setPath(self.calcPath())
319
+ self.updateLabelPosition()
320
+
321
+ def shape(self) -> QPainterPath:
322
+ """Returns ``QPainterPath`` representation of this `Edge`
323
+
324
+ :return: path representation
325
+ :rtype: ``QPainterPath``
326
+ """
327
+ return self.calcPath()
328
+
329
+ def paint(self, painter, QStyleOptionGraphicsItem, widget=None) -> None:
330
+ """Qt's overridden method to paint this Graphics Edge. Path calculated
331
+ in :func:`~nodeeditor.node_graphics_edge.QDMGraphicsEdge.calcPath` method"""
332
+ # A no-op once refreshPath() has run for this geometry (Qt's setPath
333
+ # returns early on an unchanged path); kept for consumers that only ever
334
+ # paint.
335
+ self.setPath(self.calcPath())
336
+ self.updateLabelPosition()
337
+
338
+ painter.setBrush(Qt.BrushStyle.NoBrush)
339
+
340
+ if self.hovered and self.edge.end_socket is not None:
341
+ painter.setPen(self._pen_hovered)
342
+ painter.drawPath(self.path())
343
+
344
+ if self.edge.end_socket is None:
345
+ painter.setPen(self._pen_dragging)
346
+ else:
347
+ painter.setPen(self._pen if not self.isSelected()
348
+ else self._pen_selected)
349
+
350
+ painter.drawPath(self.path())
351
+
352
+ def intersectsWith(self, p1: QPointF, p2: QPointF) -> bool:
353
+ """Does this Graphics Edge intersect with the line between point A and point B ?
354
+
355
+ :param p1: point A
356
+ :type p1: ``QPointF``
357
+ :param p2: point B
358
+ :type p2: ``QPointF``
359
+ :return: ``True`` if this `Graphics Edge` intersects
360
+ :rtype: ``bool``
361
+ """
362
+ cutpath = QPainterPath(p1)
363
+ cutpath.lineTo(p2)
364
+ path = self.calcPath()
365
+ return cutpath.intersects(path)
366
+
367
+ def calcPath(self) -> QPainterPath:
368
+ """Will handle drawing QPainterPath from Point A to B. Internally there exist self.pathCalculator which
369
+ is an instance of derived :class:`~nodeeditor.node_graphics_edge_path.GraphicsEdgePathBase` class
370
+ containing the actual `calcPath()` function - computing how the edge should look like.
371
+
372
+ :returns: ``QPainterPath`` of the edge connecting `source` and `destination`
373
+ :rtype: ``QPainterPath``
374
+ """
375
+ return self.pathCalculator.calcPath()
@@ -0,0 +1,226 @@
1
+ """
2
+ A module containing the classes that calculate the graphics path drawn for an Edge.
3
+
4
+ Every class derives from :class:`GraphicsEdgePathBase` and implements
5
+ :meth:`GraphicsEdgePathBase.calcPath`, which turns the positions of the edge's
6
+ two sockets into the :class:`~qtpy.QtGui.QPainterPath` that gets painted.
7
+ """
8
+ import math
9
+ from qtpy.QtCore import QPointF
10
+ from qtpy.QtGui import QPainterPath
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
+ from nodeeditor.node_graphics_edge import QDMGraphicsEdge
20
+
21
+
22
+ EDGE_CP_ROUNDNESS = 100 #: Bezier control point distance on the line
23
+ #: factor for square edge to change the midpoint between start and end socket
24
+ WEIGHT_SOURCE = 0.2
25
+
26
+ #: Scale EDGE_CURVATURE with distance of the edge endpoints
27
+ EDGE_IBCP_ROUNDNESS = 75
28
+ NODE_DISTANCE = 12
29
+ EDGE_CURVATURE = 2
30
+
31
+
32
+ class GraphicsEdgePathBase:
33
+ """Base Class for calculating the graphics path to draw for an graphics Edge"""
34
+
35
+ def __init__(self, owner: 'QDMGraphicsEdge') -> None:
36
+ """
37
+ :param owner: the :class:`~nodeeditor.node_graphics_edge.QDMGraphicsEdge` this path belongs to
38
+ :type owner: :class:`~nodeeditor.node_graphics_edge.QDMGraphicsEdge`
39
+ """
40
+ # keep the reference to owner GraphicsEdge class
41
+ self.owner = owner
42
+
43
+ def calcPath(self):
44
+ """Calculate the Direct line connection
45
+
46
+ :returns: ``QPainterPath`` of the graphics path to draw
47
+ :rtype: ``QPainterPath`` or ``None``
48
+ """
49
+ return None
50
+
51
+
52
+ class GraphicsEdgePathDirect(GraphicsEdgePathBase):
53
+ """Direct line connection Graphics Edge"""
54
+
55
+ def calcPath(self) -> QPainterPath:
56
+ """Calculate the Direct line connection
57
+
58
+ :returns: ``QPainterPath`` of the direct line
59
+ :rtype: ``QPainterPath``
60
+ """
61
+ path = QPainterPath(
62
+ QPointF(self.owner.posSource[0], self.owner.posSource[1]))
63
+ path.lineTo(self.owner.posDestination[0], self.owner.posDestination[1])
64
+ return path
65
+
66
+
67
+ class GraphicsEdgePathBezier(GraphicsEdgePathBase):
68
+ """Cubic line connection Graphics Edge"""
69
+
70
+ def calcPath(self) -> QPainterPath:
71
+ """Calculate the cubic Bezier line connection with 2 control points
72
+
73
+ :returns: ``QPainterPath`` of the cubic Bezier line
74
+ :rtype: ``QPainterPath``
75
+ """
76
+ s = self.owner.posSource
77
+ d = self.owner.posDestination
78
+ dist = (d[0] - s[0]) * 0.5
79
+
80
+ cpx_s: float = +dist
81
+ cpx_d: float = -dist
82
+ cpy_s: float = 0.0
83
+ cpy_d: float = 0.0
84
+
85
+ if self.owner.edge.start_socket is not None:
86
+ ssin = self.owner.edge.start_socket.is_input
87
+ ssout = self.owner.edge.start_socket.is_output
88
+
89
+ if (s[0] > d[0] and ssout) or (s[0] < d[0] and ssin):
90
+ cpx_d *= -1
91
+ cpx_s *= -1
92
+
93
+ cpy_d = (
94
+ (s[1] - d[1]) / math.fabs(
95
+ (s[1] - d[1]) if (s[1] - d[1]) != 0 else 0.00001
96
+ )
97
+ ) * EDGE_CP_ROUNDNESS
98
+ cpy_s = (
99
+ (d[1] - s[1]) / math.fabs(
100
+ (d[1] - s[1]) if (d[1] - s[1]) != 0 else 0.00001
101
+ )
102
+ ) * EDGE_CP_ROUNDNESS
103
+
104
+ path = QPainterPath(
105
+ QPointF(self.owner.posSource[0], self.owner.posSource[1]))
106
+ path.cubicTo(s[0] + cpx_s, s[1] + cpy_s, d[0] + cpx_d, d[1] + cpy_d,
107
+ self.owner.posDestination[0], self.owner.posDestination[1])
108
+
109
+ return path
110
+
111
+
112
+ class GraphicsEdgePathSquare(GraphicsEdgePathBase):
113
+ """Square line connection Graphics Edge"""
114
+
115
+ def __init__(self, *args, handle_weight: float=0.5, **kwargs) -> None:
116
+ """
117
+ :param args: positional arguments passed on to the base class
118
+ :param handle_weight: where the vertical part of the square sits, ``0.5`` being the middle
119
+ :param kwargs: keyword arguments passed on to the base class
120
+ """
121
+ super().__init__(*args, **kwargs)
122
+ self.rand = None
123
+ self.handle_weight = handle_weight
124
+
125
+ def calcPath(self):
126
+ """Calculate the square edge line connection
127
+
128
+ :returns: ``QPainterPath`` of the edge square line
129
+ :rtype: ``QPainterPath``
130
+ """
131
+
132
+ s = self.owner.posSource
133
+ d = self.owner.posDestination
134
+
135
+ mid_x = s[0] + ((d[0] - s[0]) * self.handle_weight)
136
+
137
+ path = QPainterPath(QPointF(s[0], s[1]))
138
+ path.lineTo(mid_x, s[1])
139
+ path.lineTo(mid_x, d[1])
140
+ path.lineTo(d[0], d[1])
141
+
142
+ return path
143
+
144
+
145
+ class GraphicsEdgePathImprovedSharp(GraphicsEdgePathBase):
146
+ """Better Cubic line connection Graphics Edge"""
147
+
148
+ def calcPath(self) -> QPainterPath:
149
+ """Calculate the Direct line connection
150
+
151
+ :returns: ``QPainterPath`` of the painting line
152
+ :rtype: ``QPainterPath``
153
+ """
154
+ sx, sy = self.owner.posSource
155
+ dx, dy = self.owner.posDestination
156
+ distx, disty = dx-sx, dy-sy
157
+ dist = math.sqrt(distx*distx + disty*disty)
158
+
159
+ # is start / end socket on left side?
160
+ sleft = self.owner.edge.start_socket.position <= 3
161
+
162
+ # if the drag edge started from input socket, we should connect to output socket...
163
+ eleft = self.owner.edge.start_socket.position > 3
164
+
165
+ if self.owner.edge.end_socket is not None:
166
+ eleft = self.owner.edge.end_socket.position <= 3
167
+
168
+ node_sdist = (-NODE_DISTANCE) if sleft else NODE_DISTANCE
169
+ node_edist = (-NODE_DISTANCE) if eleft else NODE_DISTANCE
170
+
171
+ path = QPainterPath(QPointF(sx, sy))
172
+
173
+ if abs(dist) > NODE_DISTANCE:
174
+ path.lineTo(sx + node_sdist, sy)
175
+ path.lineTo(dx + node_edist, dy)
176
+
177
+ path.lineTo(dx, dy)
178
+
179
+ return path
180
+
181
+
182
+ class GraphicsEdgePathImprovedBezier(GraphicsEdgePathBase):
183
+ """Better Cubic line connection Graphics Edge"""
184
+
185
+ def calcPath(self) -> QPainterPath:
186
+ """Calculate the Direct line connection
187
+
188
+ :returns: ``QPainterPath`` of the painting line
189
+ :rtype: ``QPainterPath``
190
+ """
191
+ sx, sy = self.owner.posSource
192
+ dx, dy = self.owner.posDestination
193
+ distx, disty = dx-sx, dy-sy
194
+ dist = math.sqrt(distx*distx + disty*disty)
195
+
196
+ # is start / end socket on left side?
197
+ sleft = self.owner.edge.start_socket.position <= 3
198
+
199
+ # if the drag edge started from input socket, we should connect to output socket...
200
+ eleft = self.owner.edge.start_socket.position > 3
201
+
202
+ if self.owner.edge.end_socket is not None:
203
+ eleft = self.owner.edge.end_socket.position <= 3
204
+
205
+ path = QPainterPath(QPointF(sx, sy))
206
+
207
+ if abs(dist) > NODE_DISTANCE:
208
+ curvature = max(EDGE_CURVATURE, (EDGE_CURVATURE *
209
+ abs(dist)) / EDGE_IBCP_ROUNDNESS)
210
+
211
+ node_sdist = (-NODE_DISTANCE) if sleft else NODE_DISTANCE
212
+ node_edist = (-NODE_DISTANCE) if eleft else NODE_DISTANCE
213
+
214
+ path.lineTo(sx + node_sdist, sy)
215
+
216
+ path.cubicTo(
217
+ QPointF(sx + node_sdist * curvature, sy),
218
+ QPointF(dx + node_edist * curvature, dy),
219
+ QPointF(dx + node_edist, dy)
220
+ )
221
+
222
+ path.lineTo(dx + node_edist, dy)
223
+
224
+ path.lineTo(dx, dy)
225
+
226
+ return path