nsmela/brachify

Display: Orbit Camera

Closed this issue · 13 comments

nsmela commented
Display: Orbit Camera
nsmela commented

https://github.com/tpaviot/pythonocc-core/blob/d2c3401f8565128518a0a9cb58455088f27a6af0/src/Display/qtDisplay.py#L189

    def wheelEvent(self, event):
        delta = event.angleDelta().y()
        zoom_factor = 2.0 if delta > 0 else 0.5
        self._display.ZoomFactor(zoom_factor)

    @property
    def cursor(self):
        return self._current_cursor

    @cursor.setter
    def cursor(self, value):
        if self._current_cursor != value:
            self._current_cursor = value
            if cursor := self._available_cursors.get(value):
                self.qApp.setOverrideCursor(cursor)
            else:
                self.qApp.restoreOverrideCursor()

    def mousePressEvent(self, event):
        self.setFocus()
        ev = event.pos()
        self.dragStartPosX = ev.x()
        self.dragStartPosY = ev.y()
        self._display.StartRotation(self.dragStartPosX, self.dragStartPosY)

    def mouseReleaseEvent(self, event):
        pt = event.pos()
        modifiers = event.modifiers()

        if event.button() == QtCore.Qt.LeftButton:
            if self._select_area:
                [Xmin, Ymin, dx, dy] = self._drawbox
                self._display.SelectArea(Xmin, Ymin, Xmin + dx, Ymin + dy)
                self._select_area = False
            elif modifiers == QtCore.Qt.ShiftModifier:
                self._display.ShiftSelect(pt.x(), pt.y())
            else:
                # single select otherwise
                self._display.Select(pt.x(), pt.y())

                if self._display.selected_shapes is not None:
                    self.sig_topods_selected.emit(self._display.selected_shapes)

        elif event.button() == QtCore.Qt.RightButton:
            if self._zoom_area:
                [Xmin, Ymin, dx, dy] = self._drawbox
                self._display.ZoomArea(Xmin, Ymin, Xmin + dx, Ymin + dy)
                self._zoom_area = False

        self.cursor = "arrow"

    def DrawBox(self, event):
        tolerance = 2
        pt = event.pos()
        dx = pt.x() - self.dragStartPosX
        dy = pt.y() - self.dragStartPosY
        if abs(dx) <= tolerance and abs(dy) <= tolerance:
            return
        self._drawbox = [self.dragStartPosX, self.dragStartPosY, dx, dy]

    def mouseMoveEvent(self, evt):
        pt = evt.pos()
        # buttons = int(evt.buttons())
        buttons = evt.buttons()
        modifiers = evt.modifiers()
        # ROTATE
        if buttons == QtCore.Qt.LeftButton and modifiers != QtCore.Qt.ShiftModifier:
            self.cursor = "rotate"
            self._display.Rotation(pt.x(), pt.y())
            self._drawbox = False
        elif buttons == QtCore.Qt.RightButton and modifiers != QtCore.Qt.ShiftModifier:
            self.cursor = "zoom"
            self._display.Repaint()
            self._display.DynamicZoom(
                abs(self.dragStartPosX),
                abs(self.dragStartPosY),
                abs(pt.x()),
                abs(pt.y()),
            )
            self.dragStartPosX = pt.x()
            self.dragStartPosY = pt.y()
            self._drawbox = False
        elif buttons == QtCore.Qt.MiddleButton:
            dx = pt.x() - self.dragStartPosX
            dy = pt.y() - self.dragStartPosY
            self.dragStartPosX = pt.x()
            self.dragStartPosY = pt.y()
            self.cursor = "pan"
            self._display.Pan(dx, -dy)
            self._drawbox = False
        elif buttons == QtCore.Qt.RightButton:
            self._zoom_area = True
            self.cursor = "zoom-area"
            self.DrawBox(evt)
            self.update()
        elif buttons == QtCore.Qt.LeftButton:
            self._select_area = True
            self.DrawBox(evt)
            self.update()
        else:
            self._drawbox = False
            self._display.MoveTo(pt.x(), pt.y())
            self.cursor = "arrow"

May need to create a new viewer that changes these mouse controls. This stack overflow has a good reference:

https://stackoverflow.com/a/54036907

nsmela commented

Maybe create a mouse mode controller? Passes the mouse event and viewer into a configurable class to handle mouse actions

nsmela commented

Change it so right mouse button controls the rotation and yaw and mouse wheel still handles zooming

nsmela commented

Focal point should be base or middle of cylinder? Maybe a keyboard command can raise or lower the view.
Right mouse x axis movement will rotate camera along a positive z axis. Right mouse 6 axis will rotate along a y axis that has been rotated by the right mouses x axis rotation

nsmela commented

Here's a link to the OpenCascade reference:
https://dev.opencascade.org/doc/refman/html/class_v3d___view.html

self._display.View.Turn(angleX, 0, 0) spins the whole scene around, not the desired effect

nsmela commented
import logging
import os
import sys

from OCC.Core import V3d
from OCC.Core.AIS import AIS_Manipulator
from OCC.Core.gp import gp_Trsf, gp
from OCC.Display import OCCViewer
from OCC.Display.qtDisplay import qtBaseViewer
from OCC.Display.backend import get_qt_modules

QtCore, QtGui, QtWidgets, QtOpenGL = get_qt_modules()

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
log = logging.getLogger(__name__)


# source:
# https://github.com/tpaviot/pythonocc-core/blob/d2c3401f8565128518a0a9cb58455088f27a6af0/src/Display/qtDisplay.py#L63

class OrbitCameraViewer(qtBaseViewer):
    # emit signal when selection is changed
    # is a list of TopoDS_*
    sig_topods_selected = QtCore.pyqtSignal(list)

    def __init__(self, *args):
        qtBaseViewer.__init__(self, *args)

        self.setObjectName("qt_viewer_3d")

        self._drawbox = False
        self._zoom_area = False
        self._select_area = False
        self._inited = False
        self._leftisdown = False
        self._middleisdown = False
        self._rightisdown = False
        self._selection = None
        self._drawtext = True
        self._qApp = QtWidgets.QApplication.instance()
        self._key_map = {}
        self._current_cursor = "arrow"
        self._available_cursors = {}

    @property
    def qApp(self):
        # reference to QApplication instance
        return self._qApp

    @qApp.setter
    def qApp(self, value):
        self._qApp = value

    def InitDriver(self):
        self._display.Create(window_handle=int(self.winId()), parent=self)
        # background gradient
        self._display.SetModeShaded()
        self._inited = True
        # dict mapping keys to functions
        self._key_map = {
            ord("W"): self._display.SetModeWireFrame,
            ord("S"): self._display.SetModeShaded,
            ord("A"): self._display.EnableAntiAliasing,
            ord("B"): self._display.DisableAntiAliasing,
            ord("H"): self._display.SetModeHLR,
            ord("F"): self._display.FitAll,
            ord("G"): self._display.SetSelectionMode,
        }
        self.createCursors()

    def createCursors(self):
        module_pth = os.path.abspath(os.path.dirname(__file__))
        icon_pth = os.path.join(module_pth, "icons")

        _CURSOR_PIX_ROT = QtGui.QPixmap(os.path.join(icon_pth, "cursor-rotate.png"))
        _CURSOR_PIX_PAN = QtGui.QPixmap(os.path.join(icon_pth, "cursor-pan.png"))
        _CURSOR_PIX_ZOOM = QtGui.QPixmap(os.path.join(icon_pth, "cursor-magnify.png"))
        _CURSOR_PIX_ZOOM_AREA = QtGui.QPixmap(
            os.path.join(icon_pth, "cursor-magnify-area.png")
        )

        self._available_cursors = {
            "arrow": QtGui.QCursor(QtCore.Qt.CursorShape.ArrowCursor),  # default
            "pan": QtGui.QCursor(_CURSOR_PIX_PAN),
            "rotate": QtGui.QCursor(_CURSOR_PIX_ROT),
            "zoom": QtGui.QCursor(_CURSOR_PIX_ZOOM),
            "zoom-area": QtGui.QCursor(_CURSOR_PIX_ZOOM_AREA),
        }

        self._current_cursor = "arrow"

    def keyPressEvent(self, event):
        super(OrbitCameraViewer, self).keyPressEvent(event)
        code = event.key()
        if code in self._key_map:
            self._key_map[code]()
        elif code in range(256):
            log.info(
                'key: "%s"(code %i) not mapped to any function' % (chr(code), code)
            )
        else:
            log.info("key: code %i not mapped to any function" % code)

    def focusInEvent(self, event):
        if self._inited:
            self._display.Repaint()

    def focusOutEvent(self, event):
        if self._inited:
            self._display.Repaint()

    def wheelEvent(self, event):
        delta = event.angleDelta().y()
        zoom_factor = 2.0 if delta > 0 else 0.5
        self._display.ZoomFactor(zoom_factor)

    @property
    def cursor(self):
        return self._current_cursor

    @cursor.setter
    def cursor(self, value):
        if self._current_cursor != value:
            self._current_cursor = value
            if cursor := self._available_cursors.get(value):
                self.qApp.setOverrideCursor(cursor)
            else:
                self.qApp.restoreOverrideCursor()

    def mousePressEvent(self, event):
        self.setFocus()
        ev = event.pos()
        self.dragStartPosX = ev.x()
        self.dragStartPosY = ev.y()
        self.eyeStartPos = self._display.View.Eye()
        self._display.StartRotation(self.dragStartPosX, self.dragStartPosY)

    def mouseReleaseEvent(self, event):
        pt = event.pos()
        modifiers = event.modifiers()

        if event.button() == QtCore.Qt.LeftButton:
            # single select otherwise
            self._display.Select(pt.x(), pt.y())

            if self._display.selected_shapes is not None:
                self.sig_topods_selected.emit(self._display.selected_shapes)

        self.cursor = "arrow"

    def mouseMoveEvent(self, evt):
        pt = evt.pos()
        # buttons = int(evt.buttons())
        buttons = evt.buttons()
        modifiers = evt.modifiers()
        # ROTATE
        if buttons == QtCore.Qt.RightButton:
            self.cursor = "rotate"
            sensitivity = 0.10
            #self._display.View.SetAxis(0, 0, 0, 0, 1, 1)
            angleX = self.eyeStartPos[0] - (pt.x() + self.dragStartPosX) #pt.x() * 3.149 / 18000  # convert to radians
            angleY = self.eyeStartPos[2] + (pt.y() - self.dragStartPosY)  #(pt.y() - self.dragStartPosY) * 3.14159 / 180
            #self._display.Rotation(pt.x(), 0)
            self._display.View.SetEye(angleX, self.eyeStartPos[1], angleY)
            #self._display.View.Rotate(angleX, 0, 0)
        if buttons == QtCore.Qt.MiddleButton:
            dy = (self.dragStartPosY + pt.y()) * 3.14159 / 180
            self.dragStartPosY = pt.y()
            self.cursor = "pan"
            self._display.View.SetEye(0, 0, dy)


nsmela commented

Progress

Now using a custom viewer and made it so only the right mouse button controls rotation. Need to limit the rotation somehow

This is working pretty well imo. Close for now? Or is there more to do?

nsmela commented

More to do, low priority though.