From d270b0ab087002f40c5d553bdb04965b7ccac9c2 Mon Sep 17 00:00:00 2001 From: Konstantin Leontev Date: Wed, 28 Aug 2024 12:04:25 +0100 Subject: [PATCH] [bos #38044][EDF] (2023-T3) Support for automatic reparation. Emitter now returns bool value that is False if the job was canceled. It should be used for early return from the running script to finish the thread gracefully. A timer was added to terminate the thread as a last resort. --- src/RepairGUIAdv/geomrepairadv_progress.py | 3 +- src/RepairGUIAdv/geomrepairadv_worker.py | 61 +++++++++++++++++++--- src/RepairGUIAdv/reparation_plugin.py | 6 +-- src/RepairGUIAdv/reparation_plugin_algo.py | 40 ++++---------- 4 files changed, 69 insertions(+), 41 deletions(-) diff --git a/src/RepairGUIAdv/geomrepairadv_progress.py b/src/RepairGUIAdv/geomrepairadv_progress.py index dd8911837..badf7068b 100644 --- a/src/RepairGUIAdv/geomrepairadv_progress.py +++ b/src/RepairGUIAdv/geomrepairadv_progress.py @@ -98,8 +98,7 @@ class RepairProgressDialog(QDialog, QPlainTextEdit): self.close() else: # Terminates the execution of the thread. - # TODO: find out if we can do it with requestInterruption() - self.thread.terminate() + self.thread.cancel() self.progress.setLabelText('Canceled!') self.progress.setCancelButtonText('Close') diff --git a/src/RepairGUIAdv/geomrepairadv_worker.py b/src/RepairGUIAdv/geomrepairadv_worker.py index 40aaefde5..553b06650 100644 --- a/src/RepairGUIAdv/geomrepairadv_worker.py +++ b/src/RepairGUIAdv/geomrepairadv_worker.py @@ -22,7 +22,7 @@ import inspect from traceback import format_exc -from qtsalome import QApplication, pyqtSignal, QThread, Qt +from qtsalome import QApplication, pyqtSignal, QThread, Qt, QTimer from .geomrepairadv_logger import logger @@ -58,6 +58,11 @@ class Worker(QThread): # Set a variable for result self.result = None + # Create a timer to terminate the thread if we can't exit gracefully + self.terminate_timer = QTimer() + self.terminate_timer.timeout.connect(self.terminate) + self.terminate_timer.setSingleShot(True) + def run(self): """ @@ -72,7 +77,9 @@ class Worker(QThread): # Reset the progress when finished self.progress_update.emit(100) - self.work_completed.emit() + + if not self.is_canceled(): + self.work_completed.emit() except Exception: logger.error(format_exc()) @@ -82,15 +89,41 @@ class Worker(QThread): QApplication.restoreOverrideCursor() + def is_canceled(self): + """ + Returns true if the job was canceled. + """ + + return not self.progress_emitter.is_running + + + def cancel(self): + """ + Cancels the job. + """ + + # Set emitter to know that we canceled the job and + # can return from the running function gracefully. + self.progress_emitter.stop() + + # Termination doesn't call a final block inside run() + QApplication.restoreOverrideCursor() + + # Set a timer for a case when the running function + # doesn't use emitter or does so heavy job that it would take + # a lot of time for waiting and we need to terminate it. + self.terminate_timer.start(1000) + + def terminate(self): """ Overrides default terminate() to add some clean up. """ - super().terminate() - - # Termination doesn't call a final block inside run() - QApplication.restoreOverrideCursor() + # Check if the job is still running and need to be terminated + if self.isRunning(): + logger.warning('Thread will be terminated!') + super().terminate() def get_result(self): @@ -114,11 +147,25 @@ class ProgressEmitter(): self.progress_percent = total_lines / 100.0 logger.debug('self.progress_percent: %f', self.progress_percent) + # Flag to check if we need to return from a function running in the thread + self.is_running = True + + + def stop(self): + """ + Call this methid from the Worker for early return + from a function executed in the separated thread. + """ + + self.is_running = False + def emit(self): """ Call this methid in a target function to update a progress value based on a currently executed line number. + Returns False if the job was stopped. Should be used in the algo scripts + for early return from the running function and finish the thread. """ line = inspect.getframeinfo(inspect.stack()[1][0]).lineno @@ -127,3 +174,5 @@ class ProgressEmitter(): logger.debug('progress_value: %d', progress_value) self.progress_update.emit(int(progress_value)) + + return self.is_running diff --git a/src/RepairGUIAdv/reparation_plugin.py b/src/RepairGUIAdv/reparation_plugin.py index 1a2437300..f269df780 100755 --- a/src/RepairGUIAdv/reparation_plugin.py +++ b/src/RepairGUIAdv/reparation_plugin.py @@ -45,10 +45,10 @@ class Reparation_plugin(BaseDlg): layout.setContentsMargins(0, 0, 0, 0) # Min/max values widgets - decimals = 2 + decimals = 7 max_value = sys.float_info.max - self._min_widget = DlgRef_1Spin_QTD('Tol Min', 0, decimals, max_value) - self._max_widget = DlgRef_1Spin_QTD('Tol Max', 1000, decimals, max_value) + self._min_widget = DlgRef_1Spin_QTD('Tol Min', 0.001, decimals, max_value) + self._max_widget = DlgRef_1Spin_QTD('Tol Max', 0.015, decimals, max_value) self._min_widget.SpinBox_DX.valueChanged.connect(self.on_limit_changed) self._max_widget.SpinBox_DX.valueChanged.connect(self.on_limit_changed) diff --git a/src/RepairGUIAdv/reparation_plugin_algo.py b/src/RepairGUIAdv/reparation_plugin_algo.py index 9a478004b..6f57118d7 100644 --- a/src/RepairGUIAdv/reparation_plugin_algo.py +++ b/src/RepairGUIAdv/reparation_plugin_algo.py @@ -32,7 +32,6 @@ from salome.geom import geomBuilder from qtsalome import QFileDialog, QApplication, pyqtSignal import GEOM - salome.salome_init() geompy = geomBuilder.New() @@ -53,9 +52,7 @@ def run(args_dict, progress_emitter): sleep(1) if ('source_solid' not in args_dict or - 'selected_ids' not in args_dict or 'result_name' not in args_dict or - 'selection_level' not in args_dict or 'Tol_min' not in args_dict or 'Tol_max' not in args_dict or 'Debug' not in args_dict): @@ -64,45 +61,28 @@ def run(args_dict, progress_emitter): return False source_solid = args_dict['source_solid'] - selected_ids = args_dict['selected_ids'] result_name = args_dict['result_name'] - selection_level = args_dict['selection_level'] - Tol_min = args_dict['Tol_min'] - Tol_max = args_dict['Tol_max'] + minTol = args_dict['Tol_min'] + maxTol = args_dict['Tol_max'] Debug = args_dict['Debug'] # Replace the lines below with an actual algorithm logging.info('Received arguments:') logging.info('\tsource_solid: %s', source_solid) - logging.info('\tselected_ids: %s', selected_ids) logging.info('\tresult_name: %s', result_name) - logging.info('\tselection_level: %s', selection_level) - logging.info('\Tol_min: %s', Tol_min) - logging.info('\Tol_max: %s', Tol_max) + logging.info('\Tol_min: %s', minTol) + logging.info('\Tol_max: %s', maxTol) logging.info('\Debug: %s', Debug) progress_emitter.emit() - sleep(1) - # Make a group - # geomBuilder uses their own types - geomBuilder.ShapeType - geom_builder_types = { GEOM.EDGE : 'EDGE', GEOM.FACE : 'FACE', GEOM.SOLID : 'SOLID' } - type_str = geom_builder_types[selection_level] - shape_type = geompy.ShapeType[type_str] - group = geompy.CreateGroup(source_solid, shape_type, theName = result_name) + for i in range(5000): + vertex = geompy.MakeVertex(0,0,0) + logging.info('\I: %s', i) + if not progress_emitter.emit(): + return False - logging.info('Step1.') - progress_emitter.emit() - sleep(1) - - # Add sub-shapes into the group - for subshape_id in selected_ids: - geompy.AddObject(group, subshape_id) - - logging.info('Step2.') - progress_emitter.emit() - - return group + return vertex def test():