0

I have a scene where I would like to draw a line between two points(mouse press should be the start point and mouse release as the endpoint) using the QPainterpath.

Here is a demonstration of how I want it to be.

enter image description here

Here is what's happening with my current code.

enter image description here

Below is the code I have tried

import sys
from PyQt5 import QtWidgets, QtCore, QtGui

class Scene(QtWidgets.QGraphicsScene):

    def __init__(self, *args, **kwargs):
        super(Scene, self).__init__(*args, **kwargs)
        self.point = QtCore.QPointF(0.0, 0.0)

        self.path = QtGui.QPainterPath()

        self.start_point = None
        self.end_point = None

        self.start = False

    def mousePressEvent(self, event):

        self.start_point = event.scenePos()
        self.start = True
        self.path = QtGui.QPainterPath(self.start_point)
        # self.addLine(self.line.setP1(self.start_point))

        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):

        if self.start:
            self.path.lineTo(event.scenePos())
            # self.path.moveTo(event.scenePos())
            self.addPath(self.path, QtGui.QPen(QtCore.Qt.red))

        super(Scene, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):

        if self.start:
            print(self.path)
            self.path.lineTo(event.scenePos())
            # self.path.moveTo(event.scenePos())
            self.addPath(self.path, QtGui.QPen(QtCore.Qt.red))
            self.start = False

        super(Scene, self).mouseReleaseEvent(event)

def main():
    app = QtWidgets.QApplication(sys.argv)

    view = QtWidgets.QGraphicsView()
    view.setRenderHint(QtGui.QPainter.Antialiasing)

    view.setMouseTracking(True)
    scene = Scene()

    view.setScene(scene)
    view.show()
    
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
JacksonPro
  • 3,135
  • 2
  • 6
  • 29
  • Is there a specific reason for using QPainterPath and QGraphicsPathItem instead of QLine and QGraphicsLineItem? – musicamante Jan 19 '21 at 12:03
  • @musicamante No not really I tried using QLine first when I failed I tried QPainterPath. Also, can you tell me about their difference? – JacksonPro Jan 19 '21 at 12:14

2 Answers2

3

Every time lineTo is used then a new line is created where the starting point is the last point added and the end point is the one that is passed to the function, so you see the curves since they are the union of those lines. The solution is to have 2 variables that store the start point and the end point, and be updated when necessary, then use that information to update the QGraphicsPathItem, not the QPainterPath. The same concept can be applied for QGraphicsLineItem with QLineF.

class Scene(QtWidgets.QGraphicsScene):
    def __init__(self, *args, **kwargs):
        super(Scene, self).__init__(*args, **kwargs)

        self.path_item = self.addPath(QtGui.QPainterPath())

        self.start_point = QtCore.QPointF()
        self.end_point = QtCore.QPointF()

    def mousePressEvent(self, event):
        self.start_point = event.scenePos()
        self.end_point = self.start_point
        self.update_path()
        super().mousePressEvent(event)

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

    def mouseReleaseEvent(self, event):
        self.end_point = event.scenePos()
        self.update_path()
        super(Scene, self).mouseReleaseEvent(event)

    def update_path(self):
        if not self.start_point.isNull() and not self.end_point.isNull():
            path = QtGui.QPainterPath()
            path.moveTo(self.start_point)
            path.lineTo(self.end_point)
            self.path_item.setPath(path)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thank you for answering. But, why is it that in the beginning a line is drawn to the mouse press taking it as the end point? – JacksonPro Jan 19 '21 at 14:31
  • @JacksonPro I think you can answer yourself: If you press the mouse then what should be drawn? – eyllanesc Jan 19 '21 at 14:33
  • If click it anywhere, in the beginning, the line is drawn to the mouse press from the center it doesn't wait for the mouse release. This however doesn't happen from the second press onwards. – JacksonPro Jan 19 '21 at 14:38
  • @JacksonPro That contradicts what you indicate in your post: X. In my logic it is the following: 1) When the mouse is pressed a line is drawn from where the mouse is pressed towards the same point and 2) when the mouse is moved the line is it draws from the point at 1 to the point where the mouse is currently and 3) every time the mouse is moved the 2 is done, and 4) when the mouse is released the mouse is no longer altered. Have you tested the code? – eyllanesc Jan 19 '21 at 14:45
  • 1
    @eyllanesc I believe that JacksonPro is experiencing the auto scrolling of the view that happens when the sceneRect is not explicitly set and the contents of the scene change. – musicamante Jan 19 '21 at 14:47
  • @musicamante yes that was the issue. But, if I set it the problem is that it would stop expanding does it mean I will have to do a manual resizing of the scene rect? – JacksonPro Jan 19 '21 at 14:55
  • @JacksonPro explicitly setting the sceneRect *on the view* doesn't prevent the *scene* to expand *its* sceneRect whenever its content change. – musicamante Jan 19 '21 at 15:00
  • @musicamante this is how I have set the sceneReact `self.setSceneRect(QtCore.QRectF(0, 0, 500, 500))` now I cant go beyond this rect. How could I change that? – JacksonPro Jan 19 '21 at 15:03
  • 1
    @JacksonPro if by "go beyond" you mean ensure that the view shows scrollbars and adapts them when the scene size is increased, I suggest you to create a new question as it is off topic to this one. – musicamante Jan 19 '21 at 15:35
1

Thx to @eyllanesc for anyone trying to achieve this using QLineF and QGraphicsLineItem here is the code.

import sys
from PyQt5 import QtWidgets, QtCore, QtGui


class Scene(QtWidgets.QGraphicsScene):
    def __init__(self, *args, **kwargs):
        super(Scene, self).__init__(*args, **kwargs)

        self.setSceneRect(QtCore.QRectF(0, 0, 500, 500))

        self.line = None
        self.graphics_line = None

        self.start_point = QtCore.QPointF()
        self.end_point = QtCore.QPointF()

    def mousePressEvent(self, event):
        self.start_point = event.scenePos()
        self.end_point = self.start_point

        self.line = QtCore.QLineF(self.start_point, self.end_point)
        self.graphics_line = QtWidgets.QGraphicsLineItem(self.line)

        self.update_path()
        super().mousePressEvent(event)

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

    def mouseReleaseEvent(self, event):
        self.end_point = event.scenePos()
        self.update_path()

        super(Scene, self).mouseReleaseEvent(event)

    def update_path(self):
        if not self.start_point.isNull() and not self.end_point.isNull():
            self.line.setP2(self.end_point)
            self.graphics_line.setLine(self.line)
            self.addItem(self.graphics_line)


def main():
    app = QtWidgets.QApplication(sys.argv)

    view = QtWidgets.QGraphicsView()
    view.setRenderHint(QtGui.QPainter.Antialiasing)

    view.setMouseTracking(True)
    scene = Scene()

    view.setScene(scene)
    view.show()

    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

JacksonPro
  • 3,135
  • 2
  • 6
  • 29