367 lines
13 KiB
Python
367 lines
13 KiB
Python
import sys
|
|
|
|
from PyQt5.QtWidgets import (QLabel, QMainWindow, QToolBar, QDockWidget, QAction)
|
|
from logbook import Logger
|
|
import cadquery as cq
|
|
|
|
from .widgets.editor import Editor
|
|
from .widgets.viewer import OCCViewer
|
|
from .widgets.console import ConsoleWidget
|
|
from .widgets.object_tree import ObjectTree
|
|
from .widgets.traceback_viewer import TracebackPane
|
|
from .widgets.debugger import Debugger, LocalsView
|
|
from .widgets.cq_object_inspector import CQObjectInspector
|
|
from .widgets.log import LogViewer
|
|
|
|
from . import __version__
|
|
from .utils import dock, add_actions, open_url, about_dialog, check_gtihub_for_updates, confirm
|
|
from .mixins import MainMixin
|
|
from .icons import icon
|
|
from .preferences import PreferencesWidget
|
|
|
|
#DARKMODE edits: https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets
|
|
from PyQt5.QtCore import Qt
|
|
from PyQt5.QtWidgets import QApplication
|
|
from PyQt5.QtGui import QPalette, QColor
|
|
app = QApplication([])
|
|
# Force the style to be the same on all OSs:
|
|
app.setStyle("Fusion")
|
|
# Now use a palette to switch to dark colors:
|
|
palette = QPalette()
|
|
palette.setColor(QPalette.Window, QColor(53, 53, 53))
|
|
palette.setColor(QPalette.WindowText, Qt.white)
|
|
palette.setColor(QPalette.Base, QColor(25, 25, 25))
|
|
palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
|
|
palette.setColor(QPalette.ToolTipBase, Qt.black)
|
|
palette.setColor(QPalette.ToolTipText, Qt.white)
|
|
palette.setColor(QPalette.Text, Qt.white)
|
|
palette.setColor(QPalette.Button, QColor(53, 53, 53))
|
|
palette.setColor(QPalette.ButtonText, Qt.white)
|
|
palette.setColor(QPalette.BrightText, Qt.red)
|
|
palette.setColor(QPalette.Link, QColor(42, 130, 218))
|
|
palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
|
|
palette.setColor(QPalette.HighlightedText, Qt.black)
|
|
app.setPalette(palette)
|
|
|
|
class MainWindow(QMainWindow,MainMixin):
|
|
|
|
name = 'CQ-Editor'
|
|
org = 'CadQuery'
|
|
|
|
def __init__(self,parent=None, filename=None):
|
|
|
|
super(MainWindow,self).__init__(parent)
|
|
MainMixin.__init__(self)
|
|
|
|
self.setWindowIcon(icon('app'))
|
|
|
|
self.viewer = OCCViewer(self)
|
|
self.setCentralWidget(self.viewer.canvas)
|
|
|
|
self.prepare_panes()
|
|
self.registerComponent('viewer',self.viewer)
|
|
self.prepare_toolbar()
|
|
self.prepare_menubar()
|
|
|
|
self.prepare_statusbar()
|
|
self.prepare_actions()
|
|
|
|
self.components['object_tree'].addLines()
|
|
|
|
self.prepare_console()
|
|
|
|
self.fill_dummy()
|
|
|
|
self.setup_logging()
|
|
|
|
self.restorePreferences()
|
|
self.restoreWindow()
|
|
|
|
if filename:
|
|
self.components['editor'].load_from_file(filename)
|
|
|
|
self.restoreComponentState()
|
|
|
|
def closeEvent(self,event):
|
|
|
|
self.saveWindow()
|
|
self.savePreferences()
|
|
self.saveComponentState()
|
|
|
|
if self.components['editor'].document().isModified():
|
|
|
|
rv = confirm(self, 'Confirm close', 'Close without saving?')
|
|
|
|
if rv:
|
|
event.accept()
|
|
super(MainWindow,self).closeEvent(event)
|
|
else:
|
|
event.ignore()
|
|
else:
|
|
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'))
|
|
|
|
for d in self.docks.values():
|
|
d.show()
|
|
|
|
def prepare_menubar(self):
|
|
|
|
menu = self.menuBar()
|
|
|
|
menu_file = menu.addMenu('&File')
|
|
menu_edit = menu.addMenu('&Edit')
|
|
menu_tools = menu.addMenu('&Tools')
|
|
menu_run = menu.addMenu('&Run')
|
|
menu_view = menu.addMenu('&View')
|
|
menu_help = menu.addMenu('&Help')
|
|
|
|
#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())
|
|
|
|
#global menu elements
|
|
menu_view.addSeparator()
|
|
for d in self.findChildren(QDockWidget):
|
|
menu_view.addAction(d.toggleViewAction())
|
|
|
|
menu_view.addSeparator()
|
|
for t in self.findChildren(QToolBar):
|
|
menu_view.addAction(t.toggleViewAction())
|
|
|
|
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')
|
|
|
|
for c in self.components.values():
|
|
add_actions(self.toolbar,c.toolbarActions())
|
|
|
|
self.addToolBar(self.toolbar)
|
|
|
|
def prepare_statusbar(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)
|
|
|
|
# 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)
|
|
|
|
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})
|
|
|
|
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)')
|
|
|
|
def setup_logging(self):
|
|
|
|
from logbook.compat import redirect_logging
|
|
from logbook import INFO, Logger
|
|
|
|
redirect_logging()
|
|
self.components['log'].handler.level = INFO
|
|
self.components['log'].handler.push_application()
|
|
|
|
self._logger = Logger(self.name)
|
|
|
|
def handle_exception(exc_type, exc_value, exc_traceback):
|
|
|
|
if issubclass(exc_type, KeyboardInterrupt):
|
|
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
|
return
|
|
|
|
self._logger.error("Uncaught exception occurred",
|
|
exc_info=(exc_type, exc_value, exc_traceback))
|
|
|
|
sys.excepthook = handle_exception
|
|
|
|
|
|
def edit_preferences(self):
|
|
|
|
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',
|
|
)
|
|
|
|
def check_for_cq_updates(self):
|
|
|
|
check_gtihub_for_updates(self,cq)
|
|
|
|
def documentation(self):
|
|
|
|
open_url('https://github.com/CadQuery')
|
|
|
|
def cq_documentation(self):
|
|
|
|
open_url('https://cadquery.readthedocs.io/en/latest/')
|
|
|
|
def handle_filename_change(self, fname):
|
|
|
|
new_title = fname if fname else "*"
|
|
self.setWindowTitle(f"{self.name}: {new_title}")
|
|
|
|
if __name__ == "__main__":
|
|
|
|
pass
|