diff options
author | pravindalve | 2020-06-08 19:57:56 +0530 |
---|---|---|
committer | GitHub | 2020-06-08 19:57:56 +0530 |
commit | 5c907a6e7f0c18a18094b70ba166c6bc5cdcf1ac (patch) | |
tree | 21100b78bd78d4d121d2c032c0d0fc9a6c69f40b /src | |
parent | 809cd1f50d320482600e7ab190dc5e13bff3bf02 (diff) | |
parent | 53ba24f550503c945a6ad4279e7de726442fffae (diff) | |
download | Chemical-PFD-5c907a6e7f0c18a18094b70ba166c6bc5cdcf1ac.tar.gz Chemical-PFD-5c907a6e7f0c18a18094b70ba166c6bc5cdcf1ac.tar.bz2 Chemical-PFD-5c907a6e7f0c18a18094b70ba166c6bc5cdcf1ac.zip |
Merge pull request #8 from Sumit-Sahu/master
fix path of line ,add text label ,add curve at intersection of line,make a line end on another line
Diffstat (limited to 'src')
-rw-r--r-- | src/main/python/shapes/line.py | 744 | ||||
-rw-r--r-- | src/main/python/shapes/shapes.py | 119 | ||||
-rw-r--r-- | src/main/python/utils/graphics.py | 7 | ||||
-rw-r--r-- | src/main/python/utils/undo.py | 1 | ||||
-rw-r--r-- | src/main/resources/base/svg/ellipse.svg | 2 |
5 files changed, 753 insertions, 120 deletions
diff --git a/src/main/python/shapes/line.py b/src/main/python/shapes/line.py index 310b496..dc0a09c 100644 --- a/src/main/python/shapes/line.py +++ b/src/main/python/shapes/line.py @@ -1,7 +1,9 @@ +import math from PyQt5.QtGui import QPen, QPainterPath, QBrush, QPainterPathStroker, QPainter, QCursor -from PyQt5.QtWidgets import QGraphicsItem, QGraphicsPathItem +from PyQt5.QtWidgets import QGraphicsItem, QGraphicsPathItem, QGraphicsTextItem, QMenu, QGraphicsLineItem from PyQt5.QtCore import Qt, QPointF, QRectF + class Grabber(QGraphicsPathItem): """ Extends QGraphicsPathItem to create grabber for line for moving a particular segment @@ -20,6 +22,8 @@ class Grabber(QGraphicsPathItem): self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setAcceptHoverEvents(True) + self.pen = QPen(Qt.white, -1, Qt.SolidLine) + self.brush = QBrush(Qt.transparent) def itemChange(self, change, value): """ move position of grabber after resize""" @@ -27,8 +31,29 @@ class Grabber(QGraphicsPathItem): p = QPointF(self.pos()) 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 + point1 = points[self.parentItem().refIndex] + point2 = points[self.parentItem().refIndex + 1] + point1 = self.parentItem().mapFromItem(self.parentItem().refLine, point1) + point2 = self.parentItem().mapFromItem(self.parentItem().refLine, point2) + if p.x() < min(point1.x(), point2.x()): + 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: p.setY(value.y()) + if self.parentItem().refLine and self.m_index == len(self.parentItem().points) - 2: + points = self.parentItem().refLine.points + point1 = points[self.parentItem().refIndex] + point2 = points[self.parentItem().refIndex + 1] + point1 = self.parentItem().mapFromItem(self.parentItem().refLine, point1) + point2 = self.parentItem().mapFromItem(self.parentItem().refLine, point2) + if p.y() < min(point1.y(), point2.y()): + p.setY(min(point1.y(), point2.y())) + elif p.y() > max(point1.y(), point2.y()): + p.setY(max(point1.y(), point2.y())) + movement = p - self.pos() self.m_annotation_item.movePoints(self.m_index, movement) return p @@ -37,13 +62,11 @@ class Grabber(QGraphicsPathItem): def paint(self, painter, option, widget): """paints the path of grabber only if it is selected """ - if self.isSelected(): - # show line of grabber + if self.isSelected() and not self.m_annotation_item.isSelected() : + # show parent line of grabber self.m_annotation_item.setSelected(True) - painter.setBrush(QBrush(Qt.cyan)) - color = Qt.black if self.isSelected() else Qt.white - width = 2 if self.isSelected() else -1 - painter.setPen(QPen(color, width, Qt.SolidLine)) + painter.setBrush(self.brush) + painter.setPen(self.pen) painter.drawPath(self.path()) # To paint path of shape @@ -69,7 +92,6 @@ class Grabber(QGraphicsPathItem): return self.shape().boundingRect() def mousePressEvent(self, event): - print('grabber clicked', self) super(Grabber, self).mousePressEvent(event) def hoverEnterEvent(self, event): @@ -90,32 +112,279 @@ class Grabber(QGraphicsPathItem): self.setCursor(QCursor(Qt.ArrowCursor)) super(Grabber, self).hoverLeaveEvent(event) + def show(self): + self.pen = QPen(Qt.black, 2, Qt.SolidLine) + self.brush = QBrush(Qt.cyan) + + def hide(self): + self.pen = QPen(Qt.white, -1, Qt.SolidLine) + self.brush = QBrush(Qt.transparent) + +class LineLabel(QGraphicsTextItem): + def __init__(self, pos, parent=None): + super(LineLabel, self).__init__() + self.setPlainText("abc") + self.index = None + self.gap = None + self.setFlags(QGraphicsItem.ItemIsMovable | + QGraphicsItem.ItemIsSelectable | + QGraphicsItem.ItemIsFocusable) + self.setTextInteractionFlags(Qt.NoTextInteraction) + self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) + self.setPos(pos - self.boundingRect().center()) + self.setParentItem(parent) + self.line = QGraphicsLineItem() + self.line.setParentItem(self) + self.line.setFlag(QGraphicsItem.ItemStacksBehindParent) + self.resetPos() + + def paint(self, painter, option, widget): + painter.save() + painter.setBrush(QBrush(Qt.white)) + painter.drawEllipse(self.boundingRect()) + painter.restore() + 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] + center = self.mapToParent(self.boundingRect().center()) + newPos = center + 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())) + + 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())) + newPos -= QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) + self.setPos(newPos) + + def resetPos(self): + points = self.parentItem().points + min_A = QPointF() + min_B = QPointF() + min_dis = math.inf + for i in range(len(points) - 1): + A = points[i] + B = points[i + 1] + C = QPointF(self.pos() + self.boundingRect().center()) + BAx = B.x() - A.x() + BAy = B.y() - A.y() + CAx = C.x() - A.x() + CAy = C.y() - A.y() + length = math.sqrt(BAx * BAx + BAy * BAy) + if BAx == 0: + if not min(A.y(), B.y()) <= C.y() <= max(A.y(), B.y()): + continue + if BAy == 0: + if not min(A.x(), B.x()) <= C.x() <= max(A.x(), B.x()): + continue + if length > 0: + dis = (BAx * CAy - CAx * BAy) / length + if abs(dis) < abs(min_dis): + min_dis = dis + min_A = A + min_B = B + self.index = i + point = self.mapFromScene(min_A) + if min_A.x() == min_B.x(): + self.setPos(self.parentItem().mapFromScene(QPointF(min_A.x() + 10, self.y()))) + self.gap = 10 + self.boundingRect().width() / 2 + else: + self.setPos(self.parentItem().mapFromScene(QPointF(self.x(), min_A.y() - 30))) + self.gap = -30 + self.boundingRect().height() / 2 + + def itemChange(self, change, value): + if change == QGraphicsItem.ItemPositionChange and self.scene(): + newPos = QPointF(value) + newPos += QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) + points = self.parentItem().points + firstPoint = points[self.index] + endPoint = points[self.index + 1] + if firstPoint.x() == endPoint.x(): + 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())) + elif firstPoint.y() == endPoint.y(): + 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())) + newPos -= QPointF(self.boundingRect().width() / 2, self.boundingRect().height() / 2) + return newPos + if change == QGraphicsItem.ItemPositionHasChanged and self.scene(): + self.updateGap() + self.updateLine() + return + return super(LineLabel, self).itemChange(change, value) + + def updateGap(self): + points = self.parentItem().points + firstPoint = points[self.index] + endPoint = points[self.index + 1] + firstPoint = self.mapFromParent(firstPoint) + endPoint = self.mapFromParent(endPoint) + center = self.boundingRect().center() + if firstPoint.x() == endPoint.x(): + self.gap = center.x() - firstPoint.x() + else: + self.gap = center.y() - firstPoint.y() + + def updateLine(self): + points = self.parentItem().points + firstPoint = points[self.index] + endPoint = points[self.index + 1] + point = self.mapFromParent(firstPoint) + center = self.boundingRect().center() + if firstPoint.x() == endPoint.x(): + self.line.setLine(center.x(), center.y(), point.x(), center.y()) + else: + self.line.setLine(center.x(), center.y(), center.x(), point.y()) + + def mouseDoubleClickEvent(self, event): + self.setTextInteractionFlags(Qt.TextEditorInteraction) + self.setFocus() + super(LineLabel, self).mouseDoubleClickEvent(event) + + def focusOutEvent(self, event): + super(LineLabel, self).focusOutEvent(event) + self.setTextInteractionFlags(Qt.NoTextInteraction) + + +def findIndex(line, pos): + points = line.points + min_A = QPointF() + min_B = QPointF() + min_dis = math.inf + index = -1 + for i in range(len(points) - 1): + A = points[i] + B = points[i + 1] + C = pos + BAx = B.x() - A.x() + BAy = B.y() - A.y() + CAx = C.x() - A.x() + CAy = C.y() - A.y() + length = math.sqrt(BAx * BAx + BAy * BAy) + if BAx == 0: + if not min(A.y(), B.y()) <= C.y() <= max(A.y(), B.y()): + continue + if BAy == 0: + if not min(A.x(), B.x()) <= C.x() <= max(A.x(), B.x()): + continue + if length > 0: + dis = (BAx * CAy - CAx * BAy) / length + if abs(dis) < abs(min_dis): + min_dis = dis + min_A = A + min_B = B + index = i + return index + class Line(QGraphicsPathItem): """ Extends QGraphicsPathItem to draw zig-zag line consisting of multiple points """ - penStyle = Qt.SolidLine - def __init__(self, startPoint, endPoint, **args): QGraphicsItem.__init__(self, **args) self.startPoint = startPoint self.endPoint = endPoint - #stores all points of line + # stores all points of line self.points = [] - self.points.extend([startPoint, endPoint]) self.startGripItem = None self.endGripItem = None - self._selected = False self.m_grabbers = [] - # stores current pen style of line - self.penStyle = Line.penStyle - # set graphical settings for this item + # set graphical settings for line self.setFlag(QGraphicsItem.ItemIsSelectable, True) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setAcceptHoverEvents(True) - # initiates path - self.createPath() + # store reference if line connect another line + self.refLine = None # reference line + self.refIndex = None # start index of segment to which it connects + self.commonPathsCenters = [] + self.midLines = [] + self.label = [] + + def advance(self, phase): + if not phase: + return + # items colliding with line + # items = self.collidingItems(Qt.IntersectsItemShape) + items = self.scene().items(self.shape(), Qt.IntersectsItemShape, Qt.AscendingOrder) + self.commonPathsCenters = [] + # 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() + for polygon in polygons: + center = polygon.boundingRect().center() + if polygon.size() == 5: + if item.refLine: + i = len(item.points) - 2 + x1, y1 = item.points[i].x(), item.points[i].y() + x2, y2 = item.points[i+1].x(), item.points[i+1].y() + x, y = center.x(), center.y() + if x == x1 == x2 and not min(y1, y2) + 8 <= y < max(y1, y2) - 8: + continue + elif y == y1 == y2 and not min(x1, x2) + 8 <= x < max(x1, x2) - 8: + continue + else: + self.commonPathsCenters.append(center) + else: + self.commonPathsCenters.append(center) + self.update() + + 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) + # iterating over all points of line + for i in range(len(self.points) - 1): + x1, y1 = self.points[i].x(), self.points[i].y() + x2, y2 = self.points[i + 1].x(), self.points[i + 1].y() + for point in sorted(self.commonPathsCenters, key=lambda x: x.x() + x.y(), reverse=x2 < x1 or y2 < y1): + x, y = point.x(), point.y() + if x == x1 == x2: + # vertical + if min(y1, y2) + 8 <= y < max(y1, y2) - 8: + if y2 > y1: + path.lineTo(point - QPointF(0, 8)) + path.arcTo(QRectF(x - 8, y - 8, 16, 16), 90, -180) + path.moveTo(point + QPointF(0, 8)) + else: + path.lineTo(point + QPointF(0, 8)) + path.arcTo(QRectF(x - 8, y - 8, 16, 16), -90, 180) + path.moveTo(point - QPointF(0, 8)) + elif y == y1 == y2: + # horizontal + if min(x1, x2) + 8 <= x < max(x1, x2) - 8: + if x2 > x1: + path.lineTo(point - QPointF(8, 0)) + path.arcTo(QRectF(x - 8, y - 8, 16, 16), 180, 180) + path.moveTo(point + QPointF(8, 0)) + else: + path.lineTo(point + QPointF(8, 0)) + path.arcTo(QRectF(x - 8, y - 8, 16, 16), 0, -180) + path.lineTo(point - QPointF(8, 0)) + path.lineTo(self.points[i + 1]) + + painter.drawPath(path) def createPath(self): """ @@ -125,23 +394,313 @@ class Line(QGraphicsPathItem): offset = 30 x0, y0 = self.startPoint.x(), self.startPoint.y() x1, y1 = self.endPoint.x(), self.endPoint.y() + # create path for line in process self.points = [self.startPoint, QPointF((x0 + x1) / 2, y0), QPointF((x0 + x1) / 2, y1), self.endPoint] - if self.startGripItem and self.startGripItem.m_location in ["left", "right"]: - if self.endGripItem and self.endGripItem.m_location in ["top", "bottom"]: - if self.endGripItem.m_location == "top": offset = -offset - self.points = [self.startPoint, QPointF((x0 + x1) / 2, y0), QPointF((x0 + x1) / 2, y1 + offset), - QPointF(self.endPoint.x(), y1 + offset), self.endPoint] - - if self.startGripItem and self.startGripItem.m_location in ["top", "bottom"]: - self.points = [self.startPoint, QPointF(x0, (y0 + y1) / 2), QPointF(x1, (y0 + y1) / 2), self.endPoint] - if self.endGripItem and self.endGripItem.m_location in ["left", "right"]: - self.points = [self.startPoint, QPointF(x0, (y0 + y1) / 2), QPointF(x1 - offset, (y0 + y1) / 2), - QPointF(x1 - offset, self.endPoint.y()), self.endPoint] - # draw line + # final path of line + if self.refLine: + from .shapes import LineGripItem + direction = "left" + points = self.refLine.points + point1 = points[self.refIndex] + point2 = points[self.refIndex + 1] + if point1.x() == point2.x(): + if point1.x() < self.startPoint.x(): + direction = "right" + else: + direction = "left" + elif point1.y() == point2.y(): + if point1.y() > self.startPoint.y(): + direction = "top" + else: + direction = "bottom" + + self.endGripItem = LineGripItem(self, -1, direction, self) + self.endGripItem.setPos(self.endPoint) + if self.startGripItem and self.endGripItem: + # determine ns (point next to start) + item = self.startGripItem + self.startPoint = item.parentItem().mapToScene(item.pos()) + if item.m_location == "top": + ns = QPointF(self.startPoint.x(), self.startPoint.y() - offset) + elif item.m_location == "left": + ns = QPointF(self.startPoint.x() - offset, self.startPoint.y()) + elif item.m_location == "bottom": + ns = QPointF(self.startPoint.x(), self.startPoint.y() + offset) + else: + ns = QPointF(self.startPoint.x() + offset, self.startPoint.y()) + # determine pe (point previous to end) + item = self.endGripItem + self.endPoint = item.parentItem().mapToScene(item.pos()) + if item.m_location == "top": + pe = QPointF(self.endPoint.x(), self.endPoint.y() - offset) + elif item.m_location == "left": + pe = QPointF(self.endPoint.x() - offset, self.endPoint.y()) + elif item.m_location == "bottom": + pe = QPointF(self.endPoint.x(), self.endPoint.y() + offset) + else: + pe = QPointF(self.endPoint.x() + offset, self.endPoint.y()) + + start = self.startPoint + end = self.endPoint + sheight = self.startGripItem.m_annotation_item.boundingRect().height() / 2 + swidth = self.startGripItem.m_annotation_item.boundingRect().width() / 2 + eheight = self.endGripItem.m_annotation_item.boundingRect().height() / 2 + ewidth = self.endGripItem.m_annotation_item.boundingRect().width() / 2 + + if self.startGripItem.m_location in ["right"]: + if self.endGripItem.m_location in ["top"]: + if start.x() + offset < end.x() - ewidth: + if start.y() + offset < end.y(): + self.points = [start, QPointF(end.x(), start.y()), end] + else: + self.points = [start, ns, QPointF(ns.x(), pe.y()), pe, end] + elif start.x() - 2 * swidth > end.x(): + if start.y() + sheight + offset < end.y(): + self.points = [start, ns, QPointF(ns.x(), pe.y()), pe, end] + elif start.y() - sheight - offset < end.y(): + self.points = [start, ns, QPointF(ns.x(), ns.y() - sheight - offset), + QPointF(pe.x(), ns.y() - sheight - offset), end] + else: + self.points = [start, ns, QPointF(ns.x(), pe.y()), pe, end] + else: + self.points = [start, ns, QPointF(ns.x(), pe.y()), pe, end] + if start.y() > end.y(): + x = max(end.x() + ewidth + offset, ns.x()) + self.points = [start, QPointF(x, start.y()), QPointF(x, pe.y()), pe, end] + + elif self.endGripItem.m_location in ["bottom"]: + if start.x() + offset < end.x() - ewidth: + if start.y() + offset < end.y(): + self.points = [start, ns, QPointF(ns.x(), pe.y()), pe, end] + else: + self.points = [start, QPointF(end.x(), start.y()), end] + + elif start.x() - 2 * swidth > end.x(): + if start.y() + sheight + offset < end.y(): + self.points = [start, ns, QPointF(ns.x(), pe.y()), pe, end] + elif start.y() - sheight - offset < end.y(): + y = max(pe.y(), start.y() + sheight + offset) + self.points = [start, ns, QPointF(ns.x(), y), + QPointF(pe.x(), y), end] + else: + self.points = [start, ns, QPointF(ns.x(), pe.y()), pe, end] + else: + self.points = [start, ns, QPointF(ns.x(), pe.y()), pe, end] + if start.y() < end.y(): + x = max(end.x() + ewidth + offset, ns.x()) + self.points = [start, QPointF(x, start.y()), QPointF(x, pe.y()), pe, end] + + elif self.endGripItem.m_location in ["right"]: + x = max(start.x() + offset, pe.x()) + self.points = [start, QPointF(x, start.y()), QPointF(x, end.y()), end] + if start.x() + offset < end.x() - ewidth: + if start.y() + offset > end.y() - eheight and end.y() >= start.y(): + self.points = [start, ns, QPointF(ns.x(), pe.y() - eheight), + QPointF(pe.x(), pe.y() - eheight), pe, end] + elif start.y() - offset < end.y() + eheight and end.y() <= start.y(): + self.points = [start, ns, QPointF(ns.x(), pe.y() + eheight), + QPointF(pe.x(), pe.y() + eheight), pe, end] + elif start.y() - sheight - offset < end.y() < start.y() + sheight + offset: + if end.y() < start.y(): + self.points = [start, ns, QPointF(ns.x(), ns.y() - sheight), + QPointF(pe.x(), ns.y() - sheight), pe, end] + else: + self.points = [start, ns, QPointF(ns.x(), ns.y() + sheight), + QPointF(pe.x(), ns.y() + sheight), pe, end] + + elif self.endGripItem.m_location in ["left"]: + self.points = [start, QPointF((start.x() + end.x()) / 2, start.y()), + QPointF((start.x() + end.x()) / 2, end.y()), end] + if end.x() < start.x() + offset: + if end.y() + eheight <= start.y() - sheight - offset: + self.points = [start, ns, QPointF(ns.x(), ns.y() - sheight), + QPointF(pe.x(), ns.y() - sheight), pe, end] + elif end.y() - eheight >= start.y() + sheight + offset: + self.points = [start, ns, QPointF(ns.x(), ns.y() + sheight), + QPointF(pe.x(), ns.y() + sheight), pe, end] + elif end.y() <= start.y(): + y = min(end.y() - eheight, start.y() - sheight) + self.points = [start, ns, QPointF(ns.x(), y), + QPointF(pe.x(), y), pe, end] + else: + y = max(end.y() + eheight, start.y() + sheight) + self.points = [start, ns, QPointF(ns.x(), y), + QPointF(pe.x(), y), pe, end] + + + elif self.startGripItem.m_location in ["left"]: + if self.endGripItem.m_location in ["top"]: + if start.x() + offset < end.x() - ewidth: + if end.y() > start.y() + sheight + offset: + self.points = [start, ns, QPointF(ns.x(), ns.y() + sheight), + QPointF(pe.x(), ns.y() + sheight), end] + else: + y = min(start.y() - sheight, pe.y()) + self.points = [start, ns, QPointF(ns.x(), y), + QPointF(pe.x(), y), end] + elif end.x() + ewidth >= start.x() - offset: + x = min(ns.x(), end.x() - ewidth) + self.points = [start, QPointF(x, ns.y()), + QPointF(x, pe.y()), pe, end] + else: + if end.y() >= start.y() + offset: + self.points = [start, QPointF(end.x(), start.y()), end] + else: + x = (start.x() + end.x()) / 2 + self.points = [start, QPointF(x, start.y()), + QPointF(x, pe.y()), pe, end] + + elif self.endGripItem.m_location in ["bottom"]: + if start.x() + offset < end.x() - ewidth: + if end.y() < start.y() - sheight - offset: + self.points = [start, ns, QPointF(ns.x(), ns.y() - sheight), + QPointF(pe.x(), ns.y() - sheight), end] + else: + y = max(start.y() + sheight, pe.y()) + self.points = [start, ns, QPointF(ns.x(), y), + QPointF(pe.x(), y), end] + elif end.x() + ewidth >= start.x() - offset: + x = min(ns.x(), end.x() - ewidth) + self.points = [start, QPointF(x, ns.y()), + QPointF(x, pe.y()), pe, end] + else: + if end.y() <= start.y() - offset: + self.points = [start, QPointF(end.x(), start.y()), end] + else: + x = (start.x() + end.x()) / 2 + self.points = [start, QPointF(x, start.y()), + QPointF(x, pe.y()), pe, end] + + elif self.endGripItem.m_location in ["right"]: + self.points = [start, QPointF((start.x() + end.x()) / 2, start.y()), + QPointF((start.x() + end.x()) / 2, end.y()), end] + if end.x() > start.x() + offset: + if end.y() + eheight <= start.y() - sheight - offset: + self.points = [start, ns, QPointF(ns.x(), ns.y() - sheight), + QPointF(pe.x(), ns.y() - sheight), pe, end] + elif end.y() - eheight >= start.y() + sheight + offset: + self.points = [start, ns, QPointF(ns.x(), ns.y() + sheight), + QPointF(pe.x(), ns.y() + sheight), pe, end] + elif end.y() <= start.y(): + y = min(end.y() - eheight, start.y() - sheight) + self.points = [start, ns, QPointF(ns.x(), y), + QPointF(pe.x(), y), pe, end] + else: + y = max(end.y() + eheight, start.y() + sheight) + self.points = [start, ns, QPointF(ns.x(), y), + QPointF(pe.x(), y), pe, end] + + elif self.endGripItem.m_location in ["left"]: + self.points = [start, QPointF(pe.x(), start.y()), pe, end] + if start.x() + offset < end.x(): + self.points = [start, ns, QPointF(ns.x(), end.y()), end] + if start.y() + sheight + offset > end.y() > start.y() - sheight - offset: + self.points = [start, ns, QPointF(ns.x(), ns.y() - sheight), + QPointF(pe.x(), ns.y() - sheight), pe, end] + elif end.y() - eheight - offset < start.y() < end.y() + eheight + offset: + if end.y() > start.y(): + self.points = [start, ns, QPointF(ns.x(), pe.y() - eheight), + QPointF(pe.x(), pe.y() - eheight), pe, end] + else: + self.points = [start, ns, QPointF(ns.x(), pe.y() + eheight), + QPointF(pe.x(), pe.y() + eheight), pe, end] + + + elif self.startGripItem.m_location in ["top"]: + if self.endGripItem.m_location in ["top"]: + self.points = [self.startPoint, QPointF(start.x(), pe.y()), + pe, self.endPoint] + if start.y() < end.y(): + self.points = [self.startPoint, ns, QPointF(pe.x(), ns.y()), self.endPoint] + if start.x() + swidth > end.x() > start.x() - swidth or end.x() + ewidth > start.x() > end.x() - ewidth: + x = max(start.x() + swidth, end.x() + ewidth) + x += offset + if start.x() > end.x(): + x = min(start.x() - swidth, end.x() - ewidth) + x -= offset + self.points = [start, ns, QPointF(x, ns.y()), + QPointF(x, pe.y()), pe, end] + + elif self.endGripItem.m_location in ["bottom"]: + self.points = [self.startPoint, ns, QPointF((x0 + x1) / 2, ns.y()), QPointF((x0 + x1) / 2, pe.y()), + pe, self.endPoint] + if start.y() - offset > end.y(): + self.points = [start, QPointF(start.x(), (y0 + y1) / 2), QPointF(end.x(), (y0 + y1) / 2), + self.endPoint] + elif start.x() + swidth > end.x() > start.x() - swidth or end.x() + ewidth > start.x() > end.x() - ewidth: + x = max(start.x() + swidth, end.x() + ewidth) + x += offset + if start.x() > end.x(): + x = min(start.x() - swidth, end.x() - ewidth) + x -= offset + self.points = [start, ns, QPointF(x, ns.y()), + QPointF(x, pe.y()), pe, end] + + elif self.endGripItem.m_location in ["right"]: + y = min(ns.y(), end.y() + eheight + offset) + self.points = [start, QPointF(ns.x(), y), QPointF(pe.x(), y), pe, end] + if start.x() - swidth - offset < end.x() < start.x() + swidth + offset and end.y() > start.y() + offset: + self.points = [start, ns, QPointF(ns.x() + swidth + offset, ns.y()), + QPointF(ns.x() + swidth + offset, pe.y()), end] + elif end.y() - eheight < start.y() - offset < end.y() + eheight: + self.points = [start, ns, QPointF(start.x(), end.y() - eheight), + QPointF(pe.x(), end.y() - eheight), pe, end] + + elif self.endGripItem.m_location in ["left"]: + y = min(ns.y(), end.y() + eheight + offset) + self.points = [start, QPointF(ns.x(), y), QPointF(pe.x(), y), pe, end] + if start.x() - swidth - offset < end.x() < start.x() + swidth + offset and end.y() > start.y() + offset: + self.points = [start, ns, QPointF(ns.x() - swidth - offset, ns.y()), + QPointF(ns.x() - swidth - offset, pe.y()), end] + elif end.y() - eheight < start.y() - offset < end.y() + eheight: + self.points = [start, QPointF(start.x(), end.y() - eheight), + QPointF(pe.x(), end.y() - eheight), pe, end] + + elif self.startGripItem.m_location in ["bottom"]: + if self.endGripItem.m_location in ["top"]: + self.points = [self.startPoint, ns, QPointF((x0 + x1) / 2, ns.y()), QPointF((x0 + x1) / 2, pe.y()), + pe, self.endPoint] + if start.y() < end.y(): + self.points = [self.startPoint, ns, QPointF(pe.x(), ns.y()), self.endPoint] + if start.x() + swidth > end.x() > start.x() - swidth or end.x() + ewidth > start.x() > end.x() - ewidth: + x = max(start.x() + swidth, end.x() + ewidth) + x += offset + self.points = [start, ns, QPointF(x, ns.y()), + QPointF(x, pe.y()), pe, end] + + elif self.endGripItem.m_location in ["bottom"]: + self.points = [self.startPoint, ns, QPointF((x0 + x1) / 2, ns.y()), QPointF((x0 + x1) / 2, pe.y()), + pe, self.endPoint] + if start.x() + swidth > end.x() > start.x() - swidth or end.x() + ewidth > start.x() > end.x() - ewidth: + x = max(start.x() + swidth, end.x() + ewidth) + x += offset + self.points = [start, ns, QPointF(x, ns.y()), + QPointF(x, pe.y()), pe, end] + + elif self.endGripItem.m_location in ["right"]: + y = max(ns.y(), end.y() + eheight + offset) + self.points = [start, QPointF(ns.x(), y), QPointF(pe.x(), y), pe, end] + if start.x() - swidth - offset < end.x() < start.x() + swidth + offset: + self.points = [start, ns, QPointF(ns.x() + swidth + offset, ns.y()), + QPointF(ns.x() + swidth + offset, pe.y()), end] + + elif self.endGripItem.m_location in ["left"]: + y = max(ns.y(), end.y() + eheight + offset) + self.points = [start, QPointF(ns.x(), y), QPointF(pe.x(), y), pe, end] + if start.x() - swidth - offset < end.x() < start.x() + swidth + offset: + self.points = [start, ns, QPointF(ns.x() - swidth - offset, ns.y()), + QPointF(ns.x() - swidth - offset, pe.y()), end] + + # path of line path = QPainterPath(self.startPoint) for i in range(1, len(self.points)): path.lineTo(self.points[i]) self.setPath(path) + + if self.refLine: + self.scene().removeItem(self.endGripItem) + self.endGripItem = None + self.addGrabber() if self.endGripItem: self.addGrabber() @@ -155,30 +714,30 @@ class Line(QGraphicsPathItem): path.lineTo(self.points[i]) path.lineTo(self.endPoint) self.setPath(path) + self.updateGrabber() + for label in self.label: + label.updateLabel() + self.updateMidLines() def updatePoints(self): """ updates points of line when grabber is moved :return: """ - if self.startGripItem.m_location in ["left", "right"]: + if self.startGripItem: + self.points[0] = self.startPoint point = self.points[1] - self.points[1] = QPointF(point.x(), self.startPoint.y()) - if self.endGripItem.m_location in ["left", "right"]: - point = self.points[len(self.points) - 2] - self.points[len(self.points) - 2] = QPointF(point.x(), self.endPoint.y()) + if self.startGripItem.m_location in ["left", "right"]: + self.points[1] = QPointF(point.x(), self.startPoint.y()) else: - point = self.points[len(self.points) - 2] - self.points[len(self.points) - 2] = QPointF(self.endPoint.x(), point.y()) + self.points[1] = QPointF(self.startPoint.x(), point.y()) - else: - point = self.points[1] - self.points[1] = QPointF(self.startPoint.x(), point.y()) + if self.endGripItem: + self.points[len(self.points) - 1] = self.endPoint + point = self.points[len(self.points) - 2] if self.endGripItem.m_location in ["left", "right"]: - point = self.points[len(self.points) - 2] self.points[len(self.points) - 2] = QPointF(point.x(), self.endPoint.y()) else: - point = self.points[len(self.points) - 2] self.points[len(self.points) - 2] = QPointF(self.endPoint.x(), point.y()) def shape(self): @@ -189,21 +748,6 @@ class Line(QGraphicsPathItem): path = qp.createStroke(self.path()) return path - def paint(self, painter, option, widget): - color = Qt.red if self.isSelected() else Qt.black - painter.setPen(QPen(color, 2, self.penStyle)) - painter.drawPath(self.path()) - - # To paint path of shape - # painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine)) - # painter.drawPath(self.shape()) - if self.isSelected(): - self.showGripItem() - self._selected = True - elif self._selected: - self.hideGripItem() - self._selected = False - def movePoints(self, index, movement): """move points of line """ @@ -211,21 +755,25 @@ class Line(QGraphicsPathItem): point = self.points[i] point += movement self.points[i] = point - self.updatePath() - self.updateGrabber([index]) + self.updatePath() def addGrabber(self): """adds grabber when line is moved """ - if self.startGripItem.m_location in ["left", "right"]: - direction = [Qt.Horizontal, Qt.Vertical] + for grabber in self.m_grabbers: + self.scene().removeItem(grabber) + if self.endGripItem: + count = range(1, len(self.points) - 2) else: - direction = [Qt.Vertical, Qt.Horizontal] - for i in range(1, len(self.points) - 2): - item = Grabber(self, i, direction[i - 1]) + count = range(1, len(self.points) - 1) + for i in count: + if self.points[i].x() == self.points[i + 1].x(): + direction = Qt.Horizontal + else: + direction = Qt.Vertical + item = Grabber(self, i, direction) item.setParentItem(self) item.setPos(self.pos()) - self.scene().addItem(item) self.m_grabbers.append(item) def updateGrabber(self, index_no_updates=None): @@ -233,7 +781,7 @@ class Line(QGraphicsPathItem): """ index_no_updates = index_no_updates or [] for grabber in self.m_grabbers: - if grabber.m_index in index_no_updates: continue + if grabber.m_index in index_no_updates or grabber.isSelected(): continue index = grabber.m_index startPoint = self.points[index] endPoint = self.points[index + 1] @@ -243,10 +791,18 @@ class Line(QGraphicsPathItem): grabber.setEnabled(True) def itemChange(self, change, value): - if change == QGraphicsItem.ItemSceneHasChanged and self.scene(): - # self.addGrabber() - # self.updateGrabber() + if change == QGraphicsItem.ItemSelectedHasChanged: + if value == 1: + self.showGripItem() + items = self.collidingItems(Qt.IntersectsItemShape) + for item in items: + if type(item) == type(self): + item.stackBefore(self) + self.scene().update() + else: + self.hideGripItem() return + return super(Line, self).itemChange(change, value) def updateLine(self, startPoint=None, endPoint=None): @@ -270,7 +826,35 @@ class Line(QGraphicsPathItem): item = self.endGripItem self.endPoint = item.parentItem().mapToScene(item.pos()) self.updatePath() - self.updateGrabber() + + if self.startGripItem and self.refLine: + item = self.startGripItem + self.startPoint = item.parentItem().mapToScene(item.pos()) + self.updatePath() + + def updateMidLines(self): + for line in self.midLines: + points = line.refLine.points + point1 = points[line.refIndex] + point2 = points[line.refIndex + 1] + i = len(line.points) - 1 + if point1.x() == point2.x(): + line.points[i].setX(point1.x()) + if line.points[i].y() < min(point1.y(), point2.y()): + line.points[i].setY(min(point1.y(), point2.y())) + line.points[i-1].setY(min(point1.y(), point2.y())) + elif line.points[i].y() > max(point1.y(), point2.y()): + line.points[i].setY(max(point1.y(), point2.y())) + line.points[i-1].setY(max(point1.y(), point2.y())) + elif point1.y() == point2.y(): + line.points[i].setY(point1.y()) + if line.points[i].x() < min(point1.x(), point2.x()): + line.points[i].setX(min(point1.x(), point2.x())) + line.points[i-1].setX(min(point1.x(), point2.x())) + elif line.points[i].x() > max(point1.x(), point2.x()): + line.points[i].setX(max(point1.x(), point2.x())) + 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 @@ -280,18 +864,20 @@ class Line(QGraphicsPathItem): self.scene().removeItem(self) def showGripItem(self): - """hides grip items which contains line + """shows grip items which contains line """ if self.startGripItem: self.startGripItem.show() if self.endGripItem: self.endGripItem.show() - # for grabber in self.m_grabber: - # grabber.setSelected(True) + for grabber in self.m_grabbers: + grabber.show() def hideGripItem(self): """hides grip items which contains line """ if self.startGripItem: self.startGripItem.hide() if self.endGripItem: self.endGripItem.hide() + for grabber in self.m_grabbers: + grabber.hide() def setStartGripItem(self, item): self.startGripItem = item @@ -299,6 +885,12 @@ class Line(QGraphicsPathItem): def setEndGripItem(self, item): self.endGripItem = item - def setPenStyle(self, style): - """change current pen style for line""" - self.penStyle = style + def contextMenuEvent(self, event): + """Pop up menu + :return: + """ + contextMenu = QMenu() + addLableAction = contextMenu.addAction("add Label") + action = contextMenu.exec_(event.screenPos()) + if action == addLableAction: + self.label.append(LineLabel(event.scenePos(), self))
\ No newline at end of file diff --git a/src/main/python/shapes/shapes.py b/src/main/python/shapes/shapes.py index e79ddb7..3e5cbb2 100644 --- a/src/main/python/shapes/shapes.py +++ b/src/main/python/shapes/shapes.py @@ -2,16 +2,37 @@ from PyQt5 import QtCore, QtWidgets from PyQt5.QtCore import (QEvent, QFile, QIODevice, QMimeData, QPointF, QRect, QRectF, QSizeF, Qt) from PyQt5.QtGui import (QBrush, QColor, QCursor, QDrag, QFont, QImage, - QPainter, QPainterPath, QPen, QTransform) + QPainter, QPainterPath, QPen, QTransform, QTextCursor) from PyQt5.QtSvg import QGraphicsSvgItem, QSvgRenderer from PyQt5.QtWidgets import (QGraphicsColorizeEffect, QGraphicsEllipseItem, QGraphicsItem, QGraphicsPathItem, QGraphicsProxyWidget, QGraphicsSceneHoverEvent, - QLineEdit) + QLineEdit, QMenu, QGraphicsTextItem) +from .line import Line, findIndex from utils.app import fileImporter -from .line import Line +from utils.app import fileImporter + +class ItemLabel(QGraphicsTextItem): + def __init__(self, pos, parent=None): + super().__init__(parent=parent) + self.setPlainText("abc") + self.setFlags(QGraphicsItem.ItemIsMovable | + QGraphicsItem.ItemIsSelectable | + QGraphicsItem.ItemIsFocusable) + self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) + self.setTextInteractionFlags(Qt.NoTextInteraction) + self.setPos(self.parentItem().boundingRect().bottomLeft()) + + def mouseDoubleClickEvent(self, event): + self.setTextInteractionFlags(Qt.TextEditorInteraction) + self.setFocus() + super(ItemLabel, self).mouseDoubleClickEvent(event) + + def focusOutEvent(self, event): + super(ItemLabel, self).focusOutEvent(event) + self.setTextInteractionFlags(Qt.NoTextInteraction) class GripItem(QGraphicsPathItem): @@ -53,7 +74,7 @@ class SizeGripItem(GripItem): self.width = annotation_item.boundingRect().width() path = QPainterPath() - path.addRect(QRectF(-self.width/2, -self.height/2, self.width, self.height)) + path.addRect(QRectF(-self.width / 2, -self.height / 2, self.width, self.height)) super(SizeGripItem, self).__init__(annotation_item, path=path, parent=parent) self.setFlag(QGraphicsItem.ItemIsSelectable, True) self.setFlag(QGraphicsItem.ItemIsMovable, True) @@ -84,7 +105,7 @@ class SizeGripItem(GripItem): else: self.width = self.parentItem().boundingRect().width() path = QPainterPath() - path.addRect(QRectF(-self.width/2, -self.height/2, self.width, self.height)) + path.addRect(QRectF(-self.width / 2, -self.height / 2, self.width, self.height)) self.setPath(path) def updatePosition(self): @@ -174,7 +195,7 @@ class LineGripItem(GripItem): super(LineGripItem, self).__init__(annotation_item, path=self.path, parent=parent) self.m_index = index self.m_location = location - self.connectedLines = [] + self.line = None # stores current line which is in process self.tempLine = None # keep previous hovered item when line drawing in process @@ -201,8 +222,8 @@ class LineGripItem(GripItem): self.setEnabled(False) self.setPos(pos) self.setEnabled(True) - for line in self.connectedLines: - line.updateLine() + if self.line: + self.line.updateLine() def mousePressEvent(self, mouseEvent): """Handle all mouse press for this item @@ -210,10 +231,13 @@ class LineGripItem(GripItem): if mouseEvent.button() != Qt.LeftButton: return # initialize a line and add on scene - startPoint = endPoint = self.parentItem().mapToScene(self.pos()) - self.tempLine = Line(startPoint, endPoint) - self.tempLine.setStartGripItem(self) - self.scene().addItemPlus(self.tempLine) + if self.line and not self.line.scene(): + self.line = None + + if not self.line: + startPoint = endPoint = self.parentItem().mapToScene(self.pos()) + self.tempLine = Line(startPoint, endPoint) + self.scene().addItem(self.tempLine) super().mousePressEvent(mouseEvent) def mouseMoveEvent(self, mouseEvent): @@ -241,28 +265,36 @@ class LineGripItem(GripItem): super().mouseReleaseEvent(mouseEvent) # set final position of line if self.tempLine: - item = self.scene().itemAt(mouseEvent.scenePos().x(), mouseEvent.scenePos().y(), - self.transform()) - - if type(item) == LineGripItem and item != self: - self.tempLine.setEndGripItem(item) - endPoint = item.parentItem().mapToScene(item.pos()) - self.tempLine.updateLine(endPoint=endPoint) - self.connectedLines.append(self.tempLine) - item.connectedLines.append(self.tempLine) + items = self.scene().items(QPointF(mouseEvent.scenePos().x(), mouseEvent.scenePos().y())) + for item in items: + if type(item) == LineGripItem and item != self: + if item.line and not item.line.scene(): + item.line = None + if item.line: + break + self.tempLine.setStartGripItem(self) + self.tempLine.setEndGripItem(item) + endPoint = item.parentItem().mapToScene(item.pos()) + self.tempLine.updateLine(endPoint=endPoint) + self.line = self.tempLine + item.line = self.tempLine + break + 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) + self.tempLine.updateLine(endPoint=endPoint) + item.midLines.append(self.tempLine) + self.line = self.tempLine + break + self.scene().removeItem(self.tempLine) + if self.line: + self.scene().addItemPlus(self.line) - - - elif self.tempLine and self.scene(): - self.scene().removeItem(self.tempLine) self.tempLine = None self.previousHoveredItem = None - def removeConnectedLines(self): - """removes all connected line to grip""" - for line in self.connectedLines: - line.removeFromCanvas() - def show(self): """ shows line grip item """ @@ -281,10 +313,13 @@ 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, parent=None): QGraphicsSvgItem.__init__(self, parent) self.m_type = unitOperationType + self.id = None # self.m_renderer = QSvgRenderer("svg/" + unitOperationType + ".svg") # self.m_renderer = QSvgRenderer(fileImporter(f'svg/{unitOperationType}.svg')) self.m_renderer = QSvgRenderer(fileImporter(f'svg/ellipse.svg')) @@ -303,7 +338,8 @@ class NodeItem(QGraphicsSvgItem): # grip items connected to this item self.lineGripItems = [] self.sizeGripItems = [] - + self.label = None + def boundingRect(self): """Overrides QGraphicsSvgItem's boundingRect() virtual public function and returns a valid bounding @@ -319,7 +355,10 @@ class NodeItem(QGraphicsSvgItem): """ if not self.m_renderer: QGraphicsSvgItem.paint(self, painter, option, widget) - self.m_renderer.render(painter, self.boundingRect()) + 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() @@ -380,7 +419,7 @@ class NodeItem(QGraphicsSvgItem): updates size grip items """ index_no_updates = index_no_updates or [] - for i, item in zip(range(len(self.sizeGripItems)), self.sizeGripItems): + for i, item in enumerate(self.sizeGripItems): if i not in index_no_updates: item.updatePosition() @@ -449,6 +488,16 @@ class NodeItem(QGraphicsSvgItem): item.setPen(QPen(Qt.transparent)) item.setBrush(Qt.transparent) + def contextMenuEvent(self, event): + """Pop up menu + :return: + """ + contextMenu = QMenu() + addLableAction = contextMenu.addAction("add Label") + # addLableAction.triggered.connect(self.addLabel) + action = contextMenu.exec_(event.screenPos()) + if action == addLableAction: + self.label = ItemLabel(event.scenePos(), self) # classes of pfd-symbols class AirBlownCooler(NodeItem): @@ -601,9 +650,9 @@ class InducedDraftCooling(NodeItem): super(InducedDraftCooling, self).__init__("InducedDraftCooling", parent=None) -class jacketedMixingVessel(NodeItem): +class JacketedMixingVessel(NodeItem): def __init__(self): - super(jacketedMixingVessel, self).__init__("jacketedMixingVessel", parent=None) + super(JacketedMixingVessel, self).__init__("JacketedMixingVessel", parent=None) class LiquidRingCompressor(NodeItem): diff --git a/src/main/python/utils/graphics.py b/src/main/python/utils/graphics.py index 27e02c4..a997a30 100644 --- a/src/main/python/utils/graphics.py +++ b/src/main/python/utils/graphics.py @@ -86,15 +86,9 @@ class customScene(QGraphicsScene): def __init__(self, *args, parent=None): super(customScene, self).__init__(*args, parent=parent) - self.setItemIndexMethod(QGraphicsScene.NoIndex) - self.undoStack = QUndoStack(self) #Used to store undo-redo moves self.createActions() #creates necessary actions that need to be called for undo-redo - def update(self, *args): - self.advance() - return super(customScene, self).update(*args) - def createActions(self): # helper function to create delete, undo and redo shortcuts self.deleteAction = QAction("Delete Item", self) @@ -120,7 +114,6 @@ class customScene(QGraphicsScene): def itemMoved(self, movedItem, lastPos): #item move event, checks if item is moved self.undoStack.push(moveCommand(movedItem, lastPos)) - self.advance() def addItemPlus(self, item): # extended add item method, so that a corresponding undo action is also pushed diff --git a/src/main/python/utils/undo.py b/src/main/python/utils/undo.py index 4a9f3dd..cf539e7 100644 --- a/src/main/python/utils/undo.py +++ b/src/main/python/utils/undo.py @@ -36,7 +36,6 @@ class addCommand(QUndoCommand): self.scene.addItem(self.diagramItem) self.diagramItem.setPos(self.itemPos) self.scene.clearSelection() - # print(self.diagramItem) self.scene.update() class deleteCommand(QUndoCommand): diff --git a/src/main/resources/base/svg/ellipse.svg b/src/main/resources/base/svg/ellipse.svg index 8802667..7da0ae6 100644 --- a/src/main/resources/base/svg/ellipse.svg +++ b/src/main/resources/base/svg/ellipse.svg @@ -5,4 +5,4 @@ <svg:ellipse cx="50" cy="50" rx="50" ry="50" fill="none" stroke="black" stroke-width="1" /> </svg:svg> <!-- ... --> -</svg>
\ No newline at end of file +</svg>
\ No newline at end of file |