geom/src/RepairGUIAdv/basedlg.py

468 lines
13 KiB
Python

# -*- 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_())