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 @@
# 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(

Check warning on line 96 in cq_editor/preferences.py

View check run for this annotation

Codecov / codecov/patch

cq_editor/preferences.py#L96

Added line #L96 was not covered by tests
[
"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 @@

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 @@
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"

Check warning on line 87 in cq_editor/widgets/occt_widget.py

View check run for this annotation

Codecov / codecov/patch

cq_editor/widgets/occt_widget.py#L87

Added line #L87 was not covered by tests
else:
raise ValueError(f"Unknown orbit method: {method}")

Check warning on line 89 in cq_editor/widgets/occt_widget.py

View check run for this annotation

Codecov / codecov/patch

cq_editor/widgets/occt_widget.py#L89

Added line #L89 was not covered by tests

def wheelEvent(self, event):

delta = event.angleDelta().y()
Expand All @@ -80,31 +104,51 @@
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())

Check warning on line 109 in cq_editor/widgets/occt_widget.py

View check run for this annotation

Codecov / codecov/patch

cq_editor/widgets/occt_widget.py#L109

Added line #L109 was not covered by tests
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)

Check warning on line 124 in cq_editor/widgets/occt_widget.py

View check run for this annotation

Codecov / codecov/patch

cq_editor/widgets/occt_widget.py#L124

Added line #L124 was not covered by tests
elif self._orbit_method == "Turntable":
# Control the turntable rotation manually
delta_x, delta_y = (

Check warning on line 127 in cq_editor/widgets/occt_widget.py

View check run for this annotation

Codecov / codecov/patch

cq_editor/widgets/occt_widget.py#L127

Added line #L127 was not covered by tests
x - self._previous_pos.x(),
y - self._previous_pos.y(),
)
cam = self.view.Camera()
z_rotation = gp_Trsf()
z_rotation.SetRotation(

Check warning on line 133 in cq_editor/widgets/occt_widget.py

View check run for this annotation

Codecov / codecov/patch

cq_editor/widgets/occt_widget.py#L131-L133

Added lines #L131 - L133 were not covered by tests
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)

Check warning on line 137 in cq_editor/widgets/occt_widget.py

View check run for this annotation

Codecov / codecov/patch

cq_editor/widgets/occt_widget.py#L136-L137

Added lines #L136 - L137 were not covered by tests

# 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(

Check warning on line 144 in cq_editor/widgets/occt_widget.py

View check run for this annotation

Codecov / codecov/patch

cq_editor/widgets/occt_widget.py#L144

Added line #L144 was not covered by tests
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())

Check warning on line 149 in cq_editor/widgets/occt_widget.py

View check run for this annotation

Codecov / codecov/patch

cq_editor/widgets/occt_widget.py#L149

Added line #L149 was not covered by tests

self.old_pos = pos
self._previous_pos = pos

Check warning on line 151 in cq_editor/widgets/occt_widget.py

View check run for this annotation

Codecov / codecov/patch

cq_editor/widgets/occt_widget.py#L151

Added line #L151 was not covered by tests

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 @@
"OverUnder",
],
},
{
"name": "Orbit Method",
"type": "list",
"value": "Turntable",
"values": [
"Turntable",
"Trackball",
],
},
],
)
IMAGE_EXTENSIONS = "png"
Expand Down Expand Up @@ -136,6 +145,12 @@
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"

Check warning on line 151 in cq_editor/widgets/viewer.py

View check run for this annotation

Codecov / codecov/patch

cq_editor/widgets/viewer.py#L151

Added line #L151 was not covered by tests
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