diff options
-rw-r--r-- | src/main/python/shapes/line.py | 214 | ||||
-rw-r--r-- | src/main/python/shapes/shapes.py | 422 |
2 files changed, 339 insertions, 297 deletions
diff --git a/src/main/python/shapes/line.py b/src/main/python/shapes/line.py index 66be069..1438fbc 100644 --- a/src/main/python/shapes/line.py +++ b/src/main/python/shapes/line.py @@ -14,15 +14,18 @@ class Grabber(QGraphicsPathItem): def __init__(self, annotation_line, index, direction): super(Grabber, self).__init__() - self.m_index = index + # store line to which it connects self.m_annotation_item = annotation_line - self._direction = direction + # index of first point of segment of above line + self.m_index = index + self.direction = direction self.setPath(Grabber.circle) # set graphical settings for this item self.setFlag(QGraphicsItem.ItemIsSelectable, True) self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setAcceptHoverEvents(True) + # initially make invisible self.pen = QPen(Qt.white, -1, Qt.SolidLine) self.brush = QBrush(Qt.transparent) @@ -30,7 +33,7 @@ class Grabber(QGraphicsPathItem): """ move position of grabber after resize""" if change == QGraphicsItem.ItemPositionChange and self.isEnabled(): p = QPointF(self.pos()) - if self._direction == Qt.Horizontal: + if self.direction == Qt.Horizontal: p.setX(value.x()) if self.parentItem().refLine and self.m_index == len(self.parentItem().points) - 2: points = self.parentItem().refLine.points @@ -42,7 +45,7 @@ class Grabber(QGraphicsPathItem): p.setX(min(point1.x(), point2.x())) elif p.x() > max(point1.x(), point2.x()): p.setX(max(point1.x(), point2.x())) - elif self._direction == Qt.Vertical: + elif self.direction == Qt.Vertical: p.setY(value.y()) if self.parentItem().refLine and self.m_index == len(self.parentItem().points) - 2: points = self.parentItem().refLine.points @@ -70,18 +73,16 @@ class Grabber(QGraphicsPathItem): painter.setPen(self.pen) painter.drawPath(self.path()) - # To paint path of shape - # color = Qt.red if self.isSelected() else Qt.black - # painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine)) - # painter.drawPath(self.shape()) - def shape(self): """Overrides shape method and set shape to segment on which grabber is located""" index = self.m_index + # take start and end point of segment startPoint = QPointF(self.parentItem().points[index]) endPoint = QPointF(self.parentItem().points[index + 1]) + # map in grabber's co-ordinate startPoint = self.mapFromParent(startPoint) endPoint = self.mapFromParent(endPoint) + # create path as line path = QPainterPath(startPoint) path.lineTo(endPoint) # generate outlines for path @@ -92,15 +93,12 @@ class Grabber(QGraphicsPathItem): def boundingRect(self): return self.shape().boundingRect() - def mousePressEvent(self, event): - super(Grabber, self).mousePressEvent(event) - def hoverEnterEvent(self, event): """ Changes cursor to horizontal movement or vertical movement depending on the direction of the grabber on mouse enter """ - if self._direction == Qt.Horizontal: + if self.direction == Qt.Horizontal: self.setCursor(QCursor(Qt.SplitHCursor)) else: self.setCursor(QCursor(Qt.SplitVCursor)) @@ -114,10 +112,12 @@ class Grabber(QGraphicsPathItem): super(Grabber, self).hoverLeaveEvent(event) def show(self): + # set pen to show self.pen = QPen(Qt.black, 2, Qt.SolidLine) self.brush = QBrush(Qt.cyan) def hide(self): + # set pen to transparent self.pen = QPen(Qt.white, -1, Qt.SolidLine) self.brush = QBrush(Qt.transparent) @@ -127,57 +127,69 @@ class LineLabel(QGraphicsTextItem): def __init__(self, pos, parent=None): super(LineLabel, self).__init__() + # initial text self.setPlainText("abc") + # stores index of first point of segment self.index = None + # distance from line segment self.gap = None + # set graphical setting self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable) self.setTextInteractionFlags(Qt.NoTextInteraction) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) + # set center of text label at mouse pos self.setPos(pos - self.boundingRect().center()) self.setParentItem(parent) + # add line item to test label self.line = QGraphicsLineItem() self.line.setParentItem(self) self.line.setPen(QPen(Qt.black, 2, Qt.SolidLine)) self.line.setFlag(QGraphicsItem.ItemStacksBehindParent) + # reset position of line self.resetPos() self.values = defaultdict(lambda: 0) def paint(self, painter, option, widget): - painter.save() - painter.setPen(QPen(Qt.black, 2, Qt.SolidLine)) - painter.setBrush(QBrush(Qt.white)) - - painter.drawEllipse(self.boundingRect()) - painter.restore() - + # draw ellipse shape + painter.save() # save painter + painter.setPen(QPen(Qt.black, 2, Qt.SolidLine)) # set pen to painter + painter.setBrush(QBrush(Qt.white)) # set brush + painter.drawEllipse(self.boundingRect()) # draw ellipse + painter.restore() # restore painter as before drawing ellipse super(LineLabel, self).paint(painter, option, widget) def updateLabel(self): - offset = self.gap - points = self.parentItem().points - firstPoint = points[self.index] - endPoint = points[self.index + 1] + # finds new position on segment + offset = self.gap # distance from segment + points = self.parentItem().points # points of line + firstPoint = points[self.index] # first point of segment on which label is attached + endPoint = points[self.index + 1] # second point center = self.mapToParent(self.boundingRect().center()) newPos = center + # if segment is vertical if firstPoint.x() == endPoint.x(): newPos.setX(firstPoint.x() + self.gap) if min(firstPoint.y(), endPoint.y()) > newPos.y(): newPos.setY(min(firstPoint.y(), endPoint.y())) elif newPos.y() > max(firstPoint.y(), endPoint.y()): newPos.setY(max(firstPoint.y(), endPoint.y())) - + # segment is horizontal elif firstPoint.y() == endPoint.y(): newPos.setY(firstPoint.y() + self.gap) if min(firstPoint.x(), endPoint.x()) > newPos.x(): newPos.setX(min(firstPoint.x(), endPoint.x())) elif newPos.x() > max(firstPoint.x(), endPoint.x()): newPos.setX(max(firstPoint.x(), endPoint.x())) + # transfer center positon to top left newPos -= QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) self.setPos(newPos) def resetPos(self): + """ finds the segment of line on which mouse is clicked for adding label + and resets it's position on that segment + """ points = self.parentItem().points min_A = QPointF() min_B = QPointF() @@ -213,9 +225,10 @@ class LineLabel(QGraphicsTextItem): self.gap = -30 + self.boundingRect().height() / 2 def itemChange(self, change, value): + # if position of label changes if change == QGraphicsItem.ItemPositionChange and self.scene(): - newPos = QPointF(value) - newPos += QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) + newPos = QPointF(value) # new position + newPos += QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) # pos of center points = self.parentItem().points firstPoint = points[self.index] endPoint = points[self.index + 1] @@ -229,7 +242,8 @@ class LineLabel(QGraphicsTextItem): newPos.setX(min(firstPoint.x(), endPoint.x())) elif newPos.x() > max(firstPoint.x(), endPoint.x()): newPos.setX(max(firstPoint.x(), endPoint.x())) - newPos -= QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) + newPos -= QPointF(self.boundingRect().width() / 2, + self.boundingRect().height() / 2) # change center pos to top-left return newPos if change == QGraphicsItem.ItemPositionHasChanged and self.scene(): self.updateGap() @@ -238,6 +252,7 @@ class LineLabel(QGraphicsTextItem): return super(LineLabel, self).itemChange(change, value) def updateGap(self): + # updates distance of line from it's connection segment points = self.parentItem().points firstPoint = points[self.index] endPoint = points[self.index + 1] @@ -250,6 +265,7 @@ class LineLabel(QGraphicsTextItem): self.gap = center.y() - firstPoint.y() def updateLine(self): + # updates line item of label points = self.parentItem().points firstPoint = points[self.index] endPoint = points[self.index + 1] @@ -261,6 +277,7 @@ class LineLabel(QGraphicsTextItem): self.line.setLine(center.x(), center.y(), center.x(), point.y()) def mouseDoubleClickEvent(self, event): + # set text editable self.setTextInteractionFlags(Qt.TextEditorInteraction) self.setFocus() super(LineLabel, self).mouseDoubleClickEvent(event) @@ -270,6 +287,8 @@ class LineLabel(QGraphicsTextItem): self.setTextInteractionFlags(Qt.NoTextInteraction) self.nameChanged.emit() + self.setTextInteractionFlags(Qt.NoTextInteraction) # set text non interactive + def __getstate__(self): return { "text": self.toPlainText(), @@ -277,7 +296,7 @@ class LineLabel(QGraphicsTextItem): "gap": self.gap, "pos": (self.pos().x(), self.pos().y()) } - + def __setstate__(self, dict): self.setPlainText(dict['text']) self.index = dict['index'] @@ -285,11 +304,13 @@ class LineLabel(QGraphicsTextItem): def findIndex(line, pos): + # finds index of first point of segment of line which is nearer to given position points = line.points min_A = QPointF() min_B = QPointF() min_dis = math.inf index = -1 + # iterating over points of line for i in range(len(points) - 1): A = points[i] B = points[i + 1] @@ -298,7 +319,7 @@ def findIndex(line, pos): BAy = B.y() - A.y() CAx = C.x() - A.x() CAy = C.y() - A.y() - length = math.sqrt(BAx * BAx + BAy * BAy) + length = math.sqrt(BAx * BAx + BAy * BAy) # length of segment if BAx == 0: if not min(A.y(), B.y()) <= C.y() <= max(A.y(), B.y()): continue @@ -322,10 +343,12 @@ class Line(QGraphicsPathItem): def __init__(self, startPoint, endPoint, **args): QGraphicsItem.__init__(self, **args) + # store startpoint and endpoint of line self.startPoint = startPoint self.endPoint = endPoint # stores all points of line self.points = [] + # stores grips of line self.startGripItem = None self.endGripItem = None self.m_grabbers = [] @@ -339,32 +362,38 @@ class Line(QGraphicsPathItem): self.commonPathsCenters = [] self.midLines = [] self.label = [] - self.arrowFlag = True + # if line point on rectangle grip + self.startGap = None # distance from center of start grip + self.endGap = None # distance from center of end grip def boundingRect(self): + # adjust bounding rect such that it contains intersection curve and arrow rect = self.shape().boundingRect() - rect.adjust(-10,-10,10,10) + rect.adjust(-10, -10, 10, 10) return rect - + def advance(self, phase): + """ called by scene when item moves or updates on scene + """ if not phase: return - # items colliding with line - # items = self.collidingItems(Qt.IntersectsItemShape) + # fine collision with line in ascending order items = self.scene().items(self.shape(), Qt.IntersectsItemShape, Qt.AscendingOrder) - self.commonPathsCenters = [] + self.commonPathsCenters = [] # remove all previous interaction # if item is line and stacked above for item in items: if type(item) in [type(self)]: if item == self: break - shape = item.shape() - shape = self.mapFromItem(item, item.shape()) - commonPath = self.shape().intersected(shape) - polygons = commonPath.toSubpathPolygons() + shape = item.shape() # shape of item + shape = self.mapFromItem(item, item.shape()) # map shape to self + commonPath = self.shape().intersected(shape) # find common path btw lines + polygons = commonPath.toSubpathPolygons() # chane common path to polygon for polygon in polygons: - center = polygon.boundingRect().center() + center = polygon.boundingRect().center() # center of polygon + # if collision is btw perpendicular line if polygon.size() == 5: + # ignore collision if other line has end point on this line if item.refLine: i = len(item.points) - 2 x1, y1 = item.points[i].x(), item.points[i].y() @@ -383,8 +412,7 @@ class Line(QGraphicsPathItem): def paint(self, painter, option, widget): color = Qt.red if self.isSelected() else Qt.black painter.setPen(QPen(color, 2, Qt.SolidLine)) - path = QPainterPath(self.startPoint) - arrowHead = QPolygonF() + path = QPainterPath(self.startPoint) # start path # iterating over all points of line for i in range(len(self.points) - 1): x1, y1 = self.points[i].x(), self.points[i].y() @@ -414,7 +442,8 @@ class Line(QGraphicsPathItem): path.arcTo(QRectF(x - 8, y - 8, 16, 16), 0, -180) path.lineTo(point - QPointF(8, 0)) path.lineTo(self.points[i + 1]) - if i == len(self.points) - 2 and self.arrowFlag: + # draw arrow in last segment + if i == len(self.points) - 2: arrow_size = 20.0 line = QLineF(self.points[i], self.points[i + 1]) if line.length() < 20: @@ -434,13 +463,12 @@ class Line(QGraphicsPathItem): arrowHead.append(line.p2()) arrowHead.append(arrow_p1) arrowHead.append(arrow_p2) - # path.addPolygon(arrowHead) painter.save() painter.setBrush(Qt.black) painter.drawPolygon(arrowHead) painter.restore() - painter.drawPath(path) + painter.drawPath(path) # draw final path def createPath(self): """ @@ -454,6 +482,7 @@ class Line(QGraphicsPathItem): self.points = [self.startPoint, QPointF((x0 + x1) / 2, y0), QPointF((x0 + x1) / 2, y1), self.endPoint] # final path of line if self.refLine: + # line has end point on other line from .shapes import LineGripItem direction = "left" points = self.refLine.points @@ -469,7 +498,7 @@ class Line(QGraphicsPathItem): direction = "top" else: direction = "bottom" - self.endGripItem = LineGripItem(self, -1, direction, self) + self.endGripItem = LineGripItem(-1, [0, 0, direction], self) self.endGripItem.setPos(self.endPoint) if self.startGripItem and self.endGripItem: @@ -495,8 +524,8 @@ class Line(QGraphicsPathItem): pe = QPointF(self.endPoint.x() + offset, self.endPoint.y()) start = self.startPoint end = self.endPoint - sitem = self.startGripItem.mapRectToScene(self.startGripItem.m_annotation_item.boundingRect()) - eitem = self.endGripItem.mapRectToScene(self.endGripItem.m_annotation_item.boundingRect()) + sitem = self.startGripItem.mapRectToScene(self.startGripItem.parentItem().boundingRect()) + eitem = self.endGripItem.mapRectToScene(self.endGripItem.parentItem().boundingRect()) if self.refLine: eitem = self.endGripItem.mapRectToScene(QRectF(0, 0, 0, 0)) @@ -756,6 +785,7 @@ class Line(QGraphicsPathItem): self.setPath(path) if self.refLine: + # remove added grip self.scene().removeItem(self.endGripItem) self.endGripItem = None self.addGrabber() @@ -772,9 +802,12 @@ class Line(QGraphicsPathItem): path.lineTo(self.points[i]) path.lineTo(self.endPoint) self.setPath(path) + # update grabber of line self.updateGrabber() + # update labels connected to line for label in self.label: label.updateLabel() + # update line have end point on this line self.updateMidLines() def updatePoints(self): @@ -853,6 +886,7 @@ class Line(QGraphicsPathItem): def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedHasChanged: + # on selection stack above to show curve on it if value == 1: self.showGripItem() items = self.collidingItems(Qt.IntersectsItemShape) @@ -864,46 +898,63 @@ class Line(QGraphicsPathItem): self.hideGripItem() return if change == QGraphicsItem.ItemSceneHasChanged and not self.scene(): + # if line is removed from scene for line in self.midLines: + # remove lines connected to it if line.scene(): line.scene().removeItem(line) - if self.startGripItem and self.startGripItem.line and not self.startGripItem.tempLine: - self.startGripItem.line = None - if self.endGripItem and self.endGripItem.line: - self.endGripItem.line = None + if self.startGripItem and self.startGripItem.lines and not self.startGripItem.tempLine: + # remove line reference from grips + if self in self.startGripItem.lines: self.startGripItem.lines.remove(self) + if self.endGripItem and self.endGripItem.lines: + if self in self.endGripItem.lines: self.endGripItem.lines.remove(self) if self.refLine: if self in self.refLine.midLines: self.refLine.midLines.remove(self) return super(Line, self).itemChange(change, value) - def updateLine(self, startPoint=None, endPoint=None): + def updateLine(self, endPoint=None): """This function is used to update connecting line when it add on canvas and when it's grip item moves :return: """ - self.prepareGeometryChange() - if startPoint: - self.startPoint = startPoint if endPoint: self.endPoint = endPoint self.createPath() self.updateGrabber() return - - if self.startGripItem and self.endGripItem: + # update start point + if self.startGripItem: item = self.startGripItem - self.startPoint = item.parentItem().mapToScene(item.pos()) + startPoint = item.parentItem().mapToScene(item.pos()) # center of grip + # end grip is rectangle + if item.size and item.m_location in ["top", "bottom"]: + startPoint.setX(startPoint.x() - self.startGap * item.boundingRect().width()) + elif item.size: + startPoint.setY( + startPoint.y() - self.startGap * item.boundingRect().height()) + self.startPoint = startPoint + # end point on line + if self.endGripItem: item = self.endGripItem - self.endPoint = item.parentItem().mapToScene(item.pos()) + endPoint = item.parentItem().mapToScene(item.pos()) # center of grip + # end grip is rectangle + if item.size and item.m_location in ["top", "bottom"]: + endPoint.setX( + endPoint.x() - self.endGap * item.boundingRect().width()) + elif item.size: + endPoint.setY( + endPoint.y() - self.endGap * item.boundingRect().height()) + self.endPoint = endPoint self.updatePath() - - if self.startGripItem and self.refLine: - item = self.startGripItem - self.startPoint = item.parentItem().mapToScene(item.pos()) + # end point on other line + elif self.refLine: self.updatePath() def updateMidLines(self): + """ Updates all lines connecting to it + """ for line in self.midLines: points = line.refLine.points point1 = points[line.refIndex] @@ -927,13 +978,6 @@ class Line(QGraphicsPathItem): line.points[i - 1].setX(max(point1.x(), point2.x())) line.updatePath() - def removeFromCanvas(self): - """This function is used to remove connecting line from canvas - :return: - """ - if self.scene(): - self.scene().removeItem(self) - def showGripItem(self): """shows grip items which contains line """ @@ -960,29 +1004,14 @@ class Line(QGraphicsPathItem): """Pop up menu :return: """ + # create a menu and add action contextMenu = QMenu() - addLableAction = contextMenu.addAction("add Label") - if self.arrowFlag is True: - str = "Hide Arrow" - else: - str = "Add Arrow" - changeArrowFlag = contextMenu.addAction(str) + addLableAction = contextMenu.addAction("Add Text Label") # add action for text label action = contextMenu.exec_(event.screenPos()) + # check for label action and add text label as child if action == addLableAction: - newLabel = LineLabel(event.scenePos(), self) - self.label.append(newLabel) - self.scene().labelAdded.emit(newLabel) - - if action == changeArrowFlag: - if str == "Hide Arrow": - self.arrowFlag =False - else: - self.arrowFlag =True - self.update() - - def setPenStyle(self, style): - """change current pen style for line""" - self.penStyle = style + print(event.scenePos(), event.pos()) + self.label.append(LineLabel(event.scenePos(), self)) # text label as child def __getstate__(self): return { @@ -997,5 +1026,6 @@ class Line(QGraphicsPathItem): "label": [i for i in self.label], "id": hex(id(self)) } + def __setstate__(self, dict): self.points = [QPointF(x, y) for x, y in dict["points"]] diff --git a/src/main/python/shapes/shapes.py b/src/main/python/shapes/shapes.py index 6d06cba..117ffb8 100644 --- a/src/main/python/shapes/shapes.py +++ b/src/main/python/shapes/shapes.py @@ -14,24 +14,31 @@ from utils.app import fileImporter class ItemLabel(QGraphicsTextItem): - def __init__(self, pos, parent=None): + """Extends PyQt5's QGraphicsPathItem to create text label for svg item + """ + + def __init__(self, parent=None): super().__init__(parent=parent) self.setPlainText("abc") + # graphics setting for text label self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) + # set initial no text interaction self.setTextInteractionFlags(Qt.NoTextInteraction) + # set initial position just below parent self.setPos(self.parentItem().boundingRect().bottomLeft()) def mouseDoubleClickEvent(self, event): + # make text editable self.setTextInteractionFlags(Qt.TextEditorInteraction) - self.setFocus() + self.setFocus() # set focus to text super(ItemLabel, self).mouseDoubleClickEvent(event) def focusOutEvent(self, event): super(ItemLabel, self).focusOutEvent(event) - self.setTextInteractionFlags(Qt.NoTextInteraction) + self.setTextInteractionFlags(Qt.NoTextInteraction) # make text not editable and thus movable def __getstate__(self): return { @@ -69,26 +76,21 @@ class GripItem(QGraphicsPathItem): super(GripItem, self).mouseReleaseEvent(event) -class SizeGripItem(GripItem): +class SizeGripItem(QGraphicsPathItem): """ Extends grip items for vertical and horizontal directions, with hover events and directional changes """ - def __init__(self, annotation_item, index, direction=Qt.Horizontal, parent=None): - self.width = self.height = 0 - if direction is Qt.Horizontal: - self.height = annotation_item.boundingRect().height() - else: - self.width = annotation_item.boundingRect().width() - - path = QPainterPath() - path.addRect(QRectF(-self.width / 2, -self.height / 2, self.width, self.height)) - super(SizeGripItem, self).__init__(annotation_item, path=path, parent=parent) + def __init__(self, index, direction=Qt.Horizontal, parent=None): + super(SizeGripItem, self).__init__(parent=parent) + # set graphical setting self.setFlag(QGraphicsItem.ItemIsSelectable, True) self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) - self.setPen(QPen(QColor("black"), -1)) + self.setAcceptHoverEvents(True) + self.setPen(QPen(QColor("black"), 0)) self.setZValue(2) + # property direction self._direction = direction self.m_index = index @@ -99,21 +101,16 @@ class SizeGripItem(GripItem): """ return self._direction - def paint(self, painter, option, widget): - if self.isSelected() and not self.parentItem().isSelected(): - self.parentItem().setSelected(True) - self.parentItem().setFlag(QGraphicsSvgItem.ItemIsMovable, False) - super().paint(painter, option, widget) - def updatePath(self): """updates path of size grip item """ - if self._direction is Qt.Horizontal: - self.height = self.parentItem().boundingRect().height() + width = height = 0 + if self.direction is Qt.Horizontal: + height = self.parentItem().boundingRect().height() else: - self.width = self.parentItem().boundingRect().width() + width = self.parentItem().boundingRect().width() path = QPainterPath() - path.addRect(QRectF(-self.width / 2, -self.height / 2, self.width, self.height)) + path.addRect(QRectF(-width / 2, -height / 2, width, height)) self.setPath(path) def updatePosition(self): @@ -143,9 +140,7 @@ class SizeGripItem(GripItem): """ Changes cursor to horizontal resize or vertical resize depending on the direction of the grip item on mouse enter """ - # self.setPen(QPen(QColor("black"), 2)) - # self.setBrush(QColor("red")) - if self._direction == Qt.Horizontal: + if self.direction == Qt.Horizontal: self.setCursor(QCursor(Qt.SizeHorCursor)) else: self.setCursor(QCursor(Qt.SizeVerCursor)) @@ -155,8 +150,6 @@ class SizeGripItem(GripItem): """ reverts cursor to default on mouse leave """ - # self.setPen(QPen(Qt.transparent)) - # self.setBrush(Qt.transparent) self.setCursor(QCursor(Qt.ArrowCursor)) super(SizeGripItem, self).hoverLeaveEvent(event) @@ -187,45 +180,71 @@ class SizeGripItem(GripItem): self.updatePosition() # Make parent item move able self.parentItem().setFlag(QGraphicsSvgItem.ItemIsMovable, True) - # If needed to reset transform of parent set it's position accordingly + # If needed, to reset transform of parent set it's position accordingly # self.parentItem().setPos(self.parentItem().x() + self.parentItem().transform().dx(), self.parentItem().y() + self.parentItem().transform().dy()) # self.parentItem().resetTransform() + def show(self): + # make self visible + self.setPen(QPen(QColor("black"), 2)) + + def hide(self): + # hide self by setting pen to transparent + if not self.parentItem().isSelected(): + self.setPen(QPen(Qt.transparent)) + self.setBrush(Qt.transparent) + -class LineGripItem(GripItem): +class LineGripItem(QGraphicsPathItem): """Extends grip items for connecting lines , with hover events and mouse events """ circle = QPainterPath() circle.addEllipse(QRectF(-5, -5, 10, 10)) - def __init__(self, annotation_item, index, grip, parent=None): - path = LineGripItem.circle - - super(LineGripItem, self).__init__(annotation_item, path=path, parent=parent) + def __init__(self, index, grip, parent=None): + super(LineGripItem, self).__init__(parent=parent) + self.setPath(LineGripItem.circle) + # set it's index on item self.m_index = index - self.m_location = grip[2] - self.line = None + # store position of self + self.position = QPointF(grip[0], grip[1]) + # set location + self._m_location = grip[2] + # set size in case of rectangle grip + self.size = grip[3] if len(grip) == 4 else None # stores current line which is in process self.tempLine = None + # stores lines conected to it + self.lines = [] # keep previous hovered item when line drawing in process self.previousHoveredItem = None + # set graphical settings self.setFlag(QGraphicsItem.ItemIsSelectable, True) self.setPen(QPen(QColor("black"), -1)) - self.grip = grip + self.setAcceptHoverEvents(True) + self.setCursor(QCursor(Qt.PointingHandCursor)) + + @property + def m_location(self): + return self._m_location def shape(self): + # return interactive path qp = QPainterPathStroker() qp.setWidth(8) + # create outline of path path = qp.createStroke(self.path()) return path - def paint(self, painter,option, widget): + def paint(self, painter, option, widget): + # draw path with outline of interactive area(shape) painter.setPen(self.pen()) - painter.drawPath(self.shape()) + painter.drawPath(self.shape()) # draw outline painter.setBrush(self.brush()) - if len(self.grip) ==4: + # if rectangle grip + if self.size: painter.save() - pen =self.pen() + pen = self.pen() pen.setWidth(-1) painter.setPen(pen) painter.drawPath(self.path()) @@ -238,47 +257,38 @@ class LineGripItem(GripItem): Moves position of grip item on resize """ if change == QGraphicsItem.ItemSceneHasChanged and not self.scene(): - if self.line and self.line.scene(): - self.line.scene().removeItem(self.line) + # on removing from scene remove all lines connected to it + for line in self.lines: + if line.scene(): + line.scene().removeItem(line) return super(LineGripItem, self).itemChange(change, value) - def point(self, index): - """ - yields a list of positions of grip items in a node item - """ - width = self.parentItem().boundingRect().width() - height = self.parentItem().boundingRect().height() - if 0 <= index < 4: - return [ - QPointF(0, -height / 2), - QPointF(-width / 2, 0), - QPointF(0, height / 2), - QPointF(width / 2, 0) - ][index] - def updatePosition(self): - width = self.parentItem().boundingRect().width() - height = self.parentItem().boundingRect().height() - if len(self.grip) == 4: + width = self.parentItem().boundingRect().width() # width of parent + height = self.parentItem().boundingRect().height() # height of parent + # if grip item is rectangle change it's path + if self.size: rect_width = rect_height = 4 - if self.grip[2] in ["left","right"]: - rect_height = (self.grip[3]*height)/100 + if self.m_location in ["left", "right"]: + rect_height = (self.size * height) / 100 else: - rect_width = (self.grip[3]*width)/100 - path = QPainterPath() - path.addRect(QRectF(-rect_width / 2, -rect_height / 2, rect_width, rect_height)) - self.setPath(path) - - x = (self.grip[0] * width) / 100 - y = (self.grip[1] * height) / 100 + rect_width = (self.size * width) / 100 + path = QPainterPath() # create path + path.addRect(QRectF(-rect_width / 2, -rect_height / 2, rect_width, rect_height)) # add rect to path + self.setPath(path) # set path to grip + # position according to svg co-ordinate + x = (self.position.x() * width) / 100 + y = (self.position.y() * height) / 100 + # change position into items coordinate x -= width / 2 y -= height / 2 y = -y self.setEnabled(False) - self.setPos(QPointF(x, y)) + self.setPos(QPointF(x, y)) # set pos of grip self.setEnabled(True) - if self.line: - self.line.updateLine() + # update all lines connected to it + for line in self.lines: + line.updateLine() def mousePressEvent(self, mouseEvent): """Handle all mouse press for this item @@ -286,10 +296,25 @@ class LineGripItem(GripItem): if mouseEvent.button() != Qt.LeftButton: return # initialize a line and add on scene - if not self.line: - startPoint = endPoint = self.parentItem().mapToScene(self.pos()) - self.tempLine = Line(startPoint, endPoint) - self.scene().addItem(self.tempLine) + # restrict circle grip to one line + if self.size is None and len(self.lines) > 0: + pass + else: + startPoint = self.parentItem().mapToScene(self.pos()) # first point of line + mpos = self.mapToScene(mouseEvent.pos()) # position of mouse + gap = 0 # distance from center of grip + if self.size and self.m_location in ["top", "bottom"]: + gap = (startPoint.x() - mpos.x()) / self.boundingRect().width() + startPoint.setX(mpos.x()) + + elif self.size: + gap = (startPoint.y() - mpos.y()) / self.boundingRect().height() + startPoint.setY(mpos.y()) + + endPoint = startPoint + self.tempLine = Line(startPoint, endPoint) # create a line object + self.tempLine.startGap = gap # set gap to line + self.scene().addItem(self.tempLine) # add line on scene super().mousePressEvent(mouseEvent) def mouseMoveEvent(self, mouseEvent): @@ -297,17 +322,17 @@ class LineGripItem(GripItem): """ # if line get started then update it's end point if self.tempLine: - endPoint = mouseEvent.scenePos() + endPoint = mouseEvent.scenePos() # mouse position self.tempLine.updateLine(endPoint=endPoint) - + # find item below cursor item = self.scene().itemAt(mouseEvent.scenePos().x(), mouseEvent.scenePos().y(), QTransform()) - + # hide grip of previous hovered item if self.previousHoveredItem and item != self.previousHoveredItem and \ item not in self.previousHoveredItem.lineGripItems: self.previousHoveredItem.hideGripItem() super().mouseMoveEvent(mouseEvent) - + # show grip of current hoverde item if isinstance(item, NodeItem): self.previousHoveredItem = item item.showGripItem() @@ -317,30 +342,51 @@ class LineGripItem(GripItem): super().mouseReleaseEvent(mouseEvent) # set final position of line if self.tempLine: + tag = 0 items = self.scene().items(QPointF(mouseEvent.scenePos().x(), mouseEvent.scenePos().y())) for item in items: + # end point on grip if type(item) == LineGripItem and item != self: - if item.line: + endPoint = item.parentItem().mapToScene(item.pos()) + gap = 0 + # restrict line to one grip + if item.size is None and len(item.lines) > 0: break + # in case of rectangle grip + if item.size and item.m_location in ["top", "bottom"]: + mpos = self.mapToScene(mouseEvent.pos()) + gap = (endPoint.x() - mpos.x()) / item.boundingRect().width() + endPoint.setX(mpos.x()) + elif item.size: + mpos = self.mapToScene(mouseEvent.pos()) + gap = (endPoint.y() - mpos.y()) / item.boundingRect().height() + endPoint.setY(mpos.y()) + + self.tempLine.endGap = gap self.tempLine.setStartGripItem(self) self.tempLine.setEndGripItem(item) - endPoint = item.parentItem().mapToScene(item.pos()) + # update line with end point so it sets final path self.tempLine.updateLine(endPoint=endPoint) - self.line = self.tempLine - item.line = self.tempLine + self.lines.append(self.tempLine) + item.lines.append(self.tempLine) + tag = 1 break + # end point on line elif type(item) == Line and item != self.tempLine: self.tempLine.setStartGripItem(self) endPoint = mouseEvent.scenePos() self.tempLine.refLine = item self.tempLine.refIndex = findIndex(item, endPoint) + # update line with end point so it sets final path self.tempLine.updateLine(endPoint=endPoint) - item.midLines.append(self.tempLine) - self.line = self.tempLine + item.midLines.append(self.tempLine) # stores temp line as mid line to other line + self.lines.append(self.tempLine) + tag = 1 break - self.scene().removeItem(self.tempLine) - if self.line: - self.scene().addItemPlus(self.line) + self.scene().removeItem(self.tempLine) # remove temp line from scene + # if line end point is on grip or line + if tag: + self.scene().addItemPlus(self.tempLine) # add line on scene self.tempLine = None self.previousHoveredItem = None @@ -349,12 +395,12 @@ class LineGripItem(GripItem): """ shows line grip item """ self.setPen(QPen(QColor("black"), 2)) - self.setBrush(QColor("red")) + self.setBrush(QColor("cyan")) def hide(self): """ hides line grip item """ - if (self.parentItem().isSelected() or self.isSelected()) is False: + if not self.parentItem().isSelected(): self.setPen(QPen(Qt.transparent)) self.setBrush(Qt.transparent) @@ -364,26 +410,21 @@ class NodeItem(QGraphicsSvgItem): Extends PyQt5's QGraphicsSvgItem to create the basic structure of shapes with given unit operation type """ - # set a common renderer for all svg - # renderer = QSvgRenderer(fileImporter(f'svg/ellipse.svg')) - def __init__(self, unitOperationType=None, parent=None): QGraphicsSvgItem.__init__(self, parent) self.m_type = str(unitOperationType) - self.id = None self.m_renderer = QSvgRenderer(fileImporter(f'{unitOperationType}.svg')) self.setSharedRenderer(self.m_renderer) # set initial size of item self.width = self.m_renderer.defaultSize().width() self.height = self.m_renderer.defaultSize().height() - self.rect = QRectF(-self.width / 2, -self.height / 2, self.width, self.height) # set graphical settings for this item self.setFlags(QGraphicsSvgItem.ItemIsMovable | QGraphicsSvgItem.ItemIsSelectable | QGraphicsSvgItem.ItemSendsGeometryChanges) self.setAcceptHoverEvents(True) self.setZValue(2) - # grip items connected to this item + # items connected to this item self.lineGripItems = [] self.sizeGripItems = [] self.label = None @@ -392,7 +433,7 @@ class NodeItem(QGraphicsSvgItem): """Overrides QGraphicsSvgItem's boundingRect() virtual public function and returns a valid bounding """ - return self.rect + return QRectF(-self.width / 2, -self.height / 2, self.width, self.height) def paint(self, painter, option, widget): """ @@ -401,14 +442,11 @@ class NodeItem(QGraphicsSvgItem): :param option: QStyleOptionGraphicsItem instance :param widget: QWidget instance """ + # check if render is set if not self.m_renderer: QGraphicsSvgItem.paint(self, painter, option, widget) - elif self.id: - self.m_renderer.render(painter, self.id, self.boundingRect()) else: - self.m_renderer.render(painter, self.boundingRect()) - if self.isSelected(): - self.showGripItem() + self.m_renderer.render(painter, self.boundingRect()) # render svg using painter def resize(self, index, movement): """Move grip item with changing rect of node item @@ -420,8 +458,6 @@ class NodeItem(QGraphicsSvgItem): else: self.width += movement.x() self.height += movement.y() - - self.rect = QRectF(-self.width / 2, -self.height / 2, self.width, self.height) transform = QTransform() transform.translate(movement.x() / 2, movement.y() / 2) self.setTransform(transform, True) @@ -431,7 +467,7 @@ class NodeItem(QGraphicsSvgItem): """adds grip items """ if self.scene(): - # add grip for resize it + # add grip for resizing for i, (direction) in enumerate( ( Qt.Vertical, @@ -440,19 +476,18 @@ class NodeItem(QGraphicsSvgItem): Qt.Horizontal, ) ): - item = SizeGripItem(self, i, direction, parent=self) + item = SizeGripItem(i, direction, parent=self) self.sizeGripItems.append(item) # add grip items for connecting lines for i in range(len(self.grips)): grip = self.grips[i] - item = LineGripItem(self, i, grip, parent=self) + item = LineGripItem(i, grip, parent=self) self.lineGripItems.append(item) - def updateLineGripItem(self, index_no_updates=None): + def updateLineGripItem(self): """ updates line grip items """ - # index_no_updates = index_no_updates or [] for item in self.lineGripItems: item.updatePosition() @@ -468,20 +503,27 @@ class NodeItem(QGraphicsSvgItem): def itemChange(self, change, value): """Overloads and extends QGraphicsSvgItem to also update grip items """ + # check if item selected is changed if change == QGraphicsItem.ItemSelectedHasChanged: + # show grips if selected if value is True: self.showGripItem() else: self.hideGripItem() return + # check if transform changed if change == QGraphicsItem.ItemTransformHasChanged: self.updateLineGripItem() return + # check if position is changed if change == QGraphicsItem.ItemPositionHasChanged: + # update grips self.updateLineGripItem() self.updateSizeGripItem() return + # check if item is add on scene if change == QGraphicsItem.ItemSceneHasChanged and self.scene(): + # add grips and update them self.addGripItem() self.updateLineGripItem() self.updateSizeGripItem() @@ -504,32 +546,29 @@ class NodeItem(QGraphicsSvgItem): """shows grip items of svg item """ for item in self.lineGripItems: - item.setPen(QPen(QColor("black"), 2)) - item.setBrush(QColor("red")) + item.show() for item in self.sizeGripItems: - item.setPen(QPen(QColor("black"), 2)) + item.show() def hideGripItem(self): """hide grip items of svg item """ for item in self.lineGripItems: - if item.isSelected() is False: - item.setPen(QPen(Qt.transparent)) - item.setBrush(Qt.transparent) + item.hide() for item in self.sizeGripItems: - item.setPen(QPen(Qt.transparent)) - item.setBrush(Qt.transparent) + item.hide() def contextMenuEvent(self, event): """Pop up menu :return: """ + # create a menu and add action contextMenu = QMenu() - addLabelAction = contextMenu.addAction("add Label") - # addLabelAction.triggered.connect(self.addLabel) + addLableAction = contextMenu.addAction("add Label") # add action for text label action = contextMenu.exec_(event.screenPos()) - if action == addLabelAction: - self.label = ItemLabel(event.scenePos(), self) + # check for label action and add text label as child + if action == addLableAction: + self.label = ItemLabel(self) # text label as child def __getstate__(self): return { @@ -545,7 +584,6 @@ class NodeItem(QGraphicsSvgItem): self.prepareGeometryChange() self.width = dict['width'] self.height = dict['height'] - self.rect = QRectF(-self.width / 2, -self.height / 2, self.width, self.height) self.updateSizeGripItem() @@ -685,7 +723,7 @@ class Turbine(NodeItem): super(Turbine, self).__init__("svg/Compressors/Turbine") self.grips = [ [18.06209745144267, 79.11931909160472, "top"], - [45.2091373550176, 91.385325275219, "top"], + [45.2091373550176, 91.385325275219, "top"], [18.06209745144267, 16.41537491819628, "bottom"], [45.2091373550176, 4.5725942986116, "bottom"] ] @@ -693,13 +731,14 @@ class Turbine(NodeItem): class OilGasOrPulverizedFuelFurnace(NodeItem): def __init__(self): - super(OilGasOrPulverizedFuelFurnace, self).__init__("svg/Furnaces and Boilers/Oil Gas or Pulverized Fuel Furnace") + super(OilGasOrPulverizedFuelFurnace, self).__init__( + "svg/Furnaces and Boilers/Oil Gas or Pulverized Fuel Furnace") self.grips = [ - [58.27673386073162,100,"top"], - [0,19.692723451106,"left"] , - [17.2777337415748,33.3944873323144,"left",66.7889746646288], - [100,33.3944873323144,"right",66.7889746646288], - [57.9723659874,0,"bottom",81.389264491796] + [58.27673386073162, 100, "top"], + [0, 19.692723451106, "left"], + [17.2777337415748, 33.3944873323144, "left", 66.7889746646288], + [100, 33.3944873323144, "right", 66.7889746646288], + [57.9723659874, 0, "bottom", 81.389264491796] ] @@ -707,62 +746,32 @@ class SolidFuelFurnace(NodeItem): def __init__(self): super(SolidFuelFurnace, self).__init__("svg/Furnaces and Boilers/Solid Fuel Furnace") self.grips = [ - [50,100,"top"], - [0,33.39352642259468,"left",66.78705284518936], - [100,33.39352642259468,"right",66.78705284518936], - [50,0,"bottom",100] + [50, 100, "top"], + [0, 33.39352642259468, "left", 66.78705284518936], + [100, 33.39352642259468, "right", 66.78705284518936], + [50, 0, "bottom", 100] ] -# class Exchanger(NodeItem): -# def __init__(self): -# super(Exchanger, self).__init__("svg/Heating or Cooling Arrangements/905Exchanger") -# self.grips = [ -# [92.8093483, 70.60413752309337, "right"], -# [7.913824600849647, 70.60413752309337, "left"], -# [4.136894788615162, 86.9886362, "left"] -# ] - - -# class KettleReboiler(NodeItem): -# def __init__(self): -# super(KettleReboiler, self).__init__("svg/Heating or Cooling Arrangements/907Kettle Reboiler") -# self.grips = [ -# [41.316753407496, 89.824108247573, "top"], -# [62.0517030576456, 79.183192150093, "top"], -# [41.316753407496, 6.447877022097, "bottom"], -# [62.0517030576456, 16.14847772052, "bottom"] -# ] - - class Exchanger(NodeItem): def __init__(self): super(Exchanger, self).__init__("svg/Heating or Cooling Arrangements/Exchanger") self.grips = [ - [100,31.74474612706027,"right"], - [100,62.70549343934227,"right"], - [33.68240920045628,100,"top"], - [33.68240920045628,0,"bottom"] + [100, 31.74474612706027, "right"], + [100, 62.70549343934227, "right"], + [33.68240920045628, 100, "top"], + [33.68240920045628, 0, "bottom"] ] -# class Fan(NodeItem): -# def __init__(self): -# super(Fan, self).__init__("svg/Compressors/Fan") -# self.grips = [ -# [41.4323581, 100, "top"], -# [61.1489583, 100, "top"] -# ] - - class HeatExchanger(NodeItem): def __init__(self): super(HeatExchanger, self).__init__("svg/Heating or Cooling Arrangements/Heat Exchanger") self.grips = [ - [0,47.14356681569796,"left"], - [100,47.14356681569796,"right"], - [50.92839727035332,100,"top"], - [50.92839727035332,0,"bottom"] + [0, 47.14356681569796, "left"], + [100, 47.14356681569796, "right"], + [50.92839727035332, 100, "top"], + [50.92839727035332, 0, "bottom"] ] @@ -770,8 +779,8 @@ class ImmersionCoil(NodeItem): def __init__(self): super(ImmersionCoil, self).__init__("svg/Heating or Cooling Arrangements/Immersion Coil") self.grips = [ - [44.56276981957,100,"top"], - [88.232463407718,100,"top"] + [44.56276981957, 100, "top"], + [88.232463407718, 100, "top"] ] @@ -779,36 +788,39 @@ class HorizontalVessel(NodeItem): def __init__(self): super(HorizontalVessel, self).__init__("svg/Process Vessels/Horizontal Vessel") self.grips = [ - [50,100,"top",87.08554680344], - [0,50,"left"], - [100,50,"right"], - [50,0,"bottom",87.08554680344] + [50, 100, "top", 87.08554680344], + [0, 50, "left"], + [100, 50, "right"], + [50, 0, "bottom", 87.08554680344] ] + class PackedVessel(NodeItem): def __init__(self): super(PackedVessel, self).__init__("svg/Process Vessels/Packed Vessel") self.grips = [ - [50,100,"top"], - [0,50,"left",86.703566201060], - [100,50,"right",86.703566201060], - [50,0,"bottom"] + [50, 100, "top"], + [0, 50, "left", 86.703566201060], + [100, 50, "right", 86.703566201060], + [50, 0, "bottom"] ] + class TraysOrPlates(NodeItem): def __init__(self): super(TraysOrPlates, self).__init__("svg/Process Vessels/Trays or plates") self.grips = [ ] + class VerticalVessel(NodeItem): def __init__(self): super(VerticalVessel, self).__init__("svg/Process Vessels/Vertical Vessel") self.grips = [ - [50,100,"top"], - [0,50,"left",86.703566201060], - [100,50,"right",86.703566201060], - [50,0,"bottom"] + [50, 100, "top"], + [0, 50, "left", 86.703566201060], + [100, 50, "right", 86.703566201060], + [50, 0, "bottom"] ] @@ -816,20 +828,21 @@ class Separators(NodeItem): def __init__(self): super(Separators, self).__init__("svg/Separators/Separators for Liquids, Decanter") self.grips = [ - [50,100,"top",100], - [0,50,"left",100], - [100,50,"right",100], - [50,0,"bottom",100] + [50, 100, "top", 100], + [0, 50, "left", 100], + [100, 50, "right", 100], + [50, 0, "bottom", 100] ] + class FixedRoofTank(NodeItem): def __init__(self): super(FixedRoofTank, self).__init__("svg/Storage Vessels Tanks/Fixed Roof Tank") self.grips = [ - [50,100,"top"], - [0,50,"left",100], - [100,50,"right",100], - [50,0,"bottom",100] + [50, 100, "top"], + [0, 50, "left", 100], + [100, 50, "right", 100], + [50, 0, "bottom", 100] ] @@ -837,19 +850,18 @@ class FloatingRoofTank(NodeItem): def __init__(self): super(FloatingRoofTank, self).__init__("svg/Storage Vessels Tanks/Floating Roof Tank") self.grips = [ - [0,50,"left",100], - [100,50,"right",100], - [50,0,"bottom",100] + [0, 50, "left", 100], + [100, 50, "right", 100], + [50, 0, "bottom", 100] ] + class SeparatorsForLiquidsDecanter(NodeItem): def __init__(self): super(SeparatorsForLiquidsDecanter, self).__init__("svg/Separators/Separators for Liquids, Decanter") self.grips = [ - [50,100,"top",100], - [0,50,"left",100], - [100,50,"right",100], - [50,0,"bottom",100] + [50, 100, "top", 100], + [0, 50, "left", 100], + [100, 50, "right", 100], + [50, 0, "bottom", 100] ] - - |