From e113b6239c793f2383000aaf9c66928c9c7ffd42 Mon Sep 17 00:00:00 2001 From: Konstantin Leontev Date: Tue, 12 Mar 2024 21:24:39 +0000 Subject: [PATCH] [bos #38044][EDF] (2023-T3) Support for automatic reparation. Updated UI. --- src/RepairGUIAdv/CMakeLists.txt | 1 + src/RepairGUIAdv/basedlg.py | 59 ++++++++++ src/RepairGUIAdv/geomrepairadv_common.py | 7 +- src/RepairGUIAdv/geomrepairadv_execute.py | 16 ++- src/RepairGUIAdv/locate_subshapes.py | 135 +++++++++++++++++++++- src/RepairGUIAdv/merge_faces.py | 67 +---------- src/RepairGUIAdv/merge_faces_algo.py | 7 +- src/RepairGUIAdv/subshapes_basedlg.py | 99 ++++++++++++++++ src/RepairGUIAdv/union_edges.py | 17 +-- 9 files changed, 326 insertions(+), 82 deletions(-) create mode 100644 src/RepairGUIAdv/subshapes_basedlg.py diff --git a/src/RepairGUIAdv/CMakeLists.txt b/src/RepairGUIAdv/CMakeLists.txt index d2251be45..c64ea9db2 100644 --- a/src/RepairGUIAdv/CMakeLists.txt +++ b/src/RepairGUIAdv/CMakeLists.txt @@ -45,6 +45,7 @@ IF(SALOME_BUILD_GUI) SET(_gui_SCRIPTS basedlg.py basedlg.ui + subshapes_basedlg.py ${geom_dlg_ref}/DlgRef_1Sel_QTD.ui DlgRef_1Spin_QTD.ui # copied because original was promoted to SalomeApp_DoubleSpinBox ) diff --git a/src/RepairGUIAdv/basedlg.py b/src/RepairGUIAdv/basedlg.py index e69ceefe0..b21c75950 100644 --- a/src/RepairGUIAdv/basedlg.py +++ b/src/RepairGUIAdv/basedlg.py @@ -32,6 +32,7 @@ from libGEOM_Swig import GEOM_Swig from .basedlg_ui import Ui_BaseDlg from .geomrepairadv_execute import execute +from .geomrepairadv_logger import logger from .geomrepairadv_common import DlgRef_1Sel_QTD, \ GEOM_RESULT_NAME_GRP, NAME_LBL, GEOM_SELECTED_LBL, GEOM_SELECTED_SHAPE import GEOM @@ -236,6 +237,23 @@ class BaseDlg(Ui_BaseDlg, QWidget): return self._result_widget.LineEdit1.text() + def set_selection_level(self, selection_level): + """ + Sets selection level. + + Args: + selection_level - GEOM selection level. + + Returns: + None. + """ + + self._selection_level = selection_level + + # Update selection for current object + self.on_select_object() + + def set_selection(self, entry = None): """ Sets selection level to self._selection_level or resets it. @@ -291,6 +309,47 @@ class BaseDlg(Ui_BaseDlg, QWidget): self.set_selection(entry) + def get_local_selection(self): + """ + Returns selected sub-shapes ids. + + Args: + None. + + Returns: + List of ids. + """ + + geom_swig = GEOM_Swig() + selected_ids = geom_swig.getLocalSelection() + logger.debug('selected_ids: %s', selected_ids) + + return selected_ids + + + def is_selection_valid(self, selected_ids, min_selected): + """ + Checks number of sub-shapes ids. + + Args: + selected_ids - list of selected. + + Returns: + True if we have valid number of ids. + """ + + if len(selected_ids) < min_selected: + QMessageBox.warning( + None, + 'Warning', + 'The algorithm needs at least {} selected sub-shapes!\n' + 'Operation was canceled.'.format(min_selected) + ) + return False + + return True + + def closeEvent(self, event): """ Overrides default close envent to reset selection level. diff --git a/src/RepairGUIAdv/geomrepairadv_common.py b/src/RepairGUIAdv/geomrepairadv_common.py index 06e91a039..4d2feafb7 100644 --- a/src/RepairGUIAdv/geomrepairadv_common.py +++ b/src/RepairGUIAdv/geomrepairadv_common.py @@ -53,7 +53,12 @@ class DlgRef_1Spin_QTD(Ui_DlgRef_1Spin_QTD, QWidget): We need it because a class generated from ui file is derived from an object and cannot be added as a widget to a dialog's layout. """ - def __init__(self): + def __init__(self, title, default_value, decimals, max_value): QWidget.__init__(self) # Set up the user interface from Designer. self.setupUi(self) + + self.TextLabel1.setText(title) + self.SpinBox_DX.setValue(default_value) + self.SpinBox_DX.setDecimals(decimals) + self.SpinBox_DX.setMaximum(max_value) diff --git a/src/RepairGUIAdv/geomrepairadv_execute.py b/src/RepairGUIAdv/geomrepairadv_execute.py index 5be14b852..9b76c58d5 100644 --- a/src/RepairGUIAdv/geomrepairadv_execute.py +++ b/src/RepairGUIAdv/geomrepairadv_execute.py @@ -22,8 +22,9 @@ import os import sys import importlib.util +from traceback import format_exc -from qtsalome import QApplication, QFileDialog +from qtsalome import QApplication, QFileDialog, QMessageBox from salome.kernel.services import ObjectToID from salome.geom.geomtools import GeomStudyTools @@ -68,7 +69,18 @@ def module_from_filename(filename): logger.error('spec.loader is None for %s module!', module_name) return None - spec.loader.exec_module(module) + try: + spec.loader.exec_module(module) + + except FileNotFoundError: + logger.error(format_exc()) + QMessageBox.warning( + None, + 'Error', + 'Cannot find module {}!\nOperation was canceled.'.format(module_name) + ) + + return None return module diff --git a/src/RepairGUIAdv/locate_subshapes.py b/src/RepairGUIAdv/locate_subshapes.py index e73e3b3dd..8f4ec67ab 100644 --- a/src/RepairGUIAdv/locate_subshapes.py +++ b/src/RepairGUIAdv/locate_subshapes.py @@ -21,9 +21,11 @@ import sys -from qtsalome import QGridLayout, QFrame, QApplication +from qtsalome import QGridLayout, QFrame, QApplication, QComboBox, QLabel, QPushButton, QMessageBox from salome.geom.geomrepairadv.basedlg import BaseDlg + +from .geomrepairadv_common import DlgRef_1Spin_QTD import GEOM class LocateSubShapesDlg(BaseDlg): @@ -31,14 +33,141 @@ class LocateSubShapesDlg(BaseDlg): Dialog for Locate Subshapes plugin that selects the sub-shapes of a compound by length, area or volume depending on whether it is an EDGE, a FACE or a SOLID. """ - def __init__(self, selection_level = GEOM.COMPOUND): + def __init__(self, selection_level = GEOM.EDGE): # Implement widget's content here main_widget = QFrame() layout = QGridLayout(main_widget) layout.setContentsMargins(0, 0, 0, 0) + # A combobox to choose measurment type + type_label = QLabel('Shape Measurement') + self._type_widget = QComboBox() + type_items = ['Length (EDGE)', 'Area (FACE)', 'Volume (SOLID)'] + self._type_widget.insertItems(0, type_items) + self._type_widget.setToolTip('Select a type of shape measurement') + self._type_widget.currentIndexChanged.connect(self.on_measurment_type_changed) + + # Min/max values widgets + decimals = 2 + max_value = sys.float_info.max + self._min_widget = DlgRef_1Spin_QTD('Min', 0, decimals, max_value) + self._max_widget = DlgRef_1Spin_QTD('Max', 100, decimals, max_value) + + # Add select button + self._select_button = QPushButton("Show Selected Sub-shapes") + self._select_button.clicked.connect(self.on_select_button_clicked) + + # Add the widgets to layout + layout.addWidget(type_label, 0, 0) + layout.addWidget(self._type_widget, 1, 0) + layout.addWidget(self._min_widget, 2, 0) + layout.addWidget(self._max_widget, 3, 0) + layout.addWidget(self._select_button, 4, 0) + + # Init base dialog BaseDlg.__init__( - self, main_widget, 'Locate Subshapes', 'locate_subshapes_algo.py', False, selection_level) + self, + main_widget, + 'Locate Subshapes', + 'locate_subshapes_algo.py', + False, + selection_level + ) + + + def get_limits(self): + """ + Returns current values for min/max limits. + + Args: + None. + + Returns: + List of limits [min, max]. + """ + + return [self._min_widget.SpinBox_DX.value(), + self._max_widget.SpinBox_DX.value()] + + + def get_measurment_type(self, index): + """ + Returns selection level based on current measurment type. + + Args: + index - current combobox index. + + Returns: + GEOM selection level value. + """ + + measurment_types = { + 0 : GEOM.EDGE, + 1 : GEOM.FACE, + 2 : GEOM.SOLID + } + + return measurment_types[index] + + + def on_measurment_type_changed(self, index): + """ + Changes selection level on type changed. + + Args: + index - current combobox index. + + Returns: + None. + """ + + selection_level = self.get_measurment_type(index) + self.set_selection_level(selection_level) + + + def on_select_button_clicked(self): + """ + Show selection info on button click. + + Args: + None. + + Returns: + None. + """ + + #TODO: what are we going to do on this click? + # Should it do a separated script? + QMessageBox.warning(None, 'Warning', 'Not implemented yet') + + + def get_args(self): + """ + Collects arguments for a repair execution algorithm into a dictionary. + + Args: + None. + + Returns: + Dictionary with arguments for execution. + """ + + selected_ids = self.get_local_selection() + current_index = self._type_widget.currentIndex() + selection_level = self.get_measurment_type(current_index) + limits = self.get_limits() + min_selected = 1 + + if self.is_selection_valid(selected_ids, min_selected): + return { + 'selected_ids': selected_ids, + 'result_name': self.get_result_name(), + 'selection_level': selection_level, + 'min_limit': limits[0], + 'max_limit': limits[1] + } + + return None # For testing run as a module from geomrepairadv parent directory in diff --git a/src/RepairGUIAdv/merge_faces.py b/src/RepairGUIAdv/merge_faces.py index cc222b702..dc85f17c9 100644 --- a/src/RepairGUIAdv/merge_faces.py +++ b/src/RepairGUIAdv/merge_faces.py @@ -21,76 +21,19 @@ import sys -from qtsalome import QGridLayout, QFrame, QMessageBox, QApplication +from qtsalome import QApplication -from libGEOM_Swig import GEOM_Swig -from .geomrepairadv_logger import logger -from .basedlg import BaseDlg -from .geomrepairadv_common import DlgRef_1Spin_QTD +from .subshapes_basedlg import SubShapesBaseDlg import GEOM -class MergeFacesDlg(BaseDlg): +class MergeFacesDlg(SubShapesBaseDlg): """ Dialog for Merge Faces plugin that merges selected faces with a given precision. """ def __init__(self, selection_level = GEOM.FACE): - # Make layout for new widgets - main_widget = QFrame() - layout = QGridLayout(main_widget) - layout.setContentsMargins(0, 0, 0, 0) - - # Precision widget - self._precision_widget = DlgRef_1Spin_QTD() - self._precision_widget.TextLabel1.setText('Precision') - layout.addWidget(self._precision_widget, 0, 0) - - BaseDlg.__init__( - self, main_widget, 'Merge Faces', 'merge_faces_algo.py', True, selection_level) - - - def get_precision(self): - """ - Returns current precision value. - - Args: - None. - - Returns: - Double. - """ - - return self._precision_widget.SpinBox_DX.value() - - - def get_args(self): - """ - Collects arguments for a repair execution algorithm into a dictionary. - - Args: - None. - - Returns: - Dictionary with arguments for execution. - """ - - geom_swig = GEOM_Swig() - faces_ids = geom_swig.getLocalSelection() - logger.debug('faces_ids: %s', faces_ids) - - if len(faces_ids) < 2: - QMessageBox.warning( - None, - 'Warning', - 'The algorithm needs at least two selected faces!\nMerging was canceled.' - ) - return None - - return { - 'faces_ids': faces_ids, - 'result_name': self.get_result_name(), - 'precision': self.get_precision() - } + SubShapesBaseDlg.__init__( + self, 'Merge Faces', 'merge_faces_algo.py', True, selection_level, 2) # For testing run as a module from geomrepairadv parent directory in diff --git a/src/RepairGUIAdv/merge_faces_algo.py b/src/RepairGUIAdv/merge_faces_algo.py index dbe9014f4..3808baa49 100755 --- a/src/RepairGUIAdv/merge_faces_algo.py +++ b/src/RepairGUIAdv/merge_faces_algo.py @@ -52,14 +52,15 @@ def run(args_dict, progress_emitter): if ('source_solid' not in args_dict or - 'faces_ids' not in args_dict or - 'result_name' not in args_dict): + 'selected_ids' not in args_dict or + 'result_name' not in args_dict or + 'precision' not in args_dict): logging.info('Cant execute an algo because the arguments are empty!') return False source_solid = args_dict['source_solid'] - faces_ids = args_dict['faces_ids'] + faces_ids = args_dict['selected_ids'] result_name = args_dict['result_name'] diff --git a/src/RepairGUIAdv/subshapes_basedlg.py b/src/RepairGUIAdv/subshapes_basedlg.py new file mode 100644 index 000000000..7608a9cc9 --- /dev/null +++ b/src/RepairGUIAdv/subshapes_basedlg.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2024 EDF +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See https://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +# Author : Konstantin Leontev (OpenCascade S.A.S) + +import sys + +from qtsalome import QGridLayout, QFrame, QApplication + +from .basedlg import BaseDlg +from .geomrepairadv_common import DlgRef_1Spin_QTD + +class SubShapesBaseDlg(BaseDlg): + """ + Base dialog for Merge Faces and Union Edges plugins that provides + to algorithm selected faces with a given precision. + """ + + def __init__(self, window_title, algo_name, is_default_location, selection_level, min_selected): + # Set min number of selected sub-shapes + self._min_selected = min_selected + + # Make layout for new widgets + main_widget = QFrame() + layout = QGridLayout(main_widget) + layout.setContentsMargins(0, 0, 0, 0) + + # Precision widget + self._precision_widget = DlgRef_1Spin_QTD('Precision', 0, 2, 100) + layout.addWidget(self._precision_widget, 0, 0) + + BaseDlg.__init__( + self, main_widget, window_title, algo_name, is_default_location, selection_level) + + + def get_precision(self): + """ + Returns current precision value. + + Args: + None. + + Returns: + Double. + """ + + return self._precision_widget.SpinBox_DX.value() + + + def get_args(self): + """ + Collects arguments for a repair execution algorithm into a dictionary. + + Args: + None. + + Returns: + Dictionary with arguments for execution. + """ + + selected_ids = self.get_local_selection() + if self.is_selection_valid(selected_ids, self._min_selected): + return { + 'selected_ids': selected_ids, + 'result_name': self.get_result_name(), + 'precision': self.get_precision() + } + + return None + + +# For testing run as a module from geomrepairadv parent directory in +# Salome INSTALL, because the dialog needs a generated Ui_BaseDlg class +# that we don't have in the SOURCE. +# Example: +# $ python -m geomrepairadv.subshapes_basedlg +if __name__ == '__main__': + app = QApplication(sys.argv) + + dlg = SubShapesBaseDlg('Test SubShapesBaseDlg', 'merge_faces_algo.py', True, None, 1) + dlg.show() + + sys.exit(app.exec_()) diff --git a/src/RepairGUIAdv/union_edges.py b/src/RepairGUIAdv/union_edges.py index 0dbd4776a..f3c07b48e 100644 --- a/src/RepairGUIAdv/union_edges.py +++ b/src/RepairGUIAdv/union_edges.py @@ -21,23 +21,18 @@ import sys -from qtsalome import QGridLayout, QFrame, QApplication +from qtsalome import QApplication -from salome.geom.geomrepairadv.basedlg import BaseDlg +from .subshapes_basedlg import SubShapesBaseDlg import GEOM -class UnionEdgesDlg(BaseDlg): +class UnionEdgesDlg(SubShapesBaseDlg): """ Dialog for Union Edges plugin that unifies edges of selected face. """ - def __init__(self, selection_level = GEOM.COMPOUND): - # Implement widget's content here - main_widget = QFrame() - layout = QGridLayout(main_widget) - layout.setContentsMargins(0, 0, 0, 0) - - BaseDlg.__init__( - self, main_widget, 'Union Edges', 'union_edges_algo.py', False, selection_level) + def __init__(self, selection_level = GEOM.FACE): + SubShapesBaseDlg.__init__( + self, 'Union Edges', 'union_edges_algo.py', False, selection_level, 1) # For testing run as a module from geomrepairadv parent directory in -- 2.39.2