diff --git a/cq_editor/__main__.py b/cq_editor/__main__.py index 0fc8f700..0f35ebd4 100644 --- a/cq_editor/__main__.py +++ b/cq_editor/__main__.py @@ -1,3 +1,4 @@ +import signal import sys import argparse @@ -18,7 +19,10 @@ def main(): args = parser.parse_args(app.arguments()[1:]) + signal.signal(signal.SIGINT, signal.SIG_DFL) # handle Ctrl+C + win = MainWindow(filename=args.filename if args.filename else None) + win.show() sys.exit(app.exec_()) diff --git a/cq_editor/main_window.py b/cq_editor/main_window.py index 4bbee280..9bd200c3 100644 --- a/cq_editor/main_window.py +++ b/cq_editor/main_window.py @@ -1,5 +1,6 @@ import sys +from PyQt5.QtGui import QPalette, QColor from PyQt5.QtWidgets import (QLabel, QMainWindow, QToolBar, QDockWidget, QAction) from logbook import Logger import cadquery as cq @@ -30,6 +31,9 @@ def __init__(self,parent=None, filename=None): super(MainWindow,self).__init__(parent) MainMixin.__init__(self) + self.toolbar = None + self.status_label = None + self.setWindowIcon(icon('app')) # Windows workaround - makes the correct task bar icon show up. @@ -199,7 +203,13 @@ def prepare_menubar_component(self,menus,comp_menu_dict): def prepare_toolbar(self): - self.toolbar = QToolBar('Main toolbar',self,objectName='Main toolbar') + self.toolbar = QToolBar('Main toolbar', self, objectName='Main toolbar') + + p = self.toolbar.palette() + if p.color(QPalette.Background).lightnessF() < 0.5: # dark theme is active + p.setColor(QPalette.Button, QColor(120, 120, 120)) + p.setColor(QPalette.Background, QColor(170, 170, 170)) + self.toolbar.setPalette(p) for c in self.components.values(): add_actions(self.toolbar,c.toolbarActions()) diff --git a/cq_editor/widgets/occt_widget.py b/cq_editor/widgets/occt_widget.py index 172755ea..4a327e67 100755 --- a/cq_editor/widgets/occt_widget.py +++ b/cq_editor/widgets/occt_widget.py @@ -1,28 +1,26 @@ +import math from sys import platform - -from PyQt5.QtWidgets import QWidget, QApplication -from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QEvent - -import OCP - +from OCP.AIS import AIS_InteractiveContext, AIS_DisplayMode from OCP.Aspect import Aspect_DisplayConnection, Aspect_TypeOfTriedronPosition from OCP.OpenGl import OpenGl_GraphicDriver -from OCP.V3d import V3d_Viewer -from OCP.AIS import AIS_InteractiveContext, AIS_DisplayMode from OCP.Quantity import Quantity_Color +from OCP.V3d import V3d_Viewer +from OCP.gp import gp_Trsf, gp_Ax1, gp_Dir +from PyQt5.QtCore import pyqtSignal, Qt, QPoint +from PyQt5.QtWidgets import QWidget -ZOOM_STEP = 0.9 - - class OCCTWidget(QWidget): sigObjectSelected = pyqtSignal(list) - def __init__(self,parent=None): + def __init__(self, parent=None, *, + turntable_rotation: bool = True, + rotate_step: float = 0.008, + zoom_step: float = 0.1): - super(OCCTWidget,self).__init__(parent) + super(OCCTWidget, self).__init__(parent) self.setAttribute(Qt.WA_NativeWindow) self.setAttribute(Qt.WA_PaintOnScreen) @@ -30,8 +28,12 @@ def __init__(self,parent=None): self._initialized = False self._needs_update = False + self._old_pos = QPoint(0, 0) + self._rotate_step = rotate_step + self._zoom_step = zoom_step + self._turntable_rotation = turntable_rotation - #OCCT secific things + # OCCT secific things self.display_connection = Aspect_DisplayConnection() self.graphics_driver = OpenGl_GraphicDriver(self.display_connection) @@ -39,9 +41,15 @@ def __init__(self,parent=None): self.view = self.viewer.CreateView() self.context = AIS_InteractiveContext(self.viewer) - #Trihedorn, lights, etc + # Trihedorn, lights, etc self.prepare_display() - + + def set_turntable_rotation(self, new_value: bool): + if self._turntable_rotation != new_value: + self._turntable_rotation = new_value + if self._turntable_rotation: + self.view.SetUp(0, 0, 1) + def prepare_display(self): view = self.view @@ -65,49 +73,58 @@ def prepare_display(self): ctx.DefaultDrawer().SetFaceBoundaryDraw(True) def wheelEvent(self, event): - - delta = event.angleDelta().y() - factor = ZOOM_STEP if delta<0 else 1/ZOOM_STEP - + + # dividing by 120 gets number of notches on a typical scroll wheel. + # See QWheelEvent documentation + delta_notches = event.angleDelta().y() / 120 + direction = math.copysign(1, delta_notches) + factor = (1 + self._zoom_step * direction) ** abs(delta_notches) + self.view.SetZoom(factor) - def mousePressEvent(self,event): + def mousePressEvent(self, event): pos = event.pos() - + if event.button() == Qt.LeftButton: - self.view.StartRotation(pos.x(), pos.y()) + if not self._turntable_rotation: + self.view.StartRotation(pos.x(), pos.y()) elif event.button() == Qt.RightButton: self.view.StartZoomAtPoint(pos.x(), pos.y()) - self.old_pos = pos - - def mouseMoveEvent(self,event): + self._old_pos = pos + + def mouseMoveEvent(self, event): pos = event.pos() - x,y = pos.x(),pos.y() - + x, y = pos.x(), pos.y() + if event.buttons() == Qt.LeftButton: - self.view.Rotation(x,y) - + if self._turntable_rotation: + delta_x, delta_y = x - self._old_pos.x(), y - self._old_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) + else: + self.view.Rotation(x, y) + elif event.buttons() == Qt.MiddleButton: - self.view.Pan(x - self.old_pos.x(), - self.old_pos.y() - y, theToStart=True) + delta_x, delta_y = x - self._old_pos.x(), y - self._old_pos.y() + self.view.Pan(delta_x, -delta_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._old_pos.x(), y, + x, self._old_pos.y()) - self.old_pos = pos + self._old_pos = pos - def mouseReleaseEvent(self,event): + def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: pos = event.pos() - x,y = pos.x(),pos.y() - - self.context.MoveTo(x,y,self.view,True) - + self.context.MoveTo(pos.x(), pos.y(), self.view, True) self._handle_selection() def _handle_selection(self): diff --git a/cq_editor/widgets/viewer.py b/cq_editor/widgets/viewer.py index 9c5d620b..36aa0cfe 100644 --- a/cq_editor/widgets/viewer.py +++ b/cq_editor/widgets/viewer.py @@ -43,7 +43,9 @@ class OCCViewer(QWidget,ComponentMixin): 'values': ['Orthographic', 'Perspective', 'Stereo', 'MonoLeftEye', 'MonoRightEye']}, {'name': 'Stereo Mode', 'type': 'list', 'value': 'QuadBuffer', 'values': ['QuadBuffer', 'Anaglyph', 'RowInterlaced', 'ColumnInterlaced', - 'ChessBoard', 'SideBySide', 'OverUnder']}]) + 'ChessBoard', 'SideBySide', 'OverUnder']}, + {'name': 'Orbit Method', 'type': 'list', 'value': 'Turntable', 'values': ['Turntable', 'Trackball']}, + ]) IMAGE_EXTENSIONS = 'png' sigObjectSelected = pyqtSignal(list) @@ -89,6 +91,14 @@ def updatePreferences(self,*args): color2 = color1 self.canvas.view.SetBgGradientColors(color1,color2,theToUpdate=True) + orbit_method = self.preferences['Orbit Method'] + if orbit_method == 'Turntable': + self.canvas.set_turntable_rotation(True) + elif orbit_method == 'Trackball': + self.canvas.set_turntable_rotation(False) + else: + raise ValueError(orbit_method) + self.canvas.update() ctx = self.canvas.context