[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.

This commit is contained in:
Konstantin Leontev 2024-08-28 12:04:25 +01:00
parent 674f0de7ce
commit d270b0ab08
4 changed files with 69 additions and 41 deletions

View File

@ -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')

View File

@ -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,6 +77,8 @@ class Worker(QThread):
# Reset the progress when finished
self.progress_update.emit(100)
if not self.is_canceled():
self.work_completed.emit()
except Exception:
@ -82,16 +89,42 @@ 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.
"""
# Check if the job is still running and need to be terminated
if self.isRunning():
logger.warning('Thread will be terminated!')
super().terminate()
# Termination doesn't call a final block inside run()
QApplication.restoreOverrideCursor()
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

View File

@ -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)

View File

@ -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():