]> SALOME platform Git repositories - modules/geom.git/commitdiff
Salome HOME
[bos #38044][EDF] (2023-T3) Support for automatic reparation. Base implementation...
authorKonstantin Leontev <Konstantin.LEONTEV@opencascade.com>
Wed, 27 Mar 2024 11:44:58 +0000 (11:44 +0000)
committerDUC ANH HOANG <dh77501n@dsp1043837>
Thu, 23 May 2024 11:58:25 +0000 (13:58 +0200)
src/RepairGUIAdv/CMakeLists.txt
src/RepairGUIAdv/basedlg.py
src/RepairGUIAdv/geomrepairadv_execute.py
src/RepairGUIAdv/locate_subshapes.py
src/RepairGUIAdv/locate_subshapes_algo.py
src/RepairGUIAdv/locate_subshapes_limits.py [new file with mode: 0755]

index 1ef411c4f5f8d1d9668625f581b72961bf79aaea..efd485fa3770b901e8f6976db7c03fc632ce38d4 100644 (file)
@@ -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
index 484401886f61a3061298b1b98e0301e35cf7c083..2edaeeda686ff8603d1795f2a5a49e35ec0ff56d 100644 (file)
@@ -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.
index 9b76c58d5d6403c30cd6c1d17eeb940f75067505..86558e656ca33c8630d857bf59c66b7ec5e659bc 100644 (file)
@@ -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,13 +137,14 @@ 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
 
-        geompy.FuncToPythonDump(
-            selected_object,
-            result_object,
-            'from salome.geom.geomrepairadv import geomrepairadv_execute\n',
-            'geomrepairadv_execute.execute',
-            '\'' + str(algo_name) + '\', ' + args_dict_str
-        )
+        if is_dump_on:
+            geompy.FuncToPythonDump(
+                selected_object,
+                result_object,
+                'from salome.geom.geomrepairadv import geomrepairadv_execute\n',
+                'geomrepairadv_execute.execute',
+                '\'' + str(algo_name) + '\', ' + args_dict_str
+            )
 
         return result_object
 
index 21cc090a5cfb88f8fb9c58477a70788ca148e3d2..9aeacc6b45f3f0b2466107dc0b66b3ea70e4c84a 100644 (file)
@@ -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.
index 959188190c64c784c725d6443de4f42dc4c78fbe..bba7c30ccb612b16a5ba368ee18628441f74363d 100755 (executable)
@@ -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)
+    sleep(0.1)
+
+    # Make a group
+    group = geompy.CreateGroup(source_solid, selection_level, theName = result_name)
 
-    logging.warning('The algo script is not implemented! Return the copy of the source object...')
-    solid = geompy.MakeCopy(source_solid, 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()
 
-    logging.info('Done.')
+    logging.info('Group of selected sub-shapes was created.')
     progress_emitter.emit()
 
-    return solid
+    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
diff --git a/src/RepairGUIAdv/locate_subshapes_limits.py b/src/RepairGUIAdv/locate_subshapes_limits.py
new file mode 100755 (executable)
index 0000000..a65f840
--- /dev/null
@@ -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_())