geom/src/RepairGUIAdv/locate_subshapes.py

412 lines
11 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 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_())