from PyQt5.QtWidgets import QWidget, QDialog, QTreeWidgetItem, QApplication, QAction 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.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,\ Quantity_Color from OCP.Geom import Geom_Axis1Placement from OCP.gp import gp_Ax3, gp_Dir, gp_Pnt, gp_Ax1 from ..utils import layout, get_save_filename from ..mixins import ComponentMixin from ..icons import icon from ..cq_utils import to_occ_color, make_AIS, DEFAULT_FACE_COLOR from .occt_widget import OCCTWidget from pyqtgraph.parametertree import Parameter import qtawesome as qta DEFAULT_EDGE_COLOR = Quantity_Color(BLACK) DEFAULT_EDGE_WIDTH = 2 class OCCViewer(QWidget,ComponentMixin): name = '3D Viewer' preferences = Parameter.create(name='Pref',children=[ {'name': 'Fit automatically', 'type': 'bool', 'value': True}, {'name': 'Use gradient', 'type': 'bool', 'value': False}, {'name': 'Background color', 'type': 'color', 'value': (95,95,95)}, {'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': 'Projection Type', 'type': 'list', 'value': 'Orthographic', 'values': ['Orthographic', 'Perspective', 'Stereo', 'MonoLeftEye', 'MonoRightEye']}, {'name': 'Stereo Mode', 'type': 'list', 'value': 'QuadBuffer', 'values': ['QuadBuffer', 'Anaglyph', 'RowInterlaced', 'ColumnInterlaced', 'ChessBoard', 'SideBySide', 'OverUnder']}]) IMAGE_EXTENSIONS = 'png' sigObjectSelected = pyqtSignal(list) def __init__(self,parent=None): super(OCCViewer,self).__init__(parent) ComponentMixin.__init__(self) self.canvas = OCCTWidget() self.canvas.sigObjectSelected.connect(self.handle_selection) self.create_actions(self) self.layout_ = layout(self, [self.canvas,], top_widget=self, margin=0) self.setup_default_drawer() #misspelled in original self.updatePreferences() def setup_default_drawer(self): # set the default color and material material = Graphic3d_MaterialAspect(Graphic3d_NOM_JADE) shading_aspect = self.canvas.context.DefaultDrawer().ShadingAspect() shading_aspect.SetMaterial(material) shading_aspect.SetColor(DEFAULT_FACE_COLOR) # face edge lw line_aspect = self.canvas.context.DefaultDrawer().FaceBoundaryAspect() line_aspect.SetWidth(DEFAULT_EDGE_WIDTH) line_aspect.SetColor(DEFAULT_EDGE_COLOR) def updatePreferences(self,*args): color1 = to_occ_color(self.preferences['Background color']) color2 = to_occ_color(self.preferences['Background color (aux)']) if not self.preferences['Use gradient']: color2 = color1 self.canvas.view.SetBgGradientColors(color1,color2,theToUpdate=True) self.canvas.update() ctx = self.canvas.context ctx.SetDeviationCoefficient(self.preferences['Deviation']) ctx.SetDeviationAngle(self.preferences['Angular deviation']) v = self._get_view() camera = v.Camera() projection_type = self.preferences['Projection Type'] camera.SetProjectionType(getattr(Graphic3d_Camera, f'Projection_{projection_type}', Graphic3d_Camera.Projection_Orthographic)) # onle relevant for stereo projection stereo_mode = self.preferences['Stereo Mode'] params = v.ChangeRenderingParams() params.StereoMode = getattr(Graphic3d_StereoMode, f'Graphic3d_StereoMode_{stereo_mode}', Graphic3d_StereoMode.Graphic3d_StereoMode_QuadBuffer) def create_actions(self,parent): self._actions = \ {'View' : [QAction(qta.icon('fa.arrows-alt'), 'Fit (Shift+F1)', parent, shortcut='shift+F1', triggered=self.fit), QAction(QIcon(':/images/icons/isometric_view.svg'), 'Iso (Shift+F2)', parent, shortcut='shift+F2', triggered=self.iso_view), QAction(QIcon(':/images/icons/top_view.svg'), 'Top (Shift+F3)', parent, shortcut='shift+F3', triggered=self.top_view), QAction(QIcon(':/images/icons/bottom_view.svg'), 'Bottom (Shift+F4)', parent, shortcut='shift+F4', triggered=self.bottom_view), QAction(QIcon(':/images/icons/front_view.svg'), 'Front (Shift+F5)', parent, shortcut='shift+F5', triggered=self.front_view), QAction(QIcon(':/images/icons/back_view.svg'), 'Back (Shift+F6)', parent, shortcut='shift+F6', triggered=self.back_view), QAction(QIcon(':/images/icons/left_side_view.svg'), 'Left (Shift+F7)', parent, shortcut='shift+F7', triggered=self.left_view), QAction(QIcon(':/images/icons/right_side_view.svg'), 'Right (Shift+F8)', parent, shortcut='shift+F8', triggered=self.right_view), QAction(qta.icon('fa.square-o'), 'Wireframe (Shift+F9)', parent, shortcut='shift+F9', triggered=self.wireframe_view), QAction(qta.icon('fa.square'), 'Shaded (Shift+F10)', parent, shortcut='shift+F10', triggered=self.shaded_view)], 'Tools' : [QAction(icon('screenshot'), 'Screenshot', parent, triggered=self.save_screenshot)]} def toolbarActions(self): return self._actions['View'] def clear(self): self.displayed_shapes = [] self.displayed_ais = [] self.canvas.context.EraseAll(True) context = self._get_context() context.PurgeDisplay() context.RemoveAll(True) def _display(self,shape): ais = make_AIS(shape) self.canvas.context.Display(shape,True) self.displayed_shapes.append(shape) self.displayed_ais.append(ais) #self.canvas._display.Repaint() @pyqtSlot(object) def display(self,ais): context = self._get_context() context.Display(ais,True) if self.preferences['Fit automatically']: self.fit() @pyqtSlot(list) @pyqtSlot(list,bool) def display_many(self,ais_list,fit=None): context = self._get_context() for ais in ais_list: context.Display(ais,True) if self.preferences['Fit automatically'] and fit is None: self.fit() elif fit: self.fit() @pyqtSlot(QTreeWidgetItem,int) def update_item(self,item,col): ctx = self._get_context() if item.checkState(0): ctx.Display(item.ais,True) else: ctx.Erase(item.ais,True) @pyqtSlot(list) def remove_items(self,ais_items): ctx = self._get_context() for ais in ais_items: ctx.Erase(ais,True) @pyqtSlot() def redraw(self): self._get_viewer().Redraw() def fit(self): self.canvas.view.FitAll() def iso_view(self): v = self._get_view() v.SetProj(1,-1,1) v.SetTwist(0) def bottom_view(self): v = self._get_view() v.SetProj(0,0,-1) v.SetTwist(0) def top_view(self): v = self._get_view() v.SetProj(0,0,1) v.SetTwist(0) def front_view(self): v = self._get_view() v.SetProj(0,-1,0) v.SetTwist(0) def back_view(self): v = self._get_view() v.SetProj(0,1,0) v.SetTwist(0) def left_view(self): v = self._get_view() v.SetProj(-1,0,0) v.SetTwist(0) def right_view(self): v = self._get_view() v.SetProj(1,0,0) v.SetTwist(0) def shaded_view(self): c = self._get_context() c.SetDisplayMode(AIS_Shaded, True) def wireframe_view(self): c = self._get_context() c.SetDisplayMode(AIS_WireFrame, True) def show_grid(self, step=1., size=10.+1e-6, color1=(.7,.7,.7), color2=(0,0,0)): viewer = self._get_viewer() viewer.ActivateGrid(Aspect_GT_Rectangular, Aspect_GDM_Lines) viewer.SetRectangularGridGraphicValues(size, size, 0) viewer.SetRectangularGridValues(0, 0, step, step, 0) grid = viewer.Grid() grid.SetColors(Quantity_Color(*color1,TOC_RGB), Quantity_Color(*color2,TOC_RGB)) def hide_grid(self): viewer = self._get_viewer() viewer.DeactivateGrid() @pyqtSlot(bool,float) @pyqtSlot(bool) def toggle_grid(self, value : bool, dim : float = 10.): if value: self.show_grid(step=dim/20,size=dim+1e-9) else: self.hide_grid() @pyqtSlot(gp_Ax3) def set_grid_orientation(self,orientation : gp_Ax3): viewer = self._get_viewer() viewer.SetPrivilegedPlane(orientation) def show_axis(self,origin = (0,0,0), direction=(0,0,1)): ax_placement = Geom_Axis1Placement(gp_Ax1(gp_Pnt(*origin), gp_Dir(*direction))) ax = AIS_Axis(ax_placement) self._display_ais(ax) def save_screenshot(self): fname = get_save_filename(self.IMAGE_EXTENSIONS) if fname != '': self._get_view().Dump(fname) def _display_ais(self,ais): self._get_context().Display(ais) def _get_view(self): return self.canvas.view def _get_viewer(self): return self.canvas.viewer def _get_context(self): return self.canvas.context @pyqtSlot(list) def handle_selection(self,obj): self.sigObjectSelected.emit(obj) @pyqtSlot(list) def set_selected(self,ais): ctx = self._get_context() ctx.ClearSelected(False) for obj in ais: ctx.AddOrRemoveSelected(obj,False) self.redraw() if __name__ == "__main__": import sys from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox app = QApplication(sys.argv) viewer = OCCViewer() dlg = QDialog() dlg.setFixedHeight(400) dlg.setFixedWidth(600) layout(dlg,(viewer,),dlg) dlg.show() box = BRepPrimAPI_MakeBox(20,20,30) box_ais = AIS_ColoredShape(box.Shape()) viewer.display(box_ais) sys.exit(app.exec_())