mirror of
https://git.salome-platform.org/gitpub/modules/geom.git
synced 2024-12-26 09:20:35 +05:00
[bos #38044][EDF] (2023-T3) Support for automatic reparation. Base implementation of Sub-Shape dialog.
This commit is contained in:
parent
8ef86c6185
commit
cd33417290
@ -37,6 +37,7 @@ IF(SALOME_BUILD_GUI)
|
||||
geomrepairadv_worker.py
|
||||
locate_subshapes.py
|
||||
locate_subshapes_algo.py
|
||||
locate_subshapes_limits.py
|
||||
merge_faces.py
|
||||
merge_faces_algo.py
|
||||
union_edges.py
|
||||
|
@ -120,8 +120,7 @@ class BaseDlg(Ui_BaseDlg, QWidget):
|
||||
|
||||
# Execution module
|
||||
# Name of particular algo module for each repair class
|
||||
self._algo_name = ''
|
||||
self.set_algoname(algo_name, is_default_location)
|
||||
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.
|
||||
@ -130,7 +129,6 @@ class BaseDlg(Ui_BaseDlg, QWidget):
|
||||
# Default selection level
|
||||
self._selection_level = selection_level
|
||||
self._is_level_changed = False
|
||||
self._is_local_selection = False
|
||||
|
||||
# Connect selection manager
|
||||
salome_pyqt = SalomePyQt.SalomePyQt()
|
||||
@ -219,9 +217,9 @@ class BaseDlg(Ui_BaseDlg, QWidget):
|
||||
|
||||
if is_default_location:
|
||||
package_dir = Path(__file__).parent.absolute()
|
||||
self._algo_name = package_dir.joinpath(algo_name)
|
||||
return package_dir.joinpath(algo_name)
|
||||
else:
|
||||
self._algo_name = algo_name
|
||||
return algo_name
|
||||
|
||||
|
||||
def set_result_name(self, name):
|
||||
@ -252,6 +250,20 @@ class BaseDlg(Ui_BaseDlg, QWidget):
|
||||
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.
|
||||
@ -296,23 +308,18 @@ class BaseDlg(Ui_BaseDlg, QWidget):
|
||||
# Init it again if the level was changed
|
||||
if self._is_level_changed:
|
||||
geom_swig.closeLocalSelection()
|
||||
self._is_local_selection = False
|
||||
|
||||
# We need to init a local selection only once
|
||||
if not self._is_local_selection:
|
||||
# Init it here
|
||||
sel_level = geomBuilder.EnumToLong(self._selection_level)
|
||||
geom_swig.initLocalSelection(entry, sel_level)
|
||||
|
||||
self._is_local_selection = True
|
||||
else:
|
||||
# No entry - no local selection
|
||||
geom_swig.closeLocalSelection()
|
||||
self._is_local_selection = False
|
||||
|
||||
# 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.
|
||||
|
@ -85,14 +85,15 @@ def module_from_filename(filename):
|
||||
return module
|
||||
|
||||
|
||||
def execute(selected_object, algo_name, args_dict):
|
||||
def execute(selected_object, algo_name, args_dict, is_dump_on = True):
|
||||
"""
|
||||
Executes GEOM advanced repair algorithm.
|
||||
|
||||
Args:
|
||||
selected_object - geom object selected by user for algorithm
|
||||
algo_name - path to the algo module
|
||||
args_dict - dictionary with arguments those are specific for each algo.
|
||||
args_dict - dictionary with arguments those are specific for each algo
|
||||
is_dump_on - if True saves the call to the Python dump.
|
||||
|
||||
Returns:
|
||||
Result GEOM object or None if failed or canceled.
|
||||
@ -136,6 +137,7 @@ def execute(selected_object, algo_name, args_dict):
|
||||
logger.error('Could not get a result object after exec of %s file!', str(algo_name))
|
||||
return None
|
||||
|
||||
if is_dump_on:
|
||||
geompy.FuncToPythonDump(
|
||||
selected_object,
|
||||
result_object,
|
||||
|
@ -25,8 +25,10 @@ from qtsalome import QGridLayout, QFrame, QApplication, \
|
||||
QComboBox, QLabel, QPushButton, QMessageBox
|
||||
|
||||
from salome.geom.geomrepairadv.basedlg import BaseDlg
|
||||
from salome.geom import geomBuilder
|
||||
|
||||
from .geomrepairadv_common import DlgRef_1Spin_QTD
|
||||
from .geomrepairadv_execute import execute
|
||||
import GEOM
|
||||
|
||||
class LocateSubShapesDlg(BaseDlg):
|
||||
@ -34,7 +36,13 @@ 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)
|
||||
@ -48,11 +56,22 @@ class LocateSubShapesDlg(BaseDlg):
|
||||
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', 100, 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")
|
||||
@ -60,10 +79,12 @@ class LocateSubShapesDlg(BaseDlg):
|
||||
|
||||
# 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)
|
||||
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__(
|
||||
@ -91,6 +112,21 @@ class LocateSubShapesDlg(BaseDlg):
|
||||
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.
|
||||
@ -111,6 +147,62 @@ class LocateSubShapesDlg(BaseDlg):
|
||||
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.
|
||||
"""
|
||||
|
||||
if not self._selected_object:
|
||||
return
|
||||
|
||||
# Update counters
|
||||
geompy = geomBuilder.New()
|
||||
all_ids = geompy.SubShapeAllIDs(self._selected_object, self.get_selection_level())
|
||||
selected_ids = self.get_local_selection()
|
||||
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.
|
||||
@ -128,6 +220,78 @@ class LocateSubShapesDlg(BaseDlg):
|
||||
# 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_ids = geompy.SubShapeAllIDs(self._selected_object, selection_level)
|
||||
|
||||
# Iterate over ids to check if it fits to limits
|
||||
# TODO: implement selections
|
||||
limits = self.get_limits()
|
||||
for id in subshapes_ids:
|
||||
# Get a sub-shape by id
|
||||
pass
|
||||
|
||||
# Get related parameter to check it later
|
||||
param = None
|
||||
if selection_level == GEOM.EDGE:
|
||||
# Get a lenght of an edge
|
||||
pass
|
||||
elif selection_level == GEOM.FACE:
|
||||
# Get an area of a face
|
||||
pass
|
||||
elif selection_level == GEOM.SOLID:
|
||||
# Get a volume of a solid
|
||||
pass
|
||||
else:
|
||||
# We shouldn't fall here
|
||||
QMessageBox.warning(
|
||||
None, 'Warning', 'Wrong selection level: %s!' % (selection_level))
|
||||
return
|
||||
|
||||
# Check if it fits to the limits
|
||||
if param >= limits[0] and param <= limits[1]:
|
||||
# Select sub-shape
|
||||
pass
|
||||
else:
|
||||
# Deselect sub-shape
|
||||
pass
|
||||
|
||||
# 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):
|
||||
"""
|
||||
@ -140,9 +304,40 @@ class LocateSubShapesDlg(BaseDlg):
|
||||
None.
|
||||
"""
|
||||
|
||||
#TODO: what are we going to do on this click?
|
||||
# Should it do a separated script?
|
||||
QMessageBox.warning(None, 'Warning', 'Not implemented yet')
|
||||
# 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()
|
||||
}
|
||||
|
||||
limits = execute(self._selected_object, self._minmax_algo, args, False)
|
||||
if len(limits) >= 2:
|
||||
self.set_limits(limits[0], limits[1])
|
||||
|
||||
|
||||
def get_args(self):
|
||||
@ -156,24 +351,45 @@ class LocateSubShapesDlg(BaseDlg):
|
||||
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()
|
||||
current_index = self._type_widget.currentIndex()
|
||||
selection_level = self.get_measurment_type(current_index)
|
||||
limits = self.get_limits()
|
||||
min_selected = 1
|
||||
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,
|
||||
'min_limit': limits[0],
|
||||
'max_limit': limits[1]
|
||||
'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.
|
||||
|
@ -55,9 +55,7 @@ def run(args_dict, progress_emitter):
|
||||
if ('source_solid' not in args_dict or
|
||||
'selected_ids' not in args_dict or
|
||||
'result_name' not in args_dict or
|
||||
'selection_level' not in args_dict or
|
||||
'min_limit' not in args_dict or
|
||||
'max_limit' not in args_dict):
|
||||
'selection_level' not in args_dict):
|
||||
|
||||
logging.info('Cant execute an algo because the arguments are empty!')
|
||||
return False
|
||||
@ -66,8 +64,6 @@ def run(args_dict, progress_emitter):
|
||||
selected_ids = args_dict['selected_ids']
|
||||
result_name = args_dict['result_name']
|
||||
selection_level = args_dict['selection_level']
|
||||
min_limit = args_dict['min_limit']
|
||||
max_limit = args_dict['max_limit']
|
||||
|
||||
# Replace the lines below with an actual algorithm
|
||||
logging.info('Received arguments:')
|
||||
@ -75,21 +71,29 @@ def run(args_dict, progress_emitter):
|
||||
logging.info('\tselected_ids: %s', selected_ids)
|
||||
logging.info('\tresult_name: %s', result_name)
|
||||
logging.info('\tselection_level: %s', selection_level)
|
||||
logging.info('\tmin_limit: %s', min_limit)
|
||||
logging.info('\tmax_limit: %s', max_limit)
|
||||
progress_emitter.emit()
|
||||
|
||||
sleep(1)
|
||||
|
||||
logging.warning('The algo script is not implemented! Return the copy of the source object...')
|
||||
solid = geompy.MakeCopy(source_solid, result_name)
|
||||
|
||||
progress_emitter.emit()
|
||||
|
||||
logging.info('Done.')
|
||||
sleep(0.1)
|
||||
|
||||
# Make a group
|
||||
group = geompy.CreateGroup(source_solid, selection_level, theName = result_name)
|
||||
|
||||
# Iterate all over the group's ids and remove unselected
|
||||
group_ids = geompy.GetObjectIDs(group)
|
||||
logging.info('Group Sub-shapes ids: %s', group_ids)
|
||||
|
||||
for subshape_id in group_ids:
|
||||
if subshape_id not in selected_ids:
|
||||
geompy.RemoveObject(group, subshape_id)
|
||||
logging.info('\tSub-shape %s was removed!', subshape_id)
|
||||
|
||||
progress_emitter.emit()
|
||||
|
||||
return solid
|
||||
logging.info('Group of selected sub-shapes was created.')
|
||||
progress_emitter.emit()
|
||||
|
||||
return group
|
||||
|
||||
|
||||
def test():
|
||||
@ -107,17 +111,17 @@ def test():
|
||||
source_solid = geompy.ImportBREP(test_file)
|
||||
geompy.addToStudy(source_solid, "source_solid")
|
||||
|
||||
selection_level = GEOM.EDGE
|
||||
|
||||
# TODO: Implement for actual algorithm
|
||||
# Here we just use all ids.
|
||||
all_subshapes = geompy.SubShapeAllIDs(source_solid, GEOM.EDGE)
|
||||
all_subshapes = geompy.SubShapeAllIDs(source_solid, selection_level)
|
||||
|
||||
args_dict = {
|
||||
'source_solid': source_solid,
|
||||
'selected_ids': all_subshapes,
|
||||
'result_name': 'LocateSubshapes_result',
|
||||
'selection_level': GEOM.EDGE,
|
||||
'min_limit': 0.0,
|
||||
'max_limit': 99.99
|
||||
'selection_level': selection_level
|
||||
}
|
||||
|
||||
# Dummy emitter
|
||||
|
112
src/RepairGUIAdv/locate_subshapes_limits.py
Executable file
112
src/RepairGUIAdv/locate_subshapes_limits.py
Executable file
@ -0,0 +1,112 @@
|
||||
# -*- 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)
|
||||
|
||||
"""Example of algorithm script for GEOM Locate Subshapes plugin.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
from time import sleep
|
||||
|
||||
import salome
|
||||
|
||||
from salome.geom import geomBuilder
|
||||
from qtsalome import QFileDialog, QApplication, pyqtSignal
|
||||
import GEOM
|
||||
|
||||
|
||||
salome.salome_init()
|
||||
geompy = geomBuilder.New()
|
||||
|
||||
|
||||
def run(args_dict, progress_emitter):
|
||||
"""
|
||||
Helper function to call run() with arguments parsed from dictionary.
|
||||
|
||||
Args:
|
||||
args_dict - arguments as pairs string : any type value
|
||||
|
||||
Returns:
|
||||
A result object.
|
||||
"""
|
||||
|
||||
logging.info('Run Locate Subshapes algorithm.')
|
||||
progress_emitter.emit()
|
||||
|
||||
|
||||
if ('source_solid' not in args_dict or
|
||||
'selection_level' not in args_dict):
|
||||
|
||||
logging.info('Cant execute an algo because the arguments are empty!')
|
||||
return False
|
||||
|
||||
source_solid = args_dict['source_solid']
|
||||
selection_level = args_dict['selection_level']
|
||||
|
||||
# Replace the lines below with an actual algorithm
|
||||
logging.info('Received arguments:')
|
||||
logging.info('\tsource_solid: %s', source_solid)
|
||||
logging.info('\tselection_level: %s', selection_level)
|
||||
progress_emitter.emit()
|
||||
|
||||
sleep(1)
|
||||
|
||||
logging.warning('The algo script is not implemented! Return default values...')
|
||||
limits = [0.0, 100.0]
|
||||
|
||||
logging.info('Done.')
|
||||
progress_emitter.emit()
|
||||
|
||||
return limits
|
||||
|
||||
|
||||
def test():
|
||||
"""
|
||||
Tests execution of repair algo script.
|
||||
"""
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
test_file, _ = QFileDialog.getOpenFileName(None, 'Open brep', '/home', 'Brep Files (*.brep)')
|
||||
if not test_file:
|
||||
return
|
||||
|
||||
# test_file = "PartitionCube.brep"
|
||||
source_solid = geompy.ImportBREP(test_file)
|
||||
geompy.addToStudy(source_solid, "source_solid")
|
||||
|
||||
args_dict = {
|
||||
'source_solid': source_solid,
|
||||
'selection_level': GEOM.EDGE
|
||||
}
|
||||
|
||||
# Dummy emitter
|
||||
# TODO: doesn't work
|
||||
# progress_emitter = pyqtSignal()
|
||||
progress_emitter = type('DummyEmitter', (object,), {'emit': lambda self: sleep(0.1)})()
|
||||
|
||||
run(args_dict, progress_emitter)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
test()
|
||||
sys.exit(app.exec_())
|
Loading…
Reference in New Issue
Block a user