Skip to content

Commit 5c11b10

Browse files
committed
PipeItem optimizations.
1 parent e10a874 commit 5c11b10

File tree

1 file changed

+131
-114
lines changed

1 file changed

+131
-114
lines changed

NodeGraphQt/qgraphics/pipe.py

Lines changed: 131 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,26 @@ def __init__(self, input_port=None, output_port=None):
3131
self.setZValue(Z_VAL_PIPE)
3232
self.setAcceptHoverEvents(True)
3333
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable)
34+
self.setCacheMode(ITEM_CACHE_MODE)
35+
3436
self._color = PipeEnum.COLOR.value
3537
self._style = PipeEnum.DRAW_TYPE_DEFAULT.value
3638
self._active = False
3739
self._highlight = False
3840
self._input_port = input_port
3941
self._output_port = output_port
42+
4043
size = 6.0
41-
self._arrow = QtGui.QPolygonF()
42-
self._arrow.append(QtCore.QPointF(-size, size))
43-
self._arrow.append(QtCore.QPointF(0.0, -size * 1.5))
44-
self._arrow.append(QtCore.QPointF(size, size))
45-
self.setCacheMode(ITEM_CACHE_MODE)
44+
self._poly = QtGui.QPolygonF()
45+
self._poly.append(QtCore.QPointF(-size, size))
46+
self._poly.append(QtCore.QPointF(0.0, -size * 1.5))
47+
self._poly.append(QtCore.QPointF(size, size))
48+
49+
self._dir_pointer = QtWidgets.QGraphicsPolygonItem(self)
50+
self._dir_pointer.setPolygon(self._poly)
51+
self._dir_pointer.setFlag(self.ItemIsSelectable, False)
52+
53+
self.reset()
4654

4755
def __repr__(self):
4856
in_name = self._input_port.name if self._input_port else ''
@@ -63,6 +71,13 @@ def hoverLeaveEvent(self, event):
6371
if self.isSelected():
6472
self.highlight()
6573

74+
def itemChange(self, change, value):
75+
if change == self.ItemSelectedChange and self.scene():
76+
self.reset()
77+
if value:
78+
self.highlight()
79+
return super(PipeItem, self).itemChange(change, value)
80+
6681
def paint(self, painter, option, widget):
6782
"""
6883
Draws the connection line between nodes.
@@ -73,92 +88,52 @@ def paint(self, painter, option, widget):
7388
used to describe the parameters needed to draw.
7489
widget (QtWidgets.QWidget): not used.
7590
"""
91+
painter.save()
7692

77-
# only draw if a port or node is visible.
78-
is_visible = all([
79-
self._input_port.isVisible(),
80-
self._output_port.isVisible(),
81-
self._input_port.node.isVisible(),
82-
self._output_port.node.isVisible()
83-
])
84-
if not is_visible:
85-
painter.save()
86-
painter.setBrush(QtCore.Qt.NoBrush)
87-
painter.setPen(QtCore.Qt.NoPen)
88-
painter.restore()
89-
return
90-
91-
color = QtGui.QColor(*self._color)
92-
pen_style = PIPE_STYLES.get(self.style)
93-
pen_width = PipeEnum.WIDTH.value
94-
if self._active:
95-
color = QtGui.QColor(*PipeEnum.ACTIVE_COLOR.value)
96-
if pen_style == QtCore.Qt.DashDotDotLine:
97-
pen_width += 1
98-
else:
99-
pen_width += 0.35
100-
elif self._highlight:
101-
color = QtGui.QColor(*PipeEnum.HIGHLIGHT_COLOR.value)
102-
pen_style = PIPE_STYLES.get(PipeEnum.DRAW_TYPE_DEFAULT.value)
103-
93+
pen = self.pen()
10494
if self.disabled():
10595
if not self._active:
106-
color = QtGui.QColor(*PipeEnum.DISABLED_COLOR.value)
107-
pen_width += 0.2
108-
pen_style = PIPE_STYLES.get(PipeEnum.DRAW_TYPE_DOTTED.value)
109-
110-
pen = QtGui.QPen(color, pen_width, pen_style)
111-
pen.setCapStyle(QtCore.Qt.RoundCap)
112-
pen.setJoinStyle(QtCore.Qt.MiterJoin)
96+
pen.setColor(QtGui.QColor(*PipeEnum.DISABLED_COLOR.value))
97+
pen.setStyle(PIPE_STYLES.get(PipeEnum.DRAW_TYPE_DOTTED.value))
11398

114-
painter.save()
11599
painter.setPen(pen)
100+
painter.setBrush(self.brush())
116101
painter.setRenderHint(painter.Antialiasing, True)
117102
painter.drawPath(self.path())
118103

119-
# draw arrow
120-
if self.input_port and self.output_port:
121-
cen_x = self.path().pointAtPercent(0.5).x()
122-
cen_y = self.path().pointAtPercent(0.5).y()
123-
loc_pt = self.path().pointAtPercent(0.49)
124-
tgt_pt = self.path().pointAtPercent(0.51)
125-
126-
dist = math.hypot(tgt_pt.x() - cen_x, tgt_pt.y() - cen_y)
127-
if dist < 0.5:
128-
painter.restore()
129-
return
130-
131-
color.setAlpha(255)
132-
if self._highlight:
133-
painter.setBrush(QtGui.QBrush(color.lighter(150)))
134-
elif self._active or self.disabled():
135-
painter.setBrush(QtGui.QBrush(color.darker(200)))
136-
else:
137-
painter.setBrush(QtGui.QBrush(color.darker(130)))
138-
139-
pen_width = 0.6
140-
if dist < 1.0:
141-
pen_width *= (1.0 + dist)
142-
143-
pen = QtGui.QPen(color, pen_width)
144-
pen.setCapStyle(QtCore.Qt.RoundCap)
145-
pen.setJoinStyle(QtCore.Qt.MiterJoin)
146-
painter.setPen(pen)
147-
148-
transform = QtGui.QTransform()
149-
transform.translate(cen_x, cen_y)
150-
radians = math.atan2(tgt_pt.y() - loc_pt.y(),
151-
tgt_pt.x() - loc_pt.x())
152-
degrees = math.degrees(radians) - 90
153-
transform.rotate(degrees)
154-
if dist < 1.0:
155-
transform.scale(dist, dist)
156-
painter.drawPolygon(transform.map(self._arrow))
157-
158104
# QPaintDevice: Cannot destroy paint device that is being painted.
159105
painter.restore()
160106

161-
def __draw_path_cycled_vertical(self, start_port, pos1, pos2, path):
107+
@staticmethod
108+
def _calc_distance(p1, p2):
109+
x = math.pow((p2.x() - p1.x()), 2)
110+
y = math.pow((p2.y() - p1.y()), 2)
111+
return math.sqrt(x + y)
112+
113+
def _draw_direction_pointer(self):
114+
"""
115+
updates the pipe direction pointer arrow.
116+
"""
117+
if not (self.input_port and self.output_port):
118+
self._dir_pointer.setVisible(False)
119+
return
120+
121+
self._dir_pointer.setVisible(True)
122+
loc_pt = self.path().pointAtPercent(0.49)
123+
tgt_pt = self.path().pointAtPercent(0.51)
124+
radians = math.atan2(tgt_pt.y() - loc_pt.y(),
125+
tgt_pt.x() - loc_pt.x())
126+
degrees = math.degrees(radians) - 90
127+
self._dir_pointer.setRotation(degrees)
128+
self._dir_pointer.setPos(self.path().pointAtPercent(0.5))
129+
130+
cen_x = self.path().pointAtPercent(0.5).x()
131+
cen_y = self.path().pointAtPercent(0.5).y()
132+
dist = math.hypot(tgt_pt.x() - cen_x, tgt_pt.y() - cen_y)
133+
if dist < 1.0:
134+
self._dir_pointer.setScale(dist)
135+
136+
def _draw_path_cycled_vertical(self, start_port, pos1, pos2, path):
162137
"""
163138
Draw pipe vertically around node if connection is cyclic.
164139
@@ -184,7 +159,7 @@ def __draw_path_cycled_vertical(self, start_port, pos1, pos2, path):
184159
path.lineTo(start_pos)
185160
self.setPath(path)
186161

187-
def __draw_path_cycled_horizontal(self, start_port, pos1, pos2, path):
162+
def _draw_path_cycled_horizontal(self, start_port, pos1, pos2, path):
188163
"""
189164
Draw pipe horizontally around node if connection is cyclic.
190165
@@ -210,7 +185,7 @@ def __draw_path_cycled_horizontal(self, start_port, pos1, pos2, path):
210185
path.lineTo(end_pos)
211186
self.setPath(path)
212187

213-
def __draw_path_vertical(self, start_port, pos1, pos2, path):
188+
def _draw_path_vertical(self, start_port, pos1, pos2, path):
214189
"""
215190
Draws the vertical path between ports.
216191
@@ -254,7 +229,7 @@ def __draw_path_vertical(self, start_port, pos1, pos2, path):
254229
path.lineTo(pos2)
255230
self.setPath(path)
256231

257-
def __draw_path_horizontal(self, start_port, pos1, pos2, path):
232+
def _draw_path_horizontal(self, start_port, pos1, pos2, path):
258233
"""
259234
Draws the horizontal path between ports.
260235
@@ -322,6 +297,20 @@ def draw_path(self, start_port, end_port=None, cursor_pos=None):
322297
else:
323298
return
324299

300+
# visibility check for connected pipe.
301+
if self.input_port and self.output_port:
302+
is_visible = all([
303+
self._input_port.isVisible(),
304+
self._output_port.isVisible(),
305+
self._input_port.node.isVisible(),
306+
self._output_port.node.isVisible()
307+
])
308+
self.setVisible(is_visible)
309+
310+
# don't draw pipe if a port or node is not visible.
311+
if not is_visible:
312+
return
313+
325314
line = QtCore.QLineF(pos1, pos2)
326315
path = QtGui.QPainterPath()
327316

@@ -330,39 +319,49 @@ def draw_path(self, start_port, end_port=None, cursor_pos=None):
330319
if end_port and not self.viewer().acyclic:
331320
if end_port.node == start_port.node:
332321
if direction is LayoutDirectionEnum.VERTICAL.value:
333-
self.__draw_path_cycled_vertical(
322+
self._draw_path_cycled_vertical(
334323
start_port, pos1, pos2, path
335324
)
325+
self._draw_direction_pointer()
336326
return
337327
elif direction is LayoutDirectionEnum.HORIZONTAL.value:
338-
self.__draw_path_cycled_horizontal(
328+
self._draw_path_cycled_horizontal(
339329
start_port, pos1, pos2, path
340330
)
331+
self._draw_direction_pointer()
341332
return
342333

343334
path.moveTo(line.x1(), line.y1())
344335

345336
if self.viewer_pipe_layout() == PipeLayoutEnum.STRAIGHT.value:
346337
path.lineTo(pos2)
347338
self.setPath(path)
339+
self._draw_direction_pointer()
348340
return
349341

350342
if direction is LayoutDirectionEnum.VERTICAL.value:
351-
self.__draw_path_vertical(start_port, pos1, pos2, path)
343+
self._draw_path_vertical(start_port, pos1, pos2, path)
352344
elif direction is LayoutDirectionEnum.HORIZONTAL.value:
353-
self.__draw_path_horizontal(start_port, pos1, pos2, path)
345+
self._draw_path_horizontal(start_port, pos1, pos2, path)
346+
347+
self._draw_direction_pointer()
354348

355349
def reset_path(self):
350+
"""
351+
reset the pipe initial path position.
352+
"""
356353
path = QtGui.QPainterPath(QtCore.QPointF(0.0, 0.0))
357354
self.setPath(path)
358355

359-
@staticmethod
360-
def _calc_distance(p1, p2):
361-
x = math.pow((p2.x() - p1.x()), 2)
362-
y = math.pow((p2.y() - p1.y()), 2)
363-
return math.sqrt(x + y)
364-
365356
def port_from_pos(self, pos, reverse=False):
357+
"""
358+
Args:
359+
pos (QtCore.QPointF): current scene position.
360+
reverse (bool): false to return the nearest port.
361+
362+
Returns:
363+
PortItem: port item.
364+
"""
366365
inport_pos = self.input_port.scenePos()
367366
outport_pos = self.output_port.scenePos()
368367
input_dist = self._calc_distance(inport_pos, pos)
@@ -399,34 +398,59 @@ def viewer_layout_direction(self):
399398
if viewer:
400399
return viewer.get_layout_direction()
401400

401+
def set_pipe_styling(self, color, width=0.5, style=0):
402+
"""
403+
Args:
404+
color (list or tuple): (r, g, b, a) values 0-255
405+
width (float): pipe width.
406+
style (int): pipe style.
407+
"""
408+
pen = self.pen()
409+
pen.setWidth(width)
410+
pen.setColor(QtGui.QColor(*color))
411+
pen.setStyle(PIPE_STYLES.get(style))
412+
pen.setJoinStyle(QtCore.Qt.MiterJoin)
413+
pen.setCapStyle(QtCore.Qt.RoundCap)
414+
self.setPen(pen)
415+
self.setBrush(QtCore.Qt.NoBrush)
416+
417+
pen = self._dir_pointer.pen()
418+
pen.setJoinStyle(QtCore.Qt.MiterJoin)
419+
pen.setCapStyle(QtCore.Qt.RoundCap)
420+
pen.setWidth(width)
421+
pen.setColor(QtGui.QColor(*color))
422+
self._dir_pointer.setPen(pen)
423+
self._dir_pointer.setBrush(QtGui.QColor(*color).darker(200))
424+
402425
def activate(self):
403426
self._active = True
404-
color = QtGui.QColor(*PipeEnum.ACTIVE_COLOR.value)
405-
pen = QtGui.QPen(
406-
color, 2.5, PIPE_STYLES.get(PipeEnum.DRAW_TYPE_DEFAULT.value)
427+
self.set_pipe_styling(
428+
color=PipeEnum.ACTIVE_COLOR.value,
429+
width=2.5,
430+
style=PipeEnum.DRAW_TYPE_DEFAULT.value
407431
)
408-
self.setPen(pen)
409432

410433
def active(self):
411434
return self._active
412435

413436
def highlight(self):
414437
self._highlight = True
415-
color = QtGui.QColor(*PipeEnum.HIGHLIGHT_COLOR.value)
416-
pen = QtGui.QPen(
417-
color, 2, PIPE_STYLES.get(PipeEnum.DRAW_TYPE_DEFAULT.value)
438+
self.set_pipe_styling(
439+
color=PipeEnum.HIGHLIGHT_COLOR.value,
440+
width=2.5,
441+
style=PipeEnum.DRAW_TYPE_DEFAULT.value
418442
)
419-
self.setPen(pen)
420443

421444
def highlighted(self):
422445
return self._highlight
423446

424447
def reset(self):
448+
"""
449+
reset the pipe state and styling.
450+
"""
425451
self._active = False
426452
self._highlight = False
427-
color = QtGui.QColor(*self.color)
428-
pen = QtGui.QPen(color, 2, PIPE_STYLES.get(self.style))
429-
self.setPen(pen)
453+
self.set_pipe_styling(color=self.color, width=1.2, style=self.style)
430454

431455
def set_connections(self, port1, port2):
432456
ports = {
@@ -445,13 +469,6 @@ def disabled(self):
445469
return True
446470
return False
447471

448-
def itemChange(self, change, value):
449-
if change == self.ItemSelectedChange and self.scene():
450-
self.reset()
451-
if value:
452-
self.highlight()
453-
return super(PipeItem, self).itemChange(change, value)
454-
455472
@property
456473
def input_port(self):
457474
return self._input_port
@@ -515,7 +532,7 @@ def __init__(self):
515532
pen.setCapStyle(QtCore.Qt.RoundCap)
516533

517534
self._idx_pointer = LivePipePolygonItem(self)
518-
self._idx_pointer.setPolygon(self._arrow)
535+
self._idx_pointer.setPolygon(self._poly)
519536
self._idx_pointer.setBrush(color.darker(300))
520537
self._idx_pointer.setPen(pen)
521538

@@ -638,7 +655,7 @@ def draw_index_pointer(self, start_port, cursor_pos, color_mode=None):
638655
self._idx_text.setPos(*text_pos)
639656
self._idx_text.setPlainText('{}'.format(start_port.name))
640657

641-
self._idx_pointer.setPolygon(transform.map(self._arrow))
658+
self._idx_pointer.setPolygon(transform.map(self._poly))
642659

643660
if color_mode == 'accept':
644661
color = QtGui.QColor(*PipeEnum.HIGHLIGHT_COLOR.value)
@@ -660,7 +677,7 @@ class LivePipePolygonItem(QtWidgets.QGraphicsPolygonItem):
660677

661678
def __init__(self, parent):
662679
super(LivePipePolygonItem, self).__init__(parent)
663-
self.setFlag(self.ItemIsSelectable, True)
680+
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
664681

665682
def paint(self, painter, option, widget):
666683
"""

0 commit comments

Comments
 (0)