From e55569bcb2661fcb1891dc3ca40699a0553abbc7 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Mon, 20 Mar 2023 23:53:30 -0400 Subject: [PATCH] Add clipping plane support for X/Y/Z dimensions. * New widget for viewing the clipping plane settings. * Update the viewer to add clipping planes. --- cq_editor/main_window.py | 11 +++++ cq_editor/widgets/clipping_planes.py | 74 ++++++++++++++++++++++++++++ cq_editor/widgets/viewer.py | 42 ++++++++++++++-- 3 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 cq_editor/widgets/clipping_planes.py diff --git a/cq_editor/main_window.py b/cq_editor/main_window.py index de895cf7..a91cc285 100644 --- a/cq_editor/main_window.py +++ b/cq_editor/main_window.py @@ -8,6 +8,7 @@ from .widgets.viewer import OCCViewer from .widgets.console import ConsoleWidget from .widgets.object_tree import ObjectTree +from .widgets.clipping_planes import ClippingPlanes from .widgets.traceback_viewer import TracebackPane from .widgets.debugger import Debugger, LocalsView from .widgets.cq_object_inspector import CQObjectInspector @@ -128,6 +129,13 @@ def prepare_panes(self): self, defaultArea='bottom')) + self.registerComponent('clipping_planes', + ClippingPlanes(self), + lambda c: dock(c, + 'Clipping Planes', + self, + defaultArea='right')) + for d in self.docks.values(): d.show() @@ -259,6 +267,9 @@ def prepare_actions(self): self.components['debugger'].sigTraceback\ .connect(self.components['traceback_viewer'].addTraceback) + self.components['clipping_planes'].sigClippingPlaneChanged\ + .connect(self.components['viewer'].update_clipping_plane) + # trigger re-render when file is modified externally or saved self.components['editor'].triggerRerender \ .connect(self.components['debugger'].render) diff --git a/cq_editor/widgets/clipping_planes.py b/cq_editor/widgets/clipping_planes.py new file mode 100644 index 00000000..b7d86719 --- /dev/null +++ b/cq_editor/widgets/clipping_planes.py @@ -0,0 +1,74 @@ +from PyQt5.QtWidgets import QApplication, QCheckBox, QHBoxLayout, QLabel, QSpinBox, QWidget +from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal + +from qtconsole.rich_jupyter_widget import RichJupyterWidget + +from ..mixins import ComponentMixin +from ..utils import layout + +class ClippingPlane(QWidget): + + sigClippingPlaneChanged = pyqtSignal(bool,int,bool) + + def __init__(self, parent, dimension): + super(ClippingPlane, self).__init__(parent) + + self.check = QCheckBox(f"{dimension} Axis", self) + self.spinbox = QSpinBox(self) + self.spinbox.setValue(50) + self.spinbox.setRange(1, 256) + self.invert = QCheckBox("Invert", self) + + self.group = layout(self,(self.spinbox,self.invert),layout_type=QHBoxLayout, spacing=5) + + layout(self,(self.check,self.group),layout_type=QHBoxLayout, top_widget=self) + + self.group.setDisabled(True) + self.check.stateChanged.connect(self.handleChecked) + self.invert.stateChanged.connect(self.handleInvertChecked) + self.spinbox.valueChanged.connect(self.handlePositionChanged) + + def handleChecked(self, arg): + self.group.setDisabled(not self.check.checkState()) + self.emitState() + + def handleInvertChecked(self, arg): + self.emitState() + + def handlePositionChanged(self, arg): + self.emitState() + + def emitState(self): + self.sigClippingPlaneChanged.emit(self.check.checkState(), self.spinbox.value(), self.invert.checkState()) + +class ClippingPlanes(QWidget,ComponentMixin): + + name = 'Clipping Planes' + + sigClippingPlaneChanged = pyqtSignal(str,bool,int,bool) + + def __init__(self, parent): + super(ClippingPlanes, self).__init__(parent) + + self.x = ClippingPlane(self, "X") + self.x.sigClippingPlaneChanged.connect(self.onXPlaneChanged) + self.y = ClippingPlane(self, "Y") + self.y.sigClippingPlaneChanged.connect(self.onYPlaneChanged) + self.z = ClippingPlane(self, "Z") + self.z.sigClippingPlaneChanged.connect(self.onZPlaneChanged) + + layout(self,(self.x,self.y,self.z),top_widget=self) + + @pyqtSlot(bool,int,bool) + def onXPlaneChanged(self,enabled,val,inverted): + self.sigClippingPlaneChanged.emit("X",enabled,val,inverted) + + @pyqtSlot(bool,int,bool) + def onYPlaneChanged(self,enabled,val,inverted): + self.sigClippingPlaneChanged.emit("Y",enabled,val,inverted) + + @pyqtSlot(bool,int,bool) + def onZPlaneChanged(self,enabled,val,inverted): + self.sigClippingPlaneChanged.emit("Z",enabled,val,inverted) + + diff --git a/cq_editor/widgets/viewer.py b/cq_editor/widgets/viewer.py index 9c5d620b..81697715 100644 --- a/cq_editor/widgets/viewer.py +++ b/cq_editor/widgets/viewer.py @@ -3,14 +3,15 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal from PyQt5.QtGui import QIcon -from OCP.Graphic3d import Graphic3d_Camera, Graphic3d_StereoMode, Graphic3d_NOM_JADE,\ - Graphic3d_MaterialAspect +from OCP.Graphic3d import Graphic3d_Camera, Graphic3d_ClipPlane, Graphic3d_StereoMode, Graphic3d_NOM_JADE,\ + Graphic3d_MaterialAspect, Graphic3d_Texture2Dplane from OCP.AIS import AIS_Shaded,AIS_WireFrame, AIS_ColoredShape, AIS_Axis -from OCP.Aspect import Aspect_GDM_Lines, Aspect_GT_Rectangular -from OCP.Quantity import Quantity_NOC_BLACK as BLACK, Quantity_TOC_RGB as TOC_RGB,\ +from OCP.Aspect import Aspect_GDM_Lines, Aspect_GT_Rectangular, Aspect_HS_GRID +from OCP.TCollection import TCollection_AsciiString +from OCP.Quantity import Quantity_NOC_BLACK as BLACK, Quantity_NOC_WHITE as WHITE, Quantity_TOC_RGB as TOC_RGB,\ Quantity_Color from OCP.Geom import Geom_Axis1Placement -from OCP.gp import gp_Ax3, gp_Dir, gp_Pnt, gp_Ax1 +from OCP.gp import gp_Ax3, gp_Dir, gp_Pnt, gp_Ax1, gp_Pln from ..utils import layout, get_save_filename from ..mixins import ComponentMixin @@ -56,6 +57,14 @@ def __init__(self,parent=None): self.canvas = OCCTWidget() self.canvas.sigObjectSelected.connect(self.handle_selection) + + self.x_clipping_plane = self.setup_clipping_plane(gp_Pln(-1, 0.0, 0.0, 100)) + self.y_clipping_plane = self.setup_clipping_plane(gp_Pln(0.0, -1, 0.0, 100)) + self.z_clipping_plane = self.setup_clipping_plane(gp_Pln(0.0, 0.0, -1, 100)) + self.canvas.view.AddClipPlane(self.x_clipping_plane) + self.canvas.view.AddClipPlane(self.y_clipping_plane) + self.canvas.view.AddClipPlane(self.z_clipping_plane) + self.create_actions(self) self.layout_ = layout(self, @@ -66,6 +75,16 @@ def __init__(self,parent=None): self.setup_default_drawer() self.updatePreferences() + def setup_clipping_plane(self, eq): + cp = Graphic3d_ClipPlane() + cp.SetEquation(eq) + cp.SetCapping(True) + cp.SetUseObjectMaterial(True) + # cp.SetCappingTexture(Graphic3d_Texture2Dplane(TCollection_AsciiString("./clip-texture.png"))) + cp.SetOn(False) + + return cp + def setup_default_drawer(self): # set the default color and material @@ -369,6 +388,19 @@ def set_selected(self,ais): self.redraw() + @pyqtSlot(str,bool,int,bool) + def update_clipping_plane(self,plane,enabled,val,inverted): + if plane == "X": + self.x_clipping_plane.SetEquation(gp_Pln(1 if inverted else -1, 0.0, 0.0, val)) + self.x_clipping_plane.SetOn(enabled) + elif plane == "Y": + self.y_clipping_plane.SetEquation(gp_Pln(0.0, 1 if inverted else -1, 0.0, val)) + self.y_clipping_plane.SetOn(enabled) + elif plane == "Z": + self.z_clipping_plane.SetEquation(gp_Pln(0.0, 0.0, 1 if inverted else -1, val)) + self.z_clipping_plane.SetOn(enabled) + + self.redraw() if __name__ == "__main__":