draw any lines to connect two items and move them PyQt5

25 Views Asked by At

based on the code in this page, I took the liberty of adapting it to my needs, but now I'm stuck. I've added the ability to create squares from a button, I've also changed the location of the points so that they're not just on one side but above and below.

Now my problem is that I can't seal the line at one of the points. When I create the line and connect two points on two items, once I move one of the items or even both items, the connections change. If the line was connected to the point below, it changes to be connected to the point above. I'd like to seal the links regardless of movement.

Thank you for your help.

`import sys
from PyQt5 import QtWidgets, QtCore, QtGui

class CustomItem(QtWidgets.QGraphicsItem):

    def __init__(self, pointONLeft=False, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.ellipseOnLeft = pointONLeft
        self.point = None
   
    self.endPoint = None
        self.isStart = None
        self.line = None
        self.setAcceptHoverEvents(True)
        self.setFlag(self.ItemIsMovable)
        self.setFlag(self.ItemSendsGeometryChanges)
        self.lines = [] 

    def addLine(self, line, isStart):
        self.lines.append((line, isStart)) 

    def itemChange(self, change, value):
        if change == self.ItemPositionChange and self.scene():
            self.moveLineToCenter(value)
        return super(CustomItem, self).itemChange(change, value)

    def moveLineToCenter(self, newPos):
        for line, isStart in self.lines:
            xOffset = 50  
            yOffsetStart = -5  
            yOffsetEnd = 105  
            yOffset = yOffsetStart if isStart else yOffsetEnd
            newCenterPos = QtCore.QPointF(newPos.x() + xOffset, newPos.y() + yOffset)
            p1 = newCenterPos if isStart else line.line().p1()
            p2 = line.line().p2() if isStart else newCenterPos
            line.setLine(QtCore.QLineF(p1, p2))

    def containsPoint(self, pos):
        topEllipse = self.mapToScene(QtCore.QRectF(45, -5, 10, 10).adjusted(-0.5, -0.5, 0.5, 0.5))
        bottomEllipse = self.mapToScene(QtCore.QRectF(45, 95, 10, 10).adjusted(-0.5, -0.5, 0.5, 0.5))

        if topEllipse.containsPoint(pos, QtCore.Qt.OddEvenFill):
            return "top"
        elif bottomEllipse.containsPoint(pos, QtCore.Qt.OddEvenFill):
            return "bottom"
        else:
            return None

    def boundingRect(self):
        return QtCore.QRectF(-5, 0, 110, 110)

    def paint(self, painter, option, widget):
        pen = QtGui.QPen(QtCore.Qt.red)
        pen.setWidth(2)
        painter.setPen(pen)

        painter.setBrush(QtGui.QBrush(QtGui.QColor(31, 176, 224)))
        painter.drawRoundedRect(QtCore.QRectF(0, 0, 100, 100), 4, 4)  
        painter.setBrush(QtGui.QBrush(QtGui.QColor(214, 13, 36)))
        painter.drawEllipse(QtCore.QRectF(45, -5, 10, 10))
        painter.drawEllipse(QtCore.QRectF(45, 95, 10, 10))

    def containsPoint(self, pos):
        topEllipse = self.mapToScene(QtCore.QRectF(45, -5, 10, 10).adjusted(-0.5, -0.5, 0.5, 0.5))
        bottomEllipse = self.mapToScene(QtCore.QRectF(45, 95, 10, 10).adjusted(-0.5, -0.5, 0.5, 0.5))
        return topEllipse.containsPoint(pos, QtCore.Qt.OddEvenFill) or bottomEllipse.containsPoint(pos, QtCore.Qt.OddEvenFill)


class Scene(QtWidgets.QGraphicsScene):
    def __init__(self, *args, **kwargs):
        super(Scene, self).__init__(*args, **kwargs)
        self.startPoint = None
        self.endPoint = None
        self.line = None
        self.graphics_line = None
        self.item1 = None
        self.item2 = None
        self.isStart = None  

    def mousePressEvent(self, event):
        self.line = None
        self.graphics_line = None
        self.item1 = None
        self.item2 = None
        self.startPoint = None
        self.endPoint = None
        if self.itemAt(event.scenePos(), QtGui.QTransform()) and isinstance(self.itemAt(event.scenePos(), QtGui.QTransform()), CustomItem):
            self.item1 = self.itemAt(event.scenePos(), QtGui.QTransform())
            self.checkPoint1(event.scenePos())
            if self.startPoint:
                self.line = QtCore.QLineF(self.startPoint, self.endPoint)
                self.graphics_line = self.addLine(self.line)
                self.update_path()
        super(Scene, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if event.buttons() & QtCore.Qt.LeftButton and self.startPoint:
            self.endPoint = event.scenePos()
            self.update_path()
        super(Scene, self).mouseMoveEvent(event)

    def filterCollidingItems(self, items):  # filters out all the colliding items and returns only instances of CustomItem
        return [x for x in items if isinstance(x, CustomItem) and x != self.item1]

    def mouseReleaseEvent(self, event):
        if self.graphics_line:
            self.checkPoint2(event.scenePos())
            self.update_path()
            if self.item2:           
                self.item1.addLine(self.graphics_line, True)  
                self.item2.addLine(self.graphics_line, False)  
            else:
                self.removeItem(self.graphics_line)
                self.graphics_line = None
        super(Scene, self).mouseReleaseEvent(event)

    def checkPoint1(self, pos):
        if self.item1.containsPoint(pos):
            self.item1.setFlag(self.item1.ItemIsMovable, False)
            self.startPoint = self.endPoint = pos
        else:
            self.item1.setFlag(self.item1.ItemIsMovable, True)

    def checkPoint2(self, pos):
        item_lst = self.filterCollidingItems(self.graphics_line.collidingItems())
        contains = False
        if not item_lst:  # checks if there are any items in the list
            return
        for self.item2 in item_lst:
            if self.item2.containsPoint(pos):
                contains = True
                self.endPoint = pos
                break
        if not contains:
            self.item2 = None

    def update_path(self):
        if self.startPoint and self.endPoint:
            self.line.setP2(self.endPoint)
            self.graphics_line.setLine(self.line)
            if self.item1:
                self.item1.moveLineToCenter(self.item1.pos())

def main():
    app = QtWidgets.QApplication(sys.argv)
    scene = Scene()
    mainWindow = QtWidgets.QMainWindow()
    mainWindow.setWindowTitle("Créateur de carrés")
    mainWindow.resize(800, 600)  
    view = QtWidgets.QGraphicsView(scene)
    view.setViewportUpdateMode(view.FullViewportUpdate)
    view.setMouseTracking(True)
    mainWindow.setCentralWidget(view)

    
    menuBar = mainWindow.menuBar()
    fileMenu = menuBar.addMenu("&File")
    createAction = QtWidgets.QAction("&Créer Carré", mainWindow)
    createAction.triggered.connect(lambda: createSquare(scene))
    fileMenu.addAction(createAction)
    mainWindow.show()    
    sys.exit(app.exec_())

def createSquare(scene):
    item = CustomItem()
    scene.addItem(item)
    item.setPos(QtCore.QPointF(50, 50))  

if __name__ == '__main__':
    main()`


0

There are 0 best solutions below