diff --git a/cq_editor/editor.py b/cq_editor/editor.py index f0731fd..6e643fe 100644 --- a/cq_editor/editor.py +++ b/cq_editor/editor.py @@ -3,7 +3,7 @@ import spyder.utils.encoding from modulefinder import ModuleFinder from spyder.plugins.editor.widgets.codeeditor import CodeEditor -from PySide6.QtCore import pyqtSignal, QFileSystemWatcher, QTimer +from PySide6.QtCore import Signal as pyqtSignal, QFileSystemWatcher, QTimer from PySide6.QtWidgets import QAction, QFileDialog from PySide6.QtGui import QFontDatabase from path import Path @@ -17,91 +17,104 @@ from ..utils import get_save_filename, get_open_filename, confirm from ..icons import icon -class Editor(CodeEditor,ComponentMixin): - name = 'Code Editor' +class Editor(CodeEditor, ComponentMixin): + name = "Code Editor" # This signal is emitted whenever the currently-open file changes and # autoreload is enabled. triggerRerender = pyqtSignal(bool) sigFilenameChanged = pyqtSignal(str) - preferences = Parameter.create(name='Preferences',children=[ - {'name': 'Font size', 'type': 'int', 'value': 12}, - {'name': 'Autoreload', 'type': 'bool', 'value': False}, - {'name': 'Autoreload delay', 'type': 'int', 'value': 50}, - {'name': 'Autoreload: watch imported modules', 'type': 'bool', 'value': False}, - {'name': 'Line wrap', 'type': 'bool', 'value': False}, - {'name': 'Color scheme', 'type': 'list', - 'values': ['Spyder','Monokai','Zenburn'], 'value': 'Spyder'}]) + preferences = Parameter.create( + name="Preferences", + children=[ + {"name": "Font size", "type": "int", "value": 12}, + {"name": "Autoreload", "type": "bool", "value": False}, + {"name": "Autoreload delay", "type": "int", "value": 50}, + { + "name": "Autoreload: watch imported modules", + "type": "bool", + "value": False, + }, + {"name": "Line wrap", "type": "bool", "value": False}, + { + "name": "Color scheme", + "type": "list", + "values": ["Spyder", "Monokai", "Zenburn"], + "value": "Spyder", + }, + ], + ) - EXTENSIONS = 'py' - - def __init__(self,parent=None): + EXTENSIONS = "py" + def __init__(self, parent=None): self._watched_file = None - super(Editor,self).__init__(parent) + super(Editor, self).__init__(parent) ComponentMixin.__init__(self) - self.setup_editor(linenumbers=True, - markers=True, - edge_line=False, - tab_mode=False, - show_blanks=True, - font=QFontDatabase.systemFont(QFontDatabase.FixedFont), - language='Python', - filename='') + self.setup_editor( + linenumbers=True, + markers=True, + edge_line=False, + tab_mode=False, + show_blanks=True, + font=QFontDatabase.systemFont(QFontDatabase.FixedFont), + language="Python", + filename="", + ) - self._actions = \ - {'File' : [QAction(icon('new'), - 'New', - self, - shortcut='ctrl+N', - triggered=self.new), - QAction(icon('open'), - 'Open', - self, - shortcut='ctrl+O', - triggered=self.open), - QAction(icon('save'), - 'Save', - self, - shortcut='ctrl+S', - triggered=self.save), - QAction(icon('save_as'), - 'Save as', - self, - shortcut='ctrl+shift+S', - triggered=self.save_as), - QAction(icon('autoreload'), - 'Automatic reload and preview', - self,triggered=self.autoreload, - checkable=True, - checked=False, - objectName='autoreload'), - ]} + self._actions = { + "File": [ + QAction( + icon("new"), "New", self, shortcut="ctrl+N", triggered=self.new + ), + QAction( + icon("open"), "Open", self, shortcut="ctrl+O", triggered=self.open + ), + QAction( + icon("save"), "Save", self, shortcut="ctrl+S", triggered=self.save + ), + QAction( + icon("save_as"), + "Save as", + self, + shortcut="ctrl+shift+S", + triggered=self.save_as, + ), + QAction( + icon("autoreload"), + "Automatic reload and preview", + self, + triggered=self.autoreload, + checkable=True, + checked=False, + objectName="autoreload", + ), + ] + } for a in self._actions.values(): self.addActions(a) - self._fixContextMenu() # autoreload support self._file_watcher = QFileSystemWatcher(self) # we wait for 50ms after a file change for the file to be written completely self._file_watch_timer = QTimer(self) - self._file_watch_timer.setInterval(self.preferences['Autoreload delay']) + self._file_watch_timer.setInterval(self.preferences["Autoreload delay"]) self._file_watch_timer.setSingleShot(True) self._file_watcher.fileChanged.connect( - lambda val: self._file_watch_timer.start()) + lambda val: self._file_watch_timer.start() + ) self._file_watch_timer.timeout.connect(self._file_changed) self.updatePreferences() def _fixContextMenu(self): - menu = self.menu menu.removeAction(self.run_cell_action) @@ -109,52 +122,52 @@ class Editor(CodeEditor,ComponentMixin): menu.removeAction(self.run_selection_action) menu.removeAction(self.re_run_last_cell_action) - def updatePreferences(self,*args): - - self.set_color_scheme(self.preferences['Color scheme']) + def updatePreferences(self, *args): + self.set_color_scheme(self.preferences["Color scheme"]) font = self.font() - font.setPointSize(self.preferences['Font size']) + font.setPointSize(self.preferences["Font size"]) self.set_font(font) - self.findChild(QAction, 'autoreload') \ - .setChecked(self.preferences['Autoreload']) + self.findChild(QAction, "autoreload").setChecked(self.preferences["Autoreload"]) - self._file_watch_timer.setInterval(self.preferences['Autoreload delay']) + self._file_watch_timer.setInterval(self.preferences["Autoreload delay"]) - self.toggle_wrap_mode(self.preferences['Line wrap']) + self.toggle_wrap_mode(self.preferences["Line wrap"]) self._clear_watched_paths() self._watch_paths() def confirm_discard(self): - if self.modified: - rv = confirm(self,'Please confirm','Current document is not saved - do you want to continue?') + rv = confirm( + self, + "Please confirm", + "Current document is not saved - do you want to continue?", + ) else: rv = True return rv def new(self): + if not self.confirm_discard(): + return - if not self.confirm_discard(): return - - self.set_text('') - self.filename = '' + self.set_text("") + self.filename = "" self.reset_modified() def open(self): - - if not self.confirm_discard(): return + if not self.confirm_discard(): + return curr_dir = Path(self.filename).abspath().dirname() fname = get_open_filename(self.EXTENSIONS, curr_dir) - if fname != '': + if fname != "": self.load_from_file(fname) - def load_from_file(self,fname): - + def load_from_file(self, fname): self.set_text_from_file(fname) self.filename = fname self.reset_modified() @@ -164,24 +177,22 @@ class Editor(CodeEditor,ComponentMixin): # this function returns the encoding spyder used to read the file _, encoding = spyder.utils.encoding.read(fname) # spyder returns a -guessed suffix in some cases - return encoding.replace('-guessed', '') + return encoding.replace("-guessed", "") else: - return 'utf-8' + return "utf-8" def save(self): - - if self._filename != '': - - if self.preferences['Autoreload']: + if self._filename != "": + if self.preferences["Autoreload"]: self._file_watcher.blockSignals(True) self._file_watch_timer.stop() encoding = self.determine_encoding(self._filename) encoded = self.toPlainText().encode(encoding) - with open(self._filename, 'wb') as f: + with open(self._filename, "wb") as f: f.write(encoded) - if self.preferences['Autoreload']: + if self.preferences["Autoreload"]: self._file_watcher.blockSignals(False) self.triggerRerender.emit(True) @@ -191,21 +202,26 @@ class Editor(CodeEditor,ComponentMixin): self.save_as() def save_as(self): - fname = get_save_filename(self.EXTENSIONS) - if fname != '': - encoded = self.toPlainText().encode('utf-8') - with open(fname, 'wb') as f: + if fname != "": + encoded = self.toPlainText().encode("utf-8") + with open(fname, "wb") as f: f.write(encoded) self.filename = fname self.reset_modified() def _update_filewatcher(self): - if self._watched_file and (self._watched_file != self.filename or not self.preferences['Autoreload']): + if self._watched_file and ( + self._watched_file != self.filename or not self.preferences["Autoreload"] + ): self._clear_watched_paths() self._watched_file = None - if self.preferences['Autoreload'] and self.filename and self.filename != self._watched_file: + if ( + self.preferences["Autoreload"] + and self.filename + and self.filename != self._watched_file + ): self._watched_file = self._filename self._watch_paths() @@ -227,8 +243,8 @@ class Editor(CodeEditor,ComponentMixin): def _watch_paths(self): if Path(self._filename).exists(): self._file_watcher.addPath(self._filename) - if self.preferences['Autoreload: watch imported modules']: - module_paths = self.get_imported_module_paths(self._filename) + if self.preferences["Autoreload: watch imported modules"]: + module_paths = self.get_imported_module_paths(self._filename) if module_paths: self._file_watcher.addPaths(module_paths) @@ -241,51 +257,45 @@ class Editor(CodeEditor,ComponentMixin): # Turn autoreload on/off. def autoreload(self, enabled): - self.preferences['Autoreload'] = enabled + self.preferences["Autoreload"] = enabled self._update_filewatcher() def reset_modified(self): - self.document().setModified(False) - + @property def modified(self): - return self.document().isModified() - def saveComponentState(self,store): + def saveComponentState(self, store): + if self.filename != "": + store.setValue(self.name + "/state", self.filename) - if self.filename != '': - store.setValue(self.name+'/state',self.filename) + def restoreComponentState(self, store): + filename = store.value(self.name + "/state") - def restoreComponentState(self,store): - - filename = store.value(self.name+'/state') - - if filename and self.filename == '': + if filename and self.filename == "": try: self.load_from_file(filename) except IOError: - self._logger.warning(f'could not open {filename}') - + self._logger.warning(f"could not open {filename}") def get_imported_module_paths(self, module_path): - finder = ModuleFinder([os.path.dirname(module_path)]) imported_modules = [] try: finder.run_script(module_path) except SyntaxError as err: - self._logger.warning(f'Syntax error in {module_path}: {err}') + self._logger.warning(f"Syntax error in {module_path}: {err}") except Exception as err: self._logger.warning( - f'Cannot determine imported modules in {module_path}: {type(err).__name__} {err}' + f"Cannot determine imported modules in {module_path}: {type(err).__name__} {err}" ) else: for module_name, module in finder.modules.items(): - if module_name != '__main__': - path = getattr(module, '__file__', None) + if module_name != "__main__": + path = getattr(module, "__file__", None) if path is not None and os.path.isfile(path): imported_modules.append(path) @@ -293,7 +303,6 @@ class Editor(CodeEditor,ComponentMixin): if __name__ == "__main__": - from PySide6.QtWidgets import QApplication app = QApplication(sys.argv) diff --git a/cq_editor/main_window.py b/cq_editor/main_window.py index f74ab21..d7ebc82 100644 --- a/cq_editor/main_window.py +++ b/cq_editor/main_window.py @@ -1,6 +1,8 @@ import sys -from PySide6.QtWidgets import (QLabel, QMainWindow, QToolBar, QDockWidget, QAction) +from PySide6.QtWidgets import QLabel, QMainWindow, QToolBar, QDockWidget +from PySide6.QtGui import QAction +from PySide6.QtCore import Signal as pyqtSignal from logbook import Logger import cadquery as cq @@ -14,16 +16,26 @@ from .widgets.cq_object_inspector import CQObjectInspector from .widgets.log import LogViewer from . import __version__ -from .utils import dock, add_actions, open_url, about_dialog, check_gtihub_for_updates, confirm +from .utils import ( + dock, + add_actions, + open_url, + about_dialog, + check_gtihub_for_updates, + confirm, +) from .mixins import MainMixin from .icons import icon from .preferences import PreferencesWidget -#DARKMODE edits: https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets +# DARKMODE edits: https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets from PySide6.QtCore import Qt from PySide6.QtWidgets import QApplication from PySide6.QtGui import QPalette, QColor -app = QApplication([]) + +app = QApplication.instance() +if app == None: + app = QApplication([]) # Force the style to be the same on all OSs: app.setStyle("Fusion") # Now use a palette to switch to dark colors: @@ -43,141 +55,131 @@ palette.setColor(QPalette.Highlight, QColor(42, 130, 218)) palette.setColor(QPalette.HighlightedText, Qt.black) app.setPalette(palette) -class MainWindow(QMainWindow,MainMixin): - name = 'CQ-Editor' - org = 'CadQuery' +class MainWindow(QMainWindow, MainMixin): + name = "CQ-Editor" + org = "CadQuery" - def __init__(self,parent=None, filename=None): - - super(MainWindow,self).__init__(parent) + def __init__(self, parent=None, filename=None): + super(MainWindow, self).__init__(parent) MainMixin.__init__(self) - self.setWindowIcon(icon('app')) + self.setWindowIcon(icon("app")) self.viewer = OCCViewer(self) - self.setCentralWidget(self.viewer.canvas) + # self.setCentralWidget(self.viewer.canvas) - self.prepare_panes() - self.registerComponent('viewer',self.viewer) - self.prepare_toolbar() - self.prepare_menubar() + # self.prepare_panes() + # self.registerComponent("viewer", self.viewer) + # self.prepare_toolbar() + # self.prepare_menubar() - self.prepare_statusbar() - self.prepare_actions() - - self.components['object_tree'].addLines() + # self.prepare_statusbar() + # self.prepare_actions() - self.prepare_console() + # self.components["object_tree"].addLines() - self.fill_dummy() + # self.prepare_console() - self.setup_logging() + # self.fill_dummy() - self.restorePreferences() - self.restoreWindow() + # self.setup_logging() - if filename: - self.components['editor'].load_from_file(filename) + # self.restorePreferences() + # self.restoreWindow() - self.restoreComponentState() + # if filename: + # self.components["editor"].load_from_file(filename) - def closeEvent(self,event): + # self.restoreComponentState() + def closeEvent(self, event): self.saveWindow() self.savePreferences() self.saveComponentState() - if self.components['editor'].document().isModified(): + if self.components["editor"].document().isModified(): + rv = confirm(self, "Confirm close", "Close without saving?") - rv = confirm(self, 'Confirm close', 'Close without saving?') - if rv: event.accept() - super(MainWindow,self).closeEvent(event) + super(MainWindow, self).closeEvent(event) else: event.ignore() else: - super(MainWindow,self).closeEvent(event) + super(MainWindow, self).closeEvent(event) def prepare_panes(self): + self.registerComponent( + "editor", + Editor(self), + lambda c: dock(c, "Editor", self, defaultArea="left"), + ) - self.registerComponent('editor', - Editor(self), - lambda c : dock(c, - 'Editor', - self, - defaultArea='left')) + self.registerComponent( + "object_tree", + ObjectTree(self), + lambda c: dock(c, "Objects", self, defaultArea="right"), + ) - self.registerComponent('object_tree', - ObjectTree(self), - lambda c: dock(c, - 'Objects', - self, - defaultArea='right')) + self.registerComponent( + "console", + ConsoleWidget(self), + lambda c: dock(c, "Console", self, defaultArea="bottom"), + ) - self.registerComponent('console', - ConsoleWidget(self), - lambda c: dock(c, - 'Console', - self, - defaultArea='bottom')) + self.registerComponent( + "traceback_viewer", + TracebackPane(self), + lambda c: dock(c, "Current traceback", self, defaultArea="bottom"), + ) - self.registerComponent('traceback_viewer', - TracebackPane(self), - lambda c: dock(c, - 'Current traceback', - self, - defaultArea='bottom')) + self.registerComponent("debugger", Debugger(self)) - self.registerComponent('debugger',Debugger(self)) + self.registerComponent( + "variables_viewer", + LocalsView(self), + lambda c: dock(c, "Variables", self, defaultArea="right"), + ) - self.registerComponent('variables_viewer',LocalsView(self), - lambda c: dock(c, - 'Variables', - self, - defaultArea='right')) - - self.registerComponent('cq_object_inspector', - CQObjectInspector(self), - lambda c: dock(c, - 'CQ object inspector', - self, - defaultArea='right')) - self.registerComponent('log', - LogViewer(self), - lambda c: dock(c, - 'Log viewer', - self, - defaultArea='bottom')) + self.registerComponent( + "cq_object_inspector", + CQObjectInspector(self), + lambda c: dock(c, "CQ object inspector", self, defaultArea="right"), + ) + self.registerComponent( + "log", + LogViewer(self), + lambda c: dock(c, "Log viewer", self, defaultArea="bottom"), + ) for d in self.docks.values(): d.show() def prepare_menubar(self): - menu = self.menuBar() - menu_file = menu.addMenu('&File') - menu_edit = menu.addMenu('&Edit') - menu_tools = menu.addMenu('&Tools') - menu_run = menu.addMenu('&Run') - menu_view = menu.addMenu('&View') - menu_help = menu.addMenu('&Help') + menu_file = menu.addMenu("&File") + menu_edit = menu.addMenu("&Edit") + menu_tools = menu.addMenu("&Tools") + menu_run = menu.addMenu("&Run") + menu_view = menu.addMenu("&View") + menu_help = menu.addMenu("&Help") - #per component menu elements - menus = {'File' : menu_file, - 'Edit' : menu_edit, - 'Run' : menu_run, - 'Tools': menu_tools, - 'View' : menu_view, - 'Help' : menu_help} + # per component menu elements + menus = { + "File": menu_file, + "Edit": menu_edit, + "Run": menu_run, + "Tools": menu_tools, + "View": menu_view, + "Help": menu_help, + } for comp in self.components.values(): - self.prepare_menubar_component(menus, - comp.menuActions()) + self.prepare_menubar_component(menus, comp.menuActions()) - #global menu elements + # global menu elements menu_view.addSeparator() for d in self.findChildren(QDockWidget): menu_view.addAction(d.toggleViewAction()) @@ -186,181 +188,200 @@ class MainWindow(QMainWindow,MainMixin): for t in self.findChildren(QToolBar): menu_view.addAction(t.toggleViewAction()) - menu_edit.addAction( \ - QAction(icon('preferences'), - 'Preferences', - self,triggered=self.edit_preferences)) + menu_edit.addAction( + QAction( + icon("preferences"), + "Preferences", + self, + triggered=self.edit_preferences, + ) + ) - menu_help.addAction( \ - QAction(icon('help'), - 'Documentation', - self,triggered=self.documentation)) + menu_help.addAction( + QAction(icon("help"), "Documentation", self, triggered=self.documentation) + ) - menu_help.addAction( \ - QAction('CQ documentation', - self,triggered=self.cq_documentation)) + menu_help.addAction( + QAction("CQ documentation", self, triggered=self.cq_documentation) + ) - menu_help.addAction( \ - QAction(icon('about'), - 'About', - self,triggered=self.about)) - - menu_help.addAction( \ - QAction('Check for CadQuery updates', - self,triggered=self.check_for_cq_updates)) + menu_help.addAction(QAction(icon("about"), "About", self, triggered=self.about)) - def prepare_menubar_component(self,menus,comp_menu_dict): + menu_help.addAction( + QAction( + "Check for CadQuery updates", self, triggered=self.check_for_cq_updates + ) + ) - for name,action in comp_menu_dict.items(): + def prepare_menubar_component(self, menus, comp_menu_dict): + for name, action in comp_menu_dict.items(): menus[name].addActions(action) def prepare_toolbar(self): - - self.toolbar = QToolBar('Main toolbar',self,objectName='Main toolbar') + self.toolbar = QToolBar("Main toolbar", self, objectName="Main toolbar") for c in self.components.values(): - add_actions(self.toolbar,c.toolbarActions()) + add_actions(self.toolbar, c.toolbarActions()) self.addToolBar(self.toolbar) def prepare_statusbar(self): - - self.status_label = QLabel('',parent=self) + self.status_label = QLabel("", parent=self) self.statusBar().insertPermanentWidget(0, self.status_label) def prepare_actions(self): + self.components["debugger"].sigRendered.connect( + self.components["object_tree"].addObjects + ) + self.components["debugger"].sigTraceback.connect( + self.components["traceback_viewer"].addTraceback + ) + self.components["debugger"].sigLocals.connect( + self.components["variables_viewer"].update_frame + ) + self.components["debugger"].sigLocals.connect( + self.components["console"].push_vars + ) - self.components['debugger'].sigRendered\ - .connect(self.components['object_tree'].addObjects) - self.components['debugger'].sigTraceback\ - .connect(self.components['traceback_viewer'].addTraceback) - self.components['debugger'].sigLocals\ - .connect(self.components['variables_viewer'].update_frame) - self.components['debugger'].sigLocals\ - .connect(self.components['console'].push_vars) + self.components["object_tree"].sigObjectsAdded[list].connect( + self.components["viewer"].display_many + ) + self.components["object_tree"].sigObjectsAdded2[list, bool].connect( + self.components["viewer"].display_many + ) + self.components["object_tree"].sigItemChanged.connect( + self.components["viewer"].update_item + ) + self.components["object_tree"].sigObjectsRemoved.connect( + self.components["viewer"].remove_items + ) + self.components["object_tree"].sigCQObjectSelected.connect( + self.components["cq_object_inspector"].setObject + ) + self.components["object_tree"].sigObjectPropertiesChanged.connect( + self.components["viewer"].redraw + ) + self.components["object_tree"].sigAISObjectsSelected.connect( + self.components["viewer"].set_selected + ) - self.components['object_tree'].sigObjectsAdded[list]\ - .connect(self.components['viewer'].display_many) - self.components['object_tree'].sigObjectsAdded[list,bool]\ - .connect(self.components['viewer'].display_many) - self.components['object_tree'].sigItemChanged.\ - connect(self.components['viewer'].update_item) - self.components['object_tree'].sigObjectsRemoved\ - .connect(self.components['viewer'].remove_items) - self.components['object_tree'].sigCQObjectSelected\ - .connect(self.components['cq_object_inspector'].setObject) - self.components['object_tree'].sigObjectPropertiesChanged\ - .connect(self.components['viewer'].redraw) - self.components['object_tree'].sigAISObjectsSelected\ - .connect(self.components['viewer'].set_selected) + self.components["viewer"].sigObjectSelected.connect( + self.components["object_tree"].handleGraphicalSelection + ) - self.components['viewer'].sigObjectSelected\ - .connect(self.components['object_tree'].handleGraphicalSelection) + self.components["traceback_viewer"].sigHighlightLine.connect( + self.components["editor"].go_to_line + ) - self.components['traceback_viewer'].sigHighlightLine\ - .connect(self.components['editor'].go_to_line) + self.components["cq_object_inspector"].sigDisplayObjects.connect( + self.components["viewer"].display_many + ) + self.components["cq_object_inspector"].sigRemoveObjects.connect( + self.components["viewer"].remove_items + ) + self.components["cq_object_inspector"].sigShowPlane.connect( + self.components["viewer"].toggle_grid + ) + self.components["cq_object_inspector"].sigShowPlane2[bool, float].connect( + self.components["viewer"].toggle_grid + ) + self.components["cq_object_inspector"].sigChangePlane.connect( + self.components["viewer"].set_grid_orientation + ) - self.components['cq_object_inspector'].sigDisplayObjects\ - .connect(self.components['viewer'].display_many) - self.components['cq_object_inspector'].sigRemoveObjects\ - .connect(self.components['viewer'].remove_items) - self.components['cq_object_inspector'].sigShowPlane\ - .connect(self.components['viewer'].toggle_grid) - self.components['cq_object_inspector'].sigShowPlane[bool,float]\ - .connect(self.components['viewer'].toggle_grid) - self.components['cq_object_inspector'].sigChangePlane\ - .connect(self.components['viewer'].set_grid_orientation) - - self.components['debugger'].sigLocalsChanged\ - .connect(self.components['variables_viewer'].update_frame) - self.components['debugger'].sigLineChanged\ - .connect(self.components['editor'].go_to_line) - self.components['debugger'].sigDebugging\ - .connect(self.components['object_tree'].stashObjects) - self.components['debugger'].sigCQChanged\ - .connect(self.components['object_tree'].addObjects) - self.components['debugger'].sigTraceback\ - .connect(self.components['traceback_viewer'].addTraceback) + self.components["debugger"].sigLocalsChanged.connect( + self.components["variables_viewer"].update_frame + ) + self.components["debugger"].sigLineChanged.connect( + self.components["editor"].go_to_line + ) + self.components["debugger"].sigDebugging.connect( + self.components["object_tree"].stashObjects + ) + self.components["debugger"].sigCQChanged.connect( + self.components["object_tree"].addObjects + ) + self.components["debugger"].sigTraceback.connect( + self.components["traceback_viewer"].addTraceback + ) # trigger re-render when file is modified externally or saved - self.components['editor'].triggerRerender \ - .connect(self.components['debugger'].render) - self.components['editor'].sigFilenameChanged\ - .connect(self.handle_filename_change) + self.components["editor"].triggerRerender.connect( + self.components["debugger"].render + ) + self.components["editor"].sigFilenameChanged.connect( + self.handle_filename_change + ) def prepare_console(self): + console = self.components["console"] + obj_tree = self.components["object_tree"] - console = self.components['console'] - obj_tree = self.components['object_tree'] - - #application related items - console.push_vars({'self' : self}) + # application related items + console.push_vars({"self": self}) + + # CQ related items + console.push_vars( + { + "show": obj_tree.addObject, + "show_object": obj_tree.addObject, + "rand_color": self.components["debugger"]._rand_color, + "cq": cq, + "log": Logger(self.name).info, + } + ) - #CQ related items - console.push_vars({'show' : obj_tree.addObject, - 'show_object' : obj_tree.addObject, - 'rand_color' : self.components['debugger']._rand_color, - 'cq' : cq, - 'log' : Logger(self.name).info}) - def fill_dummy(self): - - self.components['editor']\ - .set_text('import cadquery as cq\nresult = cq.Workplane("XY" ).box(3, 3, 0.5).edges("|Z").fillet(0.125)') + self.components["editor"].set_text( + 'import cadquery as cq\nresult = cq.Workplane("XY" ).box(3, 3, 0.5).edges("|Z").fillet(0.125)' + ) def setup_logging(self): - from logbook.compat import redirect_logging from logbook import INFO, Logger redirect_logging() - self.components['log'].handler.level = INFO - self.components['log'].handler.push_application() + self.components["log"].handler.level = INFO + self.components["log"].handler.push_application() self._logger = Logger(self.name) def handle_exception(exc_type, exc_value, exc_traceback): - if issubclass(exc_type, KeyboardInterrupt): sys.__excepthook__(exc_type, exc_value, exc_traceback) return - self._logger.error("Uncaught exception occurred", - exc_info=(exc_type, exc_value, exc_traceback)) + self._logger.error( + "Uncaught exception occurred", + exc_info=(exc_type, exc_value, exc_traceback), + ) sys.excepthook = handle_exception - def edit_preferences(self): - - prefs = PreferencesWidget(self,self.components) + prefs = PreferencesWidget(self, self.components) prefs.exec_() def about(self): - about_dialog( self, - f'About CQ-editor', - f'PyQt GUI for CadQuery.\nVersion: {__version__}.\nSource Code: https://github.com/CadQuery/CQ-editor', + f"About CQ-editor", + f"PyQt GUI for CadQuery.\nVersion: {__version__}.\nSource Code: https://github.com/CadQuery/CQ-editor", ) - + def check_for_cq_updates(self): - - check_gtihub_for_updates(self,cq) + check_gtihub_for_updates(self, cq) def documentation(self): - - open_url('https://github.com/CadQuery') + open_url("https://github.com/CadQuery") def cq_documentation(self): - - open_url('https://cadquery.readthedocs.io/en/latest/') + open_url("https://cadquery.readthedocs.io/en/latest/") def handle_filename_change(self, fname): - new_title = fname if fname else "*" self.setWindowTitle(f"{self.name}: {new_title}") -if __name__ == "__main__": +if __name__ == "__main__": pass diff --git a/cq_editor/mixins.py b/cq_editor/mixins.py index 0605a0c..4c6091a 100644 --- a/cq_editor/mixins.py +++ b/cq_editor/mixins.py @@ -10,72 +10,67 @@ from functools import reduce from operator import add from logbook import Logger -from PySide6.QtCore import pyqtSlot, QSettings +from PySide6.QtCore import QSettings +from PySide6.QtCore import Slot as pyqtSlot + class MainMixin(object): - - name = 'Main' - org = 'Unknown' + name = "Main" + org = "Unknown" components = {} docks = {} preferences = None def __init__(self): + self.settings = QSettings(self.org, self.name) - self.settings = QSettings(self.org,self.name) - - def registerComponent(self,name,component,dock=None): - + def registerComponent(self, name, component, dock=None): self.components[name] = component if dock: self.docks[name] = dock(component) def saveWindow(self): - - self.settings.setValue('geometry',self.saveGeometry()) - self.settings.setValue('windowState',self.saveState()) + self.settings.setValue("geometry", self.saveGeometry()) + self.settings.setValue("windowState", self.saveState()) def restoreWindow(self): - - if self.settings.value('geometry'): - self.restoreGeometry(self.settings.value('geometry')) - if self.settings.value('windowState'): - self.restoreState(self.settings.value('windowState')) + if self.settings.value("geometry"): + self.restoreGeometry(self.settings.value("geometry")) + if self.settings.value("windowState"): + self.restoreState(self.settings.value("windowState")) def savePreferences(self): - settings = self.settings if self.preferences: - settings.setValue('General',self.preferences.saveState()) + settings.setValue("General", self.preferences.saveState()) for comp in (c for c in self.components.values() if c.preferences): - settings.setValue(comp.name,comp.preferences.saveState()) + settings.setValue(comp.name, comp.preferences.saveState()) def restorePreferences(self): - settings = self.settings - if self.preferences and settings.value('General'): - self.preferences.restoreState(settings.value('General'), - removeChildren=False) + if self.preferences and settings.value("General"): + self.preferences.restoreState( + settings.value("General"), removeChildren=False + ) for comp in (c for c in self.components.values() if c.preferences): if settings.value(comp.name): - comp.preferences.restoreState(settings.value(comp.name), - removeChildren=False) + comp.preferences.restoreState( + settings.value(comp.name), removeChildren=False + ) def saveComponentState(self): - settings = self.settings for comp in self.components.values(): comp.saveComponentState(settings) def restoreComponentState(self): - settings = self.settings for comp in self.components.values(): @@ -83,42 +78,32 @@ class MainMixin(object): class ComponentMixin(object): - - - name = 'Component' + name = "Component" preferences = None _actions = {} - def __init__(self): - if self.preferences: - self.preferences.sigTreeStateChanged.\ - connect(self.updatePreferences) - + self.preferences.sigTreeStateChanged.connect(self.updatePreferences) + self._logger = Logger(self.name) def menuActions(self): - return self._actions def toolbarActions(self): - if len(self._actions) > 0: - return reduce(add,[a for a in self._actions.values()]) + return reduce(add, [a for a in self._actions.values()]) else: return [] - @pyqtSlot(object,object) - def updatePreferences(self,*args): - + @pyqtSlot(object, object) + def updatePreferences(self, *args): pass - def saveComponentState(self,store): - + def saveComponentState(self, store): pass - def restoreComponentState(self,store): - + def restoreComponentState(self, store): pass diff --git a/cq_editor/preferences.py b/cq_editor/preferences.py index 93b1429..d274e6a 100644 --- a/cq_editor/preferences.py +++ b/cq_editor/preferences.py @@ -1,6 +1,5 @@ -from PySide6.QtWidgets import (QTreeWidget, QTreeWidgetItem, - QStackedWidget, QDialog) -from PySide6.QtCore import pyqtSlot, Qt +from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem, QStackedWidget, QDialog +from PySide6.QtCore import Slot as pyqtSlot, Qt from pyqtgraph.parametertree import ParameterTree @@ -8,55 +7,54 @@ from .utils import splitter, layout class PreferencesTreeItem(QTreeWidgetItem): - - def __init__(self,name,widget,): - - super(PreferencesTreeItem,self).__init__(name) + def __init__( + self, + name, + widget, + ): + super(PreferencesTreeItem, self).__init__(name) self.widget = widget + class PreferencesWidget(QDialog): - - def __init__(self,parent,components): - - super(PreferencesWidget,self).__init__( - parent, - Qt.Window | Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint, - windowTitle='Preferences') - + def __init__(self, parent, components): + super(PreferencesWidget, self).__init__( + parent, + Qt.Window | Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint, + windowTitle="Preferences", + ) + self.stacked = QStackedWidget(self) - self.preferences_tree = QTreeWidget(self, - headerHidden=True, - itemsExpandable=False, - rootIsDecorated=False, - columnCount=1) - + self.preferences_tree = QTreeWidget( + self, + headerHidden=True, + itemsExpandable=False, + rootIsDecorated=False, + columnCount=1, + ) + self.root = self.preferences_tree.invisibleRootItem() - - self.add('General', - parent) - + + self.add("General", parent) + for v in parent.components.values(): - self.add(v.name,v) - - self.splitter = splitter((self.preferences_tree,self.stacked),(2,5)) - layout(self,(self.splitter,),self) - + self.add(v.name, v) + + self.splitter = splitter((self.preferences_tree, self.stacked), (2, 5)) + layout(self, (self.splitter,), self) + self.preferences_tree.currentItemChanged.connect(self.handleSelection) - def add(self,name,component): - + def add(self, name, component): if component.preferences: widget = ParameterTree() widget.setHeaderHidden(True) - widget.setParameters(component.preferences,showTop=False) - self.root.addChild(PreferencesTreeItem((name,), - widget)) - + widget.setParameters(component.preferences, showTop=False) + self.root.addChild(PreferencesTreeItem((name,), widget)) + self.stacked.addWidget(widget) - - @pyqtSlot(QTreeWidgetItem,QTreeWidgetItem) - def handleSelection(self,item,*args): - + + @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) + def handleSelection(self, item, *args): if item: self.stacked.setCurrentWidget(item.widget) - diff --git a/cq_editor/utils.py b/cq_editor/utils.py index 95c1d57..41552a3 100644 --- a/cq_editor/utils.py +++ b/cq_editor/utils.py @@ -7,17 +7,22 @@ from PySide6.QtGui import QDesktopServices from PySide6.QtCore import QUrl from PySide6.QtWidgets import QFileDialog, QMessageBox -DOCK_POSITIONS = {'right' : QtCore.Qt.RightDockWidgetArea, - 'left' : QtCore.Qt.LeftDockWidgetArea, - 'top' : QtCore.Qt.TopDockWidgetArea, - 'bottom' : QtCore.Qt.BottomDockWidgetArea} +DOCK_POSITIONS = { + "right": QtCore.Qt.RightDockWidgetArea, + "left": QtCore.Qt.LeftDockWidgetArea, + "top": QtCore.Qt.TopDockWidgetArea, + "bottom": QtCore.Qt.BottomDockWidgetArea, +} -def layout(parent,items, - top_widget = None, - layout_type = QtWidgets.QVBoxLayout, - margin = 2, - spacing = 0): - + +def layout( + parent, + items, + top_widget=None, + layout_type=QtWidgets.QVBoxLayout, + margin=2, + spacing=0, +): if not top_widget: top_widget = QtWidgets.QWidget(parent) top_widget_was_none = True @@ -25,110 +30,124 @@ def layout(parent,items, top_widget_was_none = False layout = layout_type(top_widget) top_widget.setLayout(layout) - - for item in items: layout.addWidget(item) + + for item in items: + layout.addWidget(item) layout.setSpacing(spacing) - layout.setContentsMargins(margin,margin,margin,margin) - + layout.setContentsMargins(margin, margin, margin, margin) + if top_widget_was_none: return top_widget else: return layout - -def splitter(items, - stretch_factors = None, - orientation=QtCore.Qt.Horizontal): - + + +def splitter(items, stretch_factors=None, orientation=QtCore.Qt.Horizontal): sp = QtWidgets.QSplitter(orientation) - - for item in items: sp.addWidget(item) - + + for item in items: + sp.addWidget(item) + if stretch_factors: - for i,s in enumerate(stretch_factors): - sp.setStretchFactor(i,s) - - + for i, s in enumerate(stretch_factors): + sp.setStretchFactor(i, s) + return sp -def dock(widget, - title, - parent, - allowedAreas = QtCore.Qt.AllDockWidgetAreas, - defaultArea = 'right', - name=None, - icon = None): - - dock = QtWidgets.QDockWidget(title,parent,objectName=title) - - if name: dock.setObjectName(name) - if icon: dock.toggleViewAction().setIcon(icon) - + +def dock( + widget, + title, + parent, + allowedAreas=QtCore.Qt.AllDockWidgetAreas, + defaultArea="right", + name=None, + icon=None, +): + dock = QtWidgets.QDockWidget(title, parent, objectName=title) + + if name: + dock.setObjectName(name) + if icon: + dock.toggleViewAction().setIcon(icon) + dock.setAllowedAreas(allowedAreas) dock.setWidget(widget) action = dock.toggleViewAction() action.setText(title) - - dock.setFeatures(QtWidgets.QDockWidget.DockWidgetFeatures(\ - QtWidgets.QDockWidget.AllDockWidgetFeatures)) - - parent.addDockWidget(DOCK_POSITIONS[defaultArea], - dock) - + + dock.setFeatures( + QtWidgets.QDockWidget.DockWidgetFeatures( + QtWidgets.QDockWidget.DockWidgetFloatable + ) + ) + + parent.addDockWidget(DOCK_POSITIONS[defaultArea], dock) + return dock -def add_actions(menu,actions): - + +def add_actions(menu, actions): if len(actions) > 0: menu.addActions(actions) menu.addSeparator() - + + def open_url(url): - - QDesktopServices.openUrl(QUrl(url)) - -def about_dialog(parent,title,text): - - QtWidgets.QMessageBox.about(parent,title,text) - + QDesktopServices.openUrl(QUrl(url)) + + +def about_dialog(parent, title, text): + QtWidgets.QMessageBox.about(parent, title, text) + + def get_save_filename(suffix): - - rv,_ = QFileDialog.getSaveFileName(filter='*.{}'.format(suffix)) - if rv != '' and not rv.endswith(suffix): rv += '.'+suffix - + rv, _ = QFileDialog.getSaveFileName(filter="*.{}".format(suffix)) + if rv != "" and not rv.endswith(suffix): + rv += "." + suffix + return rv + def get_open_filename(suffix, curr_dir): - - rv,_ = QFileDialog.getOpenFileName(directory=curr_dir, filter='*.{}'.format(suffix)) - if rv != '' and not rv.endswith(suffix): rv += '.'+suffix - + rv, _ = QFileDialog.getOpenFileName( + directory=curr_dir, filter="*.{}".format(suffix) + ) + if rv != "" and not rv.endswith(suffix): + rv += "." + suffix + return rv -def check_gtihub_for_updates(parent, - mod, - github_org='cadquery', - github_proj='cadquery'): - - url = f'https://api.github.com/repos/{github_org}/{github_proj}/releases' + +def check_gtihub_for_updates( + parent, mod, github_org="cadquery", github_proj="cadquery" +): + url = f"https://api.github.com/repos/{github_org}/{github_proj}/releases" resp = requests.get(url).json() - - newer = [el['tag_name'] for el in resp if not el['draft'] and \ - parse_version(el['tag_name']) > parse_version(mod.__version__)] - + + newer = [ + el["tag_name"] + for el in resp + if not el["draft"] + and parse_version(el["tag_name"]) > parse_version(mod.__version__) + ] + if newer: - title='Updates available' - text=f'There are newer versions of {github_proj} ' \ - f'available on github:\n' + '\n'.join(newer) - + title = "Updates available" + text = ( + f"There are newer versions of {github_proj} " + f"available on github:\n" + "\n".join(newer) + ) + else: - title='No updates available' - text=f'You are already using the latest version of {github_proj}' - - QtWidgets.QMessageBox.about(parent,title,text) - -def confirm(parent,title,msg): - + title = "No updates available" + text = f"You are already using the latest version of {github_proj}" + + QtWidgets.QMessageBox.about(parent, title, text) + + +def confirm(parent, title, msg): rv = QMessageBox.question(parent, title, msg, QMessageBox.Yes, QMessageBox.No) - + return True if rv == QMessageBox.Yes else False diff --git a/cq_editor/widgets/console.py b/cq_editor/widgets/console.py index 34d901d..87dfbaf 100644 --- a/cq_editor/widgets/console.py +++ b/cq_editor/widgets/console.py @@ -1,23 +1,23 @@ from PySide6.QtWidgets import QApplication -from PySide6.QtCore import pyqtSlot +from PySide6.QtCore import Slot as pyqtSlot from qtconsole.rich_jupyter_widget import RichJupyterWidget from qtconsole.inprocess import QtInProcessKernelManager from ..mixins import ComponentMixin -class ConsoleWidget(RichJupyterWidget,ComponentMixin): - - name = 'Console' + +class ConsoleWidget(RichJupyterWidget, ComponentMixin): + name = "Console" def __init__(self, customBanner=None, namespace=dict(), *args, **kwargs): super(ConsoleWidget, self).__init__(*args, **kwargs) -# if not customBanner is None: -# self.banner = customBanner + # if not customBanner is None: + # self.banner = customBanner self.font_size = 6 - self.style_sheet = ''' - ''' - self.syntax_style = 'zenburn' #CHANGES FOR DARKMODE - + """ + self.syntax_style = "zenburn" # CHANGES FOR DARKMODE + self.kernel_manager = kernel_manager = QtInProcessKernelManager() kernel_manager.start_kernel(show_banner=False) - kernel_manager.kernel.gui = 'qt' + kernel_manager.kernel.gui = "qt" kernel_manager.kernel.shell.banner1 = "" - + self.kernel_client = kernel_client = self._kernel_manager.client() kernel_client.start_channels() @@ -51,9 +51,9 @@ class ConsoleWidget(RichJupyterWidget,ComponentMixin): QApplication.instance().exit() self.exit_requested.connect(stop) - + self.clear() - + self.push_vars(namespace) @pyqtSlot(dict) @@ -70,7 +70,6 @@ class ConsoleWidget(RichJupyterWidget,ComponentMixin): """ self._control.clear() - def print_text(self, text): """ Prints some plain text to the console @@ -82,20 +81,17 @@ class ConsoleWidget(RichJupyterWidget,ComponentMixin): Execute a command in the frame of the console widget """ self._execute(command, False) - - def _banner_default(self): - - return '' - + def _banner_default(self): + return "" + + if __name__ == "__main__": - - import sys - + app = QApplication(sys.argv) - - console = ConsoleWidget(customBanner='IPython console test') + + console = ConsoleWidget(customBanner="IPython console test") console.show() - + sys.exit(app.exec_()) diff --git a/cq_editor/widgets/cq_object_inspector.py b/cq_editor/widgets/cq_object_inspector.py index 2e30464..52029df 100644 --- a/cq_editor/widgets/cq_object_inspector.py +++ b/cq_editor/widgets/cq_object_inspector.py @@ -1,6 +1,6 @@ -from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem, QAction -from PySide6.QtCore import Qt, pyqtSlot, pyqtSignal - +from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem +from PySide6.QtCore import Qt, Slot as pyqtSlot, Signal as pyqtSignal +from PySide6.QtGui import QAction from OCP.AIS import AIS_ColoredShape from OCP.gp import gp_Ax3 @@ -10,63 +10,62 @@ from ..mixins import ComponentMixin from ..icons import icon - class CQChildItem(QTreeWidgetItem): - - def __init__(self,cq_item,**kwargs): - - super(CQChildItem,self).\ - __init__([type(cq_item).__name__,str(cq_item)],**kwargs) - + def __init__(self, cq_item, **kwargs): + super(CQChildItem, self).__init__( + [type(cq_item).__name__, str(cq_item)], **kwargs + ) + self.cq_item = cq_item + class CQStackItem(QTreeWidgetItem): - - def __init__(self,name,workplane=None,**kwargs): - - super(CQStackItem,self).__init__([name,''],**kwargs) - + def __init__(self, name, workplane=None, **kwargs): + super(CQStackItem, self).__init__([name, ""], **kwargs) + self.workplane = workplane -class CQObjectInspector(QTreeWidget,ComponentMixin): - - name = 'CQ Object Inspector' - +class CQObjectInspector(QTreeWidget, ComponentMixin): + name = "CQ Object Inspector" + sigRemoveObjects = pyqtSignal(list) - sigDisplayObjects = pyqtSignal(list,bool) - sigShowPlane = pyqtSignal([bool],[bool,float]) + sigDisplayObjects = pyqtSignal(list, bool) + sigShowPlane = pyqtSignal(bool) + sigShowPlane2 = pyqtSignal(bool, float) sigChangePlane = pyqtSignal(gp_Ax3) - - def __init__(self,parent): - - super(CQObjectInspector,self).__init__(parent) + + def __init__(self, parent): + super(CQObjectInspector, self).__init__(parent) self.setHeaderHidden(False) self.setRootIsDecorated(True) self.setContextMenuPolicy(Qt.ActionsContextMenu) self.setColumnCount(2) - self.setHeaderLabels(['Type','Value']) - + self.setHeaderLabels(["Type", "Value"]) + self.root = self.invisibleRootItem() self.inspected_items = [] - - self._toolbar_actions = \ - [QAction(icon('inspect'),'Inspect CQ object',self,\ - toggled=self.inspect,checkable=True)] - + + self._toolbar_actions = [ + QAction( + icon("inspect"), + "Inspect CQ object", + self, + toggled=self.inspect, + checkable=True, + ) + ] + self.addActions(self._toolbar_actions) - + def menuActions(self): - - return {'Tools' : self._toolbar_actions} - + return {"Tools": self._toolbar_actions} + def toolbarActions(self): - return self._toolbar_actions - + @pyqtSlot(bool) - def inspect(self,value): - + def inspect(self, value): if value: self.itemSelectionChanged.connect(self.handleSelection) self.itemSelectionChanged.emit() @@ -74,56 +73,52 @@ class CQObjectInspector(QTreeWidget,ComponentMixin): self.itemSelectionChanged.disconnect(self.handleSelection) self.sigRemoveObjects.emit(self.inspected_items) self.sigShowPlane.emit(False) - - @pyqtSlot() + + @pyqtSlot() def handleSelection(self): - inspected_items = self.inspected_items self.sigRemoveObjects.emit(inspected_items) inspected_items.clear() - + items = self.selectedItems() if len(items) == 0: return - + item = items[-1] if type(item) is CQStackItem: cq_plane = item.workplane.plane dim = item.workplane.largestDimension() - plane = gp_Ax3(cq_plane.origin.toPnt(), - cq_plane.zDir.toDir(), - cq_plane.xDir.toDir()) + plane = gp_Ax3( + cq_plane.origin.toPnt(), cq_plane.zDir.toDir(), cq_plane.xDir.toDir() + ) self.sigChangePlane.emit(plane) - self.sigShowPlane[bool,float].emit(True,dim) - + self.sigShowPlane[bool, float].emit(True, dim) + for child in (item.child(i) for i in range(item.childCount())): obj = child.cq_item - if hasattr(obj,'wrapped') and type(obj) != Vector: + if hasattr(obj, "wrapped") and type(obj) != Vector: ais = AIS_ColoredShape(obj.wrapped) inspected_items.append(ais) - + else: self.sigShowPlane.emit(False) obj = item.cq_item - if hasattr(obj,'wrapped') and type(obj) != Vector: + if hasattr(obj, "wrapped") and type(obj) != Vector: ais = AIS_ColoredShape(obj.wrapped) inspected_items.append(ais) - - self.sigDisplayObjects.emit(inspected_items,False) - + + self.sigDisplayObjects.emit(inspected_items, False) + @pyqtSlot(object) - def setObject(self,cq_obj): - + def setObject(self, cq_obj): self.root.takeChildren() - + # iterate through parent objects if they exist - while getattr(cq_obj, 'parent', None): - current_frame = CQStackItem(str(cq_obj.plane.origin),workplane=cq_obj) + while getattr(cq_obj, "parent", None): + current_frame = CQStackItem(str(cq_obj.plane.origin), workplane=cq_obj) self.root.addChild(current_frame) - + for obj in cq_obj.objects: current_frame.addChild(CQChildItem(obj)) - + cq_obj = cq_obj.parent - - diff --git a/cq_editor/widgets/debugger.py b/cq_editor/widgets/debugger.py index 4ccd5e8..c040962 100644 --- a/cq_editor/widgets/debugger.py +++ b/cq_editor/widgets/debugger.py @@ -9,12 +9,13 @@ from PySide6 import QtCore from PySide6.QtCore import ( Qt, QObject, - pyqtSlot, - pyqtSignal, + Slot as pyqtSlot, + Signal as pyqtSignal, QEventLoop, QAbstractTableModel, ) -from PySide6.QtWidgets import QAction, QTableView +from PySide6.QtWidgets import QTableView +from PySide6.QtGui import QAction from logbook import info from path import Path from pyqtgraph.parametertree import Parameter diff --git a/cq_editor/widgets/editor.py b/cq_editor/widgets/editor.py index c8bdc80..a0ca916 100644 --- a/cq_editor/widgets/editor.py +++ b/cq_editor/widgets/editor.py @@ -2,9 +2,11 @@ import os from modulefinder import ModuleFinder from spyder.plugins.editor.widgets.codeeditor import CodeEditor -from PySide6.QtCore import pyqtSignal, QFileSystemWatcher, QTimer -from PySide6.QtWidgets import QAction, QFileDialog -from PySide6.QtGui import QFontDatabase +from PySide6.QtCore import Signal as pyqtSignal +from PySide6.QtCore import QFileSystemWatcher, QTimerEvent, QTimer + +from PySide6.QtWidgets import QFileDialog +from PySide6.QtGui import QFontDatabase, QAction from path import Path import sys @@ -16,91 +18,104 @@ from ..utils import get_save_filename, get_open_filename, confirm from ..icons import icon -class Editor(CodeEditor,ComponentMixin): - name = 'Code Editor' +class Editor(CodeEditor, ComponentMixin): + name = "Code Editor" # This signal is emitted whenever the currently-open file changes and # autoreload is enabled. triggerRerender = pyqtSignal(bool) sigFilenameChanged = pyqtSignal(str) - preferences = Parameter.create(name='Preferences',children=[ - {'name': 'Font size', 'type': 'int', 'value': 12}, - {'name': 'Autoreload', 'type': 'bool', 'value': False}, - {'name': 'Autoreload delay', 'type': 'int', 'value': 50}, - {'name': 'Autoreload: watch imported modules', 'type': 'bool', 'value': False}, - {'name': 'Line wrap', 'type': 'bool', 'value': False}, - {'name': 'Color scheme', 'type': 'list', - 'values': ['Spyder','Monokai','Zenburn'], 'value': 'Spyder'}]) + preferences = Parameter.create( + name="Preferences", + children=[ + {"name": "Font size", "type": "int", "value": 12}, + {"name": "Autoreload", "type": "bool", "value": False}, + {"name": "Autoreload delay", "type": "int", "value": 50}, + { + "name": "Autoreload: watch imported modules", + "type": "bool", + "value": False, + }, + {"name": "Line wrap", "type": "bool", "value": False}, + { + "name": "Color scheme", + "type": "list", + "values": ["Spyder", "Monokai", "Zenburn"], + "value": "Spyder", + }, + ], + ) - EXTENSIONS = 'py' - - def __init__(self,parent=None): + EXTENSIONS = "py" + def __init__(self, parent=None): self._watched_file = None - super(Editor,self).__init__(parent) + super(Editor, self).__init__(parent) ComponentMixin.__init__(self) - self.setup_editor(linenumbers=True, - markers=True, - edge_line=False, - tab_mode=False, - show_blanks=True, - font=QFontDatabase.systemFont(QFontDatabase.FixedFont), - language='Python', - filename='') + self.setup_editor( + linenumbers=True, + markers=True, + edge_line=False, + tab_mode=False, + show_blanks=True, + font=QFontDatabase.systemFont(QFontDatabase.FixedFont), + language="Python", + filename="", + ) - self._actions = \ - {'File' : [QAction(icon('new'), - 'New', - self, - shortcut='ctrl+N', - triggered=self.new), - QAction(icon('open'), - 'Open', - self, - shortcut='ctrl+O', - triggered=self.open), - QAction(icon('save'), - 'Save', - self, - shortcut='ctrl+S', - triggered=self.save), - QAction(icon('save_as'), - 'Save as', - self, - shortcut='ctrl+shift+S', - triggered=self.save_as), - QAction(icon('autoreload'), - 'Automatic reload and preview', - self,triggered=self.autoreload, - checkable=True, - checked=False, - objectName='autoreload'), - ]} + self._actions = { + "File": [ + QAction( + icon("new"), "New", self, shortcut="ctrl+N", triggered=self.new + ), + QAction( + icon("open"), "Open", self, shortcut="ctrl+O", triggered=self.open + ), + QAction( + icon("save"), "Save", self, shortcut="ctrl+S", triggered=self.save + ), + QAction( + icon("save_as"), + "Save as", + self, + shortcut="ctrl+shift+S", + triggered=self.save_as, + ), + QAction( + icon("autoreload"), + "Automatic reload and preview", + self, + triggered=self.autoreload, + checkable=True, + checked=False, + objectName="autoreload", + ), + ] + } for a in self._actions.values(): self.addActions(a) - self._fixContextMenu() # autoreload support self._file_watcher = QFileSystemWatcher(self) # we wait for 50ms after a file change for the file to be written completely self._file_watch_timer = QTimer(self) - self._file_watch_timer.setInterval(self.preferences['Autoreload delay']) + self._file_watch_timer.setInterval(self.preferences["Autoreload delay"]) self._file_watch_timer.setSingleShot(True) self._file_watcher.fileChanged.connect( - lambda val: self._file_watch_timer.start()) + lambda val: self._file_watch_timer.start() + ) self._file_watch_timer.timeout.connect(self._file_changed) self.updatePreferences() def _fixContextMenu(self): - menu = self.menu menu.removeAction(self.run_cell_action) @@ -108,68 +123,66 @@ class Editor(CodeEditor,ComponentMixin): menu.removeAction(self.run_selection_action) menu.removeAction(self.re_run_last_cell_action) - def updatePreferences(self,*args): - - self.set_color_scheme(self.preferences['Color scheme']) + def updatePreferences(self, *args): + self.set_color_scheme(self.preferences["Color scheme"]) font = self.font() - font.setPointSize(self.preferences['Font size']) + font.setPointSize(self.preferences["Font size"]) self.set_font(font) - self.findChild(QAction, 'autoreload') \ - .setChecked(self.preferences['Autoreload']) + self.findChild(QAction, "autoreload").setChecked(self.preferences["Autoreload"]) - self._file_watch_timer.setInterval(self.preferences['Autoreload delay']) + self._file_watch_timer.setInterval(self.preferences["Autoreload delay"]) - self.toggle_wrap_mode(self.preferences['Line wrap']) + self.toggle_wrap_mode(self.preferences["Line wrap"]) self._clear_watched_paths() self._watch_paths() def confirm_discard(self): - if self.modified: - rv = confirm(self,'Please confirm','Current document is not saved - do you want to continue?') + rv = confirm( + self, + "Please confirm", + "Current document is not saved - do you want to continue?", + ) else: rv = True return rv def new(self): + if not self.confirm_discard(): + return - if not self.confirm_discard(): return - - self.set_text('') - self.filename = '' + self.set_text("") + self.filename = "" self.reset_modified() def open(self): - - if not self.confirm_discard(): return + if not self.confirm_discard(): + return curr_dir = Path(self.filename).abspath().dirname() fname = get_open_filename(self.EXTENSIONS, curr_dir) - if fname != '': + if fname != "": self.load_from_file(fname) - def load_from_file(self,fname): - + def load_from_file(self, fname): self.set_text_from_file(fname) self.filename = fname self.reset_modified() def save(self): - - if self._filename != '': - - if self.preferences['Autoreload']: + if self._filename != "": + if self.preferences["Autoreload"]: self._file_watcher.blockSignals(True) self._file_watch_timer.stop() - with open(self._filename, 'w') as f: + with open(self._filename, "w") as f: f.write(self.toPlainText()) - if self.preferences['Autoreload']: + if self.preferences["Autoreload"]: self._file_watcher.blockSignals(False) self.triggerRerender.emit(True) @@ -179,20 +192,25 @@ class Editor(CodeEditor,ComponentMixin): self.save_as() def save_as(self): - fname = get_save_filename(self.EXTENSIONS) - if fname != '': - with open(fname,'w') as f: + if fname != "": + with open(fname, "w") as f: f.write(self.toPlainText()) self.filename = fname self.reset_modified() def _update_filewatcher(self): - if self._watched_file and (self._watched_file != self.filename or not self.preferences['Autoreload']): + if self._watched_file and ( + self._watched_file != self.filename or not self.preferences["Autoreload"] + ): self._clear_watched_paths() self._watched_file = None - if self.preferences['Autoreload'] and self.filename and self.filename != self._watched_file: + if ( + self.preferences["Autoreload"] + and self.filename + and self.filename != self._watched_file + ): self._watched_file = self._filename self._watch_paths() @@ -214,8 +232,8 @@ class Editor(CodeEditor,ComponentMixin): def _watch_paths(self): if Path(self._filename).exists(): self._file_watcher.addPath(self._filename) - if self.preferences['Autoreload: watch imported modules']: - module_paths = self.get_imported_module_paths(self._filename) + if self.preferences["Autoreload: watch imported modules"]: + module_paths = self.get_imported_module_paths(self._filename) if module_paths: self._file_watcher.addPaths(module_paths) @@ -228,51 +246,45 @@ class Editor(CodeEditor,ComponentMixin): # Turn autoreload on/off. def autoreload(self, enabled): - self.preferences['Autoreload'] = enabled + self.preferences["Autoreload"] = enabled self._update_filewatcher() def reset_modified(self): - self.document().setModified(False) - + @property def modified(self): - return self.document().isModified() - def saveComponentState(self,store): + def saveComponentState(self, store): + if self.filename != "": + store.setValue(self.name + "/state", self.filename) - if self.filename != '': - store.setValue(self.name+'/state',self.filename) + def restoreComponentState(self, store): + filename = store.value(self.name + "/state") - def restoreComponentState(self,store): - - filename = store.value(self.name+'/state') - - if filename and self.filename == '': + if filename and self.filename == "": try: self.load_from_file(filename) except IOError: - self._logger.warning(f'could not open {filename}') - + self._logger.warning(f"could not open {filename}") def get_imported_module_paths(self, module_path): - finder = ModuleFinder([os.path.dirname(module_path)]) imported_modules = [] try: finder.run_script(module_path) except SyntaxError as err: - self._logger.warning(f'Syntax error in {module_path}: {err}') + self._logger.warning(f"Syntax error in {module_path}: {err}") except Exception as err: self._logger.warning( - f'Cannot determine imported modules in {module_path}: {type(err).__name__} {err}' + f"Cannot determine imported modules in {module_path}: {type(err).__name__} {err}" ) else: for module_name, module in finder.modules.items(): - if module_name != '__main__': - path = getattr(module, '__file__', None) + if module_name != "__main__": + path = getattr(module, "__file__", None) if path is not None and os.path.isfile(path): imported_modules.append(path) @@ -280,7 +292,6 @@ class Editor(CodeEditor,ComponentMixin): if __name__ == "__main__": - from PySide6.QtWidgets import QApplication app = QApplication(sys.argv) diff --git a/cq_editor/widgets/object_tree.py b/cq_editor/widgets/object_tree.py index d0aa78b..3ebb20c 100644 --- a/cq_editor/widgets/object_tree.py +++ b/cq_editor/widgets/object_tree.py @@ -1,12 +1,12 @@ from PySide6.QtWidgets import ( QTreeWidget, QTreeWidgetItem, - QAction, QMenu, QWidget, QAbstractItemView, ) -from PySide6.QtCore import Qt, pyqtSlot, pyqtSignal +from PySide6.QtGui import QAction +from PySide6.QtCore import Qt, Slot as pyqtSlot, Signal as pyqtSignal from pyqtgraph.parametertree import Parameter, ParameterTree from OCP.AIS import AIS_Line @@ -114,7 +114,8 @@ class ObjectTree(QWidget, ComponentMixin): ], ) - sigObjectsAdded = pyqtSignal([list], [list, bool]) + sigObjectsAdded = pyqtSignal(list) + sigObjectsAdded2 = pyqtSignal(list, bool) sigObjectsRemoved = pyqtSignal(list) sigCQObjectSelected = pyqtSignal(object) sigAISObjectsSelected = pyqtSignal(list) diff --git a/cq_editor/widgets/occt_widget.py b/cq_editor/widgets/occt_widget.py index 825dc0b..fe21bd0 100644 --- a/cq_editor/widgets/occt_widget.py +++ b/cq_editor/widgets/occt_widget.py @@ -2,7 +2,7 @@ from sys import platform from PySide6.QtWidgets import QWidget, QApplication -from PySide6.QtCore import pyqtSlot, pyqtSignal, Qt, QEvent +from PySide6.QtCore import Slot as pyqtSlot, Signal as pyqtSignal, Qt, QEvent import OCP @@ -15,161 +15,144 @@ from OCP.Quantity import Quantity_Color, Quantity_TOC_RGB as TOC_RGB ZOOM_STEP = 0.9 - + class OCCTWidget(QWidget): - sigObjectSelected = pyqtSignal(list) - - def __init__(self,parent=None): - - super(OCCTWidget,self).__init__(parent) - + + def __init__(self, parent=None): + super(OCCTWidget, self).__init__(parent) + self.setAttribute(Qt.WA_NativeWindow) self.setAttribute(Qt.WA_PaintOnScreen) self.setAttribute(Qt.WA_NoSystemBackground) - + self._initialized = False self._needs_update = False - - #OCCT secific things + + # OCCT secific things self.display_connection = Aspect_DisplayConnection() self.graphics_driver = OpenGl_GraphicDriver(self.display_connection) - + self.viewer = V3d_Viewer(self.graphics_driver) self.view = self.viewer.CreateView() self.context = AIS_InteractiveContext(self.viewer) - - #Trihedorn, lights, etc + + # Trihedorn, lights, etc self.prepare_display() - + def prepare_display(self): - view = self.view - + params = view.ChangeRenderingParams() params.NbMsaaSamples = 8 params.IsAntialiasingEnabled = True - + view.TriedronDisplay( - Aspect_TypeOfTriedronPosition.Aspect_TOTP_RIGHT_LOWER, - Quantity_Color(), 0.1) - - view.ZBufferTriedronSetup(Quantity_Color(*(0.2, 0.0, 0.0), TOC_RGB)) + Aspect_TypeOfTriedronPosition.Aspect_TOTP_RIGHT_LOWER, Quantity_Color(), 0.1 + ) + + view.ZBufferTriedronSetup(Quantity_Color(*(0.2, 0.0, 0.0), TOC_RGB)) viewer = self.viewer - + viewer.SetDefaultLights() viewer.SetLightOn() - + ctx = self.context - + ctx.SetDisplayMode(AIS_DisplayMode.AIS_Shaded, True) ctx.DefaultDrawer().SetFaceBoundaryDraw(True) - + def wheelEvent(self, event): - delta = event.angleDelta().y() - factor = ZOOM_STEP if delta<0 else 1/ZOOM_STEP - + factor = ZOOM_STEP if delta < 0 else 1 / ZOOM_STEP + 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()) elif event.button() == Qt.RightButton: self.view.StartZoomAtPoint(pos.x(), pos.y()) - + self.old_pos = pos - - def mouseMoveEvent(self,event): - + + 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) - + 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) - + self.view.Pan(x - self.old_pos.x(), self.old_pos.y() - y, theToStart=True) + elif event.buttons() == Qt.RightButton: - self.view.Pan(x - self.old_pos.x(), - self.old_pos.y() - y, theToStart=True) - + self.view.Pan(x - self.old_pos.x(), self.old_pos.y() - y, theToStart=True) + 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) - + x, y = pos.x(), pos.y() + + self.context.MoveTo(x, y, self.view, True) + self._handle_selection() - + def _handle_selection(self): - self.context.Select(True) self.context.InitSelected() - + selected = [] if self.context.HasSelectedShape(): selected.append(self.context.SelectedShape()) - + self.sigObjectSelected.emit(selected) def paintEngine(self): - return None - + def paintEvent(self, event): - if not self._initialized: self._initialize() else: self.view.Redraw() def showEvent(self, event): - - super(OCCTWidget,self).showEvent(event) - - def resizeEvent(self, event): - - super(OCCTWidget,self).resizeEvent(event) - - self.view.MustBeResized() - - def _initialize(self): + super(OCCTWidget, self).showEvent(event) + def resizeEvent(self, event): + super(OCCTWidget, self).resizeEvent(event) + + self.view.MustBeResized() + + def _initialize(self): wins = { - 'darwin' : self._get_window_osx, - 'linux' : self._get_window_linux, - 'win32': self._get_window_win + "darwin": self._get_window_osx, + "linux": self._get_window_linux, + "win32": self._get_window_win, } - self.view.SetWindow(wins.get(platform,self._get_window_linux)(self.winId())) + self.view.SetWindow(wins.get(platform, self._get_window_linux)(self.winId())) self._initialized = True - - def _get_window_win(self,wid): - + + def _get_window_win(self, wid): from OCP.WNT import WNT_Window - + + print(wid) return WNT_Window(wid.ascapsule()) - def _get_window_linux(self,wid): - + def _get_window_linux(self, wid): from OCP.Xw import Xw_Window - - return Xw_Window(self.display_connection,int(wid)) - - def _get_window_osx(self,wid): - + + return Xw_Window(self.display_connection, int(wid)) + + def _get_window_osx(self, wid): from OCP.Cocoa import Cocoa_Window - + return Cocoa_Window(wid.ascapsule()) diff --git a/cq_editor/widgets/traceback_viewer.py b/cq_editor/widgets/traceback_viewer.py index e675af5..a8c04b3 100644 --- a/cq_editor/widgets/traceback_viewer.py +++ b/cq_editor/widgets/traceback_viewer.py @@ -1,97 +1,89 @@ from traceback import extract_tb, format_exception_only -from PySide6.QtWidgets import (QWidget, QTreeWidget, QTreeWidgetItem, QAction, - QLabel) -from PySide6.QtCore import Qt, pyqtSlot, pyqtSignal +from PySide6.QtWidgets import QWidget, QTreeWidget, QTreeWidgetItem, QLabel +from PySide6.QtGui import QAction +from PySide6.QtCore import Qt, Slot as pyqtSlot, Signal as pyqtSignal from ..mixins import ComponentMixin from ..utils import layout -class TracebackTree(QTreeWidget): - name = 'Traceback Viewer' - - def __init__(self,parent): - - super(TracebackTree,self).__init__(parent) +class TracebackTree(QTreeWidget): + name = "Traceback Viewer" + + def __init__(self, parent): + super(TracebackTree, self).__init__(parent) self.setHeaderHidden(False) self.setItemsExpandable(False) self.setRootIsDecorated(False) self.setContextMenuPolicy(Qt.ActionsContextMenu) - + self.setColumnCount(3) - self.setHeaderLabels(['File','Line','Code']) - - + self.setHeaderLabels(["File", "Line", "Code"]) + self.root = self.invisibleRootItem() -class TracebackPane(QWidget,ComponentMixin): - + +class TracebackPane(QWidget, ComponentMixin): sigHighlightLine = pyqtSignal(int) - - def __init__(self,parent): - - super(TracebackPane,self).__init__(parent) - + + def __init__(self, parent): + super(TracebackPane, self).__init__(parent) + self.tree = TracebackTree(self) self.current_exception = QLabel(self) - self.current_exception.setStyleSheet(\ - "QLabel {color : red; }"); - - layout(self, - (self.current_exception, - self.tree), - self) - + self.current_exception.setStyleSheet("QLabel {color : red; }") + + layout(self, (self.current_exception, self.tree), self) + self.tree.currentItemChanged.connect(self.handleSelection) - - @pyqtSlot(object,str) - def addTraceback(self,exc_info,code): - + + @pyqtSlot(object, str) + def addTraceback(self, exc_info, code): self.tree.clear() - + if exc_info: - t,exc,tb = exc_info - + t, exc, tb = exc_info + root = self.tree.root code = code.splitlines() - tb = [t for t in extract_tb(tb) if '' in t.filename] #ignore highest frames (debug, exec) - + tb = [ + t for t in extract_tb(tb) if "" in t.filename + ] # ignore highest frames (debug, exec) + for el in tb: - #workaround of the traceback module - if el.line == '': - line = code[el.lineno-1].strip() + # workaround of the traceback module + if el.line == "": + line = code[el.lineno - 1].strip() else: line = el.line - - root.addChild(QTreeWidgetItem([el.filename, - str(el.lineno), - line])) + + root.addChild(QTreeWidgetItem([el.filename, str(el.lineno), line])) exc_name = t.__name__ exc_msg = str(exc) - exc_msg = exc_msg.replace('<', '<').replace('>', '>') #replace <> + exc_msg = exc_msg.replace("<", "<").replace(">", ">") # replace <> + + self.current_exception.setText("{}: {}".format(exc_name, exc_msg)) - self.current_exception.\ - setText('{}: {}'.format(exc_name,exc_msg)) - # handle the special case of a SyntaxError - if t is SyntaxError: - root.addChild(QTreeWidgetItem( - [exc.filename, - str(exc.lineno), - exc.text.strip() if exc.text else ''] - )) + if t is SyntaxError: + root.addChild( + QTreeWidgetItem( + [ + exc.filename, + str(exc.lineno), + exc.text.strip() if exc.text else "", + ] + ) + ) else: - self.current_exception.setText('') + self.current_exception.setText("") - @pyqtSlot(QTreeWidgetItem,QTreeWidgetItem) - def handleSelection(self,item,*args): - + @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) + def handleSelection(self, item, *args): if item: - f,line = item.data(0,0),int(item.data(1,0)) - - if '' in f: + f, line = item.data(0, 0), int(item.data(1, 0)) + + if "" in f: self.sigHighlightLine.emit(line) - - diff --git a/cq_editor/widgets/viewer.py b/cq_editor/widgets/viewer.py index f3e4b51..35c1968 100644 --- a/cq_editor/widgets/viewer.py +++ b/cq_editor/widgets/viewer.py @@ -1,14 +1,21 @@ -from PySide6.QtWidgets import QWidget, QDialog, QTreeWidgetItem, QApplication, QAction +from PySide6.QtWidgets import QWidget, QDialog, QTreeWidgetItem, QApplication -from PySide6.QtCore import pyqtSlot, pyqtSignal -from PySide6.QtGui import QIcon +from PySide6.QtCore import Slot as pyqtSlot, Signal as pyqtSignal +from PySide6.QtGui import QIcon, QAction -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.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.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 @@ -25,30 +32,66 @@ import qtawesome as qta DEFAULT_EDGE_COLOR = Quantity_Color(BLACK) DEFAULT_EDGE_WIDTH = 2 -class OCCViewer(QWidget,ComponentMixin): - name = '3D Viewer' +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' + 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) + def __init__(self, parent=None): + super(OCCViewer, self).__init__(parent) ComponentMixin.__init__(self) self.canvas = OCCTWidget() @@ -56,15 +99,18 @@ class OCCViewer(QWidget,ComponentMixin): self.create_actions(self) - self.layout_ = layout(self, - [self.canvas,], - top_widget=self, - margin=0) - self.setup_default_drawer() #misspelled in original + 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): + def setup_default_drawer(self): # set the default color and material material = Graphic3d_MaterialAspect(Graphic3d_NOM_JADE) @@ -76,99 +122,129 @@ class OCCViewer(QWidget,ComponentMixin): 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)']) + 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']: + if not self.preferences["Use gradient"]: color2 = color1 - self.canvas.view.SetBgGradientColors(color1,color2,theToUpdate=True) - + 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']) + 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)) + 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'] + stereo_mode = self.preferences["Stereo Mode"] params = v.ChangeRenderingParams() - params.StereoMode = getattr(Graphic3d_StereoMode, f'Graphic3d_StereoMode_{stereo_mode}', - Graphic3d_StereoMode.Graphic3d_StereoMode_QuadBuffer) + 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 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'] - + return self._actions["View"] def clear(self): - self.displayed_shapes = [] self.displayed_ais = [] self.canvas.context.EraseAll(True) @@ -176,199 +252,167 @@ class OCCViewer(QWidget,ComponentMixin): context.PurgeDisplay() context.RemoveAll(True) - def _display(self,shape): - + def _display(self, shape): ais = make_AIS(shape) - self.canvas.context.Display(shape,True) + self.canvas.context.Display(shape, True) self.displayed_shapes.append(shape) self.displayed_ais.append(ais) - #self.canvas._display.Repaint() + # self.canvas._display.Repaint() @pyqtSlot(object) - def display(self,ais): - + def display(self, ais): context = self._get_context() - context.Display(ais,True) + context.Display(ais, True) - if self.preferences['Fit automatically']: self.fit() + if self.preferences["Fit automatically"]: + self.fit() @pyqtSlot(list) - @pyqtSlot(list,bool) - def display_many(self,ais_list,fit=None): - + @pyqtSlot(list, bool) + def display_many(self, ais_list, fit=None): context = self._get_context() for ais in ais_list: - context.Display(ais,True) + context.Display(ais, True) - if self.preferences['Fit automatically'] and fit is None: + if self.preferences["Fit automatically"] and fit is None: self.fit() elif fit: self.fit() - @pyqtSlot(QTreeWidgetItem,int) - def update_item(self,item,col): - + @pyqtSlot(QTreeWidgetItem, int) + def update_item(self, item, col): ctx = self._get_context() if item.checkState(0): - ctx.Display(item.ais,True) + ctx.Display(item.ais, True) else: - ctx.Erase(item.ais,True) + ctx.Erase(item.ais, True) @pyqtSlot(list) - def remove_items(self,ais_items): - + def remove_items(self, ais_items): ctx = self._get_context() - for ais in ais_items: ctx.Erase(ais,True) + 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.SetProj(1, -1, 1) v.SetTwist(0) def bottom_view(self): - v = self._get_view() - v.SetProj(0,0,-1) + v.SetProj(0, 0, -1) v.SetTwist(0) def top_view(self): - v = self._get_view() - v.SetProj(0,0,1) + v.SetProj(0, 0, 1) v.SetTwist(0) def front_view(self): - v = self._get_view() - v.SetProj(0,-1,0) + v.SetProj(0, -1, 0) v.SetTwist(0) def back_view(self): - v = self._get_view() - v.SetProj(0,1,0) + v.SetProj(0, 1, 0) v.SetTwist(0) def left_view(self): - v = self._get_view() - v.SetProj(-1,0,0) + v.SetProj(-1, 0, 0) v.SetTwist(0) def right_view(self): - v = self._get_view() - v.SetProj(1,0,0) + 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)): - + def show_grid( + self, step=1.0, size=10.0 + 1e-6, color1=(0.7, 0.7, 0.7), color2=(0, 0, 0) + ): viewer = self._get_viewer() - viewer.ActivateGrid(Aspect_GT_Rectangular, - Aspect_GDM_Lines) + 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)) + 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, float) @pyqtSlot(bool) - def toggle_grid(self, - value : bool, - dim : float = 10.): - + def toggle_grid(self, value: bool, dim: float = 10.0): if value: - self.show_grid(step=dim/20,size=dim+1e-9) + 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): - + 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))) + 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): + 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): - + def handle_selection(self, obj): self.sigObjectSelected.emit(obj) @pyqtSlot(list) - def set_selected(self,ais): - + def set_selected(self, ais): ctx = self._get_context() ctx.ClearSelected(False) for obj in ais: - ctx.AddOrRemoveSelected(obj,False) + ctx.AddOrRemoveSelected(obj, False) self.redraw() if __name__ == "__main__": - + # pass import sys from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox @@ -379,10 +423,10 @@ if __name__ == "__main__": dlg.setFixedHeight(400) dlg.setFixedWidth(600) - layout(dlg,(viewer,),dlg) + layout(dlg, (viewer,), dlg) dlg.show() - box = BRepPrimAPI_MakeBox(20,20,30) + box = BRepPrimAPI_MakeBox(20, 20, 30) box_ais = AIS_ColoredShape(box.Shape()) viewer.display(box_ais)