# -*- 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, \ QComboBox, QLabel, QPushButton, QMessageBox from salome.geom.geomrepairadv.basedlg import BaseDlg from salome.geom import geomBuilder from libGEOM_Swig import GEOM_Swig from .geomrepairadv_common import DlgRef_1Spin_QTD from .geomrepairadv_execute import execute from .geomrepairadv_logger import logger import GEOM 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. """ SUBSHAPES_LABEL_TEXT = 'Sub-shapes: ' def __init__(self, selection_level = GEOM.EDGE): # Path to Min/max script self._minmax_algo = self.set_algoname('locate_subshapes_limits.py', True) # 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) # Add Min/Max button self._minmax_button = QPushButton("Compute Min/Max") self._minmax_button.clicked.connect(self.on_minmax_button_clicked) # 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', 1000, 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) # Sub-shapes number self._subshapes_selected = 0 self._subshapes_total = 0 self._subshapes_label = QLabel() # 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._minmax_button, 1, 0) layout.addWidget(self._type_widget, 2, 0) layout.addWidget(self._min_widget, 3, 0) layout.addWidget(self._max_widget, 4, 0) layout.addWidget(self._subshapes_label, 5, 0) layout.addWidget(self._select_button, 6, 0) # Init base dialog BaseDlg.__init__( self, main_widget, 'Locate Subshapes', 'locate_subshapes_algo.py', True, selection_level ) # Adjust setup from a base class self._sel_subshape_widget.hide() self._is_copy_on = False # disable making a copy of object for algo script 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 set_limits(self, min_value, max_value): """ Sets given values for min/max limits. Args: None. Returns: None. """ self._min_widget.SpinBox_DX.setValue(min_value) self._max_widget.SpinBox_DX.setValue(max_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 set_subshapes_counters(self, selected, total): """ Set counters for selected and total subshapes. Args: None. Returns: None. """ self._subshapes_selected = selected self._subshapes_total = total def update_subshapes_label(self): """ Updates a text of Sub-Shapes label. Args: None. Returns: None. """ selected = str(self._subshapes_selected) total = str(self._subshapes_total) self._subshapes_label.setText(self.SUBSHAPES_LABEL_TEXT + selected + '/' + total) def update_subshapes_info(self): """ Updates all info about Sub-Shapes in the dialog. Args: None. Returns: None. """ # Default values when we don't have selected shapes selected_ids = [] all_ids = [] if self._selected_object: geompy = geomBuilder.New() selected_ids = self.get_local_selection() all_ids = geompy.SubShapeAllIDs(self._selected_object, self.get_selection_level()) # Update counters self.set_subshapes_counters(len(selected_ids), len(all_ids)) # Update label self.update_subshapes_label() 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) # Clear pre selected sub-shapes list self.on_select_subshape() self.update_subshapes_info() def select_subshapes_in_limits(self): """ Updates a text of Sub-Shapes label. Args: None. Returns: None. """ if not self._selected_object: return # Get all sub-shapes geompy = geomBuilder.New() selection_level = self.get_selection_level() subshapes = geompy.SubShapeAll(self._selected_object, selection_level) # Iterate over ids to check if it fits to limits param_ids = { GEOM.EDGE : 0, GEOM.FACE : 1, GEOM.SOLID : 2 } param_index = param_ids[selection_level] ids_to_select = [] limits = self.get_limits() for shape in subshapes: # Get properties as a list [theLength, theSurfArea, theVolume] properties = geompy.BasicProperties(shape) param = properties[param_index] logger.debug('param: {}'.format(param)) # Check if it fits to the limits if param >= limits[0] and param <= limits[1]: # TODO: implement selections with GEOM_Swig_LocalSelector or something... # Select sub-shape sub_shape_id = geompy.GetSubShapeID(self._selected_object, shape) ids_to_select.append(sub_shape_id) else: # Deselect sub-shape pass # Select sub-shapes with collected ids geom_swig = GEOM_Swig() geom_swig.setLocalSelection(ids_to_select) logger.debug('ids_to_select: {}'.format(ids_to_select)) # Update displayed info self.update_subshapes_info() def on_limit_changed(self): """ One of the limits was changed. Args: None. Returns: None. """ # TODO: Do we need an interactive change here? # self.select_subshapes_in_limits() def on_select_button_clicked(self): """ Show selection info on button click. Args: None. Returns: None. """ # Doesn't make any sence without selected object if not self._selected_object: QMessageBox.warning( None, 'Warning', 'You must select an object to see sub-shapes selected!') return self.select_subshapes_in_limits() def on_minmax_button_clicked(self): """ Compute Min/Max limits on button click. Args: None. Returns: None. """ # Doesn't make any sence without selected object if not self._selected_object: QMessageBox.warning(None, 'Warning', 'You must select an object to compute!') return # Execute a separated script the same way as it is expected for on_apply() but without dump args = { 'result_name': 'dummy', 'selection_level': self.get_selection_level() } # Making Python dump and copy of selected object are disabled # because we don't need for both of them here. limits = execute(self._selected_object, self._minmax_algo, args, False, False) if len(limits) >= 2: self.set_limits(limits[0], limits[1]) def get_args(self): """ Collects arguments for a repair execution algorithm into a dictionary. Args: None. Returns: Dictionary with arguments for execution. """ # Update selection with a current values # TODO: should we call it here? # In a worst case scenario we can run it twice # if a user has just pressed selection button. self.select_subshapes_in_limits() # Collect current values for the execution selected_ids = self.get_local_selection() selection_level = self.get_selection_level() min_selected = 0 if self.is_selection_valid(selected_ids, min_selected): return { 'selected_ids': selected_ids, 'result_name': self.get_result_name(), 'selection_level': selection_level } return None def on_select_object(self): """ Override parent's method to display sub-shapes info. Args: None. Returns: None. """ # Call parent method first super().on_select_object() # Update displayed info self.update_subshapes_info() # 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.locate_subshapes if __name__ == '__main__': app = QApplication(sys.argv) dlg = LocateSubShapesDlg(None) dlg.show() sys.exit(app.exec_())