# -*- 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 pathlib import Path from qtsalome import Qt, QWidget, QMessageBox, QApplication, QGridLayout from salome.gui import helper from salome.kernel.studyedit import EDITOR from salome.kernel.services import IDToObject, ObjectToID from salome.geom import geomBuilder from libGEOM_Swig import GEOM_Swig import SalomePyQt 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, GEOM_SELECTED_SUBSHAPE import GEOM class BaseDlg(Ui_BaseDlg, QWidget): """ Base dialog for all GEOM repair widgets. Manages standard buttons (Apply and Close, Apply, Close, Help) and adds a child widget specific for each algorithm that uses this dialog as a base class. """ # Collection of derived singletons _instances = {} def __new__(cls, *args, **kwargs): """ Returns a singleton instance of the plugin's dialog. It is mandatory in order to call show without a parent. """ if cls._instances.get(cls, None) is None: cls._instances[cls] = super(BaseDlg, cls).__new__(cls, *args, **kwargs) return BaseDlg._instances[cls] def __init__(self, child_widget, window_title, algo_name, is_default_location, selection_level): """ First inits the base part of dialog, then puts in place a widget, implemented for a child class. Args: child_widget - object to display algorithm specific UI window_title - string to display in the dialog's title bar algo_name - path to specific algorithm module is_default_location - if True, then algo file in the same directory. """ QWidget.__init__(self) # Set up the generic user interface from Designer. self.setupUi(self) self.setWindowTitle(window_title) # Selection widgets are common for every algorithm at the moment # Widget for result shape # Prepend a result name with a window title without spaces self._result_name = ''.join(window_title.split()) + '_' self._result_widget = DlgRef_1Sel_QTD() self._result_widget.GroupBox1.setTitle(GEOM_RESULT_NAME_GRP) self._result_widget.TextLabel1.setText(NAME_LBL) self._result_widget.LineEdit1.setText(self._result_name) self._result_widget.PushButton1.hide() # Widget for selected shape self._selected_widget = DlgRef_1Sel_QTD() self._selected_widget.GroupBox1.setTitle(GEOM_SELECTED_LBL) self._selected_widget.TextLabel1.setText(GEOM_SELECTED_SHAPE) self._selected_widget.PushButton1.clicked.connect(self.on_select_object) # A widget to show selected sub-shapes self._sel_subshape_widget = self.create_sel_subshape_widget() # Keep references to selected object and its temporary copy # that we need to pass for execution instead of original one. # TODO: decide if we really need to pass a copy. self._selected_object = None # Put the common widgets and a child widget for a specific algorithm # in a place right above standard buttons (defined by child_placeholder). self.child_layout = QGridLayout(self.child_placeholder) self.child_layout.setContentsMargins(0, 0, 0, 0) self.child_layout.addWidget(self._result_widget, 0, 0) self.child_layout.addWidget(self._selected_widget, 1, 0) self.child_layout.addWidget(self._sel_subshape_widget, 2, 0) if child_widget: self.child_layout.addWidget(child_widget, 3, 0) # Set basic button's actions self.buttonOk.clicked.connect(self.on_apply_close) self.buttonApply.clicked.connect(self.on_apply) self.buttonClose.clicked.connect(self.close) self.buttonHelp.clicked.connect(self.on_help) # Execution module # Name of particular algo module for each repair class self._algo_name = self.set_algoname(algo_name, is_default_location) # Let it be always on top of the application. # We need it because this dialog will run without parent. self.setWindowFlags(Qt.WindowStaysOnTopHint) # Default selection level self._selection_level = selection_level self._is_level_changed = False # Connect selection manager salome_pyqt = SalomePyQt.SalomePyQt() self._sel_manager = salome_pyqt.getSelection() self._sel_connection = \ self._sel_manager.currentSelectionChanged.connect(self.on_select_object) self._sel_subshape_connection = \ self._sel_manager.currentSelectionChanged.connect(self.on_select_subshape) # Check if we already have selected object self.on_select_object() # Default args for execution self._is_dump_on = True # enables Python dump self._is_copy_on = True # enables passing copy of object into algo script def on_apply_close(self): """ Calls on pressing Apply and Close button. """ self.execute() self.close() def on_apply(self): """ Calls on pressing Apply button. """ self.execute() def on_help(self): """ Calls on pressing Help button. """ QMessageBox.about(None, "Help", "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. """ return {} def execute(self): """ Executes actual algorithm. Args: None. Returns: None """ if not self._selected_object: QMessageBox.warning( None, 'Warning', 'You must select an object to repair!' ) return args_dict = self.get_args() if args_dict: execute(self._selected_object, self._algo_name, args_dict, self._is_dump_on, self._is_copy_on) def set_algoname(self, algo_name, is_default_location): """ Sets the path to the algorithm. Args: algo_name - an algorithm's name. is_default_location - if True, then algo file in the same directory. Returns: None """ if is_default_location: package_dir = Path(__file__).parent.absolute() return package_dir.joinpath(algo_name) else: return algo_name def set_result_name(self, name): """ Sets a name of the result shape. Args: name - a provided name. Returns: None. """ self._result_widget.LineEdit1.setText(name) def get_result_name(self): """ Sets a name of the result shape. Args: None. Returns: A name in the related edit line of the dialog. """ return self._result_widget.LineEdit1.text() def get_selection_level(self): """ Return current selection level. Args: None. Returns: Selection level. """ return self._selection_level def set_selection_level(self, selection_level): """ Sets selection level. Args: selection_level - GEOM selection level. Returns: None. """ # Set the flag to process local selection properly if self._selection_level == selection_level: self._is_level_changed = False else: self._is_level_changed = True 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. Args: entry - an item currently selected in the objects browser. Returns: None. """ if not self._selection_level: return geom_swig = GEOM_Swig() # Set level of selection for specific entry if entry: # Init it again if the level was changed if self._is_level_changed: geom_swig.closeLocalSelection() # Init it here sel_level = geomBuilder.EnumToLong(self._selection_level) geom_swig.initLocalSelection(entry, sel_level) else: # No entry - no local selection geom_swig.closeLocalSelection() # We don't need the flag after selection was set self._is_level_changed = False def on_select_object(self): """ Adds selected object to a dialog. Args: None. Returns: None. """ # Get selected object sobject, entry = helper.getSObjectSelected() # Update selected widget and object if sobject and entry: source_name = EDITOR.getName(sobject) self.set_result_name(self._result_name + source_name) self._selected_widget.LineEdit1.setText(source_name) # Check if we selected other object in a browser - # we need to set a level flag to init a local selection again prev_entry = ObjectToID(self._selected_object, EDITOR.study) if prev_entry != entry: self._is_level_changed = True self._selected_object = IDToObject(entry, EDITOR.study) else: self.set_result_name(self._result_name) self._selected_widget.LineEdit1.clear() self._selected_object = None entry = None # Selection level 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 create_sel_subshape_widget(self): """ Returns a widget that lists preliminarily selected for processing sub_shapes. Args: None. Returns: A new widget. """ sel_subshape_widget = DlgRef_1Sel_QTD() sel_subshape_widget.TextLabel1.setText(GEOM_SELECTED_SUBSHAPE) sel_subshape_widget.PushButton1.clicked.connect(self.on_select_subshape) return sel_subshape_widget def on_select_subshape(self): """ Updates pre selected widget. Args: None. Returns: None. """ selected_ids = self.get_local_selection() selected_ids_str = ', '.join(str(id) for id in selected_ids) self._sel_subshape_widget.LineEdit1.setText(selected_ids_str) def closeEvent(self, event): """ Overrides default close envent to reset selection level. """ super().closeEvent(event) # Clean up all selection changes self._sel_manager.currentSelectionChanged.disconnect(self._sel_connection) self._sel_manager.currentSelectionChanged.disconnect(self._sel_subshape_connection) self.set_selection(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.basedlg if __name__ == '__main__': app = QApplication(sys.argv) dlg = BaseDlg(None, 'Test base dialog', 'test_algo', True, None) dlg.show() sys.exit(app.exec_())