From 853c0f4391c4cd185b3ee7178a1303b11eda3ef6 Mon Sep 17 00:00:00 2001 From: Matthew Broadway Date: Sun, 21 Feb 2021 13:16:13 +0000 Subject: [PATCH] status bar messages to indicate rendering and viewing progress --- cq_editor/main_window.py | 47 +++++++++++++++++++++++---- cq_editor/widgets/debugger.py | 2 ++ cq_editor/widgets/object_tree.py | 20 +++++++----- cq_editor/widgets/traceback_viewer.py | 3 +- cq_editor/widgets/viewer.py | 37 +++++++++------------ 5 files changed, 71 insertions(+), 38 deletions(-) diff --git a/cq_editor/main_window.py b/cq_editor/main_window.py index d66890ec..259e7574 100644 --- a/cq_editor/main_window.py +++ b/cq_editor/main_window.py @@ -1,6 +1,9 @@ import sys -from PyQt5.QtWidgets import (QLabel, QMainWindow, QToolBar, QDockWidget, QAction) +from typing import Optional +from PyQt5.QtCore import pyqtSlot, Qt +from PyQt5.QtGui import QPalette, QColor +from PyQt5.QtWidgets import (QLabel, QMainWindow, QToolBar, QDockWidget, QAction, QApplication) import cadquery as cq @@ -55,6 +58,8 @@ def __init__(self,parent=None): self.restoreWindow() self.restoreComponentState() + self.on_idle() + def closeEvent(self,event): self.saveWindow() @@ -192,7 +197,7 @@ 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()) + add_actions(self.toolbar, c.toolbarActions()) self.addToolBar(self.toolbar) @@ -203,18 +208,25 @@ def prepare_statusbar(self): def prepare_actions(self): + self.components['debugger'].sigRenderStarted \ + .connect(self.on_render_start) self.components['debugger'].sigRendered\ .connect(self.components['object_tree'].addObjects) self.components['debugger'].sigTraceback\ .connect(self.components['traceback_viewer'].addTraceback) + self.components['debugger'].sigRendered \ + .connect(lambda _: self.on_idle()) + self.components['debugger'].sigTraceback \ + .connect(lambda _: self.on_idle()) + 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]\ + self.components['object_tree'].sigObjectsAdded[list, list]\ + .connect(lambda objects, names: self.components['viewer'].display_many(objects, None, names)) + self.components['object_tree'].sigObjectsAdded[list, bool, list]\ .connect(self.components['viewer'].display_many) self.components['object_tree'].sigItemChanged.\ connect(self.components['viewer'].update_item) @@ -229,6 +241,8 @@ def prepare_actions(self): self.components['viewer'].sigObjectSelected\ .connect(self.components['object_tree'].handleGraphicalSelection) + self.components['viewer'].sigDisplayProgress \ + .connect(self.on_display_progress) self.components['traceback_viewer'].sigHighlightLine\ .connect(self.components['editor'].go_to_line) @@ -332,6 +346,25 @@ def handle_filename_change(self, fname): new_title = fname if fname else "*" self.setWindowTitle(f"{self.name}: {new_title}") -if __name__ == "__main__": + def on_idle(self): + self.set_status_message('Idle', '#000000') - pass + @pyqtSlot() + def on_render_start(self): + self.set_status_message('Rendering...', '#ff0000') + + @pyqtSlot(int, int, str) + def on_display_progress(self, current: int, total: int, name: Optional[str]): + if current == total: + self.on_idle() + else: + message = f'Displaying Shape {current + 1} / {total}' + if name: + message += f' ({name})' + self.set_status_message(message, '#0000ff') + + def set_status_message(self, message: str, color: str): + self.statusBar().showMessage(message) + self.statusBar().setStyleSheet(f'color: {color}') + # required because rendering is currently done on the main thread + QApplication.processEvents() diff --git a/cq_editor/widgets/debugger.py b/cq_editor/widgets/debugger.py index b6810d0f..1a1d570d 100644 --- a/cq_editor/widgets/debugger.py +++ b/cq_editor/widgets/debugger.py @@ -110,6 +110,7 @@ class Debugger(QObject,ComponentMixin): {'name': 'Change working dir to script dir','type': 'bool', 'value': True}]) + sigRenderStarted = pyqtSignal() sigRendered = pyqtSignal(dict) sigLocals = pyqtSignal(dict) sigTraceback = pyqtSignal(object,str) @@ -221,6 +222,7 @@ def _cleanup_locals(self,module,injected_names): @pyqtSlot(bool) def render(self): + self.sigRenderStarted.emit() if self.preferences['Reload CQ']: reload_cq() diff --git a/cq_editor/widgets/object_tree.py b/cq_editor/widgets/object_tree.py index c0ea1dcf..3775b5da 100644 --- a/cq_editor/widgets/object_tree.py +++ b/cq_editor/widgets/object_tree.py @@ -91,7 +91,7 @@ class ObjectTree(QWidget,ComponentMixin): {'name': 'Clear all before each run', 'type': 'bool', 'value': True}, {'name': 'STL precision','type': 'float', 'value': .1}]) - sigObjectsAdded = pyqtSignal([list],[list,bool]) + sigObjectsAdded = pyqtSignal([list, list],[list, bool, list]) sigObjectsRemoved = pyqtSignal(list) sigCQObjectSelected = pyqtSignal(object) sigAISObjectsSelected = pyqtSignal(list) @@ -195,6 +195,7 @@ def addLines(self): origin = (0,0,0) ais_list = [] + names = [] for name,color,direction in zip(('X','Y','Z'), ('red','lawngreen','blue'), @@ -208,8 +209,9 @@ def addLines(self): ais=line)) ais_list.append(line) + names.append(name) - self.sigObjectsAdded.emit(ais_list) + self.sigObjectsAdded.emit(ais_list, names) def _current_properties(self): @@ -242,6 +244,7 @@ def addObjects(self,objects,clean=False,root=None): self.removeObjects() ais_list = [] + names = [] #remove empty objects objects_f = {k:v for k,v in objects.items() if not is_obj_empty(v.shape)} @@ -260,20 +263,21 @@ def addObjects(self,objects,clean=False,root=None): if child.properties['Visible']: ais_list.append(ais) - + names.append(name) + root.addChild(child) if request_fit_view: - self.sigObjectsAdded[list,bool].emit(ais_list,True) + self.sigObjectsAdded[list, bool, list].emit(ais_list, True, names) else: - self.sigObjectsAdded[list].emit(ais_list) + self.sigObjectsAdded[list, list].emit(ais_list, names) @pyqtSlot(object,str,object) def addObject(self,obj,name='',options={}): root = self.CQ - ais,shape_display = make_AIS(obj, options) + ais, shape_display = make_AIS(obj, options) root.addChild(ObjectTreeItem(name, shape=obj, @@ -281,7 +285,7 @@ def addObject(self,obj,name='',options={}): ais=ais, sig=self.sigObjectPropertiesChanged)) - self.sigObjectsAdded.emit([ais]) + self.sigObjectsAdded.emit([ais], name) @pyqtSlot(list) @pyqtSlot() @@ -305,7 +309,7 @@ def stashObjects(self,action : bool): self.removeObjects() self.CQ.addChildren(self._stash) ais_list = [el.ais for el in self._stash] - self.sigObjectsAdded.emit(ais_list) + self.sigObjectsAdded.emit(ais_list, [''] * len(ais_list)) @pyqtSlot() def removeSelected(self): diff --git a/cq_editor/widgets/traceback_viewer.py b/cq_editor/widgets/traceback_viewer.py index d5e0baa6..875429b1 100644 --- a/cq_editor/widgets/traceback_viewer.py +++ b/cq_editor/widgets/traceback_viewer.py @@ -35,8 +35,7 @@ def __init__(self,parent): self.tree = TracebackTree(self) self.current_exception = QLabel(self) - self.current_exception.setStyleSheet(\ - "QLabel {color : red; }"); + self.current_exception.setStyleSheet("QLabel {color : red; }"); layout(self, (self.current_exception, diff --git a/cq_editor/widgets/viewer.py b/cq_editor/widgets/viewer.py index a3d4c361..c58416f8 100644 --- a/cq_editor/widgets/viewer.py +++ b/cq_editor/widgets/viewer.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from typing import List, Optional from PyQt5.QtWidgets import (QWidget, QPushButton, QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QFileDialog, QHBoxLayout, QFrame, QLabel, QApplication, @@ -45,6 +46,7 @@ class OCCViewer(QWidget,ComponentMixin): IMAGE_EXTENSIONS = 'png' sigObjectSelected = pyqtSignal(list) + sigDisplayProgress = pyqtSignal(int, int, str) def __init__(self,parent=None): @@ -140,31 +142,23 @@ def clear(self): context.PurgeDisplay() context.RemoveAll(True) - def _display(self,shape): - - ais = make_AIS(shape) - self.canvas.context.Display(shape,True) - - self.displayed_shapes.append(shape) - self.displayed_ais.append(ais) - - #self.canvas._display.Repaint() - @pyqtSlot(object) - def display(self,ais): - - context = self._get_context() - context.Display(ais,True) - - if self.preferences['Fit automatically']: self.fit() + def display(self, ais): + self.display_many([ais]) @pyqtSlot(list) - @pyqtSlot(list,bool) - def display_many(self,ais_list,fit=None): + @pyqtSlot(list, bool, list) + def display_many(self, ais_list, fit: Optional[bool] = None, names: Optional[List] = None): + if names is None: + names = [None] * len(ais_list) + assert len(ais_list) == len(names) context = self._get_context() - for ais in ais_list: - context.Display(ais,True) + num_objects = len(ais_list) + for i, (ais, name) in enumerate(zip(ais_list, names)): + self.sigDisplayProgress.emit(i, num_objects, name) + context.Display(ais, True) + self.sigDisplayProgress.emit(num_objects, num_objects, None) if self.preferences['Fit automatically'] and fit is None: self.fit() @@ -184,7 +178,8 @@ def update_item(self,item,col): 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):