From 5c11b10b9460152a2a2cab403b0db238b96bbf17 Mon Sep 17 00:00:00 2001 From: jchanvfx Date: Mon, 8 May 2023 21:47:18 +1200 Subject: [PATCH 1/4] PipeItem optimizations. --- NodeGraphQt/qgraphics/pipe.py | 245 ++++++++++++++++++---------------- 1 file changed, 131 insertions(+), 114 deletions(-) diff --git a/NodeGraphQt/qgraphics/pipe.py b/NodeGraphQt/qgraphics/pipe.py index 684a1f39..cd557156 100644 --- a/NodeGraphQt/qgraphics/pipe.py +++ b/NodeGraphQt/qgraphics/pipe.py @@ -31,18 +31,26 @@ def __init__(self, input_port=None, output_port=None): self.setZValue(Z_VAL_PIPE) self.setAcceptHoverEvents(True) self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable) + self.setCacheMode(ITEM_CACHE_MODE) + self._color = PipeEnum.COLOR.value self._style = PipeEnum.DRAW_TYPE_DEFAULT.value self._active = False self._highlight = False self._input_port = input_port self._output_port = output_port + size = 6.0 - self._arrow = QtGui.QPolygonF() - self._arrow.append(QtCore.QPointF(-size, size)) - self._arrow.append(QtCore.QPointF(0.0, -size * 1.5)) - self._arrow.append(QtCore.QPointF(size, size)) - self.setCacheMode(ITEM_CACHE_MODE) + self._poly = QtGui.QPolygonF() + self._poly.append(QtCore.QPointF(-size, size)) + self._poly.append(QtCore.QPointF(0.0, -size * 1.5)) + self._poly.append(QtCore.QPointF(size, size)) + + self._dir_pointer = QtWidgets.QGraphicsPolygonItem(self) + self._dir_pointer.setPolygon(self._poly) + self._dir_pointer.setFlag(self.ItemIsSelectable, False) + + self.reset() def __repr__(self): in_name = self._input_port.name if self._input_port else '' @@ -63,6 +71,13 @@ def hoverLeaveEvent(self, event): if self.isSelected(): self.highlight() + def itemChange(self, change, value): + if change == self.ItemSelectedChange and self.scene(): + self.reset() + if value: + self.highlight() + return super(PipeItem, self).itemChange(change, value) + def paint(self, painter, option, widget): """ Draws the connection line between nodes. @@ -73,92 +88,52 @@ def paint(self, painter, option, widget): used to describe the parameters needed to draw. widget (QtWidgets.QWidget): not used. """ + painter.save() - # only draw if a port or node is visible. - is_visible = all([ - self._input_port.isVisible(), - self._output_port.isVisible(), - self._input_port.node.isVisible(), - self._output_port.node.isVisible() - ]) - if not is_visible: - painter.save() - painter.setBrush(QtCore.Qt.NoBrush) - painter.setPen(QtCore.Qt.NoPen) - painter.restore() - return - - color = QtGui.QColor(*self._color) - pen_style = PIPE_STYLES.get(self.style) - pen_width = PipeEnum.WIDTH.value - if self._active: - color = QtGui.QColor(*PipeEnum.ACTIVE_COLOR.value) - if pen_style == QtCore.Qt.DashDotDotLine: - pen_width += 1 - else: - pen_width += 0.35 - elif self._highlight: - color = QtGui.QColor(*PipeEnum.HIGHLIGHT_COLOR.value) - pen_style = PIPE_STYLES.get(PipeEnum.DRAW_TYPE_DEFAULT.value) - + pen = self.pen() if self.disabled(): if not self._active: - color = QtGui.QColor(*PipeEnum.DISABLED_COLOR.value) - pen_width += 0.2 - pen_style = PIPE_STYLES.get(PipeEnum.DRAW_TYPE_DOTTED.value) - - pen = QtGui.QPen(color, pen_width, pen_style) - pen.setCapStyle(QtCore.Qt.RoundCap) - pen.setJoinStyle(QtCore.Qt.MiterJoin) + pen.setColor(QtGui.QColor(*PipeEnum.DISABLED_COLOR.value)) + pen.setStyle(PIPE_STYLES.get(PipeEnum.DRAW_TYPE_DOTTED.value)) - painter.save() painter.setPen(pen) + painter.setBrush(self.brush()) painter.setRenderHint(painter.Antialiasing, True) painter.drawPath(self.path()) - # draw arrow - if self.input_port and self.output_port: - cen_x = self.path().pointAtPercent(0.5).x() - cen_y = self.path().pointAtPercent(0.5).y() - loc_pt = self.path().pointAtPercent(0.49) - tgt_pt = self.path().pointAtPercent(0.51) - - dist = math.hypot(tgt_pt.x() - cen_x, tgt_pt.y() - cen_y) - if dist < 0.5: - painter.restore() - return - - color.setAlpha(255) - if self._highlight: - painter.setBrush(QtGui.QBrush(color.lighter(150))) - elif self._active or self.disabled(): - painter.setBrush(QtGui.QBrush(color.darker(200))) - else: - painter.setBrush(QtGui.QBrush(color.darker(130))) - - pen_width = 0.6 - if dist < 1.0: - pen_width *= (1.0 + dist) - - pen = QtGui.QPen(color, pen_width) - pen.setCapStyle(QtCore.Qt.RoundCap) - pen.setJoinStyle(QtCore.Qt.MiterJoin) - painter.setPen(pen) - - transform = QtGui.QTransform() - transform.translate(cen_x, cen_y) - radians = math.atan2(tgt_pt.y() - loc_pt.y(), - tgt_pt.x() - loc_pt.x()) - degrees = math.degrees(radians) - 90 - transform.rotate(degrees) - if dist < 1.0: - transform.scale(dist, dist) - painter.drawPolygon(transform.map(self._arrow)) - # QPaintDevice: Cannot destroy paint device that is being painted. painter.restore() - def __draw_path_cycled_vertical(self, start_port, pos1, pos2, path): + @staticmethod + def _calc_distance(p1, p2): + x = math.pow((p2.x() - p1.x()), 2) + y = math.pow((p2.y() - p1.y()), 2) + return math.sqrt(x + y) + + def _draw_direction_pointer(self): + """ + updates the pipe direction pointer arrow. + """ + if not (self.input_port and self.output_port): + self._dir_pointer.setVisible(False) + return + + self._dir_pointer.setVisible(True) + loc_pt = self.path().pointAtPercent(0.49) + tgt_pt = self.path().pointAtPercent(0.51) + radians = math.atan2(tgt_pt.y() - loc_pt.y(), + tgt_pt.x() - loc_pt.x()) + degrees = math.degrees(radians) - 90 + self._dir_pointer.setRotation(degrees) + self._dir_pointer.setPos(self.path().pointAtPercent(0.5)) + + cen_x = self.path().pointAtPercent(0.5).x() + cen_y = self.path().pointAtPercent(0.5).y() + dist = math.hypot(tgt_pt.x() - cen_x, tgt_pt.y() - cen_y) + if dist < 1.0: + self._dir_pointer.setScale(dist) + + def _draw_path_cycled_vertical(self, start_port, pos1, pos2, path): """ Draw pipe vertically around node if connection is cyclic. @@ -184,7 +159,7 @@ def __draw_path_cycled_vertical(self, start_port, pos1, pos2, path): path.lineTo(start_pos) self.setPath(path) - def __draw_path_cycled_horizontal(self, start_port, pos1, pos2, path): + def _draw_path_cycled_horizontal(self, start_port, pos1, pos2, path): """ Draw pipe horizontally around node if connection is cyclic. @@ -210,7 +185,7 @@ def __draw_path_cycled_horizontal(self, start_port, pos1, pos2, path): path.lineTo(end_pos) self.setPath(path) - def __draw_path_vertical(self, start_port, pos1, pos2, path): + def _draw_path_vertical(self, start_port, pos1, pos2, path): """ Draws the vertical path between ports. @@ -254,7 +229,7 @@ def __draw_path_vertical(self, start_port, pos1, pos2, path): path.lineTo(pos2) self.setPath(path) - def __draw_path_horizontal(self, start_port, pos1, pos2, path): + def _draw_path_horizontal(self, start_port, pos1, pos2, path): """ Draws the horizontal path between ports. @@ -322,6 +297,20 @@ def draw_path(self, start_port, end_port=None, cursor_pos=None): else: return + # visibility check for connected pipe. + if self.input_port and self.output_port: + is_visible = all([ + self._input_port.isVisible(), + self._output_port.isVisible(), + self._input_port.node.isVisible(), + self._output_port.node.isVisible() + ]) + self.setVisible(is_visible) + + # don't draw pipe if a port or node is not visible. + if not is_visible: + return + line = QtCore.QLineF(pos1, pos2) path = QtGui.QPainterPath() @@ -330,14 +319,16 @@ def draw_path(self, start_port, end_port=None, cursor_pos=None): if end_port and not self.viewer().acyclic: if end_port.node == start_port.node: if direction is LayoutDirectionEnum.VERTICAL.value: - self.__draw_path_cycled_vertical( + self._draw_path_cycled_vertical( start_port, pos1, pos2, path ) + self._draw_direction_pointer() return elif direction is LayoutDirectionEnum.HORIZONTAL.value: - self.__draw_path_cycled_horizontal( + self._draw_path_cycled_horizontal( start_port, pos1, pos2, path ) + self._draw_direction_pointer() return path.moveTo(line.x1(), line.y1()) @@ -345,24 +336,32 @@ def draw_path(self, start_port, end_port=None, cursor_pos=None): if self.viewer_pipe_layout() == PipeLayoutEnum.STRAIGHT.value: path.lineTo(pos2) self.setPath(path) + self._draw_direction_pointer() return if direction is LayoutDirectionEnum.VERTICAL.value: - self.__draw_path_vertical(start_port, pos1, pos2, path) + self._draw_path_vertical(start_port, pos1, pos2, path) elif direction is LayoutDirectionEnum.HORIZONTAL.value: - self.__draw_path_horizontal(start_port, pos1, pos2, path) + self._draw_path_horizontal(start_port, pos1, pos2, path) + + self._draw_direction_pointer() def reset_path(self): + """ + reset the pipe initial path position. + """ path = QtGui.QPainterPath(QtCore.QPointF(0.0, 0.0)) self.setPath(path) - @staticmethod - def _calc_distance(p1, p2): - x = math.pow((p2.x() - p1.x()), 2) - y = math.pow((p2.y() - p1.y()), 2) - return math.sqrt(x + y) - def port_from_pos(self, pos, reverse=False): + """ + Args: + pos (QtCore.QPointF): current scene position. + reverse (bool): false to return the nearest port. + + Returns: + PortItem: port item. + """ inport_pos = self.input_port.scenePos() outport_pos = self.output_port.scenePos() input_dist = self._calc_distance(inport_pos, pos) @@ -399,34 +398,59 @@ def viewer_layout_direction(self): if viewer: return viewer.get_layout_direction() + def set_pipe_styling(self, color, width=0.5, style=0): + """ + Args: + color (list or tuple): (r, g, b, a) values 0-255 + width (float): pipe width. + style (int): pipe style. + """ + pen = self.pen() + pen.setWidth(width) + pen.setColor(QtGui.QColor(*color)) + pen.setStyle(PIPE_STYLES.get(style)) + pen.setJoinStyle(QtCore.Qt.MiterJoin) + pen.setCapStyle(QtCore.Qt.RoundCap) + self.setPen(pen) + self.setBrush(QtCore.Qt.NoBrush) + + pen = self._dir_pointer.pen() + pen.setJoinStyle(QtCore.Qt.MiterJoin) + pen.setCapStyle(QtCore.Qt.RoundCap) + pen.setWidth(width) + pen.setColor(QtGui.QColor(*color)) + self._dir_pointer.setPen(pen) + self._dir_pointer.setBrush(QtGui.QColor(*color).darker(200)) + def activate(self): self._active = True - color = QtGui.QColor(*PipeEnum.ACTIVE_COLOR.value) - pen = QtGui.QPen( - color, 2.5, PIPE_STYLES.get(PipeEnum.DRAW_TYPE_DEFAULT.value) + self.set_pipe_styling( + color=PipeEnum.ACTIVE_COLOR.value, + width=2.5, + style=PipeEnum.DRAW_TYPE_DEFAULT.value ) - self.setPen(pen) def active(self): return self._active def highlight(self): self._highlight = True - color = QtGui.QColor(*PipeEnum.HIGHLIGHT_COLOR.value) - pen = QtGui.QPen( - color, 2, PIPE_STYLES.get(PipeEnum.DRAW_TYPE_DEFAULT.value) + self.set_pipe_styling( + color=PipeEnum.HIGHLIGHT_COLOR.value, + width=2.5, + style=PipeEnum.DRAW_TYPE_DEFAULT.value ) - self.setPen(pen) def highlighted(self): return self._highlight def reset(self): + """ + reset the pipe state and styling. + """ self._active = False self._highlight = False - color = QtGui.QColor(*self.color) - pen = QtGui.QPen(color, 2, PIPE_STYLES.get(self.style)) - self.setPen(pen) + self.set_pipe_styling(color=self.color, width=1.2, style=self.style) def set_connections(self, port1, port2): ports = { @@ -445,13 +469,6 @@ def disabled(self): return True return False - def itemChange(self, change, value): - if change == self.ItemSelectedChange and self.scene(): - self.reset() - if value: - self.highlight() - return super(PipeItem, self).itemChange(change, value) - @property def input_port(self): return self._input_port @@ -515,7 +532,7 @@ def __init__(self): pen.setCapStyle(QtCore.Qt.RoundCap) self._idx_pointer = LivePipePolygonItem(self) - self._idx_pointer.setPolygon(self._arrow) + self._idx_pointer.setPolygon(self._poly) self._idx_pointer.setBrush(color.darker(300)) self._idx_pointer.setPen(pen) @@ -638,7 +655,7 @@ def draw_index_pointer(self, start_port, cursor_pos, color_mode=None): self._idx_text.setPos(*text_pos) self._idx_text.setPlainText('{}'.format(start_port.name)) - self._idx_pointer.setPolygon(transform.map(self._arrow)) + self._idx_pointer.setPolygon(transform.map(self._poly)) if color_mode == 'accept': color = QtGui.QColor(*PipeEnum.HIGHLIGHT_COLOR.value) @@ -660,7 +677,7 @@ class LivePipePolygonItem(QtWidgets.QGraphicsPolygonItem): def __init__(self, parent): super(LivePipePolygonItem, self).__init__(parent) - self.setFlag(self.ItemIsSelectable, True) + self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True) def paint(self, painter, option, widget): """ From da2d37810cb12a102ee270f250e5d4251daa7566 Mon Sep 17 00:00:00 2001 From: jchanvfx Date: Mon, 8 May 2023 22:25:08 +1200 Subject: [PATCH 2/4] LivePipeItem optimizations. --- NodeGraphQt/qgraphics/pipe.py | 80 ++++++----------------------------- 1 file changed, 12 insertions(+), 68 deletions(-) diff --git a/NodeGraphQt/qgraphics/pipe.py b/NodeGraphQt/qgraphics/pipe.py index cd557156..467f708c 100644 --- a/NodeGraphQt/qgraphics/pipe.py +++ b/NodeGraphQt/qgraphics/pipe.py @@ -524,87 +524,31 @@ class LivePipeItem(PipeItem): def __init__(self): super(LivePipeItem, self).__init__() self.setZValue(Z_VAL_NODE_WIDGET + 1) + self.shift_selected = False - color = QtGui.QColor(*PipeEnum.ACTIVE_COLOR.value) - pen = QtGui.QPen(color, PipeEnum.WIDTH.value + 0.35) - pen.setJoinStyle(QtCore.Qt.MiterJoin) - pen.setCapStyle(QtCore.Qt.RoundCap) + self._color = PipeEnum.ACTIVE_COLOR.value + self._style = PipeEnum.DRAW_TYPE_DASHED.value + + self.set_pipe_styling(color=self.color, width=2.5, style=self.style) self._idx_pointer = LivePipePolygonItem(self) self._idx_pointer.setPolygon(self._poly) - self._idx_pointer.setBrush(color.darker(300)) + self._idx_pointer.setBrush(QtGui.QColor(*self.color).darker(300)) + pen = self._idx_pointer.pen() + pen.setWidth(self.pen().width()) + pen.setColor(self.pen().color()) + pen.setJoinStyle(QtCore.Qt.MiterJoin) self._idx_pointer.setPen(pen) - color = QtGui.QColor(*PipeEnum.ACTIVE_COLOR.value) - color.setAlpha(50) + color = self.pen().color() + color.setAlpha(80) self._idx_text = QtWidgets.QGraphicsTextItem(self) self._idx_text.setDefaultTextColor(color) font = self._idx_text.font() font.setPointSize(7) self._idx_text.setFont(font) - def paint(self, painter, option, widget): - """ - Draws the connection line. - - Args: - painter (QtGui.QPainter): painter used for drawing the item. - option (QtGui.QStyleOptionGraphicsItem): - used to describe the parameters needed to draw. - widget (QtWidgets.QWidget): not used. - """ - color = QtGui.QColor(*PipeEnum.ACTIVE_COLOR.value) - pen_style = PIPE_STYLES.get(PipeEnum.DRAW_TYPE_DASHED.value) - pen_width = PipeEnum.WIDTH.value + 0.35 - - pen = QtGui.QPen(color, pen_width) - pen.setStyle(pen_style) - pen.setCapStyle(QtCore.Qt.RoundCap) - - painter.save() - painter.setPen(pen) - painter.setRenderHint(painter.Antialiasing, True) - painter.drawPath(self.path()) - - cen_x = self.path().pointAtPercent(0.5).x() - cen_y = self.path().pointAtPercent(0.5).y() - loc_pt = self.path().pointAtPercent(0.9) - tgt_pt = self.path().pointAtPercent(1.0) - - dist = math.hypot(tgt_pt.x() - cen_x, tgt_pt.y() - cen_y) - if dist < 0.05: - painter.restore() - return - - # draw middle circle - size = 10.0 - if dist < 50.0: - size *= (dist / 50.0) - rect = QtCore.QRectF(cen_x-(size/2), cen_y-(size/2), size, size) - painter.setBrush(color) - painter.setPen(QtGui.QPen(color.darker(130), pen_width)) - painter.drawEllipse(rect) - - # draw arrow - color.setAlpha(255) - painter.setBrush(color.darker(200)) - - pen_width = 0.6 - if dist < 1.0: - pen_width *= 1.0 + dist - painter.setPen(QtGui.QPen(color, pen_width)) - - transform = QtGui.QTransform() - transform.translate(tgt_pt.x(), tgt_pt.y()) - - radians = math.atan2(tgt_pt.y() - loc_pt.y(), - tgt_pt.x() - loc_pt.x()) - degrees = math.degrees(radians) + 90 - transform.rotate(degrees) - - painter.restore() - def draw_path(self, start_port, end_port=None, cursor_pos=None, color_mode=None): """ From d5493184e16094735c8acfa4af1df8fa42205931 Mon Sep 17 00:00:00 2001 From: jchanvfx Date: Mon, 8 May 2023 22:25:44 +1200 Subject: [PATCH 3/4] backdrop node calc size fix. --- NodeGraphQt/qgraphics/node_backdrop.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/NodeGraphQt/qgraphics/node_backdrop.py b/NodeGraphQt/qgraphics/node_backdrop.py index 4e806653..628d2186 100644 --- a/NodeGraphQt/qgraphics/node_backdrop.py +++ b/NodeGraphQt/qgraphics/node_backdrop.py @@ -258,8 +258,16 @@ def get_nodes(self, inc_intersects=False): def calc_backdrop_size(self, nodes=None): nodes = nodes or self.get_nodes(True) + if nodes: + nodes_rect = self._combined_rect(nodes) + else: + center = self.mapToScene(self.boundingRect().center()) + nodes_rect = QtCore.QRectF( + center.x(), center.y(), + self._min_size[0], self._min_size[1] + ) + padding = 40 - nodes_rect = self._combined_rect(nodes) return { 'pos': [ nodes_rect.x() - padding, nodes_rect.y() - padding From c77945f45ac3ee65552cc9244ad43bccf0379429 Mon Sep 17 00:00:00 2001 From: jchanvfx Date: Mon, 8 May 2023 22:26:53 +1200 Subject: [PATCH 4/4] version bump --- NodeGraphQt/pkg_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NodeGraphQt/pkg_info.py b/NodeGraphQt/pkg_info.py index 56d18635..0848489c 100644 --- a/NodeGraphQt/pkg_info.py +++ b/NodeGraphQt/pkg_info.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -__version__ = '0.5.10' +__version__ = '0.5.11' __status__ = 'Work in Progress' __license__ = 'MIT'