@@ -56,6 +56,8 @@ def __init__(self,
5656 self .model_path = Path (model_path )
5757 self .threads = threads
5858 self .default_res = resolution
59+ self .model = None
60+ self .plot_manager = None
5961
6062 def loadGui (self , use_settings_pkl = True ):
6163
@@ -114,15 +116,19 @@ def loadGui(self, use_settings_pkl=True):
114116 self .statusBar ().addPermanentWidget (self .coord_label )
115117 self .coord_label .hide ()
116118
119+ self .plot_manager = self .model .plot_manager
120+ self .plot_manager .plot_started .connect (self ._on_plot_started )
121+ self .plot_manager .plot_queued .connect (self ._on_plot_queued )
122+ self .plot_manager .plot_finished .connect (self ._on_plot_finished )
123+ self .plot_manager .plot_error .connect (self ._on_plot_error )
124+ self .plot_manager .plot_idle .connect (self ._on_plot_idle )
125+
117126 # Load Plot
118- self .statusBar ().showMessage ('Generating Plot...' )
119127 self .geometryPanel .update ()
120128 self .tallyPanel .update ()
121129 self .colorDialog .updateDialogValues ()
122- self .statusBar ().showMessage ('' )
123130
124- # Timer allows GUI to render before plot finishes loading
125- QtCore .QTimer .singleShot (0 , self .showCurrentView )
131+ QtCore .QTimer .singleShot (0 , self .requestPlotUpdate )
126132
127133 self .plotIm .frozen = False
128134
@@ -421,10 +427,17 @@ def updateEditMenu(self):
421427 changed = self .model .currentView != self .model .defaultView
422428 self .restoreAction .setDisabled (not changed )
423429
430+ toggle_actions = (self .maskingAction , self .highlightingAct ,
431+ self .outlineAct , self .overlapAct )
432+ # Temporarily block signals to avoid triggering plot update
433+ for action in toggle_actions :
434+ action .blockSignals (True )
424435 self .maskingAction .setChecked (self .model .currentView .masking )
425436 self .highlightingAct .setChecked (self .model .currentView .highlighting )
426437 self .outlineAct .setChecked (self .model .currentView .outlinesCell )
427438 self .overlapAct .setChecked (self .model .currentView .color_overlaps )
439+ for action in toggle_actions :
440+ action .blockSignals (False )
428441
429442 num_previous_views = len (self .model .previousViews )
430443 self .undoAction .setText ('&Undo ({})' .format (num_previous_views ))
@@ -466,11 +479,14 @@ def saveBatchImage(self, view_file):
466479 cv = self .model .currentView
467480 # load the view from file
468481 self .loadViewFile (view_file )
482+ self .waitForPlotIdle ()
469483 self .plotIm .saveImage (view_file .replace ('.pltvw' , '' ))
470484
471485 # Menu and shared methods
472486 def loadModel (self , reload = False , use_settings_pkl = True ):
473487 if reload :
488+ if self .plot_manager is not None :
489+ self .plot_manager .wait_for_idle ()
474490 self .resetModels ()
475491 else :
476492 self .model = PlotModel (use_settings_pkl , self .model_path , self .default_res )
@@ -632,66 +648,48 @@ def plotSourceSites(self):
632648
633649 def applyChanges (self ):
634650 if self .model .activeView != self .model .currentView :
635- self .statusBar ().showMessage ('Generating Plot...' )
636- QApplication .processEvents ()
637651 if self .model .activeView .selectedTally is not None :
638652 self .tallyPanel .updateModel ()
639653 self .updateMeshAnnotations ()
640654 self .model .storeCurrent ()
641655 self .model .subsequentViews = []
642- self .plotIm .generatePixmap ()
643- self .resetModels ()
644- self .showCurrentView ()
645- self .statusBar ().showMessage ('' )
656+ self .requestPlotUpdate ()
646657 else :
647658 self .statusBar ().showMessage ('No changes to apply.' , 3000 )
648659
649660 def undo (self ):
650- self .statusBar ().showMessage ('Generating Plot...' )
651- QApplication .processEvents ()
652-
661+ if not self .model .previousViews :
662+ return
653663 self .model .undo ()
654- self .resetModels ()
655- self .showCurrentView ()
656664 self .geometryPanel .update ()
657665 self .colorDialog .updateDialogValues ()
666+ self .requestPlotUpdate ()
658667
659668 if not self .model .previousViews :
660669 self .undoAction .setDisabled (True )
661670 self .redoAction .setDisabled (False )
662- self .statusBar ().showMessage ('' )
663671
664672 def redo (self ):
665- self .statusBar ().showMessage ('Generating Plot...' )
666- QApplication .processEvents ()
667-
673+ if not self .model .subsequentViews :
674+ return
668675 self .model .redo ()
669- self .resetModels ()
670- self .showCurrentView ()
671676 self .geometryPanel .update ()
672677 self .colorDialog .updateDialogValues ()
678+ self .requestPlotUpdate ()
673679
674680 if not self .model .subsequentViews :
675681 self .redoAction .setDisabled (True )
676682 self .undoAction .setDisabled (False )
677- self .statusBar ().showMessage ('' )
678683
679684 def restoreDefault (self ):
680685 if self .model .currentView != self .model .defaultView :
681-
682- self .statusBar ().showMessage ('Generating Plot...' )
683- QApplication .processEvents ()
684-
685686 self .model .storeCurrent ()
686687 self .model .activeView .adopt_plotbase (self .model .defaultView )
687- self .plotIm .generatePixmap ()
688- self .resetModels ()
689- self .showCurrentView ()
690688 self .geometryPanel .update ()
691689 self .colorDialog .updateDialogValues ()
690+ self .requestPlotUpdate ()
692691
693692 self .model .subsequentViews = []
694- self .statusBar ().showMessage ('' )
695693
696694 def editBasis (self , basis , apply = False ):
697695 self .model .activeView .basis = basis
@@ -1199,6 +1197,9 @@ def resizeEvent(self, event):
11991197 self .shortcutOverlay .resize (self .width (), self .height ())
12001198
12011199 def closeEvent (self , event ):
1200+ if self .plot_manager is not None :
1201+ self .plot_manager .wait_for_idle (timeout_ms = 250 )
1202+ self .plot_manager .shutdown ()
12021203 settings = QtCore .QSettings ()
12031204 settings .setValue ("mainWindow/Size" , self .size ())
12041205 settings .setValue ("mainWindow/Position" , self .pos ())
@@ -1213,6 +1214,53 @@ def closeEvent(self, event):
12131214
12141215 self .saveSettings ()
12151216
1217+ def requestPlotUpdate (self , view = None ):
1218+ if self .model is None :
1219+ return
1220+ if view is None :
1221+ view = self .model .activeView
1222+ view_snapshot = copy .deepcopy (view )
1223+ if self .model .can_reuse_maps (view_snapshot ):
1224+ view_params = self .model .view_params_payload (view_snapshot )
1225+ self .plot_manager .set_latest_view_params (view_params )
1226+ self .plot_manager .clear_pending ()
1227+ self .model .makePlot (view_snapshot , self .model .ids_map , self .model .properties )
1228+ self .resetModels ()
1229+ self .showCurrentView ()
1230+ if not self .plot_manager .is_busy :
1231+ self ._on_plot_idle ()
1232+ return
1233+ view_params = self .model .view_params_payload (view_snapshot )
1234+ self .plot_manager .enqueue (view_snapshot , view_params )
1235+
1236+ def waitForPlotIdle (self , timeout_ms = None ):
1237+ if self .plot_manager is not None :
1238+ return self .plot_manager .wait_for_idle (timeout_ms )
1239+ return True
1240+
1241+ def _on_plot_started (self ):
1242+ self .plotIm .showUpdatingOverlay ("Generating Plot..." )
1243+
1244+ def _on_plot_queued (self ):
1245+ self .plotIm .showUpdatingOverlay ("Generating Plot... (update queued)" )
1246+
1247+ def _on_plot_finished (self , view_snapshot , view_params , ids_map , properties ):
1248+ if view_params != self .plot_manager .latest_view_params :
1249+ return
1250+ self .model .makePlot (view_snapshot , ids_map , properties )
1251+ self .resetModels ()
1252+ self .showCurrentView ()
1253+
1254+ def _on_plot_error (self , error_msg ):
1255+ msg_box = QMessageBox ()
1256+ msg_box .setText (f"Failed to generate plot:\n \n { error_msg } " )
1257+ msg_box .setIcon (QMessageBox .Warning )
1258+ msg_box .setStandardButtons (QMessageBox .Ok )
1259+ msg_box .exec ()
1260+
1261+ def _on_plot_idle (self ):
1262+ self .plotIm .hideUpdatingOverlay ()
1263+
12161264 def saveSettings (self ):
12171265 if self .model .statepoint :
12181266 self .model .statepoint .close ()
0 commit comments