pythonpyqt5qgraphicsviewqgraphicssceneqgraphicspixmapitem

Add ports to the QGraphicsPixmapItem object


img

I've created something like this. I have created a QGraphicsPixmapItem object that I can add to QGraphicsView. I want to add ports to these objects I created. And I want to make connections between these ports and the objects I have created. I don't know how to do it. can you help me ?

my code


class Part(QGraphicsPixmapItem):
    def __init__(self, name):
        super().__init__()
        self.name = name
        self.setFlags(self.ItemIsSelectable | self.ItemIsMovable | self.ItemIsFocusable)
        self.label = QLabel("Deneme")
        self.pixmap = QPixmap("image/" + name + ".png")
        self.setPixmap(self.pixmap)

        self.inputs = {}
        self.outputs = {}

    def keyPressEvent(self, event):
        if (event.matches(QKeySequence.Delete)):
            self.delete()

    def contextMenuEvent(self, event):
        menu = QMenu()
        delete = menu.addAction('Delete')
        delete.triggered.connect(self.delete)
        menu.exec_(event.screenPos())

    def showInfo(self,event):
        pass

    def delete(self):
        # Delete all connection connected to the part
        for input in self.inputs:
            while len(self.inputs[input].connection) > 0:
                self.inputs[input].connection[0].delete()
        for output in self.outputs:
            while len(self.outputs[output].connection) > 0:
                self.outputs[output].connection[0].delete()
            # Delete the part
        self.scene().removeItem(self)




Solution

  • Using a QGraphicsPixmapItem as a basis for making connections is a bad choice because it is difficult to detect where the terminals are, instead I propose to use a QGraphicsPathItem that has as children to QGraphicsLineItem which may be the connection nodes. For the implementation I based on my old answer.

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class Edge(QtWidgets.QGraphicsLineItem):
        def __init__(self, source, dest, parent=None):
            super().__init__(parent)
            self.source = source
            self.dest = dest
            self.source.addEdge(self)
            self.dest.addEdge(self)
            self.setPen(QtGui.QPen(QtCore.Qt.red, 1.75))
            self.adjust()
    
        def adjust(self):
            self.prepareGeometryChange()
            self.setLine(QtCore.QLineF(self.dest.end(), self.source.end()))
    
    
    class TerminalItem(QtWidgets.QGraphicsLineItem):
        def __init__(self, line, parent=None):
            super().__init__(line, parent)
            self.setAcceptHoverEvents(True)
            self.setPen(QtGui.QPen(QtGui.QColor(QtCore.Qt.transparent), 4))
            self.edges = []
    
        def addEdge(self, edge):
            self.edges.append(edge)
    
        def mousePressEvent(self, event):
            event.accept()
    
        def mouseMoveEvent(self, event):
            event.accept()
    
        def end(self):
            return self.mapToScene(self.line().p1())
    
        def hoverEnterEvent(self, event):
            self.setPen(QtGui.QPen(QtGui.QColor(QtCore.Qt.red), 4))
    
        def hoverLeaveEvent(self, event):
            self.setPen(QtGui.QPen(QtGui.QColor(QtCore.Qt.transparent), 4))
    
    
    class PortItem(QtWidgets.QGraphicsPathItem):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.end_ports = []
            self.setFlags(
                QtWidgets.QGraphicsItem.ItemIsMovable
                | QtWidgets.QGraphicsItem.ItemSendsGeometryChanges
            )
            self.create_path()
            self.create_end_ports()
    
        def create_path(self):
            raise NotImplementedError
    
        def create_end_ports(self):
            raise NotImplementedError
    
        def itemChange(self, change, value):
            if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged:
                for ports in self.end_ports:
                    for edge in ports.edges:
                        edge.adjust()
            return super().itemChange(change, value)
    
    
    class ANDPortItem(PortItem):
        def create_path(self):
            path = QtGui.QPainterPath()
            s = 20
            path.moveTo(0, 1)
            path.lineTo(2, 1)
            path.lineTo(2, 0)
            path.lineTo(6, 0)
            path.arcTo(QtCore.QRectF(4, 0, 4, 4), 90, -180)
            path.lineTo(2, 4)
            path.lineTo(2, 1)
            path.moveTo(2, 3)
            path.lineTo(0, 3)
            path.moveTo(8, 2)
            path.lineTo(10, 2)
            tr = QtGui.QTransform()
            tr.scale(s, s)
            path = tr.map(path)
            self.setPath(path)
    
        def create_end_ports(self):
            s = 20
            for p1, p2 in (
                ((0, 1), (0.5, 1)),
                ((0, 3), (0.5, 3)),
                ((10, 2), (9.5, 2)),
            ):
                item = TerminalItem(
                    QtCore.QLineF(s * QtCore.QPointF(*p1), s * QtCore.QPointF(*p2)),
                    self,
                )
                self.end_ports.append(item)
    
    
    class NANDPortItem(ANDPortItem):
        def create_path(self):
            path = QtGui.QPainterPath()
            s = 20
            path.moveTo(0, 1)
            path.lineTo(2, 1)
            path.lineTo(2, 0)
            path.lineTo(6, 0)
            path.arcTo(QtCore.QRectF(4, 0, 4, 4), 90, -180)
            path.lineTo(2, 4)
            path.lineTo(2, 1)
            path.moveTo(2, 3)
            path.lineTo(0, 3)
            path.moveTo(8, 2)
            path.addEllipse(QtCore.QRectF(8, 2 - 0.25, 0.5, 0.5))
            path.moveTo(8.5, 2)
            path.lineTo(10, 2)
            tr = QtGui.QTransform()
            tr.scale(s, s)
            path = tr.map(path)
            self.setPath(path)
    
    
    class BulBItem(PortItem):
        def create_path(self):
            s = 20
            path = QtGui.QPainterPath()
            path.moveTo(0, 2)
            path.lineTo(1, 2)
            path.addEllipse(QtCore.QRectF(1, 0, 4, 4))
            tr = QtGui.QTransform()
            tr.scale(s, s)
            path = tr.map(path)
            self.setPath(path)
    
        def create_end_ports(self):
            s = 20
            item = TerminalItem(
                QtCore.QLineF(
                    s * QtCore.QPointF(0, 2), s * QtCore.QPointF(0.75, 2)
                ),
                self,
            )
            self.end_ports.append(item)
    
    
    class ClockItem(PortItem):
        def create_path(self):
            s = 20
            path = QtGui.QPainterPath()
            path.moveTo(0, 2)
            path.lineTo(1, 2)
            path.addRect(QtCore.QRectF(1, 0, 4, 4))
            tr = QtGui.QTransform()
            tr.scale(s, s)
            path = tr.map(path)
            self.setPath(path)
    
        def create_end_ports(self):
            s = 20
            item = TerminalItem(
                QtCore.QLineF(
                    s * QtCore.QPointF(0, 2), s * QtCore.QPointF(0.75, 2)
                ),
                self,
            )
            self.end_ports.append(item)
    
    
    class GraphicsView(QtWidgets.QGraphicsView):
        def __init__(self, scene=None, parent=None):
            super().__init__(scene, parent)
            self.setRenderHints(QtGui.QPainter.Antialiasing)
            self.line_item = QtWidgets.QGraphicsLineItem()
            self.line_item.setPen(QtGui.QPen(QtGui.QColor("black"), 4))
            self.scene().addItem(self.line_item)
            self.line_item.hide()
    
            self.start_item = None
    
        def mousePressEvent(self, event):
            items = self.items(event.pos())
            for item in items:
                if isinstance(item, TerminalItem):
                    item.setPen(QtGui.QPen(QtCore.Qt.red, 4))
                    self.line_item.show()
                    sp = item.end()
                    line = QtCore.QLineF(sp, sp)
                    self.line_item.setLine(line)
                    self.start_item = item
                    break
            super().mousePressEvent(event)
    
        def mouseMoveEvent(self, event):
            if self.line_item.isVisible():
                l = self.line_item.line()
                l.setP2(self.mapToScene(event.pos()))
                self.line_item.setLine(l)
                for item in self.items():
                    if (
                        isinstance(item, TerminalItem)
                        and item is not self.start_item
                    ):
                        item.setPen(QtGui.QPen(QtCore.Qt.transparent, 4))
    
                for item in self.items(event.pos()):
                    if isinstance(item, TerminalItem):
                        item.setPen(QtGui.QPen(QtCore.Qt.red, 4))
            super().mouseMoveEvent(event)
    
        def mouseReleaseEvent(self, event):
            if self.line_item.isVisible():
                self.line_item.hide()
                for item in self.items():
                    if isinstance(item, TerminalItem) and item:
                        item.setPen(QtGui.QPen(QtCore.Qt.transparent, 4))
                end_item = None
                for item in self.items(event.pos()):
                    if isinstance(item, TerminalItem):
                        end_item = item
                        break
                if end_item is not None:
                    edge = Edge(self.start_item, end_item)
                    self.scene().addItem(edge)
                    self.start_item = None
            super().mouseReleaseEvent(event)
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
            scene = QtWidgets.QGraphicsScene()
            graphicsview = GraphicsView(scene)
            for t, p in (
                (ANDPortItem, QtCore.QPointF(0, 0)),
                (BulBItem, QtCore.QPointF(250, 0)),
                (ClockItem, QtCore.QPointF(0, 250)),
                (NANDPortItem, QtCore.QPointF(250, 250)),
            ):
                item = t()
                item.setPos(p)
                scene.addItem(item)
            self.setCentralWidget(graphicsview)
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.resize(640, 480)
        w.show()
        sys.exit(app.exec_())
    

    enter image description here