From fff414d3b67d0e3c5da662f4b418fa3329706ec4 Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Fri, 14 Feb 2025 09:18:26 -0500 Subject: [PATCH] Lint check on source code --- bundle.py | 22 +- collect_icons.py | 27 +- cq_editor/__main__.py | 10 +- cq_editor/cq_utils.py | 101 +- cq_editor/cqe_run.py | 10 +- cq_editor/icons.py | 96 +- cq_editor/main_window.py | 455 +++++---- cq_editor/mixins.py | 58 +- cq_editor/preferences.py | 124 +-- cq_editor/utils.py | 203 ++-- cq_editor/widgets/console.py | 39 +- cq_editor/widgets/cq_object_inspector.py | 129 +-- cq_editor/widgets/debugger.py | 302 +++--- cq_editor/widgets/editor.py | 214 ++-- cq_editor/widgets/log.py | 49 +- cq_editor/widgets/object_tree.py | 332 +++--- cq_editor/widgets/occt_widget.py | 162 ++- cq_editor/widgets/traceback_viewer.py | 118 +-- cq_editor/widgets/viewer.py | 380 ++++--- pyinstaller/pyi_rth_fontconfig.py | 6 +- pyinstaller/pyi_rth_occ.py | 8 +- run.py | 8 +- setup.py | 27 +- tests/test_app.py | 1176 ++++++++++++---------- 24 files changed, 2211 insertions(+), 1845 deletions(-) diff --git a/bundle.py b/bundle.py index 6cfaf381..abb6aeb3 100644 --- a/bundle.py +++ b/bundle.py @@ -4,21 +4,21 @@ from shutil import make_archive from cq_editor import __version__ as version -out_p = Path('dist/CQ-editor') +out_p = Path("dist/CQ-editor") out_p.rmtree_p() -build_p = Path('build') +build_p = Path("build") build_p.rmtree_p() system("pyinstaller pyinstaller.spec") -if platform == 'linux': +if platform == "linux": with out_p: - p = Path('.').glob('libpython*')[0] - p.symlink(p.split(".so")[0]+".so") - - make_archive(f'CQ-editor-{version}-linux64','bztar', out_p / '..', 'CQ-editor') - -elif platform == 'win32': - - make_archive(f'CQ-editor-{version}-win64','zip', out_p / '..', 'CQ-editor') + p = Path(".").glob("libpython*")[0] + p.symlink(p.split(".so")[0] + ".so") + + make_archive(f"CQ-editor-{version}-linux64", "bztar", out_p / "..", "CQ-editor") + +elif platform == "win32": + + make_archive(f"CQ-editor-{version}-win64", "zip", out_p / "..", "CQ-editor") diff --git a/collect_icons.py b/collect_icons.py index e252236b..3f8b6b20 100644 --- a/collect_icons.py +++ b/collect_icons.py @@ -2,29 +2,28 @@ from subprocess import call from os import remove -TEMPLATE = \ -''' +TEMPLATE = """ {} -''' +""" -ITEM_TEMPLATE = '{}' +ITEM_TEMPLATE = "{}" -QRC_OUT = 'icons.qrc' -RES_OUT = 'src/icons_res.py' -TOOL = 'pyrcc5' +QRC_OUT = "icons.qrc" +RES_OUT = "src/icons_res.py" +TOOL = "pyrcc5" items = [] -for i in glob('icons/*.svg'): +for i in glob("icons/*.svg"): items.append(ITEM_TEMPLATE.format(i)) - - -qrc_text = TEMPLATE.format('\n'.join(items)) -with open(QRC_OUT,'w') as f: + +qrc_text = TEMPLATE.format("\n".join(items)) + +with open(QRC_OUT, "w") as f: f.write(qrc_text) - -call([TOOL,QRC_OUT,'-o',RES_OUT]) + +call([TOOL, QRC_OUT, "-o", RES_OUT]) remove(QRC_OUT) diff --git a/cq_editor/__main__.py b/cq_editor/__main__.py index 0fc8f700..2298ea56 100644 --- a/cq_editor/__main__.py +++ b/cq_editor/__main__.py @@ -3,18 +3,18 @@ from PyQt5.QtWidgets import QApplication -NAME = 'CQ-editor' +NAME = "CQ-editor" -#need to initialize QApp here, otherewise svg icons do not work on windows -app = QApplication(sys.argv, - applicationName=NAME) +# need to initialize QApp here, otherewise svg icons do not work on windows +app = QApplication(sys.argv, applicationName=NAME) from .main_window import MainWindow + def main(): parser = argparse.ArgumentParser(description=NAME) - parser.add_argument('filename',nargs='?',default=None) + parser.add_argument("filename", nargs="?", default=None) args = parser.parse_args(app.arguments()[1:]) diff --git a/cq_editor/cq_utils.py b/cq_editor/cq_utils.py index 2378a82c..88c64483 100644 --- a/cq_editor/cq_utils.py +++ b/cq_editor/cq_utils.py @@ -8,8 +8,11 @@ from OCP.XCAFPrs import XCAFPrs_AISObject from OCP.TopoDS import TopoDS_Shape from OCP.AIS import AIS_InteractiveObject, AIS_Shape -from OCP.Quantity import \ - Quantity_TOC_RGB as TOC_RGB, Quantity_Color, Quantity_NOC_GOLD as GOLD +from OCP.Quantity import ( + Quantity_TOC_RGB as TOC_RGB, + Quantity_Color, + Quantity_NOC_GOLD as GOLD, +) from OCP.Graphic3d import Graphic3d_NOM_JADE, Graphic3d_MaterialAspect from PyQt5.QtGui import QColor @@ -25,26 +28,33 @@ def is_cq_obj(obj): return isinstance(obj, (Workplane, Shape, Assembly, Sketch)) -def find_cq_objects(results : dict): +def find_cq_objects(results: dict): - return {k:SimpleNamespace(shape=v,options={}) for k,v in results.items() if is_cq_obj(v)} + return { + k: SimpleNamespace(shape=v, options={}) + for k, v in results.items() + if is_cq_obj(v) + } -def to_compound(obj : Union[cq.Workplane, List[cq.Workplane], cq.Shape, List[cq.Shape], cq.Sketch]): +def to_compound( + obj: Union[cq.Workplane, List[cq.Workplane], cq.Shape, List[cq.Shape], cq.Sketch], +): vals = [] - if isinstance(obj,cq.Workplane): + if isinstance(obj, cq.Workplane): vals.extend(obj.vals()) - elif isinstance(obj,cq.Shape): + elif isinstance(obj, cq.Shape): vals.append(obj) - elif isinstance(obj,list) and isinstance(obj[0],cq.Workplane): - for o in obj: vals.extend(o.vals()) - elif isinstance(obj,list) and isinstance(obj[0],cq.Shape): + elif isinstance(obj, list) and isinstance(obj[0], cq.Workplane): + for o in obj: + vals.extend(o.vals()) + elif isinstance(obj, list) and isinstance(obj[0], cq.Shape): vals.extend(obj) elif isinstance(obj, TopoDS_Shape): vals.append(cq.Shape.cast(obj)) - elif isinstance(obj,list) and isinstance(obj[0],TopoDS_Shape): + elif isinstance(obj, list) and isinstance(obj[0], TopoDS_Shape): vals.extend(cq.Shape.cast(o) for o in obj) elif isinstance(obj, cq.Sketch): if obj._faces: @@ -52,21 +62,32 @@ def to_compound(obj : Union[cq.Workplane, List[cq.Workplane], cq.Shape, List[cq. else: vals.extend(obj._edges) else: - raise ValueError(f'Invalid type {type(obj)}') + raise ValueError(f"Invalid type {type(obj)}") return cq.Compound.makeCompound(vals) -def to_workplane(obj : cq.Shape): +def to_workplane(obj: cq.Shape): - rv = cq.Workplane('XY') - rv.objects = [obj,] + rv = cq.Workplane("XY") + rv.objects = [ + obj, + ] return rv -def make_AIS(obj : Union[cq.Workplane, List[cq.Workplane], cq.Shape, List[cq.Shape], cq.Assembly, AIS_InteractiveObject], - options={}): +def make_AIS( + obj: Union[ + cq.Workplane, + List[cq.Workplane], + cq.Shape, + List[cq.Shape], + cq.Assembly, + AIS_InteractiveObject, + ], + options={}, +): shape = None @@ -82,28 +103,29 @@ def make_AIS(obj : Union[cq.Workplane, List[cq.Workplane], cq.Shape, List[cq.Sha set_material(ais, DEFAULT_MATERIAL) set_color(ais, DEFAULT_FACE_COLOR) - if 'alpha' in options: - set_transparency(ais, options['alpha']) - if 'color' in options: - set_color(ais, to_occ_color(options['color'])) - if 'rgba' in options: - r,g,b,a = options['rgba'] - set_color(ais, to_occ_color((r,g,b))) + if "alpha" in options: + set_transparency(ais, options["alpha"]) + if "color" in options: + set_color(ais, to_occ_color(options["color"])) + if "rgba" in options: + r, g, b, a = options["rgba"] + set_color(ais, to_occ_color((r, g, b))) set_transparency(ais, a) - return ais,shape + return ais, shape -def export(obj : Union[cq.Workplane, List[cq.Workplane]], type : str, - file, precision=1e-1): +def export( + obj: Union[cq.Workplane, List[cq.Workplane]], type: str, file, precision=1e-1 +): comp = to_compound(obj) - if type == 'stl': + if type == "stl": comp.exportStl(file, tolerance=precision) - elif type == 'step': + elif type == "step": comp.exportStep(file) - elif type == 'brep': + elif type == "brep": comp.exportBrep(file) @@ -116,17 +138,14 @@ def to_occ_color(color) -> Quantity_Color: elif isinstance(color[0], float): color = QColor.fromRgbF(*color) else: - raise ValueError('Unknown color format') + raise ValueError("Unknown color format") else: color = QColor(color) - return Quantity_Color(color.redF(), - color.greenF(), - color.blueF(), - TOC_RGB) + return Quantity_Color(color.redF(), color.greenF(), color.blueF(), TOC_RGB) -def get_occ_color(obj : Union[AIS_InteractiveObject, Quantity_Color]) -> QColor: +def get_occ_color(obj: Union[AIS_InteractiveObject, Quantity_Color]) -> QColor: if isinstance(obj, AIS_InteractiveObject): color = Quantity_Color() @@ -137,7 +156,7 @@ def get_occ_color(obj : Union[AIS_InteractiveObject, Quantity_Color]) -> QColor: return QColor.fromRgbF(color.Red(), color.Green(), color.Blue()) -def set_color(ais : AIS_Shape, color : Quantity_Color) -> AIS_Shape: +def set_color(ais: AIS_Shape, color: Quantity_Color) -> AIS_Shape: drawer = ais.Attributes() drawer.SetupOwnShadingAspect() @@ -146,7 +165,7 @@ def set_color(ais : AIS_Shape, color : Quantity_Color) -> AIS_Shape: return ais -def set_material(ais : AIS_Shape, material: Graphic3d_MaterialAspect) -> AIS_Shape: +def set_material(ais: AIS_Shape, material: Graphic3d_MaterialAspect) -> AIS_Shape: drawer = ais.Attributes() drawer.SetupOwnShadingAspect() @@ -155,7 +174,7 @@ def set_material(ais : AIS_Shape, material: Graphic3d_MaterialAspect) -> AIS_Sha return ais -def set_transparency(ais : AIS_Shape, alpha: float) -> AIS_Shape: +def set_transparency(ais: AIS_Shape, alpha: float) -> AIS_Shape: drawer = ais.Attributes() drawer.SetupOwnShadingAspect() @@ -184,13 +203,13 @@ def reload_cq(): reload(cq.occ_impl.exporters.dxf) reload(cq.occ_impl.exporters.amf) reload(cq.occ_impl.exporters.json) - #reload(cq.occ_impl.exporters.assembly) + # reload(cq.occ_impl.exporters.assembly) reload(cq.occ_impl.exporters) reload(cq.assembly) reload(cq) -def is_obj_empty(obj : Union[cq.Workplane,cq.Shape]) -> bool: +def is_obj_empty(obj: Union[cq.Workplane, cq.Shape]) -> bool: rv = False diff --git a/cq_editor/cqe_run.py b/cq_editor/cqe_run.py index bbd79682..038f7d31 100644 --- a/cq_editor/cqe_run.py +++ b/cq_editor/cqe_run.py @@ -1,13 +1,13 @@ import os, sys, asyncio -if 'CASROOT' in os.environ: - del os.environ['CASROOT'] +if "CASROOT" in os.environ: + del os.environ["CASROOT"] -if sys.platform == 'win32': +if sys.platform == "win32": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) from cq_editor.__main__ import main -if __name__ == '__main__': - main() \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/cq_editor/icons.py b/cq_editor/icons.py index 572e4a01..4679eaa5 100644 --- a/cq_editor/icons.py +++ b/cq_editor/icons.py @@ -9,52 +9,76 @@ from PyQt5.QtGui import QIcon from . import icons_res -_icons = { - 'app' : QIcon(":/images/icons/cadquery_logo_dark.svg") - } + +_icons = {"app": QIcon(":/images/icons/cadquery_logo_dark.svg")} import qtawesome as qta _icons_specs = { - 'new' : (('fa.file-o',),{}), - 'open' : (('fa.folder-open-o',),{}), + "new": (("fa.file-o",), {}), + "open": (("fa.folder-open-o",), {}), # borrowed from spider-ide - 'autoreload': [('fa.repeat', 'fa.clock-o'), {'options': [{'scale_factor': 0.75, 'offset': (-0.1, -0.1)}, {'scale_factor': 0.5, 'offset': (0.25, 0.25)}]}], - 'save' : (('fa.save',),{}), - 'save_as': (('fa.save','fa.pencil'), - {'options':[{'scale_factor': 1,}, - {'scale_factor': 0.8, - 'offset': (0.2, 0.2)}]}), - 'run' : (('fa.play',),{}), - 'delete' : (('fa.trash',),{}), - 'delete-many' : (('fa.trash','fa.trash',), - {'options' : \ - [{'scale_factor': 0.8, - 'offset': (0.2, 0.2), - 'color': 'gray'}, - {'scale_factor': 0.8}]}), - 'help' : (('fa.life-ring',),{}), - 'about': (('fa.info',),{}), - 'preferences' : (('fa.cogs',),{}), - 'inspect' : (('fa.cubes','fa.search'), - {'options' : \ - [{'scale_factor': 0.8, - 'offset': (0,0), - 'color': 'gray'},{}]}), - 'screenshot' : (('fa.camera',),{}), - 'screenshot-save' : (('fa.save','fa.camera'), - {'options' : \ - [{'scale_factor': 0.8}, - {'scale_factor': 0.8, - 'offset': (.2,.2)}]}), - 'toggle-comment' : (('fa.hashtag',),{}), + "autoreload": [ + ("fa.repeat", "fa.clock-o"), + { + "options": [ + {"scale_factor": 0.75, "offset": (-0.1, -0.1)}, + {"scale_factor": 0.5, "offset": (0.25, 0.25)}, + ] + }, + ], + "save": (("fa.save",), {}), + "save_as": ( + ("fa.save", "fa.pencil"), + { + "options": [ + { + "scale_factor": 1, + }, + {"scale_factor": 0.8, "offset": (0.2, 0.2)}, + ] + }, + ), + "run": (("fa.play",), {}), + "delete": (("fa.trash",), {}), + "delete-many": ( + ( + "fa.trash", + "fa.trash", + ), + { + "options": [ + {"scale_factor": 0.8, "offset": (0.2, 0.2), "color": "gray"}, + {"scale_factor": 0.8}, + ] + }, + ), + "help": (("fa.life-ring",), {}), + "about": (("fa.info",), {}), + "preferences": (("fa.cogs",), {}), + "inspect": ( + ("fa.cubes", "fa.search"), + {"options": [{"scale_factor": 0.8, "offset": (0, 0), "color": "gray"}, {}]}, + ), + "screenshot": (("fa.camera",), {}), + "screenshot-save": ( + ("fa.save", "fa.camera"), + { + "options": [ + {"scale_factor": 0.8}, + {"scale_factor": 0.8, "offset": (0.2, 0.2)}, + ] + }, + ), + "toggle-comment": (("fa.hashtag",), {}), } + def icon(name): if name in _icons: return _icons[name] - args,kwargs = _icons_specs[name] + args, kwargs = _icons_specs[name] - return qta.icon(*args,**kwargs) \ No newline at end of file + return qta.icon(*args, **kwargs) diff --git a/cq_editor/main_window.py b/cq_editor/main_window.py index 4948289d..9b1e7907 100644 --- a/cq_editor/main_window.py +++ b/cq_editor/main_window.py @@ -1,7 +1,7 @@ import sys from PyQt5.QtCore import QObject, pyqtSignal -from PyQt5.QtWidgets import (QLabel, QMainWindow, QToolBar, QDockWidget, QAction) +from PyQt5.QtWidgets import QLabel, QMainWindow, QToolBar, QDockWidget, QAction from logbook import Logger import cadquery as cq @@ -15,7 +15,14 @@ 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 @@ -42,36 +49,38 @@ def new_stdout_write(text: str): PRINT_REDIRECTOR = _PrintRedirectorSingleton() -class MainWindow(QMainWindow,MainMixin): - name = 'CQ-Editor' - org = 'CadQuery' +class MainWindow(QMainWindow, MainMixin): - def __init__(self,parent=None, filename=None): + name = "CQ-Editor" + org = "CadQuery" - 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")) # Windows workaround - makes the correct task bar icon show up. if sys.platform == "win32": import ctypes - myappid = 'cq-editor' # arbitrary string + + myappid = "cq-editor" # arbitrary string ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) self.viewer = OCCViewer(self) self.setCentralWidget(self.viewer.canvas) self.prepare_panes() - self.registerComponent('viewer',self.viewer) + self.registerComponent("viewer", self.viewer) self.prepare_toolbar() self.prepare_menubar() self.prepare_statusbar() self.prepare_actions() - - self.components['object_tree'].addLines() + + self.components["object_tree"].addLines() self.prepare_console() @@ -83,112 +92,110 @@ def __init__(self,parent=None, filename=None): self.restoreWindow() # Let the user know when the file has been modified - self.components['editor'].document().modificationChanged.connect(self.update_window_title) + self.components["editor"].document().modificationChanged.connect( + self.update_window_title + ) if filename: - self.components['editor'].load_from_file(filename) + self.components["editor"].load_from_file(filename) self.restoreComponentState() - def closeEvent(self,event): + 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('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('traceback_viewer', - TracebackPane(self), - lambda c: dock(c, - 'Current traceback', - self, - defaultArea='bottom')) - - self.registerComponent('debugger',Debugger(self)) - - 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( + "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( + "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("debugger", Debugger(self)) + + 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"), + ) for d in self.docks.values(): d.show() - PRINT_REDIRECTOR.sigStdoutWrite.connect(lambda text: self.components['log'].append(text)) - + PRINT_REDIRECTOR.sigStdoutWrite.connect( + lambda text: self.components["log"].append(text) + ) 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') - - #per component menu elements - menus = {'File' : menu_file, - 'Edit' : menu_edit, - 'Run' : menu_run, - 'Tools': menu_tools, - 'View' : menu_view, - 'Help' : menu_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, + } 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()) @@ -197,133 +204,168 @@ def prepare_menubar(self): for t in self.findChildren(QToolBar): menu_view.addAction(t.toggleViewAction()) - menu_edit.addAction( \ - QAction(icon('toggle-comment'), - 'Toggle Comment', - self, - shortcut='ctrl+/', - triggered=self.components['editor'].toggle_comment)) - 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('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)) - - def prepare_menubar_component(self,menus,comp_menu_dict): - - for name,action in comp_menu_dict.items(): + menu_edit.addAction( + QAction( + icon("toggle-comment"), + "Toggle Comment", + self, + shortcut="ctrl+/", + triggered=self.components["editor"].toggle_comment, + ) + ) + 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("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 + ) + ) + + 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['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['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'].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"].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"].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["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"].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 + ) # 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'] - - #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}) + console = self.components["console"] + obj_tree = self.components["object_tree"] + + # 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, + } + ) 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)\nshow_object(result)') + self.components["editor"].set_text( + 'import cadquery as cq\nresult = cq.Workplane("XY" ).box(3, 3, 0.5).edges("|Z").fillet(0.125)\nshow_object(result)' + ) def setup_logging(self): @@ -331,8 +373,8 @@ def setup_logging(self): 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) @@ -342,36 +384,37 @@ def handle_exception(exc_type, exc_value, exc_traceback): 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): @@ -382,9 +425,9 @@ def update_window_title(self, modified): """ Allows updating the window title to show that the document has been modified. """ - title = self.windowTitle().rstrip('*') + title = self.windowTitle().rstrip("*") if modified: - title += '*' + title += "*" self.setWindowTitle(title) diff --git a/cq_editor/mixins.py b/cq_editor/mixins.py index f48f0d23..f614ffe6 100644 --- a/cq_editor/mixins.py +++ b/cq_editor/mixins.py @@ -12,10 +12,11 @@ from PyQt5.QtCore import pyqtSlot, QSettings + class MainMixin(object): - name = 'Main' - org = 'Unknown' + name = "Main" + org = "Unknown" components = {} docks = {} @@ -23,9 +24,9 @@ class MainMixin(object): 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 @@ -34,38 +35,40 @@ def registerComponent(self,name,component,dock=None): 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): @@ -84,19 +87,16 @@ def restoreComponentState(self): 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): @@ -106,19 +106,19 @@ def menuActions(self): 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 \ No newline at end of file + pass diff --git a/cq_editor/preferences.py b/cq_editor/preferences.py index f312caae..4591a6d2 100644 --- a/cq_editor/preferences.py +++ b/cq_editor/preferences.py @@ -1,5 +1,4 @@ -from PyQt5.QtWidgets import (QTreeWidget, QTreeWidgetItem, - QStackedWidget, QDialog) +from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QStackedWidget, QDialog from PyQt5.QtCore import pyqtSlot, Qt from pyqtgraph.parametertree import ParameterTree @@ -8,77 +7,90 @@ 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) # PyQtGraph is not setting items in drop down lists properly, so we do it manually for child in component.preferences.children(): # Fill the editor color scheme drop down list - if child.name() == 'Color scheme': - child.setLimits(['Spyder','Monokai','Zenburn']) + if child.name() == "Color scheme": + child.setLimits(["Spyder", "Monokai", "Zenburn"]) # Fill the camera projection type - elif child.name() == 'Projection Type': - child.setLimits(['Orthographic', - 'Perspective', - 'Stereo', - 'MonoLeftEye', - 'MonoRightEye']) + elif child.name() == "Projection Type": + child.setLimits( + [ + "Orthographic", + "Perspective", + "Stereo", + "MonoLeftEye", + "MonoRightEye", + ] + ) # Fill the stereo mode, or lack thereof - elif child.name() == 'Stereo Mode': - child.setLimits(['QuadBuffer', - 'Anaglyph', - 'RowInterlaced', - 'ColumnInterlaced', - 'ChessBoard', - 'SideBySide', - 'OverUnder']) - - @pyqtSlot(QTreeWidgetItem,QTreeWidgetItem) - def handleSelection(self,item,*args): - + elif child.name() == "Stereo Mode": + child.setLimits( + [ + "QuadBuffer", + "Anaglyph", + "RowInterlaced", + "ColumnInterlaced", + "ChessBoard", + "SideBySide", + "OverUnder", + ] + ) + + @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 6e4cebcd..dde99ff0 100644 --- a/cq_editor/utils.py +++ b/cq_editor/utils.py @@ -7,17 +7,23 @@ from PyQt5.QtCore import QUrl from PyQt5.QtWidgets import QFileDialog, QMessageBox -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): - +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, +): + if not top_widget: top_widget = QtWidgets.QWidget(parent) top_widget_was_none = True @@ -25,110 +31,133 @@ 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.AllDockWidgetFeatures + ) + ) + + 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 77fd1dcc..3ed51a74 100644 --- a/cq_editor/widgets/console.py +++ b/cq_editor/widgets/console.py @@ -6,22 +6,23 @@ 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.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() @@ -31,9 +32,9 @@ def stop(): QApplication.instance().exit() self.exit_requested.connect(stop) - + self.clear() - + self.push_vars(namespace) @pyqtSlot(dict) @@ -50,7 +51,6 @@ def clear(self): """ self._control.clear() - def print_text(self, text): """ Prints some plain text to the console @@ -62,20 +62,19 @@ def execute_command(self, command): Execute a command in the frame of the console widget """ self._execute(command, False) - + def _banner_default(self): - - return '' - + 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 c8c3d37c..b9faac01 100644 --- a/cq_editor/widgets/cq_object_inspector.py +++ b/cq_editor/widgets/cq_object_inspector.py @@ -10,63 +10,70 @@ 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], [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 +81,54 @@ def inspect(self,value): 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 - - \ No newline at end of file diff --git a/cq_editor/widgets/debugger.py b/cq_editor/widgets/debugger.py index 70d5795f..b911f55d 100644 --- a/cq_editor/widgets/debugger.py +++ b/cq_editor/widgets/debugger.py @@ -8,19 +8,26 @@ import cadquery as cq from PyQt5 import QtCore -from PyQt5.QtCore import Qt, QObject, pyqtSlot, pyqtSignal, QEventLoop, QAbstractTableModel +from PyQt5.QtCore import ( + Qt, + QObject, + pyqtSlot, + pyqtSignal, + QEventLoop, + QAbstractTableModel, +) from PyQt5.QtWidgets import QAction, QTableView from logbook import info from path import Path from pyqtgraph.parametertree import Parameter from spyder.utils.icon_manager import icon -from random import randrange as rrr,seed +from random import randrange as rrr, seed from ..cq_utils import find_cq_objects, reload_cq from ..mixins import ComponentMixin -DUMMY_FILE = '' +DUMMY_FILE = "" class DbgState(Enum): @@ -30,35 +37,39 @@ class DbgState(Enum): STEP_IN = auto() RETURN = auto() + class DbgEevent(object): - LINE = 'line' - CALL = 'call' - RETURN = 'return' + LINE = "line" + CALL = "call" + RETURN = "return" + class LocalsModel(QAbstractTableModel): - HEADER = ('Name','Type', 'Value') + HEADER = ("Name", "Type", "Value") - def __init__(self,parent): + def __init__(self, parent): - super(LocalsModel,self).__init__(parent) + super(LocalsModel, self).__init__(parent) self.frame = None - def update_frame(self,frame): - - self.frame = \ - [(k,type(v).__name__, str(v)) for k,v in frame.items() if not k.startswith('_')] + def update_frame(self, frame): + self.frame = [ + (k, type(v).__name__, str(v)) + for k, v in frame.items() + if not k.startswith("_") + ] - def rowCount(self,parent=QtCore.QModelIndex()): + def rowCount(self, parent=QtCore.QModelIndex()): if self.frame: return len(self.frame) else: return 0 - def columnCount(self,parent=QtCore.QModelIndex()): + def columnCount(self, parent=QtCore.QModelIndex()): return 3 @@ -76,13 +87,13 @@ def data(self, index, role): return QtCore.QVariant() -class LocalsView(QTableView,ComponentMixin): +class LocalsView(QTableView, ComponentMixin): - name = 'Variables' + name = "Variables" - def __init__(self,parent): + def __init__(self, parent): - super(LocalsView,self).__init__(parent) + super(LocalsView, self).__init__(parent) ComponentMixin.__init__(self) header = self.horizontalHeader() @@ -92,98 +103,109 @@ def __init__(self,parent): vheader.setVisible(False) @pyqtSlot(dict) - def update_frame(self,frame): + def update_frame(self, frame): model = LocalsModel(self) model.update_frame(frame) self.setModel(model) -class Debugger(QObject,ComponentMixin): - name = 'Debugger' +class Debugger(QObject, ComponentMixin): - preferences = Parameter.create(name='Preferences',children=[ - {'name': 'Reload CQ', 'type': 'bool', 'value': False}, - {'name': 'Add script dir to path','type': 'bool', 'value': True}, - {'name': 'Change working dir to script dir','type': 'bool', 'value': True}, - {'name': 'Reload imported modules', 'type': 'bool', 'value': True}, - ]) + name = "Debugger" + preferences = Parameter.create( + name="Preferences", + children=[ + {"name": "Reload CQ", "type": "bool", "value": False}, + {"name": "Add script dir to path", "type": "bool", "value": True}, + {"name": "Change working dir to script dir", "type": "bool", "value": True}, + {"name": "Reload imported modules", "type": "bool", "value": True}, + ], + ) sigRendered = pyqtSignal(dict) sigLocals = pyqtSignal(dict) - sigTraceback = pyqtSignal(object,str) + sigTraceback = pyqtSignal(object, str) sigFrameChanged = pyqtSignal(object) sigLineChanged = pyqtSignal(int) sigLocalsChanged = pyqtSignal(dict) - sigCQChanged = pyqtSignal(dict,bool) + sigCQChanged = pyqtSignal(dict, bool) sigDebugging = pyqtSignal(bool) - _frames : List[FrameType] - _stop_debugging : bool + _frames: List[FrameType] + _stop_debugging: bool - def __init__(self,parent): + def __init__(self, parent): - super(Debugger,self).__init__(parent) + super(Debugger, self).__init__(parent) ComponentMixin.__init__(self) self.inner_event_loop = QEventLoop(self) - self._actions = \ - {'Run' : [QAction(icon('run'), - 'Render', - self, - shortcut='F5', - triggered=self.render), - QAction(icon('debug'), - 'Debug', - self, - checkable=True, - shortcut='ctrl+F5', - triggered=self.debug), - QAction(icon('arrow-step-over'), - 'Step', - self, - shortcut='ctrl+F10', - triggered=lambda: self.debug_cmd(DbgState.STEP)), - QAction(icon('arrow-step-in'), - 'Step in', - self, - shortcut='ctrl+F11', - triggered=lambda: self.debug_cmd(DbgState.STEP_IN)), - QAction(icon('arrow-continue'), - 'Continue', - self, - shortcut='ctrl+F12', - triggered=lambda: self.debug_cmd(DbgState.CONT)) - ]} + self._actions = { + "Run": [ + QAction( + icon("run"), "Render", self, shortcut="F5", triggered=self.render + ), + QAction( + icon("debug"), + "Debug", + self, + checkable=True, + shortcut="ctrl+F5", + triggered=self.debug, + ), + QAction( + icon("arrow-step-over"), + "Step", + self, + shortcut="ctrl+F10", + triggered=lambda: self.debug_cmd(DbgState.STEP), + ), + QAction( + icon("arrow-step-in"), + "Step in", + self, + shortcut="ctrl+F11", + triggered=lambda: self.debug_cmd(DbgState.STEP_IN), + ), + QAction( + icon("arrow-continue"), + "Continue", + self, + shortcut="ctrl+F12", + triggered=lambda: self.debug_cmd(DbgState.CONT), + ), + ] + } self._frames = [] self._stop_debugging = False def get_current_script(self): - return self.parent().components['editor'].get_text_with_eol() - + return self.parent().components["editor"].get_text_with_eol() + def get_current_script_path(self): - + filename = self.parent().components["editor"].filename if filename: return Path(filename).absolute() def get_breakpoints(self): - return self.parent().components['editor'].debugger.get_breakpoints() + return self.parent().components["editor"].debugger.get_breakpoints() def compile_code(self, cq_script, cq_script_path=None): try: - module = ModuleType('__cq_main__') + module = ModuleType("__cq_main__") if cq_script_path: module.__dict__["__file__"] = cq_script_path - cq_code = compile(cq_script, DUMMY_FILE, 'exec') + cq_code = compile(cq_script, DUMMY_FILE, "exec") return cq_code, module except Exception: self.sigTraceback.emit(sys.exc_info(), cq_script) @@ -194,100 +216,103 @@ def _exec(self, code, locals_dict, globals_dict): with ExitStack() as stack: p = (self.get_current_script_path() or Path("")).absolute().dirname() - if self.preferences['Add script dir to path'] and p.exists(): - sys.path.insert(0,p) + if self.preferences["Add script dir to path"] and p.exists(): + sys.path.insert(0, p) stack.callback(sys.path.remove, p) - if self.preferences['Change working dir to script dir'] and p.exists(): + if self.preferences["Change working dir to script dir"] and p.exists(): stack.enter_context(p) - if self.preferences['Reload imported modules']: + if self.preferences["Reload imported modules"]: stack.enter_context(module_manager()) exec(code, locals_dict, globals_dict) @staticmethod - def _rand_color(alpha = 0., cfloat=False): - #helper function to generate a random color dict - #for CQ-editor's show_object function + def _rand_color(alpha=0.0, cfloat=False): + # helper function to generate a random color dict + # for CQ-editor's show_object function lower = 10 - upper = 100 #not too high to keep color brightness in check - if cfloat: #for two output types depending on need + upper = 100 # not too high to keep color brightness in check + if cfloat: # for two output types depending on need return ( - (rrr(lower,upper)/255), - (rrr(lower,upper)/255), - (rrr(lower,upper)/255), - alpha, - ) - return {"alpha": alpha, - "color": ( - rrr(lower,upper), - rrr(lower,upper), - rrr(lower,upper), - )} - - def _inject_locals(self,module): + (rrr(lower, upper) / 255), + (rrr(lower, upper) / 255), + (rrr(lower, upper) / 255), + alpha, + ) + return { + "alpha": alpha, + "color": ( + rrr(lower, upper), + rrr(lower, upper), + rrr(lower, upper), + ), + } + + def _inject_locals(self, module): cq_objects = {} - def _show_object(obj,name=None, options={}): + def _show_object(obj, name=None, options={}): if name: - cq_objects.update({name : SimpleNamespace(shape=obj,options=options)}) + cq_objects.update({name: SimpleNamespace(shape=obj, options=options)}) else: - #get locals of the enclosing scope + # get locals of the enclosing scope d = currentframe().f_back.f_locals - #try to find the name + # try to find the name try: name = list(d.keys())[list(d.values()).index(obj)] except ValueError: - #use id if not found + # use id if not found name = str(id(obj)) - cq_objects.update({name : SimpleNamespace(shape=obj,options=options)}) + cq_objects.update({name: SimpleNamespace(shape=obj, options=options)}) - def _debug(obj,name=None): + def _debug(obj, name=None): - _show_object(obj,name,options=dict(color='red',alpha=0.2)) + _show_object(obj, name, options=dict(color="red", alpha=0.2)) - module.__dict__['show_object'] = _show_object - module.__dict__['debug'] = _debug - module.__dict__['rand_color'] = self._rand_color - module.__dict__['log'] = lambda x: info(str(x)) - module.__dict__['cq'] = cq + module.__dict__["show_object"] = _show_object + module.__dict__["debug"] = _debug + module.__dict__["rand_color"] = self._rand_color + module.__dict__["log"] = lambda x: info(str(x)) + module.__dict__["cq"] = cq - return cq_objects, set(module.__dict__)-{'cq'} + return cq_objects, set(module.__dict__) - {"cq"} - def _cleanup_locals(self,module,injected_names): + def _cleanup_locals(self, module, injected_names): - for name in injected_names: module.__dict__.pop(name) + for name in injected_names: + module.__dict__.pop(name) @pyqtSlot(bool) def render(self): seed(59798267586177) - if self.preferences['Reload CQ']: + if self.preferences["Reload CQ"]: reload_cq() cq_script = self.get_current_script() cq_script_path = self.get_current_script_path() - cq_code,module = self.compile_code(cq_script, cq_script_path) + cq_code, module = self.compile_code(cq_script, cq_script_path) - if cq_code is None: return + if cq_code is None: + return - cq_objects,injected_names = self._inject_locals(module) + cq_objects, injected_names = self._inject_locals(module) try: self._exec(cq_code, module.__dict__, module.__dict__) - #remove the special methods - self._cleanup_locals(module,injected_names) + # remove the special methods + self._cleanup_locals(module, injected_names) - #collect all CQ objects if no explicit show_object was called + # collect all CQ objects if no explicit show_object was called if len(cq_objects) == 0: cq_objects = find_cq_objects(module.__dict__) self.sigRendered.emit(cq_objects) - self.sigTraceback.emit(None, - cq_script) + self.sigTraceback.emit(None, cq_script) self.sigLocals.emit(module.__dict__) except Exception: exc_info = sys.exc_info() @@ -296,10 +321,10 @@ def render(self): @property def breakpoints(self): - return [ el[0] for el in self.get_breakpoints()] + return [el[0] for el in self.get_breakpoints()] @pyqtSlot(bool) - def debug(self,value): + def debug(self, value): # used to stop the debugging session early self._stop_debugging = False @@ -312,39 +337,37 @@ def debug(self,value): self.script = self.get_current_script() cq_script_path = self.get_current_script_path() - code,module = self.compile_code(self.script, cq_script_path) + code, module = self.compile_code(self.script, cq_script_path) if code is None: self.sigDebugging.emit(False) - self._actions['Run'][1].setChecked(False) + self._actions["Run"][1].setChecked(False) return - cq_objects,injected_names = self._inject_locals(module) + cq_objects, injected_names = self._inject_locals(module) - #clear possible traceback - self.sigTraceback.emit(None, - self.script) + # clear possible traceback + self.sigTraceback.emit(None, self.script) try: sys.settrace(self.trace_callback) - exec(code,module.__dict__,module.__dict__) + exec(code, module.__dict__, module.__dict__) except BdbQuit: pass except Exception: exc_info = sys.exc_info() sys.last_traceback = exc_info[-1] - self.sigTraceback.emit(exc_info, - self.script) + self.sigTraceback.emit(exc_info, self.script) finally: sys.settrace(previous_trace) self.sigDebugging.emit(False) - self._actions['Run'][1].setChecked(False) + self._actions["Run"][1].setChecked(False) if len(cq_objects) == 0: cq_objects = find_cq_objects(module.__dict__) self.sigRendered.emit(cq_objects) - self._cleanup_locals(module,injected_names) + self._cleanup_locals(module, injected_names) self.sigLocals.emit(module.__dict__) self._frames = [] @@ -353,32 +376,33 @@ def debug(self,value): self._stop_debugging = True self.inner_event_loop.exit(0) - def debug_cmd(self,state=DbgState.STEP): + def debug_cmd(self, state=DbgState.STEP): self.state = state self.inner_event_loop.exit(0) - - def trace_callback(self,frame,event,arg): + def trace_callback(self, frame, event, arg): filename = frame.f_code.co_filename - if filename==DUMMY_FILE: + if filename == DUMMY_FILE: if not self._frames: self._frames.append(frame) - self.trace_local(frame,event,arg) + self.trace_local(frame, event, arg) return self.trace_callback else: return None - def trace_local(self,frame,event,arg): + def trace_local(self, frame, event, arg): lineno = frame.f_lineno if event in (DbgEevent.LINE,): - if (self.state in (DbgState.STEP, DbgState.STEP_IN) and frame is self._frames[-1]) \ - or (lineno in self.breakpoints): + if ( + self.state in (DbgState.STEP, DbgState.STEP_IN) + and frame is self._frames[-1] + ) or (lineno in self.breakpoints): if lineno in self.breakpoints: self._frames.append(frame) @@ -386,7 +410,7 @@ def trace_local(self,frame,event,arg): self.sigLineChanged.emit(lineno) self.sigFrameChanged.emit(frame) self.sigLocalsChanged.emit(frame.f_locals) - self.sigCQChanged.emit(find_cq_objects(frame.f_locals),True) + self.sigCQChanged.emit(find_cq_objects(frame.f_locals), True) self.inner_event_loop.exec_() @@ -403,12 +427,12 @@ def trace_local(self,frame,event,arg): self._frames.append(frame) if self._stop_debugging: - raise BdbQuit #stop debugging if requested + raise BdbQuit # stop debugging if requested @contextmanager def module_manager(): - """ unloads any modules loaded while the context manager is active """ + """unloads any modules loaded while the context manager is active""" loaded_modules = set(sys.modules.keys()) try: diff --git a/cq_editor/widgets/editor.py b/cq_editor/widgets/editor.py index 0e6b2c98..2ed9b788 100644 --- a/cq_editor/widgets/editor.py +++ b/cq_editor/widgets/editor.py @@ -17,85 +17,101 @@ 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'}]) - - EXTENSIONS = 'py' - - def __init__(self,parent=None): + 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): 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._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.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", + ), + ] + } 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() @@ -109,20 +125,19 @@ def _fixContextMenu(self): menu.removeAction(self.run_selection_action) menu.removeAction(self.re_run_last_cell_action) - def updatePreferences(self,*args): + def updatePreferences(self, *args): - self.set_color_scheme(self.preferences['Color scheme']) + 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() @@ -130,7 +145,11 @@ def updatePreferences(self,*args): 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 @@ -138,22 +157,24 @@ def confirm_discard(self): 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).absolute().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 @@ -165,8 +186,8 @@ def save(self): save-as dialog. """ - if self._filename != '': - with open(self._filename, 'w', encoding='utf-8') as f: + if self._filename != "": + with open(self._filename, "w", encoding="utf-8") as f: f.write(self.toPlainText()) # Let the editor and the rest of the app know that the file is no longer dirty @@ -178,8 +199,8 @@ def save(self): def save_as(self): fname = get_save_filename(self.EXTENSIONS) - if fname != '': - with open(fname, 'w', encoding='utf-8') as f: + if fname != "": + with open(fname, "w", encoding="utf-8") as f: f.write(self.toPlainText()) self.filename = fname @@ -189,14 +210,20 @@ def toggle_comment(self): """ Allows us to mark the document as modified when the user toggles a comment. """ - super(Editor,self).toggle_comment() + super(Editor, self).toggle_comment() self.document().setModified(True) 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() @@ -218,8 +245,8 @@ def _clear_watched_paths(self): 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) @@ -233,33 +260,32 @@ def _file_changed(self): # 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): + def restoreComponentState(self, store): - filename = store.value(self.name+'/state') + 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): @@ -269,15 +295,15 @@ def get_imported_module_paths(self, module_path): 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) diff --git a/cq_editor/widgets/log.py b/cq_editor/widgets/log.py index 6d2da57c..a962c918 100644 --- a/cq_editor/widgets/log.py +++ b/cq_editor/widgets/log.py @@ -7,51 +7,56 @@ from ..mixins import ComponentMixin + def strip_escape_sequences(input_string): # Regular expression pattern to match ANSI escape codes - escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]') + escape_pattern = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]") # Use re.sub to replace escape codes with an empty string - clean_string = re.sub(escape_pattern, '', input_string) + clean_string = re.sub(escape_pattern, "", input_string) return clean_string + class _QtLogHandlerQObject(QObject): sigRecordEmit = pyqtSignal(str) -class QtLogHandler(logging.Handler,logging.StringFormatterHandlerMixin): - - def __init__(self, log_widget,*args,**kwargs): - - super(QtLogHandler,self).__init__(*args,**kwargs) - - log_format_string = '[{record.time:%H:%M:%S%z}] {record.level_name}: {record.message}' - - logging.StringFormatterHandlerMixin.__init__(self,log_format_string) - +class QtLogHandler(logging.Handler, logging.StringFormatterHandlerMixin): + + def __init__(self, log_widget, *args, **kwargs): + + super(QtLogHandler, self).__init__(*args, **kwargs) + + log_format_string = ( + "[{record.time:%H:%M:%S%z}] {record.level_name}: {record.message}" + ) + + logging.StringFormatterHandlerMixin.__init__(self, log_format_string) + self._qobject = _QtLogHandlerQObject() self._qobject.sigRecordEmit.connect(log_widget.append) def emit(self, record): self._qobject.sigRecordEmit.emit(self.format(record) + "\n") + class LogViewer(QPlainTextEdit, ComponentMixin): - - name = 'Log viewer' - - def __init__(self,*args,**kwargs): - - super(LogViewer,self).__init__(*args,**kwargs) + + name = "Log viewer" + + def __init__(self, *args, **kwargs): + + super(LogViewer, self).__init__(*args, **kwargs) self._MAX_ROWS = 500 - + self.setReadOnly(True) self.setMaximumBlockCount(self._MAX_ROWS) self.setLineWrapMode(QPlainTextEdit.NoWrap) - + self.handler = QtLogHandler(self) - - def append(self,msg): + + def append(self, msg): """Append text to the panel with ANSI escape sequences stipped.""" self.moveCursor(QtGui.QTextCursor.End) self.insertPlainText(strip_escape_sequences(msg)) diff --git a/cq_editor/widgets/object_tree.py b/cq_editor/widgets/object_tree.py index 1778bfd5..8725d50a 100644 --- a/cq_editor/widgets/object_tree.py +++ b/cq_editor/widgets/object_tree.py @@ -1,4 +1,11 @@ -from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAction, QMenu, QWidget, QAbstractItemView +from PyQt5.QtWidgets import ( + QTreeWidget, + QTreeWidgetItem, + QAction, + QMenu, + QWidget, + QAbstractItemView, +) from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal from pyqtgraph.parametertree import Parameter, ParameterTree @@ -9,107 +16,129 @@ from ..mixins import ComponentMixin from ..icons import icon -from ..cq_utils import make_AIS, export, to_occ_color, is_obj_empty, get_occ_color, set_color +from ..cq_utils import ( + make_AIS, + export, + to_occ_color, + is_obj_empty, + get_occ_color, + set_color, +) from .viewer import DEFAULT_FACE_COLOR from ..utils import splitter, layout, get_save_filename + class TopTreeItem(QTreeWidgetItem): - def __init__(self,*args,**kwargs): + def __init__(self, *args, **kwargs): + + super(TopTreeItem, self).__init__(*args, **kwargs) - super(TopTreeItem,self).__init__(*args,**kwargs) class ObjectTreeItem(QTreeWidgetItem): - props = [{'name': 'Name', 'type': 'str', 'value': ''}, - {'name': 'Color', 'type': 'color', 'value': "#f4a824"}, - {'name': 'Alpha', 'type': 'float', 'value': 0, 'limits': (0,1), 'step': 1e-1}, - {'name': 'Visible', 'type': 'bool','value': True}] - - def __init__(self, - name, - ais=None, - shape=None, - shape_display=None, - sig=None, - alpha=0., - color='#f4a824', - **kwargs): - - super(ObjectTreeItem,self).__init__([name],**kwargs) - self.setFlags( self.flags() | Qt.ItemIsUserCheckable) - self.setCheckState(0,Qt.Checked) + props = [ + {"name": "Name", "type": "str", "value": ""}, + {"name": "Color", "type": "color", "value": "#f4a824"}, + {"name": "Alpha", "type": "float", "value": 0, "limits": (0, 1), "step": 1e-1}, + {"name": "Visible", "type": "bool", "value": True}, + ] + + def __init__( + self, + name, + ais=None, + shape=None, + shape_display=None, + sig=None, + alpha=0.0, + color="#f4a824", + **kwargs, + ): + + super(ObjectTreeItem, self).__init__([name], **kwargs) + self.setFlags(self.flags() | Qt.ItemIsUserCheckable) + self.setCheckState(0, Qt.Checked) self.ais = ais self.shape = shape self.shape_display = shape_display self.sig = sig - self.properties = Parameter.create(name='Properties', - children=self.props) + self.properties = Parameter.create(name="Properties", children=self.props) - self.properties['Name'] = name - self.properties['Alpha'] = ais.Transparency() - self.properties['Color'] = get_occ_color(ais) if ais and ais.HasColor() else get_occ_color(DEFAULT_FACE_COLOR) + self.properties["Name"] = name + self.properties["Alpha"] = ais.Transparency() + self.properties["Color"] = ( + get_occ_color(ais) + if ais and ais.HasColor() + else get_occ_color(DEFAULT_FACE_COLOR) + ) self.properties.sigTreeStateChanged.connect(self.propertiesChanged) def propertiesChanged(self, properties, changed): changed_prop = changed[0][0] - self.setData(0,0,self.properties['Name']) - self.ais.SetTransparency(self.properties['Alpha']) + self.setData(0, 0, self.properties["Name"]) + self.ais.SetTransparency(self.properties["Alpha"]) - if changed_prop.name() == 'Color': - set_color(self.ais, to_occ_color(self.properties['Color'])) + if changed_prop.name() == "Color": + set_color(self.ais, to_occ_color(self.properties["Color"])) self.ais.Redisplay() - if self.properties['Visible']: - self.setCheckState(0,Qt.Checked) + if self.properties["Visible"]: + self.setCheckState(0, Qt.Checked) else: - self.setCheckState(0,Qt.Unchecked) + self.setCheckState(0, Qt.Unchecked) if self.sig: self.sig.emit() + class CQRootItem(TopTreeItem): - def __init__(self,*args,**kwargs): + def __init__(self, *args, **kwargs): - super(CQRootItem,self).__init__(['CQ models'],*args,**kwargs) + super(CQRootItem, self).__init__(["CQ models"], *args, **kwargs) class HelpersRootItem(TopTreeItem): - def __init__(self,*args,**kwargs): + def __init__(self, *args, **kwargs): - super(HelpersRootItem,self).__init__(['Helpers'],*args,**kwargs) + super(HelpersRootItem, self).__init__(["Helpers"], *args, **kwargs) -class ObjectTree(QWidget,ComponentMixin): +class ObjectTree(QWidget, ComponentMixin): - name = 'Object Tree' + name = "Object Tree" _stash = [] - preferences = Parameter.create(name='Preferences',children=[ - {'name': 'Preserve properties on reload', 'type': 'bool', 'value': False}, - {'name': 'Clear all before each run', 'type': 'bool', 'value': True}, - {'name': 'STL precision','type': 'float', 'value': .1}]) + preferences = Parameter.create( + name="Preferences", + children=[ + {"name": "Preserve properties on reload", "type": "bool", "value": False}, + {"name": "Clear all before each run", "type": "bool", "value": True}, + {"name": "STL precision", "type": "float", "value": 0.1}, + ], + ) - sigObjectsAdded = pyqtSignal([list],[list,bool]) + sigObjectsAdded = pyqtSignal([list], [list, bool]) sigObjectsRemoved = pyqtSignal(list) sigCQObjectSelected = pyqtSignal(object) sigAISObjectsSelected = pyqtSignal(list) - sigItemChanged = pyqtSignal(QTreeWidgetItem,int) + sigItemChanged = pyqtSignal(QTreeWidgetItem, int) sigObjectPropertiesChanged = pyqtSignal() - def __init__(self,parent): + def __init__(self, parent): - super(ObjectTree,self).__init__(parent) + super(ObjectTree, self).__init__(parent) - self.tree = tree = QTreeWidget(self, - selectionMode=QAbstractItemView.ExtendedSelection) + self.tree = tree = QTreeWidget( + self, selectionMode=QAbstractItemView.ExtendedSelection + ) self.properties_editor = ParameterTree(self) tree.setHeaderHidden(True) @@ -117,10 +146,9 @@ def __init__(self,parent): tree.setRootIsDecorated(False) tree.setContextMenuPolicy(Qt.ActionsContextMenu) - #forward itemChanged singal - tree.itemChanged.connect(\ - lambda item,col: self.sigItemChanged.emit(item,col)) - #handle visibility changes form tree + # forward itemChanged singal + tree.itemChanged.connect(lambda item, col: self.sigItemChanged.emit(item, col)) + # handle visibility changes form tree tree.itemChanged.connect(self.handleChecked) self.CQ = CQRootItem() @@ -129,33 +157,34 @@ def __init__(self,parent): root = tree.invisibleRootItem() root.addChild(self.CQ) root.addChild(self.Helpers) - + tree.expandToDepth(1) - self._export_STL_action = \ - QAction('Export as STL', - self, - enabled=False, - triggered=lambda: \ - self.export('stl', - self.preferences['STL precision'])) - - self._export_STEP_action = \ - QAction('Export as STEP', - self, - enabled=False, - triggered=lambda: \ - self.export('step')) - - self._clear_current_action = QAction(icon('delete'), - 'Clear current', - self, - enabled=False, - triggered=self.removeSelected) - - self._toolbar_actions = \ - [QAction(icon('delete-many'),'Clear all',self,triggered=self.removeObjects), - self._clear_current_action,] + self._export_STL_action = QAction( + "Export as STL", + self, + enabled=False, + triggered=lambda: self.export("stl", self.preferences["STL precision"]), + ) + + self._export_STEP_action = QAction( + "Export as STEP", self, enabled=False, triggered=lambda: self.export("step") + ) + + self._clear_current_action = QAction( + icon("delete"), + "Clear current", + self, + enabled=False, + triggered=self.removeSelected, + ) + + self._toolbar_actions = [ + QAction( + icon("delete-many"), "Clear all", self, triggered=self.removeObjects + ), + self._clear_current_action, + ] self.prepareMenu() @@ -164,34 +193,34 @@ def __init__(self,parent): self.prepareLayout() - def prepareMenu(self): self.tree.setContextMenuPolicy(Qt.CustomContextMenu) self._context_menu = QMenu(self) self._context_menu.addActions(self._toolbar_actions) - self._context_menu.addActions((self._export_STL_action, - self._export_STEP_action)) + self._context_menu.addActions( + (self._export_STL_action, self._export_STEP_action) + ) def prepareLayout(self): - self._splitter = splitter((self.tree,self.properties_editor), - stretch_factors = (2,1), - orientation=Qt.Vertical) - layout(self,(self._splitter,),top_widget=self) + self._splitter = splitter( + (self.tree, self.properties_editor), + stretch_factors=(2, 1), + orientation=Qt.Vertical, + ) + layout(self, (self._splitter,), top_widget=self) self._splitter.show() - def showMenu(self,position): + def showMenu(self, position): self._context_menu.exec_(self.tree.viewport().mapToGlobal(position)) - def menuActions(self): - return {'Tools' : [self._export_STL_action, - self._export_STEP_action]} + return {"Tools": [self._export_STL_action, self._export_STEP_action]} def toolbarActions(self): @@ -199,19 +228,19 @@ def toolbarActions(self): def addLines(self): - origin = (0,0,0) + origin = (0, 0, 0) ais_list = [] - for name,color,direction in zip(('X','Y','Z'), - ('red','lawngreen','blue'), - ((1,0,0),(0,1,0),(0,0,1))): - line_placement = Geom_Line(gp_Ax1(gp_Pnt(*origin), - gp_Dir(*direction))) + for name, color, direction in zip( + ("X", "Y", "Z"), + ("red", "lawngreen", "blue"), + ((1, 0, 0), (0, 1, 0), (0, 0, 1)), + ): + line_placement = Geom_Line(gp_Ax1(gp_Pnt(*origin), gp_Dir(*direction))) line = AIS_Line(line_placement) line.SetColor(to_occ_color(color)) - - self.Helpers.addChild(ObjectTreeItem(name, - ais=line)) + + self.Helpers.addChild(ObjectTreeItem(name, ais=line)) ais_list.append(line) @@ -222,78 +251,85 @@ def _current_properties(self): current_params = {} for i in range(self.CQ.childCount()): child = self.CQ.child(i) - current_params[child.properties['Name']] = child.properties + current_params[child.properties["Name"]] = child.properties return current_params - def _restore_properties(self,obj,properties): + def _restore_properties(self, obj, properties): - for p in properties[obj.properties['Name']]: + for p in properties[obj.properties["Name"]]: obj.properties[p.name()] = p.value() - @pyqtSlot(dict,bool) + @pyqtSlot(dict, bool) @pyqtSlot(dict) - def addObjects(self,objects,clean=False,root=None): + def addObjects(self, objects, clean=False, root=None): if root is None: root = self.CQ request_fit_view = True if root.childCount() == 0 else False - preserve_props = self.preferences['Preserve properties on reload'] - + preserve_props = self.preferences["Preserve properties on reload"] + if preserve_props: current_props = self._current_properties() - if clean or self.preferences['Clear all before each run']: + if clean or self.preferences["Clear all before each run"]: self.removeObjects() ais_list = [] - #remove empty objects - objects_f = {k:v for k,v in objects.items() if not is_obj_empty(v.shape)} - - for name,obj in objects_f.items(): - ais,shape_display = make_AIS(obj.shape,obj.options) - - child = ObjectTreeItem(name, - shape=obj.shape, - shape_display=shape_display, - ais=ais, - sig=self.sigObjectPropertiesChanged) - + # remove empty objects + objects_f = {k: v for k, v in objects.items() if not is_obj_empty(v.shape)} + + for name, obj in objects_f.items(): + ais, shape_display = make_AIS(obj.shape, obj.options) + + child = ObjectTreeItem( + name, + shape=obj.shape, + shape_display=shape_display, + ais=ais, + sig=self.sigObjectPropertiesChanged, + ) + if preserve_props and name in current_props: - self._restore_properties(child,current_props) - - if child.properties['Visible']: + self._restore_properties(child, current_props) + + if child.properties["Visible"]: ais_list.append(ais) - + root.addChild(child) if request_fit_view: - self.sigObjectsAdded[list,bool].emit(ais_list,True) + self.sigObjectsAdded[list, bool].emit(ais_list, True) else: self.sigObjectsAdded[list].emit(ais_list) - @pyqtSlot(object,str,object) - def addObject(self,obj,name='',options=None): + @pyqtSlot(object, str, object) + def addObject(self, obj, name="", options=None): - if options is None: options={} + if options is None: + options = {} root = self.CQ - ais,shape_display = make_AIS(obj, options) + ais, shape_display = make_AIS(obj, options) - root.addChild(ObjectTreeItem(name, - shape=obj, - shape_display=shape_display, - ais=ais, - sig=self.sigObjectPropertiesChanged)) + root.addChild( + ObjectTreeItem( + name, + shape=obj, + shape_display=shape_display, + ais=ais, + sig=self.sigObjectPropertiesChanged, + ) + ) self.sigObjectsAdded.emit([ais]) @pyqtSlot(list) @pyqtSlot() - def removeObjects(self,objects=None): + def removeObjects(self, objects=None): if objects: removed_items_ais = [self.CQ.takeChild(i).ais for i in objects] @@ -303,7 +339,7 @@ def removeObjects(self,objects=None): self.sigObjectsRemoved.emit(removed_items_ais) @pyqtSlot(bool) - def stashObjects(self,action : bool): + def stashObjects(self, action: bool): if action: self._stash = self.CQ.takeChildren() @@ -323,7 +359,7 @@ def removeSelected(self): self.removeObjects(rows) - def export(self,export_type,precision=None): + def export(self, export_type, precision=None): items = self.tree.selectedItems() @@ -336,13 +372,13 @@ def export(self,export_type,precision=None): shapes = [item.shape for item in items if item.parent() is self.CQ] fname = get_save_filename(export_type) - if fname != '': - export(shapes,export_type,fname,precision) + if fname != "": + export(shapes, export_type, fname, precision) @pyqtSlot() def handleSelection(self): - items =self.tree.selectedItems() + items = self.tree.selectedItems() if len(items) == 0: self._export_STL_action.setEnabled(False) self._export_STEP_action.setEnabled(False) @@ -359,10 +395,9 @@ def handleSelection(self): self._export_STEP_action.setEnabled(True) self._clear_current_action.setEnabled(True) self.sigCQObjectSelected.emit(item.shape) - self.properties_editor.setParameters(item.properties, - showTop=False) + self.properties_editor.setParameters(item.properties, showTop=False) self.properties_editor.setEnabled(True) - elif item is self.CQ and item.childCount()>0: + elif item is self.CQ and item.childCount() > 0: self._export_STL_action.setEnabled(True) self._export_STEP_action.setEnabled(True) else: @@ -373,7 +408,7 @@ def handleSelection(self): self.properties_editor.clear() @pyqtSlot(list) - def handleGraphicalSelection(self,shapes): + def handleGraphicalSelection(self, shapes): self.tree.clearSelection() @@ -384,14 +419,11 @@ def handleGraphicalSelection(self,shapes): if item.ais.Shape().IsEqual(shape): item.setSelected(True) - @pyqtSlot(QTreeWidgetItem,int) - def handleChecked(self,item,col): + @pyqtSlot(QTreeWidgetItem, int) + def handleChecked(self, item, col): if type(item) is ObjectTreeItem: if item.checkState(0): - item.properties['Visible'] = True + item.properties["Visible"] = True else: - item.properties['Visible'] = False - - - + item.properties["Visible"] = False diff --git a/cq_editor/widgets/occt_widget.py b/cq_editor/widgets/occt_widget.py index 172755ea..cd8215f3 100755 --- a/cq_editor/widgets/occt_widget.py +++ b/cq_editor/widgets/occt_widget.py @@ -15,159 +15,157 @@ 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) - + Aspect_TypeOfTriedronPosition.Aspect_TOTP_RIGHT_LOWER, Quantity_Color(), 0.1 + ) + 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.ZoomAtPoint(self.old_pos.x(), y, - x, self.old_pos.y()) - + self.view.ZoomAtPoint(self.old_pos.x(), y, x, self.old_pos.y()) + self.old_pos = pos - - 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) - + + super(OCCTWidget, self).showEvent(event) + def resizeEvent(self, event): - - super(OCCTWidget,self).resizeEvent(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 - + 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 244d9acc..b02f378f 100644 --- a/cq_editor/widgets/traceback_viewer.py +++ b/cq_editor/widgets/traceback_viewer.py @@ -1,50 +1,46 @@ from traceback import extract_tb, format_exception_only from itertools import dropwhile -from PyQt5.QtWidgets import (QWidget, QTreeWidget, QTreeWidgetItem, QAction, - QLabel) +from PyQt5.QtWidgets import QWidget, QTreeWidget, QTreeWidgetItem, QAction, QLabel from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal from PyQt5.QtGui import QFontMetrics from ..mixins import ComponentMixin from ..utils import layout + class TracebackTree(QTreeWidget): - name = 'Traceback Viewer' - - def __init__(self,parent): - - super(TracebackTree,self).__init__(parent) + 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) def truncate_text(self, text, max_length=100): @@ -52,60 +48,64 @@ def truncate_text(self, text, max_length=100): Used to prevent the label from expanding the window width off the screen. """ metrics = QFontMetrics(self.current_exception.font()) - elided_text = metrics.elidedText(text, Qt.ElideRight, self.current_exception.width() - 75) + elided_text = metrics.elidedText( + text, Qt.ElideRight, self.current_exception.width() - 75 + ) return elided_text - @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() for el in dropwhile( - lambda el: 'string>' not in el.filename, extract_tb(tb) + lambda el: "string>" not in el.filename, extract_tb(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 <> truncated_msg = self.truncate_text(exc_msg) - self.current_exception.setText('{}: {}'.format(exc_name,truncated_msg)) + self.current_exception.setText( + "{}: {}".format(exc_name, truncated_msg) + ) self.current_exception.setToolTip(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.setToolTip('') + self.current_exception.setText("") + self.current_exception.setToolTip("") + + @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 2f54eaca..0750d626 100644 --- a/cq_editor/widgets/viewer.py +++ b/cq_editor/widgets/viewer.py @@ -3,12 +3,19 @@ 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.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 @@ -23,33 +30,70 @@ 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': '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' +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": "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): + def __init__(self, parent=None): - super(OCCViewer,self).__init__(parent) + super(OCCViewer, self).__init__(parent) ComponentMixin.__init__(self) self.canvas = OCCTWidget() @@ -57,10 +101,14 @@ def __init__(self,parent=None): self.create_actions(self) - self.layout_ = layout(self, - [self.canvas,], - top_widget=self, - margin=0) + self.layout_ = layout( + self, + [ + self.canvas, + ], + top_widget=self, + margin=0, + ) self.setup_default_drawer() self.updatePreferences() @@ -79,95 +127,129 @@ def setup_default_drawer(self): line_aspect.SetWidth(DEFAULT_EDGE_WIDTH) line_aspect.SetColor(DEFAULT_EDGE_COLOR) - def updatePreferences(self,*args): + def updatePreferences(self, *args): - color1 = to_occ_color(self.preferences['Background color']) - color2 = to_occ_color(self.preferences['Background color (aux)']) + 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) - - 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)]} + 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'] - + return self._actions["View"] def clear(self): @@ -178,50 +260,52 @@ def clear(self): 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): @@ -235,43 +319,43 @@ def fit(self): 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): @@ -284,61 +368,55 @@ 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)): + 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_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) + if fname != "": + self._get_view().Dump(fname) - def _display_ais(self,ais): + def _display_ais(self, ais): self._get_context().Display(ais) - def _get_view(self): return self.canvas.view @@ -352,18 +430,18 @@ 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() @@ -380,10 +458,10 @@ def set_selected(self,ais): 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) diff --git a/pyinstaller/pyi_rth_fontconfig.py b/pyinstaller/pyi_rth_fontconfig.py index c406f67d..93326e22 100644 --- a/pyinstaller/pyi_rth_fontconfig.py +++ b/pyinstaller/pyi_rth_fontconfig.py @@ -1,6 +1,6 @@ import os import sys -if sys.platform.startswith('linux'): - os.environ['FONTCONFIG_FILE'] = '/etc/fonts/fonts.conf' - os.environ['FONTCONFIG_PATH'] = '/etc/fonts/' +if sys.platform.startswith("linux"): + os.environ["FONTCONFIG_FILE"] = "/etc/fonts/fonts.conf" + os.environ["FONTCONFIG_PATH"] = "/etc/fonts/" diff --git a/pyinstaller/pyi_rth_occ.py b/pyinstaller/pyi_rth_occ.py index f10104d2..9dc0932c 100644 --- a/pyinstaller/pyi_rth_occ.py +++ b/pyinstaller/pyi_rth_occ.py @@ -1,7 +1,7 @@ from os import environ as env -env['CASROOT'] = 'opencascade' +env["CASROOT"] = "opencascade" -env['CSF_ShadersDirectory'] = 'opencascade/src/Shaders' -env['CSF_UnitsLexicon'] = 'opencascade/src/UnitsAPI/Lexi_Expr.dat' -env['CSF_UnitsDefinition'] = 'opencascade/src/UnitsAPI/Units.dat' +env["CSF_ShadersDirectory"] = "opencascade/src/Shaders" +env["CSF_UnitsLexicon"] = "opencascade/src/UnitsAPI/Lexi_Expr.dat" +env["CSF_UnitsDefinition"] = "opencascade/src/UnitsAPI/Units.dat" diff --git a/run.py b/run.py index 8c0badf6..606ac60f 100644 --- a/run.py +++ b/run.py @@ -3,14 +3,14 @@ faulthandler.enable() -if 'CASROOT' in os.environ: - del os.environ['CASROOT'] +if "CASROOT" in os.environ: + del os.environ["CASROOT"] -if sys.platform == 'win32': +if sys.platform == "win32": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) from cq_editor.__main__ import main -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/setup.py b/setup.py index 8943e67f..1141c444 100644 --- a/setup.py +++ b/setup.py @@ -3,25 +3,30 @@ from setuptools import setup, find_packages + def read(rel_path): here = os.path.abspath(os.path.dirname(__file__)) - with codecs.open(os.path.join(here, rel_path), 'r') as fp: + with codecs.open(os.path.join(here, rel_path), "r") as fp: return fp.read() + def get_version(rel_path): for line in read(rel_path).splitlines(): - if line.startswith('__version__'): + if line.startswith("__version__"): delim = '"' if '"' in line else "'" return line.split(delim)[1] else: raise RuntimeError("Unable to find version string.") -setup(name='CQ-editor', - version=get_version('cq_editor/_version.py'), - packages=find_packages(), - entry_points={ - 'gui_scripts': [ - 'cq-editor = cq_editor.__main__:main', - 'CQ-editor = cq_editor.__main__:main' - ]} - ) + +setup( + name="CQ-editor", + version=get_version("cq_editor/_version.py"), + packages=find_packages(), + entry_points={ + "gui_scripts": [ + "cq-editor = cq_editor.__main__:main", + "CQ-editor = cq_editor.__main__:main", + ] + }, +) diff --git a/tests/test_app.py b/tests/test_app.py index db0c9a02..ef547c91 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,7 +1,7 @@ from path import Path import os, sys, asyncio -if sys.platform == 'win32': +if sys.platform == "win32": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) from multiprocessing import Process @@ -17,61 +17,54 @@ from cq_editor.widgets.editor import Editor from cq_editor.cq_utils import export, get_occ_color -code = \ -'''import cadquery as cq +code = """import cadquery as cq result = cq.Workplane("XY" ) result = result.box(3, 3, 0.5) -result = result.edges("|Z").fillet(0.125)''' +result = result.edges("|Z").fillet(0.125)""" -code_bigger_object = \ -'''import cadquery as cq +code_bigger_object = """import cadquery as cq result = cq.Workplane("XY" ) result = result.box(20, 20, 0.5) result = result.edges("|Z").fillet(0.125) -''' +""" -code_show_Workplane = \ -'''import cadquery as cq +code_show_Workplane = """import cadquery as cq result = cq.Workplane("XY" ) result = result.box(3, 3, 0.5) result = result.edges("|Z").fillet(0.125) show_object(result) -''' +""" -code_show_Workplane_named = \ -'''import cadquery as cq +code_show_Workplane_named = """import cadquery as cq result = cq.Workplane("XY" ) result = result.box(3, 3, 0.5) result = result.edges("|Z").fillet(0.125) log('test') show_object(result,name='test') -''' +""" -code_show_Shape = \ -'''import cadquery as cq +code_show_Shape = """import cadquery as cq result = cq.Workplane("XY" ) result = result.box(3, 3, 0.5) result = result.edges("|Z").fillet(0.125) show_object(result.val()) -''' +""" -code_debug_Workplane = \ -'''import cadquery as cq +code_debug_Workplane = """import cadquery as cq result = cq.Workplane("XY" ) result = result.box(3, 3, 0.5) result = result.edges("|Z").fillet(0.125) debug(result) -''' +""" -code_multi = \ -'''import cadquery as cq +code_multi = """import cadquery as cq result1 = cq.Workplane("XY" ).box(3, 3, 0.5) result2 = cq.Workplane("XY" ).box(3, 3, 0.5).translate((0,15,0)) -''' +""" code_nested_top = """import test_nested_bottom """ @@ -91,31 +84,35 @@ sk = cq.Sketch().rect(1,1) """ + def _modify_file(code, path="test.py"): with open(path, "w", 1) as f: f.write(code) def modify_file(code, path="test.py"): - p = Process(target=_modify_file, args=(code,path)) + p = Process(target=_modify_file, args=(code, path)) p.start() p.join() + def get_center(widget): pos = widget.pos() - pos.setX(pos.x()+widget.width()//2) - pos.setY(pos.y()+widget.height()//2) + pos.setX(pos.x() + widget.width() // 2) + pos.setY(pos.y() + widget.height() // 2) return pos + def get_bottom_left(widget): pos = widget.pos() - pos.setY(pos.y()+widget.height()) + pos.setY(pos.y() + widget.height()) return pos + def get_rgba(ais): alpha = ais.Transparency() @@ -123,28 +120,30 @@ def get_rgba(ais): return color.redF(), color.greenF(), color.blueF(), alpha + @pytest.fixture -def main(qtbot,mocker): +def main(qtbot, mocker): - mocker.patch.object(QMessageBox, 'question', return_value=QMessageBox.Yes) + mocker.patch.object(QMessageBox, "question", return_value=QMessageBox.Yes) win = MainWindow() win.show() qtbot.addWidget(win) - editor = win.components['editor'] + editor = win.components["editor"] editor.set_text(code) - debugger = win.components['debugger'] - debugger._actions['Run'][0].triggered.emit() + debugger = win.components["debugger"] + debugger._actions["Run"][0].triggered.emit() return qtbot, win + @pytest.fixture -def main_clean(qtbot,mocker): +def main_clean(qtbot, mocker): - mocker.patch.object(QMessageBox, 'question', return_value=QMessageBox.Yes) + mocker.patch.object(QMessageBox, "question", return_value=QMessageBox.Yes) win = MainWindow() win.show() @@ -152,15 +151,16 @@ def main_clean(qtbot,mocker): qtbot.addWidget(win) qtbot.waitForWindowShown(win) - editor = win.components['editor'] + editor = win.components["editor"] editor.set_text(code) return qtbot, win + @pytest.fixture -def main_clean_do_not_close(qtbot,mocker): +def main_clean_do_not_close(qtbot, mocker): - mocker.patch.object(QMessageBox, 'question', return_value=QMessageBox.No) + mocker.patch.object(QMessageBox, "question", return_value=QMessageBox.No) win = MainWindow() win.show() @@ -168,16 +168,17 @@ def main_clean_do_not_close(qtbot,mocker): qtbot.addWidget(win) qtbot.waitForWindowShown(win) - editor = win.components['editor'] + editor = win.components["editor"] editor.set_text(code) return qtbot, win + @pytest.fixture -def main_multi(qtbot,mocker): +def main_multi(qtbot, mocker): - mocker.patch.object(QMessageBox, 'question', return_value=QMessageBox.Yes) - mocker.patch.object(QFileDialog, 'getSaveFileName', return_value=('out.step','')) + mocker.patch.object(QMessageBox, "question", return_value=QMessageBox.Yes) + mocker.patch.object(QFileDialog, "getSaveFileName", return_value=("out.step", "")) win = MainWindow() win.show() @@ -185,116 +186,120 @@ def main_multi(qtbot,mocker): qtbot.addWidget(win) qtbot.waitForWindowShown(win) - editor = win.components['editor'] + editor = win.components["editor"] editor.set_text(code_multi) - debugger = win.components['debugger'] - debugger._actions['Run'][0].triggered.emit() + debugger = win.components["debugger"] + debugger._actions["Run"][0].triggered.emit() return qtbot, win + def test_render(main): qtbot, win = main - obj_tree_comp = win.components['object_tree'] - editor = win.components['editor'] - debugger = win.components['debugger'] - console = win.components['console'] - log = win.components['log'] + obj_tree_comp = win.components["object_tree"] + editor = win.components["editor"] + debugger = win.components["debugger"] + console = win.components["console"] + log = win.components["log"] # enable CQ reloading - debugger.preferences['Reload CQ'] = True + debugger.preferences["Reload CQ"] = True # check that object was rendered - assert(obj_tree_comp.CQ.childCount() == 1) + assert obj_tree_comp.CQ.childCount() == 1 # check that object was removed obj_tree_comp._toolbar_actions[0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 0) + assert obj_tree_comp.CQ.childCount() == 0 # check that object was rendered usin explicit show_object call editor.set_text(code_show_Workplane) - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 1) + assert obj_tree_comp.CQ.childCount() == 1 obj_tree_comp._toolbar_actions[0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 0) + assert obj_tree_comp.CQ.childCount() == 0 # check that cq.Shape object was rendered using explicit show_object call editor.set_text(code_show_Shape) - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 1) + assert obj_tree_comp.CQ.childCount() == 1 obj_tree_comp._toolbar_actions[0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 0) + assert obj_tree_comp.CQ.childCount() == 0 # test rendering via console console.execute(code_show_Workplane) - assert(obj_tree_comp.CQ.childCount() == 1) + assert obj_tree_comp.CQ.childCount() == 1 obj_tree_comp._toolbar_actions[0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 0) + assert obj_tree_comp.CQ.childCount() == 0 console.execute(code_show_Shape) - assert(obj_tree_comp.CQ.childCount() == 1) + assert obj_tree_comp.CQ.childCount() == 1 # check object rendering using show_object call with a name specified and # debug call editor.set_text(code_show_Workplane_named) - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() qtbot.wait(100) - assert(obj_tree_comp.CQ.child(0).text(0) == 'test') - assert('test' in log.toPlainText().splitlines()[-1]) + assert obj_tree_comp.CQ.child(0).text(0) == "test" + assert "test" in log.toPlainText().splitlines()[-1] # cq reloading check obj_tree_comp._toolbar_actions[0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 0) + assert obj_tree_comp.CQ.childCount() == 0 editor.set_text(code_reload_issue) - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() qtbot.wait(100) - assert(obj_tree_comp.CQ.childCount() == 3) + assert obj_tree_comp.CQ.childCount() == 3 - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() qtbot.wait(100) - assert(obj_tree_comp.CQ.childCount() == 3) + assert obj_tree_comp.CQ.childCount() == 3 -def test_export(main,mocker): + +def test_export(main, mocker): qtbot, win = main - debugger = win.components['debugger'] - debugger._actions['Run'][0].triggered.emit() + debugger = win.components["debugger"] + debugger._actions["Run"][0].triggered.emit() - #set focus - obj_tree = win.components['object_tree'].tree - obj_tree_comp = win.components['object_tree'] + # set focus + obj_tree = win.components["object_tree"].tree + obj_tree_comp = win.components["object_tree"] qtbot.mouseClick(obj_tree, Qt.LeftButton) qtbot.keyClick(obj_tree, Qt.Key_Down) qtbot.keyClick(obj_tree, Qt.Key_Down) - #export STL - mocker.patch.object(QFileDialog, 'getSaveFileName', return_value=('out.stl','')) + # export STL + mocker.patch.object(QFileDialog, "getSaveFileName", return_value=("out.stl", "")) obj_tree_comp._export_STL_action.triggered.emit() - assert(os.path.isfile('out.stl')) + assert os.path.isfile("out.stl") - #export STEP - mocker.patch.object(QFileDialog, 'getSaveFileName', return_value=('out.step','')) + # export STEP + mocker.patch.object(QFileDialog, "getSaveFileName", return_value=("out.step", "")) obj_tree_comp._export_STEP_action.triggered.emit() - assert(os.path.isfile('out.step')) + assert os.path.isfile("out.step") + + # clean + os.remove("out.step") + os.remove("out.stl") - #clean - os.remove('out.step') - os.remove('out.stl') def number_visible_items(viewer): from OCP.AIS import AIS_ListOfInteractive + l = AIS_ListOfInteractive() viewer_ctx = viewer._get_context() @@ -302,186 +307,223 @@ def number_visible_items(viewer): return l.Extent() + def test_inspect(main): qtbot, win = main - #set focus and make invisible - obj_tree = win.components['object_tree'].tree + # set focus and make invisible + obj_tree = win.components["object_tree"].tree qtbot.mouseClick(obj_tree, Qt.LeftButton) qtbot.keyClick(obj_tree, Qt.Key_Down) qtbot.keyClick(obj_tree, Qt.Key_Down) qtbot.keyClick(obj_tree, Qt.Key_Space) - #enable object inspector - insp = win.components['cq_object_inspector'] + # enable object inspector + insp = win.components["cq_object_inspector"] insp._toolbar_actions[0].toggled.emit(True) - #check if all stack items are visible in the tree - assert(insp.root.childCount() == 3) + # check if all stack items are visible in the tree + assert insp.root.childCount() == 3 - #check if correct number of items is displayed - viewer = win.components['viewer'] + # check if correct number of items is displayed + viewer = win.components["viewer"] insp.setCurrentItem(insp.root.child(0)) - assert(number_visible_items(viewer) == 4) + assert number_visible_items(viewer) == 4 insp.setCurrentItem(insp.root.child(1)) - assert(number_visible_items(viewer) == 7) + assert number_visible_items(viewer) == 7 insp.setCurrentItem(insp.root.child(2)) - assert(number_visible_items(viewer) == 4) + assert number_visible_items(viewer) == 4 insp._toolbar_actions[0].toggled.emit(False) - assert(number_visible_items(viewer) == 3) + assert number_visible_items(viewer) == 3 + class event_loop(object): - '''Used to mock the QEventLoop for the debugger component - ''' + """Used to mock the QEventLoop for the debugger component""" - def __init__(self,callbacks): + def __init__(self, callbacks): self.callbacks = callbacks self.i = 0 def exec_(self): - if self.i 0) - assert(conv_line_ends(editor.get_text_with_eol()) == code) + # check that loading from file works properly + editor.load_from_file("test.py") + assert len(editor.get_text_with_eol()) > 0 + assert conv_line_ends(editor.get_text_with_eol()) == code - #check that loading from file works properly + # check that loading from file works properly editor.new() - assert(editor.get_text_with_eol() == '') + assert editor.get_text_with_eol() == "" - #monkeypatch QFileDialog methods + # monkeypatch QFileDialog methods def filename(*args, **kwargs): - return 'test.py',None + return "test.py", None def filename2(*args, **kwargs): - return 'test2.py',None + return "test2.py", None - monkeypatch.setattr(QFileDialog, 'getOpenFileName', - staticmethod(filename)) + monkeypatch.setattr(QFileDialog, "getOpenFileName", staticmethod(filename)) - monkeypatch.setattr(QFileDialog, 'getSaveFileName', - staticmethod(filename2)) + monkeypatch.setattr(QFileDialog, "getSaveFileName", staticmethod(filename2)) - #check that open file works properly + # check that open file works properly editor.open() - assert(conv_line_ends(editor.get_text_with_eol()) == code) + assert conv_line_ends(editor.get_text_with_eol()) == code - #check that save file works properly + # check that save file works properly editor.new() qtbot.mouseClick(editor, Qt.LeftButton) - qtbot.keyClick(editor,Qt.Key_A) + qtbot.keyClick(editor, Qt.Key_A) - assert(editor.document().isModified() == True) + assert editor.document().isModified() == True - editor.filename = 'test2.py' + editor.filename = "test2.py" editor.save() - assert(editor.document().isModified() == False) + assert editor.document().isModified() == False - monkeypatch.setattr(QFileDialog, 'getOpenFileName', - staticmethod(filename2)) + monkeypatch.setattr(QFileDialog, "getOpenFileName", staticmethod(filename2)) editor.open() - assert(editor.get_text_with_eol() == 'a') + assert editor.get_text_with_eol() == "a" - #check that save as works properly - os.remove('test2.py') + # check that save as works properly + os.remove("test2.py") editor.save_as() - assert(os.path.exists(filename2()[0])) + assert os.path.exists(filename2()[0]) - #test persistance - settings = QSettings('test') + # test persistance + settings = QSettings("test") editor.saveComponentState(settings) editor.new() - assert(editor.get_text_with_eol() == '') + assert editor.get_text_with_eol() == "" editor.restoreComponentState(settings) - assert(editor.get_text_with_eol() == 'a') + assert editor.get_text_with_eol() == "a" - #test error handling - os.remove('test2.py') - assert(not os.path.exists('test2.py')) + # test error handling + os.remove("test2.py") + assert not os.path.exists("test2.py") editor.restoreComponentState(settings) + @pytest.mark.repeat(1) -def test_editor_autoreload(monkeypatch,editor): +def test_editor_autoreload(monkeypatch, editor): qtbot, editor = editor @@ -668,13 +709,13 @@ def test_editor_autoreload(monkeypatch,editor): # start out with autoreload enabled editor.autoreload(True) - with open('test.py','w') as f: + with open("test.py", "w") as f: f.write(code) - assert(editor.get_text_with_eol() == '') + assert editor.get_text_with_eol() == "" - editor.load_from_file('test.py') - assert(len(editor.get_text_with_eol()) > 0) + editor.load_from_file("test.py") + assert len(editor.get_text_with_eol()) > 0 # wait for reload. with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT): @@ -682,7 +723,7 @@ def test_editor_autoreload(monkeypatch,editor): modify_file(code_bigger_object) # check that editor has updated file contents - assert(code_bigger_object.splitlines()[2] in editor.get_text_with_eol()) + assert code_bigger_object.splitlines()[2] in editor.get_text_with_eol() # disable autoreload editor.autoreload(False) @@ -696,7 +737,7 @@ def test_editor_autoreload(monkeypatch,editor): modify_file(code) # editor should continue showing old contents since autoreload is disabled. - assert(code_bigger_object.splitlines()[2] in editor.get_text_with_eol()) + assert code_bigger_object.splitlines()[2] in editor.get_text_with_eol() # Saving a file with autoreload disabled should not trigger a rerender. with pytest.raises(pytestqt.exceptions.TimeoutError): @@ -709,6 +750,7 @@ def test_editor_autoreload(monkeypatch,editor): with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT): editor.save() + def test_autoreload_nested(editor): qtbot, editor = editor @@ -716,156 +758,158 @@ def test_autoreload_nested(editor): TIMEOUT = 500 editor.autoreload(True) - editor.preferences['Autoreload: watch imported modules'] = True + editor.preferences["Autoreload: watch imported modules"] = True - with open('test_nested_top.py','w') as f: + with open("test_nested_top.py", "w") as f: f.write(code_nested_top) - with open('test_nested_bottom.py','w') as f: + with open("test_nested_bottom.py", "w") as f: f.write("") - assert(editor.get_text_with_eol() == '') + assert editor.get_text_with_eol() == "" - editor.load_from_file('test_nested_top.py') - assert(len(editor.get_text_with_eol()) > 0) + editor.load_from_file("test_nested_top.py") + assert len(editor.get_text_with_eol()) > 0 # wait for reload. with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT): # modify file - NB: separate process is needed to avoid Windows quirks - modify_file(code_nested_bottom, 'test_nested_bottom.py') + modify_file(code_nested_bottom, "test_nested_bottom.py") + def test_console(main): qtbot, win = main - console = win.components['console'] + console = win.components["console"] # test execute_command a = [] - console.push_vars({'a' : a}) - console.execute_command('a.append(1)') - assert(len(a) == 1) + console.push_vars({"a": a}) + console.execute_command("a.append(1)") + assert len(a) == 1 # test print_text pos_orig = console._prompt_pos - console.print_text('a') - assert(console._prompt_pos == pos_orig + len('a')) + console.print_text("a") + assert console._prompt_pos == pos_orig + len("a") + def test_viewer(main): qtbot, win = main - viewer = win.components['viewer'] + viewer = win.components["viewer"] + + # not sure how to test this, so only smoke tests - #not sure how to test this, so only smoke tests + # trigger all 'View' actions + actions = viewer._actions["View"] + for a in actions: + a.trigger() - #trigger all 'View' actions - actions = viewer._actions['View'] - for a in actions: a.trigger() -code_module = \ -'''def dummy(): return True''' +code_module = """def dummy(): return True""" + +code_import = """from module import dummy +assert(dummy())""" -code_import = \ -'''from module import dummy -assert(dummy())''' def test_module_import(main): qtbot, win = main - editor = win.components['editor'] - debugger = win.components['debugger'] - traceback_view = win.components['traceback_viewer'] + editor = win.components["editor"] + debugger = win.components["debugger"] + traceback_view = win.components["traceback_viewer"] - #save the dummy module - with open('module.py','w') as f: + # save the dummy module + with open("module.py", "w") as f: f.write(code_module) - #run the code importing this module + # run the code importing this module editor.set_text(code_import) - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() + + # verify that no exception was generated + assert traceback_view.current_exception.text() == "" - #verify that no exception was generated - assert(traceback_view.current_exception.text() == '') def test_auto_fit_view(main_clean): - def concat(eye,proj,scale): - return eye+proj+(scale,) + def concat(eye, proj, scale): + return eye + proj + (scale,) - def approx_view_properties(eye,proj,scale): + def approx_view_properties(eye, proj, scale): - return pytest.approx(eye+proj+(scale,)) + return pytest.approx(eye + proj + (scale,)) qtbot, win = main_clean - editor = win.components['editor'] - debugger = win.components['debugger'] - viewer = win.components['viewer'] - object_tree = win.components['object_tree'] + editor = win.components["editor"] + debugger = win.components["debugger"] + viewer = win.components["viewer"] + object_tree = win.components["object_tree"] view = viewer.canvas.view - viewer.preferences['Fit automatically'] = False - eye0,proj0,scale0 = view.Eye(),view.Proj(),view.Scale() + viewer.preferences["Fit automatically"] = False + eye0, proj0, scale0 = view.Eye(), view.Proj(), view.Scale() # check if camera position is adjusted automatically when rendering for the # first time debugger.render() - eye1,proj1,scale1 = view.Eye(),view.Proj(),view.Scale() - assert( concat(eye0,proj0,scale0) != \ - approx_view_properties(eye1,proj1,scale1) ) + eye1, proj1, scale1 = view.Eye(), view.Proj(), view.Scale() + assert concat(eye0, proj0, scale0) != approx_view_properties(eye1, proj1, scale1) # check if camera position is not changed fter code change editor.set_text(code_bigger_object) debugger.render() - eye2,proj2,scale2 = view.Eye(),view.Proj(),view.Scale() - assert( concat(eye1,proj1,scale1) == \ - approx_view_properties(eye2,proj2,scale2) ) + eye2, proj2, scale2 = view.Eye(), view.Proj(), view.Scale() + assert concat(eye1, proj1, scale1) == approx_view_properties(eye2, proj2, scale2) # check if position is adjusted automatically after erasing all objects object_tree.removeObjects() debugger.render() - eye3,proj3,scale3 = view.Eye(),view.Proj(),view.Scale() - assert( concat(eye2,proj2,scale2) != \ - approx_view_properties(eye3,proj3,scale3) ) + eye3, proj3, scale3 = view.Eye(), view.Proj(), view.Scale() + assert concat(eye2, proj2, scale2) != approx_view_properties(eye3, proj3, scale3) # check if position is adjusted automatically if settings are changed - viewer.preferences['Fit automatically'] = True + viewer.preferences["Fit automatically"] = True editor.set_text(code) debugger.render() - eye4,proj4,scale4 = view.Eye(),view.Proj(),view.Scale() - assert( concat(eye3,proj3,scale3) != \ - approx_view_properties(eye4,proj4,scale4) ) + eye4, proj4, scale4 = view.Eye(), view.Proj(), view.Scale() + assert concat(eye3, proj3, scale3) != approx_view_properties(eye4, proj4, scale4) + def test_preserve_properties(main): qtbot, win = main - debugger = win.components['debugger'] - debugger._actions['Run'][0].triggered.emit() + debugger = win.components["debugger"] + debugger._actions["Run"][0].triggered.emit() - object_tree = win.components['object_tree'] - object_tree.preferences['Preserve properties on reload'] = True + object_tree = win.components["object_tree"] + object_tree.preferences["Preserve properties on reload"] = True - assert(object_tree.CQ.childCount() == 1) + assert object_tree.CQ.childCount() == 1 props = object_tree.CQ.child(0).properties - props['Visible'] = False - props['Color'] = '#caffee' - props['Alpha'] = 0.5 + props["Visible"] = False + props["Color"] = "#caffee" + props["Alpha"] = 0.5 - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() - assert(object_tree.CQ.childCount() == 1) + assert object_tree.CQ.childCount() == 1 props = object_tree.CQ.child(0).properties - assert(props['Visible'] == False) - assert(props['Color'].name() == '#caffee') - assert(props['Alpha'] == 0.5) + assert props["Visible"] == False + assert props["Color"].name() == "#caffee" + assert props["Alpha"] == 0.5 -def test_selection(main_multi,mocker): + +def test_selection(main_multi, mocker): qtbot, win = main_multi - viewer = win.components['viewer'] - object_tree = win.components['object_tree'] + viewer = win.components["viewer"] + object_tree = win.components["object_tree"] CQ = object_tree.CQ obj1 = CQ.child(0) @@ -876,23 +920,23 @@ def test_selection(main_multi,mocker): obj2.setSelected(True) object_tree._export_STEP_action.triggered.emit() - imported = cq.importers.importStep('out.step') - assert(len(imported.solids().vals()) == 2) + imported = cq.importers.importStep("out.step") + assert len(imported.solids().vals()) == 2 # export with one selected objects obj2.setSelected(False) object_tree._export_STEP_action.triggered.emit() - imported = cq.importers.importStep('out.step') - assert(len(imported.solids().vals()) == 1) + imported = cq.importers.importStep("out.step") + assert len(imported.solids().vals()) == 1 # export with one selected objects obj1.setSelected(False) CQ.setSelected(True) object_tree._export_STEP_action.triggered.emit() - imported = cq.importers.importStep('out.step') - assert(len(imported.solids().vals()) == 2) + imported = cq.importers.importStep("out.step") + assert len(imported.solids().vals()) == 2 # check if viewer and object tree are properly connected CQ.setSelected(False) @@ -905,15 +949,15 @@ def test_selection(main_multi,mocker): while ctx.MoreSelected(): shapes.append(ctx.SelectedShape()) ctx.NextSelected() - assert(len(shapes) == 2) + assert len(shapes) == 2 viewer.fit() qtbot.mouseClick(viewer.canvas, Qt.LeftButton) - assert(len(object_tree.tree.selectedItems()) == 0) + assert len(object_tree.tree.selectedItems()) == 0 viewer.sigObjectSelected.emit([obj1.shape_display.wrapped]) - assert(len(object_tree.tree.selectedItems()) == 1) + assert len(object_tree.tree.selectedItems()) == 1 # go through different handleSelection paths qtbot.mouseClick(object_tree.tree, Qt.LeftButton) @@ -922,111 +966,121 @@ def test_selection(main_multi,mocker): qtbot.keyClick(object_tree.tree, Qt.Key_Down) qtbot.keyClick(object_tree.tree, Qt.Key_Down) - assert(object_tree._export_STL_action.isEnabled() == False) - assert(object_tree._export_STEP_action.isEnabled() == False) - assert(object_tree._clear_current_action.isEnabled() == False) - assert(object_tree.properties_editor.isEnabled() == False) + assert object_tree._export_STL_action.isEnabled() == False + assert object_tree._export_STEP_action.isEnabled() == False + assert object_tree._clear_current_action.isEnabled() == False + assert object_tree.properties_editor.isEnabled() == False + def test_closing(main_clean_do_not_close): - qtbot,win = main_clean_do_not_close + qtbot, win = main_clean_do_not_close - editor = win.components['editor'] + editor = win.components["editor"] # make sure that windows is visible - assert(win.isVisible()) + assert win.isVisible() # should not quit win.close() - assert(win.isVisible()) + assert win.isVisible() # should quit editor.reset_modified() win.close() - assert(not win.isVisible()) + assert not win.isVisible() + -def test_check_for_updates(main,mocker): +def test_check_for_updates(main, mocker): - qtbot,win = main + qtbot, win = main # patch requests import requests - mocker.patch.object(requests.models.Response,'json', - return_value=[{'tag_name' : '0.0.2','draft' : False}]) + + mocker.patch.object( + requests.models.Response, + "json", + return_value=[{"tag_name": "0.0.2", "draft": False}], + ) # stub QMessageBox about about_stub = mocker.stub() - mocker.patch.object(QMessageBox, 'about', about_stub) + mocker.patch.object(QMessageBox, "about", about_stub) import cadquery - cadquery.__version__ = '0.0.1' + cadquery.__version__ = "0.0.1" win.check_for_cq_updates() - assert(about_stub.call_args[0][1] == 'Updates available') + assert about_stub.call_args[0][1] == "Updates available" - cadquery.__version__ = '0.0.3' + cadquery.__version__ = "0.0.3" win.check_for_cq_updates() - assert(about_stub.call_args[0][1] == 'No updates available') + assert about_stub.call_args[0][1] == "No updates available" + -@pytest.mark.skipif(sys.platform.startswith('linux'),reason='Segfault workaround for linux') -def test_screenshot(main,mocker): +@pytest.mark.skipif( + sys.platform.startswith("linux"), reason="Segfault workaround for linux" +) +def test_screenshot(main, mocker): - qtbot,win = main + qtbot, win = main + + mocker.patch.object(QFileDialog, "getSaveFileName", return_value=("out.png", "")) - mocker.patch.object(QFileDialog, 'getSaveFileName', return_value=('out.png','')) + viewer = win.components["viewer"] + viewer._actions["Tools"][0].triggered.emit() - viewer = win.components['viewer'] - viewer._actions['Tools'][0].triggered.emit() + assert os.path.exists("out.png") - assert(os.path.exists('out.png')) def test_resize(main): - qtbot,win = main - editor = win.components['editor'] + qtbot, win = main + editor = win.components["editor"] editor.hide() qtbot.wait(50) editor.show() qtbot.wait(50) -code_simple_step = \ -'''import cadquery as cq + +code_simple_step = """import cadquery as cq imported = cq.importers.importStep('shape.step') -''' +""" + def test_relative_references(main): # create code with a relative reference in a subdirectory - p = Path('test_relative_references') + p = Path("test_relative_references") p.mkdir_p() - p_code = p.joinpath('code.py') + p_code = p.joinpath("code.py") p_code.write_text(code_simple_step) # create the referenced step file shape = cq.Workplane("XY").box(1, 1, 1) - p_step = p.joinpath('shape.step') + p_step = p.joinpath("shape.step") export(shape, "step", p_step) # open code qtbot, win = main - editor = win.components['editor'] + editor = win.components["editor"] editor.load_from_file(p_code) # render - debugger = win.components['debugger'] - debugger._actions['Run'][0].triggered.emit() + debugger = win.components["debugger"] + debugger._actions["Run"][0].triggered.emit() # assert no errors - traceback_view = win.components['traceback_viewer'] - assert(traceback_view.current_exception.text() == '') + traceback_view = win.components["traceback_viewer"] + assert traceback_view.current_exception.text() == "" # assert one object has been rendered - obj_tree_comp = win.components['object_tree'] - assert(obj_tree_comp.CQ.childCount() == 1) + obj_tree_comp = win.components["object_tree"] + assert obj_tree_comp.CQ.childCount() == 1 # clean up p_code.remove_p() p_step.remove_p() p.rmdir_p() -code_color = \ -''' +code_color = """ import cadquery as cq result = cq.Workplane("XY" ).box(1, 1, 1) @@ -1037,19 +1091,20 @@ def test_relative_references(main): show_object(result, name ='5', options=dict(alpha=0.5,color=(1.,0,0))) show_object(result, name ='6', options=dict(rgba=(1.,0,0,.5))) show_object(result, name ='7', options=dict(color=('ff','cc','dd'))) -''' +""" + def test_render_colors(main_clean): qtbot, win = main_clean - obj_tree = win.components['object_tree'] - editor = win.components['editor'] - debugger = win.components['debugger'] - log = win.components['log'] + obj_tree = win.components["object_tree"] + editor = win.components["editor"] + debugger = win.components["debugger"] + log = win.components["log"] editor.set_text(code_color) - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() CQ = obj_tree.CQ @@ -1057,42 +1112,43 @@ def test_render_colors(main_clean): assert not CQ.child(0).ais.HasColor() # object 2 - r,g,b,a = get_rgba(CQ.child(1).ais) - assert( a == 0.5 ) - assert( r == 1.0 ) - assert( g == 0.0 ) + r, g, b, a = get_rgba(CQ.child(1).ais) + assert a == 0.5 + assert r == 1.0 + assert g == 0.0 # object 3 - r,g,b,a = get_rgba(CQ.child(2).ais) - assert( a == 0.5) - assert( r == 1.0 ) + r, g, b, a = get_rgba(CQ.child(2).ais) + assert a == 0.5 + assert r == 1.0 # object 4 - r,g,b,a = get_rgba(CQ.child(3).ais) - assert( a == 0.5 ) - assert( r == 1.0 ) + r, g, b, a = get_rgba(CQ.child(3).ais) + assert a == 0.5 + assert r == 1.0 # object 5 - r,g,b,a = get_rgba(CQ.child(4).ais) - assert( a == 0.5 ) - assert( r == 1.0 ) + r, g, b, a = get_rgba(CQ.child(4).ais) + assert a == 0.5 + assert r == 1.0 # object 6 - r,g,b,a = get_rgba(CQ.child(5).ais) - assert( a == 0.5 ) - assert( r == 1.0 ) + r, g, b, a = get_rgba(CQ.child(5).ais) + assert a == 0.5 + assert r == 1.0 # check if error occured qtbot.wait(100) - assert('Unknown color format' in log.toPlainText().splitlines()[-1]) + assert "Unknown color format" in log.toPlainText().splitlines()[-1] + def test_render_colors_console(main_clean): qtbot, win = main_clean - obj_tree = win.components['object_tree'] - log = win.components['log'] - console = win.components['console'] + obj_tree = win.components["object_tree"] + log = win.components["log"] + console = win.components["console"] console.execute_command(code_color) @@ -1102,54 +1158,55 @@ def test_render_colors_console(main_clean): assert not CQ.child(0).ais.HasColor() # object 2 - r,g,b,a = get_rgba(CQ.child(1).ais) - assert( a == 0.5 ) - assert( r == 1.0 ) + r, g, b, a = get_rgba(CQ.child(1).ais) + assert a == 0.5 + assert r == 1.0 # object 3 - r,g,b,a = get_rgba(CQ.child(2).ais) - assert( a == 0.5) - assert( r == 1.0 ) + r, g, b, a = get_rgba(CQ.child(2).ais) + assert a == 0.5 + assert r == 1.0 # object 4 - r,g,b,a = get_rgba(CQ.child(3).ais) - assert( a == 0.5 ) - assert( r == 1.0 ) + r, g, b, a = get_rgba(CQ.child(3).ais) + assert a == 0.5 + assert r == 1.0 # object 5 - r,g,b,a = get_rgba(CQ.child(4).ais) - assert( a == 0.5 ) - assert( r == 1.0 ) + r, g, b, a = get_rgba(CQ.child(4).ais) + assert a == 0.5 + assert r == 1.0 # object 6 - r,g,b,a = get_rgba(CQ.child(5).ais) - assert( a == 0.5 ) - assert( r == 1.0 ) + r, g, b, a = get_rgba(CQ.child(5).ais) + assert a == 0.5 + assert r == 1.0 # check if error occured qtbot.wait(100) - assert('Unknown color format' in log.toPlainText().splitlines()[-1]) + assert "Unknown color format" in log.toPlainText().splitlines()[-1] -code_shading = \ -''' + +code_shading = """ import cadquery as cq res1 = cq.Workplane('XY').box(5, 7, 5) res2 = cq.Workplane('XY').box(8, 5, 4) show_object(res1) show_object(res2,options={"alpha":0}) -''' +""" + def test_shading_aspect(main_clean): qtbot, win = main_clean - obj_tree = win.components['object_tree'] - editor = win.components['editor'] - debugger = win.components['debugger'] + obj_tree = win.components["object_tree"] + editor = win.components["editor"] + debugger = win.components["debugger"] editor.set_text(code_shading) - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() CQ = obj_tree.CQ @@ -1160,147 +1217,148 @@ def test_shading_aspect(main_clean): # verify that they are the same assert ma1.Shininess() == ma2.Shininess() -def test_confirm_new(monkeypatch,editor): + +def test_confirm_new(monkeypatch, editor): qtbot, editor = editor - #check that initial state is as expected - assert(editor.modified == False) + # check that initial state is as expected + assert editor.modified == False editor.document().setPlainText(code) - assert(editor.modified == True) + assert editor.modified == True - #monkeypatch the confirmation dialog and run both scenarios + # monkeypatch the confirmation dialog and run both scenarios def cancel(*args, **kwargs): return QMessageBox.No def ok(*args, **kwargs): return QMessageBox.Yes - monkeypatch.setattr(QMessageBox, 'question', - staticmethod(cancel)) + monkeypatch.setattr(QMessageBox, "question", staticmethod(cancel)) editor.new() - assert(editor.modified == True) - assert(conv_line_ends(editor.get_text_with_eol()) == code) + assert editor.modified == True + assert conv_line_ends(editor.get_text_with_eol()) == code - monkeypatch.setattr(QMessageBox, 'question', - staticmethod(ok)) + monkeypatch.setattr(QMessageBox, "question", staticmethod(ok)) editor.new() - assert(editor.modified == False) - assert(editor.get_text_with_eol() == '') + assert editor.modified == False + assert editor.get_text_with_eol() == "" -code_show_topods = \ -''' + +code_show_topods = """ import cadquery as cq result = cq.Workplane("XY" ).box(1, 1, 1) show_object(result.val().wrapped) -''' +""" + def test_render_topods(main): qtbot, win = main - obj_tree_comp = win.components['object_tree'] - editor = win.components['editor'] - debugger = win.components['debugger'] - console = win.components['console'] + obj_tree_comp = win.components["object_tree"] + editor = win.components["editor"] + debugger = win.components["debugger"] + console = win.components["console"] # check that object was rendered - assert(obj_tree_comp.CQ.childCount() == 1) + assert obj_tree_comp.CQ.childCount() == 1 # check that object was removed obj_tree_comp._toolbar_actions[0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 0) + assert obj_tree_comp.CQ.childCount() == 0 # check that object was rendered usin explicit show_object call editor.set_text(code_show_topods) - debugger._actions['Run'][0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 1) + debugger._actions["Run"][0].triggered.emit() + assert obj_tree_comp.CQ.childCount() == 1 # test rendering of topods object via console - console.execute('show(result.val().wrapped)') - assert(obj_tree_comp.CQ.childCount() == 2) + console.execute("show(result.val().wrapped)") + assert obj_tree_comp.CQ.childCount() == 2 # test rendering of list of topods object via console - console.execute('show([result.val().wrapped,result.val().wrapped])') - assert(obj_tree_comp.CQ.childCount() == 3) + console.execute("show([result.val().wrapped,result.val().wrapped])") + assert obj_tree_comp.CQ.childCount() == 3 -code_show_shape_list = \ -''' +code_show_shape_list = """ import cadquery as cq result1 = cq.Workplane("XY" ).box(1, 1, 1).val() result2 = cq.Workplane("XY",origin=(0,1,1)).box(1, 1, 1).val() show_object(result1) show_object([result1,result2]) -''' +""" + def test_render_shape_list(main): qtbot, win = main - log = win.components['log'] + log = win.components["log"] - obj_tree_comp = win.components['object_tree'] - editor = win.components['editor'] - debugger = win.components['debugger'] - console = win.components['console'] + obj_tree_comp = win.components["object_tree"] + editor = win.components["editor"] + debugger = win.components["debugger"] + console = win.components["console"] # check that object was removed obj_tree_comp._toolbar_actions[0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 0) + assert obj_tree_comp.CQ.childCount() == 0 # check that object was rendered usin explicit show_object call editor.set_text(code_show_shape_list) - debugger._actions['Run'][0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 2) + debugger._actions["Run"][0].triggered.emit() + assert obj_tree_comp.CQ.childCount() == 2 # test rendering of Shape via console - console.execute('show(result1)') - console.execute('show([result1,result2])') - assert(obj_tree_comp.CQ.childCount() == 4) + console.execute("show(result1)") + console.execute("show([result1,result2])") + assert obj_tree_comp.CQ.childCount() == 4 # smoke test exception in show console.execute('show("a")') -code_show_assy = \ -'''import cadquery as cq + +code_show_assy = """import cadquery as cq result1 = cq.Workplane("XY" ).box(3, 3, 0.5) assy = cq.Assembly(result1) show_object(assy) -''' +""" + def test_render_assy(main): qtbot, win = main - obj_tree_comp = win.components['object_tree'] - editor = win.components['editor'] - debugger = win.components['debugger'] - console = win.components['console'] + obj_tree_comp = win.components["object_tree"] + editor = win.components["editor"] + debugger = win.components["debugger"] + console = win.components["console"] # check that object was removed obj_tree_comp._toolbar_actions[0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 0) + assert obj_tree_comp.CQ.childCount() == 0 # check that object was rendered usin explicit show_object call editor.set_text(code_show_assy) - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() qtbot.wait(500) - assert(obj_tree_comp.CQ.childCount() == 1) + assert obj_tree_comp.CQ.childCount() == 1 # test rendering via console - console.execute('show(assy)') + console.execute("show(assy)") qtbot.wait(500) - assert(obj_tree_comp.CQ.childCount() == 2) + assert obj_tree_comp.CQ.childCount() == 2 -code_show_ais = \ -'''import cadquery as cq + +code_show_ais = """import cadquery as cq from cadquery.occ_impl.assembly import toCAF import OCP @@ -1312,101 +1370,107 @@ def test_render_assy(main): ais = OCP.XCAFPrs.XCAFPrs_AISObject(lab) show_object(ais) -''' +""" + def test_render_ais(main): qtbot, win = main - obj_tree_comp = win.components['object_tree'] - editor = win.components['editor'] - debugger = win.components['debugger'] - console = win.components['console'] + obj_tree_comp = win.components["object_tree"] + editor = win.components["editor"] + debugger = win.components["debugger"] + console = win.components["console"] # check that object was removed obj_tree_comp._toolbar_actions[0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 0) + assert obj_tree_comp.CQ.childCount() == 0 # check that object was rendered usin explicit show_object call editor.set_text(code_show_ais) - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() qtbot.wait(500) - assert(obj_tree_comp.CQ.childCount() == 1) + assert obj_tree_comp.CQ.childCount() == 1 # test rendering via console - console.execute('show(ais)') + console.execute("show(ais)") qtbot.wait(500) - assert(obj_tree_comp.CQ.childCount() == 2) + assert obj_tree_comp.CQ.childCount() == 2 + -code_show_sketch = \ -'''import cadquery as cq +code_show_sketch = """import cadquery as cq s1 = cq.Sketch().rect(1,1) s2 = cq.Sketch().segment((0,0), (0,3.),"s1") show_object(s1) show_object(s2) -''' +""" + def test_render_sketch(main): qtbot, win = main - obj_tree_comp = win.components['object_tree'] - editor = win.components['editor'] - debugger = win.components['debugger'] - console = win.components['console'] + obj_tree_comp = win.components["object_tree"] + editor = win.components["editor"] + debugger = win.components["debugger"] + console = win.components["console"] # check that object was removed obj_tree_comp._toolbar_actions[0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 0) + assert obj_tree_comp.CQ.childCount() == 0 # check that object was rendered usin explicit show_object call editor.set_text(code_show_sketch) - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() qtbot.wait(500) - assert(obj_tree_comp.CQ.childCount() == 2) + assert obj_tree_comp.CQ.childCount() == 2 # test rendering via console - console.execute('show(s1); show(s2)') + console.execute("show(s1); show(s2)") qtbot.wait(500) - assert(obj_tree_comp.CQ.childCount() == 4) + assert obj_tree_comp.CQ.childCount() == 4 + def test_window_title(monkeypatch, main): - fname = 'test_window_title.py' + fname = "test_window_title.py" - with open(fname, 'w') as f: + with open(fname, "w") as f: f.write(code) qtbot, win = main - #monkeypatch QFileDialog methods + # monkeypatch QFileDialog methods def filename(*args, **kwargs): return fname, None - monkeypatch.setattr(QFileDialog, 'getOpenFileName', - staticmethod(filename)) + monkeypatch.setattr(QFileDialog, "getOpenFileName", staticmethod(filename)) win.components["editor"].open() - assert(win.windowTitle().endswith(fname)) + assert win.windowTitle().endswith(fname) # handle a new file win.components["editor"].new() # I don't really care what the title is, as long as it's not a filename - assert(not win.windowTitle().endswith('.py')) + assert not win.windowTitle().endswith(".py") + def test_module_discovery(tmp_path, editor): qtbot, editor = editor - with open(tmp_path.joinpath('main.py'), 'w') as f: - f.write('import b') + with open(tmp_path.joinpath("main.py"), "w") as f: + f.write("import b") + + assert editor.get_imported_module_paths(str(tmp_path.joinpath("main.py"))) == [] - assert editor.get_imported_module_paths(str(tmp_path.joinpath('main.py'))) == [] + tmp_path.joinpath("b.py").touch() - tmp_path.joinpath('b.py').touch() + assert editor.get_imported_module_paths(str(tmp_path.joinpath("main.py"))) == [ + str(tmp_path.joinpath("b.py")) + ] - assert editor.get_imported_module_paths(str(tmp_path.joinpath('main.py'))) == [str(tmp_path.joinpath('b.py'))] def test_launch_syntax_error(tmp_path): @@ -1421,23 +1485,23 @@ def test_launch_syntax_error(tmp_path): editor.load_from_file(inputfile) win.show() - assert(win.isVisible()) + assert win.isVisible() -code_import_module_makebox = \ -""" + +code_import_module_makebox = """ from module_makebox import * z = 1 r = makebox(z) """ -code_module_makebox = \ -""" +code_module_makebox = """ import cadquery as cq def makebox(z): zval = z + 1 return cq.Workplane().box(1, 1, zval) """ + def test_reload_import_handle_error(tmp_path, main): TIMEOUT = 500 @@ -1458,18 +1522,18 @@ def test_reload_import_handle_error(tmp_path, main): # run, verify that no exception was generated editor.load_from_file(script) debugger._actions["Run"][0].triggered.emit() - assert(traceback_view.current_exception.text() == "") + assert traceback_view.current_exception.text() == "" # save the module with an error with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT): lines = code_module_makebox.splitlines() - lines.remove(" zval = z + 1") # introduce NameError + lines.remove(" zval = z + 1") # introduce NameError lines = "\n".join(lines) modify_file(lines, module_file) # verify NameError is generated debugger._actions["Run"][0].triggered.emit() - assert("NameError" in traceback_view.current_exception.text()) + assert "NameError" in traceback_view.current_exception.text() # revert the error, verify rerender is triggered with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT): @@ -1477,7 +1541,8 @@ def test_reload_import_handle_error(tmp_path, main): # verify that no exception was generated debugger._actions["Run"][0].triggered.emit() - assert(traceback_view.current_exception.text() == "") + assert traceback_view.current_exception.text() == "" + def test_modulefinder(tmp_path, main): @@ -1486,7 +1551,7 @@ def test_modulefinder(tmp_path, main): editor = win.components["editor"] debugger = win.components["debugger"] traceback_view = win.components["traceback_viewer"] - log = win.components['log'] + log = win.components["log"] editor.autoreload(True) editor.preferences["Autoreload: watch imported modules"] = True @@ -1499,56 +1564,58 @@ def test_modulefinder(tmp_path, main): modify_file("import emptydir", script) qtbot.wait(100) - assert("Cannot determine imported modules" in log.toPlainText().splitlines()[-1]) + assert "Cannot determine imported modules" in log.toPlainText().splitlines()[-1] + def test_show_all(main): qtbot, win = main - editor = win.components['editor'] - debugger = win.components['debugger'] - object_tree = win.components['object_tree'] + editor = win.components["editor"] + debugger = win.components["debugger"] + object_tree = win.components["object_tree"] # remove all objects object_tree.removeObjects() - assert(object_tree.CQ.childCount() == 0) + assert object_tree.CQ.childCount() == 0 # add code wtih Shape, Workplane, Assy, Sketch editor.set_text(code_show_all) # Run and check if all are shown - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() - assert(object_tree.CQ.childCount() == 4) + assert object_tree.CQ.childCount() == 4 -code_randcolor = \ -"""import cadquery as cq + +code_randcolor = """import cadquery as cq b = cq.Workplane().box(8, 3, 4) for i in range(10): show_object(b.translate((0,5*i,0)), options=rand_color(alpha=0)) show_object(b.translate((0,5*i,0)), options=rand_color(0, True)) """ + def test_randcolor(main): - + qtbot, win = main - obj_tree_comp = win.components['object_tree'] - editor = win.components['editor'] - debugger = win.components['debugger'] - console = win.components['console'] + obj_tree_comp = win.components["object_tree"] + editor = win.components["editor"] + debugger = win.components["debugger"] + console = win.components["console"] # check that object was removed obj_tree_comp._toolbar_actions[0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 0) + assert obj_tree_comp.CQ.childCount() == 0 # check that object was rendered usin explicit show_object call editor.set_text(code_randcolor) - debugger._actions['Run'][0].triggered.emit() - assert(obj_tree_comp.CQ.childCount() == 2*10) + debugger._actions["Run"][0].triggered.emit() + assert obj_tree_comp.CQ.childCount() == 2 * 10 -code_show_wo_name = \ -""" + +code_show_wo_name = """ import cadquery as cq res = cq.Workplane().box(1,1,1) @@ -1557,28 +1624,29 @@ def test_randcolor(main): show_object(cq.Workplane().box(1,1,1)) """ + def test_show_without_name(main): qtbot, win = main - editor = win.components['editor'] - debugger = win.components['debugger'] - object_tree = win.components['object_tree'] + editor = win.components["editor"] + debugger = win.components["debugger"] + object_tree = win.components["object_tree"] # remove all objects object_tree.removeObjects() - assert(object_tree.CQ.childCount() == 0) + assert object_tree.CQ.childCount() == 0 # add code wtih Shape, Workplane, Assy, Sketch editor.set_text(code_show_wo_name) # Run and check if all are shown - debugger._actions['Run'][0].triggered.emit() + debugger._actions["Run"][0].triggered.emit() - assert(object_tree.CQ.childCount() == 2) + assert object_tree.CQ.childCount() == 2 # Check the name of the first object - assert(object_tree.CQ.child(0).text(0) == "res") + assert object_tree.CQ.child(0).text(0) == "res" # Check that the name of the seconf object is an int int(object_tree.CQ.child(1).text(0))