1

A month ago I was asking, how to make scene translated and I got great help and understood how make it work, and after a month working with it/adding staff I noticed that when you zoom in quite close to geometry or somewhere else, translation getting worth and worth.

You have to zoom in very close to geometry to see problem. Here is some code:

from PyQt5.QtGui import QColor, QPolygonF, QPen, QBrush
from PyQt5.QtCore import Qt, QPointF, QPoint, pyqtSignal
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QGraphicsView, QGraphicsScene, QGraphicsPolygonItem, QApplication, \
    QFrame, QSizePolicy
points_list = [[60.1, 19.6, 0.0], [60.1, 6.5, 0.0], [60.1, -6.5, 0.0], [60.1, -19.6, 0.0], [60.1, -19.6, 0.0],
               [20.0, -19.6, 0.0], [-20, -19.6, 0.0], [-60.1, -19.6, 0.0], [-60.1, -19.6, 0.0], [-60.1, -6.5, 0.0],
               [-60.1, 6.5, 0.0], [-60.1, 19.6, 0.0], [-60.1, 19.6, 0.0], [-20.0, 19.6, 0.0], [20.0, 19.6, 0.0],
               [60.1, 19.6, 0.0]]


class MainWindow(QDialog):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent=parent)
        self.create()

    def create(self, **kwargs):
        main_layout = QVBoxLayout()
        graphics = MainGraphicsWidget()
        main_layout.addWidget(graphics)
        self.setLayout(main_layout)

class MainGraphicsWidget(QGraphicsView):
    zoom_signal = pyqtSignal(bool)

    def __init__(self, parent=None):
        super(MainGraphicsWidget, self).__init__(parent)
        self._scene = QGraphicsScene(backgroundBrush=Qt.gray)
        self.__zoom = 0
        self.setScene(self._scene)
        self.setTransformationAnchor(QGraphicsView.NoAnchor)
        #self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setBackgroundBrush(QBrush(QColor(30, 30, 30)))
        self.setFrameShape(QFrame.NoFrame)
        self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
        # self.sceneRect = self._scene.sceneRect()
        self.testButton = GraphicsButton()
        self._scene.addItem(self.testButton)
        self.startPos = None


    def mousePressEvent(self, event):
        if event.modifiers() & Qt.AltModifier and event.button() == Qt.LeftButton:
            self.startPos = event.pos()
        else:
            super(MainGraphicsWidget, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        modifierPressed = QApplication.keyboardModifiers()
        if self.startPos is not None:
            delta = self.startPos - event.pos()
            transform = self.transform()
            deltaX = delta.x() / transform.m11()
            deltaY = delta.y() / transform.m22() 
            self.setSceneRect(self.sceneRect().translated(deltaX, deltaY))
            self.startPos = event.pos()
        else:
            super(MainGraphicsWidget, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        self.startPos = None
        super(MainGraphicsWidget, self).mouseReleaseEvent(event)


    def wheelEvent(self, event):
        if event.angleDelta().y() > 0:
            factor = 1.25
            self.__zoom += 1
        else:
            factor = 0.8
            self.__zoom -= 1
        self.scale(factor, factor)
        self.zoom_signal.emit(self.__zoom < 10)


class GraphicsButton(QGraphicsPolygonItem):
    def __init__(self, parent=None):
        super(GraphicsButton, self).__init__(parent)
        self.myPolygon = QPolygonF([QPointF(v1, v2) for v1, v2, v3 in points_list])
        self.setPen(QPen(QColor(0, 0, 0), 0, Qt.SolidLine, Qt.FlatCap, Qt.MiterJoin))
        self.setPolygon(self.myPolygon)
        self.setBrush(QColor(220, 40, 30))


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    window = MainWindow()
    window.setGeometry(500, 100, 500, 900)
    window.show()
    sys.exit(app.exec_())

(translate starts to work with Alt + Left Click )

I guess problem is that when you zoom in way too close, your cursor is still moving along same coordinates(they don't get smaller) and it starts to translate slower, which I don't take in calculation in my code. But then why sometimes my sceneRect().translated() stops moving at all? Do I zoom in to the point that I don't even cross with my cursor ( 1x, 1y ) at least ?

So my question is, if my theory correct , can somebody advise my how to take in calculation of delta , zoom in and zoom out? Thank you and sorry for my english

Edit: Here is screenshot of around how much zoom in you need to see problem.

enter image description here

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Vlad
  • 387
  • 3
  • 17

2 Answers2

2

sceneRect is in coordinates of the scene, and not of the view so for your calculations you must convert the mouse points to coordinates of the scene using the mapToScene method. This modification should also be applied to the rectangle of the viewport that is projected in the scene since that is not equal to the sceneRect

class MainGraphicsWidget(QGraphicsView):
    zoom_signal = pyqtSignal(bool)

    def __init__(self, parent=None):
        # ...
        self.startPos = QPointF()

    def mousePressEvent(self, event):
        if event.modifiers() & Qt.AltModifier and event.button() == Qt.LeftButton:
            self.startPos = self.mapToScene(event.pos())
        else:
            super(MainGraphicsWidget, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if not self.startPos.isNull():
            delta = self.startPos - self.mapToScene(event.pos())
            r = self.mapToScene(self.viewport().rect()).boundingRect()
            self.setSceneRect(r.translated(delta))
        else:
            super(MainGraphicsWidget, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        self.startPos = QPointF()
        super(MainGraphicsWidget, self).mouseReleaseEvent(event)

    def wheelEvent(self, event):
        # ...
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thanks for corrections, I didn't know about that. Sadly it doesn't solve problem. Delta is still becomes 0 at some point and traslation stops working. I added screenshot of how much you need to zoom in to see problem clearly. – Vlad Jan 17 '20 at 09:49
1

The Answer above is partially correct. How I am seeing it, your approach is trying to translate the sceneRect by factoring in the viewport scale. "m11 and m22". However, when you zoom in, your viewport scale will be less than 1. The translate function does not accept values below 1. Try printing out DeltaX when you zoom in, you will see that the scene will only move if DeltaX > 1.

The two approaches you can take are:

A) Manually round up the Delta axis' when they are greater than 0, to something like 1.1, which is greater than 1.

transform = self.transform()
deltaX = delta.x() / transform.m11()
deltaY = delta.y() / transform.m22()
            
if 1 > deltaX > 0.1:
   deltaX = 1.1
            
if 1 > deltaY > 0.1:
   deltaY = 1.1

B) Like eyllanesc mentioned above, project the viewport to the scene so any translations made will be in scene space relative to viewport space. Additionally, so zoom positioning remains consistent, you can also translate mouse position into scene space while scaling the scene. Finally, eyllanesc adds a line of code that is redundant and does properly translate. You do not need to reset the origin position of the mouse when the mouse button is pressed.

You can therefore get something like this.

def __init__(self, parent):
    super(GraphicsThingy, self).__init__(parent)

    self.setTransformationAnchor(QGraphicsView.NoAnchor)
    self.setResizeAnchor(QGraphicsView.NoAnchor)

    self.installEventFilter(self)

    self.mousePos = None

def wheelEvent(self, event):

    if event.modifiers() & Qt.ControlModifier:
        scaleDelta = event.angleDelta().y() / 35
        factor = pow(self.zoomMultiplier, scaleDelta)

        originalPos = self.mapToScene(event.pos())

        self.scale(factor, factor)
        newPos = self.mapToScene(event.pos())

        deltaPos = newPos - originalPos
        self.translate(deltaPos.x(), deltaPos.y())

    event.accept()

def mousePressEvent(self, event):

    if event.button() == Qt.MiddleButton:
        self.mousePos = self.mapToScene(event.pos())

    event.accept()

def mouseMoveEvent(self, event):

    if self.movePos is not None:
        delta = self.mousePos - self.mapFromScene(event.pos())
        # Keeps scrolling speed consistent with zoom. Max(v,1) Ensures that the scale value will not multiply the scrolling speed. 
        delta.setX(delta.x() / max(self.transform().m11(),1))
        delta.setY(delta.y() / max(self.transform().m22(),1))

        # Change scene scene rect position based on delta
        rect = self.mapToScene(self.viewport().rect()).boundingRect()
        self.setSceneRect(rect.translated(delta))
        
        self.update()

    event.accept()

def mouseReleaseEvent(self, event):

    self.mousePos = None