From ae8408361818925ab24ae63e0808da57543269b7 Mon Sep 17 00:00:00 2001 From: Matthew Broadway Date: Mon, 25 Jan 2021 23:54:54 +0000 Subject: [PATCH 1/4] handle Ctrl+C --- cq_editor/__main__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cq_editor/__main__.py b/cq_editor/__main__.py index 3de81d97..547fd580 100644 --- a/cq_editor/__main__.py +++ b/cq_editor/__main__.py @@ -1,3 +1,4 @@ +import signal import sys import argparse @@ -23,6 +24,8 @@ def main(): if args.filename: win.components['editor'].load_from_file(args.filename) + signal.signal(signal.SIGINT, signal.SIG_DFL) # handle Ctrl+C + win.show() sys.exit(app.exec_()) From 0613140aa05e3acd558c1d66abc7a3613042bde8 Mon Sep 17 00:00:00 2001 From: Matthew Broadway Date: Tue, 26 Jan 2021 19:48:21 +0000 Subject: [PATCH 2/4] correct toolbar for better visibility when using a dark theme --- cq_editor/main_window.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cq_editor/main_window.py b/cq_editor/main_window.py index d66890ec..558ec6ea 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) import cadquery as cq @@ -30,6 +31,9 @@ def __init__(self,parent=None): super(MainWindow,self).__init__(parent) MainMixin.__init__(self) + self.toolbar = None + self.status_label = None + self.setWindowIcon(icon('app')) self.viewer = OCCViewer(self) @@ -189,7 +193,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()) From 5b65f4d92f5388d875a14b0aedc0ffd7845379ab Mon Sep 17 00:00:00 2001 From: Matthew Broadway Date: Tue, 26 Jan 2021 20:14:36 +0000 Subject: [PATCH 3/4] added orbital rotation mode and take scroll delta into account when zooming --- cq_editor/widgets/occt_widget.py | 97 +++++++++++++++++++------------- cq_editor/widgets/viewer.py | 6 +- 2 files changed, 61 insertions(+), 42 deletions(-) diff --git a/cq_editor/widgets/occt_widget.py b/cq_editor/widgets/occt_widget.py index 172755ea..844d614a 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, *, + orbital_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._orbital_rotation = orbital_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_orbital_rotation(self, new_value: bool): + if self._orbital_rotation != new_value: + self._orbital_rotation = new_value + if self._orbital_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._orbital_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._orbital_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 a3d4c361..1882ea88 100644 --- a/cq_editor/widgets/viewer.py +++ b/cq_editor/widgets/viewer.py @@ -39,8 +39,9 @@ class OCCViewer(QWidget,ComponentMixin): {'name': 'Background color (aux)', 'type': 'color', 'value': (30,30,30)}, {'name': 'Default object color', 'type': 'color', 'value': "FF0"}, {'name': 'Deviation', 'type': 'float', 'value': 1e-5, 'dec': True, 'step': 1}, - {'name': 'Angular deviation', 'type': 'float', 'value': 0.1, 'dec': True, 'step': 1}]) - + {'name': 'Angular deviation', 'type': 'float', 'value': 0.1, 'dec': True, 'step': 1}, + {'name': 'Orbital rotation', 'type': 'bool', 'value': True}]) + IMAGE_EXTENSIONS = 'png' @@ -71,6 +72,7 @@ def updatePreferences(self,*args): if not self.preferences['Use gradient']: color2 = color1 self.canvas.view.SetBgGradientColors(color1,color2,theToUpdate=True) + self.canvas.set_orbital_rotation(self.preferences['Orbital rotation']) self.canvas.update() From 84968a9aa102eee2d1dca8568e3640e452958145 Mon Sep 17 00:00:00 2001 From: Matthew Broadway Date: Fri, 12 Jan 2024 21:07:38 +0000 Subject: [PATCH 4/4] renamed setting to match blender terminology --- cq_editor/widgets/occt_widget.py | 16 ++++++++-------- cq_editor/widgets/viewer.py | 11 +++++++++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/cq_editor/widgets/occt_widget.py b/cq_editor/widgets/occt_widget.py index 844d614a..4a327e67 100755 --- a/cq_editor/widgets/occt_widget.py +++ b/cq_editor/widgets/occt_widget.py @@ -16,7 +16,7 @@ class OCCTWidget(QWidget): sigObjectSelected = pyqtSignal(list) def __init__(self, parent=None, *, - orbital_rotation: bool = True, + turntable_rotation: bool = True, rotate_step: float = 0.008, zoom_step: float = 0.1): @@ -31,7 +31,7 @@ def __init__(self, parent=None, *, self._old_pos = QPoint(0, 0) self._rotate_step = rotate_step self._zoom_step = zoom_step - self._orbital_rotation = orbital_rotation + self._turntable_rotation = turntable_rotation # OCCT secific things self.display_connection = Aspect_DisplayConnection() @@ -44,10 +44,10 @@ def __init__(self, parent=None, *, # Trihedorn, lights, etc self.prepare_display() - def set_orbital_rotation(self, new_value: bool): - if self._orbital_rotation != new_value: - self._orbital_rotation = new_value - if self._orbital_rotation: + 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): @@ -87,7 +87,7 @@ def mousePressEvent(self, event): pos = event.pos() if event.button() == Qt.LeftButton: - if not self._orbital_rotation: + if not self._turntable_rotation: self.view.StartRotation(pos.x(), pos.y()) elif event.button() == Qt.RightButton: self.view.StartZoomAtPoint(pos.x(), pos.y()) @@ -100,7 +100,7 @@ def mouseMoveEvent(self, event): x, y = pos.x(), pos.y() if event.buttons() == Qt.LeftButton: - if self._orbital_rotation: + 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() diff --git a/cq_editor/widgets/viewer.py b/cq_editor/widgets/viewer.py index b8710d60..36aa0cfe 100644 --- a/cq_editor/widgets/viewer.py +++ b/cq_editor/widgets/viewer.py @@ -44,7 +44,7 @@ class OCCViewer(QWidget,ComponentMixin): {'name': 'Stereo Mode', 'type': 'list', 'value': 'QuadBuffer', 'values': ['QuadBuffer', 'Anaglyph', 'RowInterlaced', 'ColumnInterlaced', 'ChessBoard', 'SideBySide', 'OverUnder']}, - {'name': 'Orbital rotation', 'type': 'bool', 'value': True}, + {'name': 'Orbit Method', 'type': 'list', 'value': 'Turntable', 'values': ['Turntable', 'Trackball']}, ]) IMAGE_EXTENSIONS = 'png' @@ -90,7 +90,14 @@ def updatePreferences(self,*args): if not self.preferences['Use gradient']: color2 = color1 self.canvas.view.SetBgGradientColors(color1,color2,theToUpdate=True) - self.canvas.set_orbital_rotation(self.preferences['Orbital rotation']) + + 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()