Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cq_editor/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ def add(self, name, component):
# Fill the light/dark theme in the general settings
elif child.name() == "Light/Dark Theme":
child.setLimits(["Light", "Dark"])
# Fill the orbit method
elif child.name() == "Orbit Method":
child.setLimits(
[
"Turntable",
"Trackball",
]
)

@pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
def handleSelection(self, item, *args):
Expand Down
58 changes: 51 additions & 7 deletions cq_editor/widgets/occt_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@


from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QEvent
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint

import OCP

from OCP.Aspect import Aspect_DisplayConnection, Aspect_TypeOfTriedronPosition
from OCP.OpenGl import OpenGl_GraphicDriver
from OCP.V3d import V3d_Viewer
from OCP.gp import gp_Trsf, gp_Ax1, gp_Dir
from OCP.AIS import AIS_InteractiveContext, AIS_DisplayMode
from OCP.Quantity import Quantity_Color

Expand All @@ -30,6 +31,15 @@ def __init__(self, parent=None):

self._initialized = False
self._needs_update = False
self._previous_pos = QPoint(
0, 0 # Keeps track of where the previous mouse position
)
self._rotate_step = (
0.008 # Controls the speed of rotation with the turntable orbit method
)

# Orbit method settings
self._orbit_method = "Turntable"

# OCCT secific things
self.display_connection = Aspect_DisplayConnection()
Expand Down Expand Up @@ -64,6 +74,20 @@ def prepare_display(self):
ctx.SetDisplayMode(AIS_DisplayMode.AIS_Shaded, True)
ctx.DefaultDrawer().SetFaceBoundaryDraw(True)

def set_orbit_method(self, method):
"""
Set the orbit method for the OCCT view.
"""

# Keep track of which orbit method is used
if method == "Turntable":
self._orbit_method = "Turntable"
self.view.SetUp(0, 0, 1)
elif method == "Trackball":
self._orbit_method = "Trackball"
else:
raise ValueError(f"Unknown orbit method: {method}")

def wheelEvent(self, event):

delta = event.angleDelta().y()
Expand All @@ -80,31 +104,51 @@ def mousePressEvent(self, event):
self.pending_select = True
self.left_press = pos

self.view.StartRotation(pos.x(), pos.y())
# We only start the rotation if the orbit method is set to Trackball
if self._orbit_method == "Trackball":
self.view.StartRotation(pos.x(), pos.y())
elif event.button() == Qt.RightButton:
self.view.StartZoomAtPoint(pos.x(), pos.y())

self.old_pos = pos
self._previous_pos = pos

def mouseMoveEvent(self, event):

pos = event.pos()
x, y = pos.x(), pos.y()

# Check for mouse drag rotation
if event.buttons() == Qt.LeftButton:
self.view.Rotation(x, y)
# Set the rotation differently based on the orbit method
if self._orbit_method == "Trackball":
self.view.Rotation(x, y)
elif self._orbit_method == "Turntable":
# Control the turntable rotation manually
delta_x, delta_y = (
x - self._previous_pos.x(),
y - self._previous_pos.y(),
)
cam = self.view.Camera()
z_rotation = gp_Trsf()
z_rotation.SetRotation(
gp_Ax1(cam.Center(), gp_Dir(0, 0, 1)), -delta_x * self._rotate_step
)
cam.Transform(z_rotation)
self.view.Rotate(0, -delta_y * self._rotate_step, 0)

# If the user moves the mouse at all, the selection will not happen
if abs(x - self.left_press.x()) > 2 or abs(y - self.left_press.y()) > 2:
self.pending_select = False

elif event.buttons() == Qt.MiddleButton:
self.view.Pan(x - self.old_pos.x(), self.old_pos.y() - y, theToStart=True)
self.view.Pan(
x - self._previous_pos.x(), self._previous_pos.y() - y, theToStart=True
)

elif event.buttons() == Qt.RightButton:
self.view.ZoomAtPoint(self.old_pos.x(), y, x, self.old_pos.y())
self.view.ZoomAtPoint(self._previous_pos.x(), y, x, self._previous_pos.y())

self.old_pos = pos
self._previous_pos = pos

def mouseReleaseEvent(self, event):

Expand Down
15 changes: 15 additions & 0 deletions cq_editor/widgets/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ class OCCViewer(QWidget, ComponentMixin):
"OverUnder",
],
},
{
"name": "Orbit Method",
"type": "list",
"value": "Turntable",
"values": [
"Turntable",
"Trackball",
],
},
],
)
IMAGE_EXTENSIONS = "png"
Expand Down Expand Up @@ -136,6 +145,12 @@ def updatePreferences(self, *args):
color2 = color1
self.canvas.view.SetBgGradientColors(color1, color2, theToUpdate=True)

# Set the orbit method
orbit_method = self.preferences["Orbit Method"]
if not orbit_method:
orbit_method = "Trackball"
self.canvas.set_orbit_method(orbit_method)

self.canvas.update()

ctx = self.canvas.context
Expand Down
65 changes: 64 additions & 1 deletion tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
import pytestqt
import cadquery as cq

from PyQt5.QtCore import Qt, QSettings
from PyQt5.QtCore import Qt, QSettings, QPoint, QEvent
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtGui import QMouseEvent

from cq_editor.__main__ import MainWindow
from cq_editor.widgets.editor import Editor
Expand Down Expand Up @@ -1846,3 +1847,65 @@ def test_autocomplete_keystrokes(main):
qtbot.wait(250)
# Check that the completion list is still visible
assert editor.completion_list.isVisible()


def test_viewer_orbit_methods(main):
"""
Tests that mouse movements in the viewer work as expected.
"""

qtbot, win = main

viewer = win.components["viewer"]

# Make sure the editor is focused
viewer.setFocus()
qtbot.waitExposed(viewer)

# Simulate a drag to rotate
qtbot.mousePress(viewer, Qt.LeftButton)
qtbot.mouseMove(viewer, QPoint(100, 100))
qtbot.mouseMove(viewer, QPoint(300, 300))
qtbot.mouseRelease(viewer, Qt.LeftButton)

# Simulate a drag to pan
qtbot.mousePress(viewer, Qt.MiddleButton)
event = QMouseEvent(
QEvent.MouseMove,
QPoint(100, 100),
Qt.RightButton,
Qt.RightButton,
Qt.NoModifier,
)
viewer.mouseMoveEvent(event)
event = QMouseEvent(
QEvent.MouseMove,
QPoint(300, 300),
Qt.RightButton,
Qt.RightButton,
Qt.NoModifier,
)
viewer.mouseMoveEvent(event)
qtbot.mouseRelease(viewer, Qt.MiddleButton)

# Simulate drag to zoom
qtbot.mousePress(viewer, Qt.RightButton)
event = QMouseEvent(
QEvent.MouseMove,
QPoint(100, 100),
Qt.RightButton,
Qt.RightButton,
Qt.NoModifier,
)
viewer.mouseMoveEvent(event)
event = QMouseEvent(
QEvent.MouseMove,
QPoint(300, 300),
Qt.RightButton,
Qt.RightButton,
Qt.NoModifier,
)
viewer.mouseMoveEvent(event)
qtbot.mouseRelease(viewer, Qt.RightButton)

assert True