GEOMImpl GEOM_I GEOMClient GEOM_I_Superv GEOM_SWIG GEOM_PY
AdvancedEngine
STLPlugin BREPPlugin STEPPlugin IGESPlugin XAOPlugin Tools
+ RepairGUIAdv
)
##
--- /dev/null
+# Copyright (C) 2012-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 http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+IF(SALOME_BUILD_GUI)
+ INCLUDE(UsePyQt)
+
+ # We're already using these ui templates for C++ RepairGUI
+ SET(geom_dlg_ref ../DlgRef)
+
+ # scripts / static
+ SET(plugin_SCRIPTS
+ geomrepairadv_plugins.py
+ )
+
+ # base scripts
+ SET(_base_SCRIPTS
+ geomrepairadv_common.py
+ geomrepairadv_execute.py
+ geomrepairadv_logger.py
+ geomrepairadv_progress.py
+ geomrepairadv_worker.py
+ locate_subshapes.py
+ merge_faces.py
+ merge_faces_algo.py
+ union_edges.py
+ )
+
+ # gui scripts
+ SET(_gui_SCRIPTS
+ basedlg.py
+ basedlg.ui
+ ${geom_dlg_ref}/DlgRef_1Sel_QTD.ui
+ DlgRef_1Spin_QTD.ui # copied because original was promoted to SalomeApp_DoubleSpinBox
+ )
+
+ # uic files / to be processed by pyuic
+ SET(_pyuic_FILES
+ basedlg.ui
+ ${geom_dlg_ref}/DlgRef_1Sel_QTD.ui
+ DlgRef_1Spin_QTD.ui # copied because original was promoted to SalomeApp_DoubleSpinBox
+ )
+
+ # scripts / pyuic wrappings
+ PYQT_WRAP_UIC(_pyuic_SCRIPTS ${_pyuic_FILES} TARGET_NAME _target_name_pyuic)
+
+ # --- rules ---
+ SALOME_INSTALL_SCRIPTS("${plugin_SCRIPTS}" ${SALOME_GEOM_INSTALL_PLUGINS})
+ SALOME_INSTALL_SCRIPTS("${_base_SCRIPTS}" ${SALOME_INSTALL_PYTHON}/salome/geom/geomrepairadv)
+ SALOME_INSTALL_SCRIPTS("${_gui_SCRIPTS}" ${SALOME_INSTALL_PYTHON}/salome/geom/geomrepairadv)
+ SALOME_INSTALL_SCRIPTS("${_pyuic_SCRIPTS}" ${SALOME_INSTALL_PYTHON}/salome/geom/geomrepairadv TARGET_NAME _target_name_pyuic_py)
+ # add dependency of compiled py files on uic files in order
+ # to avoid races problems when compiling in parallel
+ ADD_DEPENDENCIES(${_target_name_pyuic_py} ${_target_name_pyuic})
+ENDIF()
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DlgRef_1Spin_QTD</class>
+ <widget class="QWidget" name="DlgRef_1Spin_QTD">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>114</width>
+ <height>51</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string/>
+ </property>
+ <layout class="QGridLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="GroupBox1">
+ <property name="title">
+ <string/>
+ </property>
+ <layout class="QGridLayout">
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="TextLabel1">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>TL1</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QDoubleSpinBox" name="SpinBox_DX"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <pixmapfunction>qPixmapFromMimeSource</pixmapfunction>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null
+# -*- 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 traceback import format_exc
+
+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 salome.geom.geomtools import GeomStudyTools
+from libGEOM_Swig import GEOM_Swig
+
+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
+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)
+
+ # 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
+ self._selected_copy = 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)
+ if child_widget:
+ self.child_layout.addWidget(child_widget, 2, 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
+
+ # Check if we already have selected object
+ self.on_select_object()
+
+
+ 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
+
+ # Make copy to prevent unintentional changing of a source object from the algo script
+ builder = geomBuilder.New()
+ self._selected_copy = builder.MakeCopy(
+ self._selected_object, self.get_result_name() + '_temp')
+
+ args_dict = self.get_args()
+ if args_dict:
+ # Add the copy object first
+ args_dict['source_solid'] = self._selected_copy
+
+ execute(self._algo_name, args_dict)
+ # TODO: do we need to handle here a case if the algo failed?
+
+ # Delete a copy object in any case
+ copy_entry = ObjectToID(self._selected_copy)
+ tools = GeomStudyTools()
+ tools.deleteShape(copy_entry)
+ self._selected_copy = None
+
+
+ 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()
+ self._algo_name = package_dir.joinpath(algo_name)
+ else:
+ self._algo_name = 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 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()
+
+ # Resets selection level
+ geom_swig.closeLocalSelection()
+
+ # Set level of selection for specific entry
+ if entry:
+ sel_level = geomBuilder.EnumToLong(self._selection_level)
+ geom_swig.initLocalSelection(entry, sel_level)
+
+
+ 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)
+ 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 closeEvent(self, event):
+ """
+ Overrides default close envent to reset selection level.
+ """
+
+ super().closeEvent(event)
+ 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_())
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BaseDlg</class>
+ <widget class="QDialog" name="BaseDlg">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>411</width>
+ <height>278</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string/>
+ </property>
+ <layout class="QGridLayout">
+ <item row="1" column="0">
+ <widget class="QGroupBox" name="GroupButtons">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <layout class="QHBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonOk">
+ <property name="text">
+ <string>&Apply and Close</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonApply">
+ <property name="text">
+ <string>&Apply</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>91</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonClose">
+ <property name="text">
+ <string>&Close</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonHelp">
+ <property name="text">
+ <string>&Help</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QFrame" name="child_placeholder">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>buttonOk</tabstop>
+ <tabstop>buttonApply</tabstop>
+ <tabstop>buttonClose</tabstop>
+ <tabstop>buttonHelp</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null
+# -*- 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)
+
+from qtsalome import QWidget
+from SalomePyQt import SalomePyQt
+
+from salome.geom.geomrepairadv.DlgRef_1Sel_QTD_ui import Ui_DlgRef_1Sel_QTD
+from salome.geom.geomrepairadv.DlgRef_1Spin_QTD_ui import Ui_DlgRef_1Spin_QTD
+
+# Constants from /src/GEOMGUI/GEOM_msg_en.ts
+GEOM_RESULT_NAME_GRP = 'Result name'
+NAME_LBL = 'Name'
+GEOM_SELECTED_LBL = 'Name'
+GEOM_SELECTED_SHAPE = 'Selected shape'
+
+class DlgRef_1Sel_QTD(Ui_DlgRef_1Sel_QTD, QWidget):
+ """
+ Helper class to set up a widget for any related dialog.
+ We need it because a class generated from ui file is derived from an object and
+ cannot be added as a widget to a dialog's layout.
+ """
+ def __init__(self):
+ QWidget.__init__(self)
+ # Set up the user interface from Designer.
+ self.setupUi(self)
+
+ # Set 'select' icon
+ icon = SalomePyQt.loadIcon('GEOM', 'select1.png')
+ self.PushButton1.setIcon(icon)
+
+
+class DlgRef_1Spin_QTD(Ui_DlgRef_1Spin_QTD, QWidget):
+ """
+ Helper class to set up a widget for any related dialog.
+ We need it because a class generated from ui file is derived from an object and
+ cannot be added as a widget to a dialog's layout.
+ """
+ def __init__(self):
+ QWidget.__init__(self)
+ # Set up the user interface from Designer.
+ self.setupUi(self)
--- /dev/null
+# -*- 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 os
+import sys
+import importlib.util
+
+from .geomrepairadv_progress import RepairProgressDialog
+from .geomrepairadv_logger import logger
+
+from qtsalome import Qt, QApplication, QFileDialog
+
+
+# Testing
+import salome
+from salome.geom import geomBuilder
+
+
+def module_from_filename(filename):
+ """
+ Create and execute a module by filename.
+
+ Args:
+ filename - a given python filename.
+
+ Returns:
+ Module.
+ """
+
+ # Get the module from the filename
+ basename = os.path.basename(filename)
+ module_name, _ = os.path.splitext(basename)
+
+ spec = importlib.util.spec_from_file_location(module_name, filename)
+ if not spec:
+ logger.error('Could not get a spec for %s file!', filename)
+ return None
+
+ module = importlib.util.module_from_spec(spec)
+ if not module:
+ logger.error('Could not get a module for %s file!', filename)
+ return None
+
+ sys.modules[module_name] = module
+
+ if not spec.loader:
+ logger.error('spec.loader is None for %s module!', module_name)
+ return None
+
+ spec.loader.exec_module(module)
+
+ return module
+
+
+def execute(algo_name, args_dict):
+ """
+ Executes GEOM advanced repair algorithm.
+
+ Args:
+ algo_name - path to the algo module
+ args_dict - dictionary with arguments those are specific for each algo.
+
+ Returns:
+ False if the algo failed.
+ """
+
+ logger.debug('execute() start')
+
+ # Find a module to execute
+ algo_module = module_from_filename(algo_name)
+ logger.debug('algo_module: %s', algo_module)
+ if not algo_module:
+ return False
+
+ logger.debug('Create RepairProgressDialog...')
+ progress_dlg = RepairProgressDialog(parent=None, target=algo_module.run, args=args_dict)
+ result = progress_dlg.exec()
+ logger.info('result: %s', result)
+
+
+def test_execution():
+ """
+ Tests execution of repair algo script.
+ It uses PartitionCube.brep file to run merge_faces algorithm.
+
+ Because of relative import must be run from a parent dir as a module:
+ $ python -m RepairGUIAdv.geomrepairadv_execute
+ """
+
+ salome.salome_init()
+ geompy = geomBuilder.New()
+
+ cube_file, _ = QFileDialog.getOpenFileName(None, 'Open brep', '/home', 'Brep Files (*.brep)')
+ if not cube_file:
+ return
+
+ # cube_file = "PartitionCube.brep"
+ source_solid = geompy.ImportBREP(cube_file)
+
+ # Récupération des faces à fusionner
+ face_a = geompy.GetFaceNearPoint(source_solid, geompy.MakeVertex(-143, -127, 250))
+ face_b = geompy.GetFaceNearPoint(source_solid, geompy.MakeVertex(49,-127,250))
+
+ geompy.addToStudy(source_solid, "source_solid")
+ geompy.addToStudyInFather(source_solid, face_a, "face_a")
+ geompy.addToStudyInFather(source_solid, face_b, "face_b")
+
+
+ args_dict = {
+ 'source_solid': source_solid,
+ 'face_a': face_a,
+ 'face_b': face_b,
+ 'result_name': 'MergeFaces_result'
+ }
+
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ algo_filename, _ = QFileDialog.getOpenFileName(
+ None, 'Open alogrithm script', current_dir, 'Python Files (*.py)')
+ if not algo_filename:
+ return
+
+ execute(algo_filename, args_dict)
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ test_execution()
+ sys.exit(app.exec_())
--- /dev/null
+# -*- 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 logging
+
+from qtsalome import QPlainTextEdit, pyqtSignal, QObject, pyqtSlot
+
+from salome.kernel.logger import Logger
+logger = Logger("salome.geom.geomrepairadv", level = logging.DEBUG)
+
+
+class QTextEditLogger(QObject, logging.Handler):
+ """
+ Provides a QPlainTextEdit text_widget that automaticly filled
+ by logs text from the logger handler.
+ """
+
+ append_text = pyqtSignal(str)
+
+ def __init__(self, parent):
+ super().__init__(parent)
+ super(logging.Handler).__init__()
+
+
+ self.text_widget = QPlainTextEdit(parent)
+ self.text_widget.setReadOnly(True)
+ self.append_text.connect(self.write_log)
+
+ formatter = logging.Formatter(
+ '%(levelname)s : %(asctime)s : [%(filename)s:%(funcName)s:%(lineno)s] : %(message)s')
+ self.setFormatter(formatter)
+
+ def emit(self, record):
+ msg = self.format(record)
+ self.append_text.emit(msg)
+
+
+ @pyqtSlot(str)
+ def write_log(self, log_text):
+ """
+ Appends a given log to the text widget.
+ """
+
+ self.text_widget.appendPlainText(log_text)
+ self.text_widget.centerCursor() # scroll to the bottom
--- /dev/null
+# -*- 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 salome_pluginsmanager
+
+# Plugins entry points
+# For new plugins create a function that shows related dialog,
+# then add it into plugin manager below.
+
+def locate_subshapes(context):
+ """
+ Opens Locate Subshapes plugin's dialog.
+ """
+ from salome.geom.geomrepairadv.locate_subshapes import LocateSubShapesDlg
+ dialog = LocateSubShapesDlg()
+ dialog.show()
+
+def merge_faces(context):
+ """
+ Opens Merge Faces plugin's dialog.
+ """
+ from salome.geom.geomrepairadv.merge_faces import MergeFacesDlg
+ dialog = MergeFacesDlg()
+ dialog.show()
+
+def union_edges(context):
+ """
+ Opens Union Edges plugin's dialog.
+ """
+ from salome.geom.geomrepairadv.union_edges import UnionEdgesDlg
+ dialog = UnionEdgesDlg()
+ dialog.show()
+
+
+# Add plugins to a manager with a given menu titles and tooltips
+
+salome_pluginsmanager.AddFunction(
+ 'Locate Subshapes',
+ 'Locates the sub-shapes of a compound by length, area or volume depending on whether it is an '
+ 'EDGE, a FACE or a SOLID',
+ locate_subshapes)
+
+salome_pluginsmanager.AddFunction(
+ 'Merge Faces',
+ 'Merges selected faces with a given precision',
+ merge_faces)
+
+salome_pluginsmanager.AddFunction(
+ 'Union Edges',
+ 'Merges edges of selected face',
+ union_edges)
--- /dev/null
+# -*- 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
+import logging
+from time import sleep
+
+from qtsalome import QApplication, QPlainTextEdit, \
+ QDialog, QVBoxLayout, QProgressDialog, QPushButton
+
+from .geomrepairadv_logger import QTextEditLogger
+from .geomrepairadv_worker import Worker
+
+
+class RepairProgressDialog(QDialog, QPlainTextEdit):
+ """
+ Dialog to show progress bar and log window during of execution
+ of a shape repair script.
+ """
+
+ def __init__(self, parent=None, target=None, args=None):
+ """
+ Inits progress dialogs widgets and runs a target function in a thread.
+
+ Args:
+ parent - parent dialog
+ target - target function to run
+ args - arguments to pass into a target.
+ """
+
+ super().__init__(parent)
+
+ self.setWindowTitle('Shape Repair')
+
+ # QProgressDialog uses manual layout, then it's not easy to customize it.
+ # So, we use it as a widget that is embedded into the dialog.
+ self.progress = QProgressDialog()
+ self.progress.setLabelText('Operation in progress...')
+ self.progress.setAutoClose(False)
+ self.progress.setAutoReset(False)
+
+ # Override default cancel slot to prevent progress from hiding
+ self.cancel_button = self.progress.findChild(QPushButton)
+ self.cancel_button.clicked.disconnect(self.progress.canceled)
+ self.cancel_button.clicked.connect(self.cancel)
+
+ # Helper flag to decide if we need to change button or close the dialog
+ self.canceled = False
+
+ # Set logger to redirect logs output into the text widget
+ self.log_handler = QTextEditLogger(self)
+ logging.getLogger().addHandler(self.log_handler)
+ logging.getLogger().setLevel(logging.DEBUG)
+
+ # Layout widgets
+ layout = QVBoxLayout(self)
+
+ layout.addWidget(self.log_handler.text_widget)
+ layout.addWidget(self.progress)
+
+ self.setLayout(layout)
+
+ # Setup and run target function in a working thread
+ self.thread = Worker(parent=self, target=target, args=args)
+ self.thread.start()
+
+
+ def cancel(self):
+ """
+ Replicates QProgressDialog.cancel() method.
+ """
+
+ # We need to keep the dialog opened on the first click
+ # so we can see the log output.
+ # After that we can close it with a second click.
+ if self.canceled:
+ self.close()
+ else:
+ # Terminates the execution of the thread.
+ # TODO: find out if we can do it with requestInterruption()
+ self.thread.terminate()
+
+ self.progress.setLabelText('Canceled!')
+ self.progress.setCancelButtonText('Close')
+
+ # Next click we exit
+ self.canceled = True
+
+
+ def on_failed(self):
+ """
+ Decided what to do if opreation failed.
+ """
+
+ self.progress.setLabelText('Operation failed!')
+ self.progress.setCancelButtonText('Close')
+
+ self.canceled = True
+
+
+ def on_completed(self):
+ """
+ Decided what to do when opreation completed.
+ """
+
+ self.progress.setLabelText('Completed!')
+ self.progress.setCancelButtonText('Close')
+
+ self.canceled = True
+
+
+ def value(self):
+ """
+ Replicates QProgressDialog.value() method.
+ """
+
+ return self.progress.value()
+
+
+ def setValue(self, progress):
+ """
+ Replicates QProgressDialog.setValue() method.
+
+ Args:
+ progress - a new value for a progress bar.
+ """
+
+ return self.progress.setValue(progress)
+
+
+ def close(self):
+ """
+ Process a close event to remove a log handler.
+ """
+
+ logging.getLogger().removeHandler(self.log_handler)
+
+ super().close()
+
+
+def test_thread():
+ """
+ Tests running a test function in a thread while
+ show a progress with RepairProgressDialog dialog.
+
+ Because of relative import must be run from a parent dir as a module:
+ $ python -m RepairGUIAdv.geomrepairadv_progress
+ """
+
+ progress_dlg = RepairProgressDialog(parent=None, target=test, args=None)
+ result = progress_dlg.exec()
+ logging.info('result: %s', result)
+
+
+def test(args, progress_emitter):
+ """
+ Tests logging and progress update with RepairProgressDialog.
+ """
+
+ if args:
+ pass
+
+ progress_emitter.emit()
+ logging.debug('debug msg')
+
+ sleep(2)
+
+ progress_emitter.emit()
+ logging.info('info msg')
+
+ sleep(2)
+
+ progress_emitter.emit()
+ logging.warning('warning msg')
+
+ sleep(2)
+
+ logging.error('error msg')
+ progress_emitter.emit()
+
+ sleep(2)
+
+ progress_emitter.emit()
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ test_thread()
+ sys.exit(app.exec_())
--- /dev/null
+# -*- 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 logging
+import inspect
+from traceback import format_exc
+
+from qtsalome import QApplication, pyqtSignal, QThread, Qt
+from .geomrepairadv_logger import logger
+
+
+class Worker(QThread):
+ """
+ Creates a tread to run a given target function with a progress dialog as a parent.
+ """
+
+ progress_update = pyqtSignal(int)
+ thread_failed = pyqtSignal()
+ work_completed = pyqtSignal()
+
+ def __init__(self, parent=None, target=None, args=None):
+ super().__init__(parent)
+
+ # Set target function and it's arguments
+ self.target = target
+ self.args = args
+
+ # Update a progress bar each time we receive an update signal
+ self.progress_update.connect(parent.setValue)
+ self.thread_failed.connect(parent.on_failed)
+ self.work_completed.connect(parent.on_completed)
+
+ # Calculate total amount of lines in executed function
+ source_lines = inspect.getsourcelines(target)
+ total_lines = len(source_lines[0])
+ first_line = source_lines[1]
+
+ # Set a progress emitter to update the progress from the target
+ self.progress_emitter = ProgressEmitter(self.progress_update, total_lines, first_line)
+
+
+ def run(self):
+ """
+ Runs the given target function.
+ """
+
+ try:
+ # Wait mode cursor
+ QApplication.setOverrideCursor(Qt.WaitCursor)
+
+ self.target(self.args, self.progress_emitter)
+
+ # Reset the progress when finished
+ self.progress_update.emit(100)
+ self.work_completed.emit()
+
+ except Exception:
+ logger.error(format_exc())
+ self.thread_failed.emit()
+
+ finally:
+ QApplication.restoreOverrideCursor()
+
+
+ def terminate(self):
+ """
+ Overrides default terminate() to add some clean up.
+ """
+
+ super().terminate()
+
+ # Termination doesn't call a final block inside run()
+ QApplication.restoreOverrideCursor()
+
+
+class ProgressEmitter():
+ """
+ Helper class to reduce code repetition while update progress
+ from a function executed in a separated thread.
+ """
+
+ def __init__(self, progress_update, total_lines, first_line):
+ self.progress_update = progress_update
+ self.first_line = first_line
+
+ self.progress_percent = total_lines / 100.0
+ logger.debug('self.progress_percent: %f', self.progress_percent)
+
+
+ def emit(self):
+ """
+ Call this methid in a target function to update a progress value
+ based on a currently executed line number.
+ """
+
+ line = inspect.getframeinfo(inspect.stack()[1][0]).lineno
+ logger.debug('line: %d', line)
+ progress_value = (line - self.first_line) / self.progress_percent
+ logger.debug('progress_value: %d', progress_value)
+
+ self.progress_update.emit(int(progress_value))
--- /dev/null
+# -*- 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
+
+from salome.geom.geomrepairadv.basedlg import BaseDlg
+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.
+ """
+ def __init__(self, selection_level = GEOM.COMPOUND):
+ # Implement widget's content here
+ main_widget = QFrame()
+ layout = QGridLayout(main_widget)
+ layout.setContentsMargins(0, 0, 0, 0)
+
+ BaseDlg.__init__(
+ self, main_widget, 'Locate Subshapes', 'locate_subshapes_algo.py', False, selection_level)
+
+
+# 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_())
--- /dev/null
+# -*- 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, QMessageBox, QApplication
+
+from libGEOM_Swig import GEOM_Swig
+from salome.geom import geomBuilder
+from .geomrepairadv_logger import logger
+from .basedlg import BaseDlg
+from .geomrepairadv_common import DlgRef_1Spin_QTD
+import GEOM
+
+class MergeFacesDlg(BaseDlg):
+ """
+ Dialog for Merge Faces plugin that merges selected faces with a given precision.
+ """
+
+ def __init__(self, selection_level = GEOM.FACE):
+ # Make layout for new widgets
+ main_widget = QFrame()
+ layout = QGridLayout(main_widget)
+ layout.setContentsMargins(0, 0, 0, 0)
+
+ # Precision widget
+ self._precision_widget = DlgRef_1Spin_QTD()
+ self._precision_widget.TextLabel1.setText('Precision')
+ layout.addWidget(self._precision_widget, 0, 0)
+
+ BaseDlg.__init__(
+ self, main_widget, 'Merge Faces', 'merge_faces_algo.py', True, selection_level)
+
+
+ def get_precision(self):
+ """
+ Returns current precision value.
+
+ Args:
+ None.
+
+ Returns:
+ Double.
+ """
+
+ return self._precision_widget.SpinBox_DX.value()
+
+
+ def get_args(self):
+ """
+ Collects arguments for a repair execution algorithm into a dictionary.
+
+ Args:
+ None.
+
+ Returns:
+ Dictionary with arguments for execution.
+ """
+
+ geom_swig = GEOM_Swig()
+ faces_ids = geom_swig.getLocalSelection()
+ logger.debug('faces_ids: %s', faces_ids)
+
+ if len(faces_ids) < 2:
+ QMessageBox.warning(
+ None,
+ 'Warning',
+ 'The algorithm needs at least two selected faces!\nMerging was canceled.'
+ )
+ return None
+
+ # Get faces from a temporary copy object
+ builder = geomBuilder.New()
+ faces = builder.SubShapes(self._selected_copy, faces_ids)
+ logger.debug('faces: %s', faces)
+
+ return {
+ 'face_a': faces[0],
+ 'face_b': faces[1],
+ 'result_name': self.get_result_name(),
+ 'precision': self.get_precision()
+ }
+
+
+# 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.merge_faces
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+
+ dlg = MergeFacesDlg(None)
+ dlg.show()
+
+ sys.exit(app.exec_())
--- /dev/null
+# -*- 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 Merge Faces plugin.
+"""
+
+import sys
+import logging
+from time import sleep
+
+import salome
+
+from salome.geom import geomBuilder
+from qtsalome import QFileDialog, QApplication, pyqtSignal
+
+
+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 string with result description.
+ """
+
+ logging.info('Run Merge Faces algorithm.')
+ progress_emitter.emit()
+
+
+ if ('source_solid' not in args_dict or
+ 'face_a' not in args_dict or
+ 'face_b' not in args_dict or
+ 'result_name' not in args_dict):
+
+ logging.info('Cant execute an algo because the arguments are empty!')
+ return False
+
+ source_solid = args_dict['source_solid']
+ face_a = args_dict['face_a']
+ face_b = args_dict['face_b']
+ result_name = args_dict['result_name']
+
+
+ logging.info('Creating of two faces...')
+ progress_emitter.emit()
+
+ # Fusion des deux faces
+ partition = geompy.MakePartition([face_a, face_b],[])
+ points = [geompy.GetVertexNearPoint(partition, geompy.MakeVertex(-298, 29, 250)),
+ geompy.GetVertexNearPoint(partition, geompy.MakeVertex(178, 29, 250)),
+ geompy.GetVertexNearPoint(partition, geompy.MakeVertex(178, -282, 250)),
+ geompy.GetVertexNearPoint(partition, geompy.MakeVertex(-298, -282, 250))]
+ wire = geompy.MakePolyline(points,True)
+ fused_face = geompy.MakeFaceWires([wire], True)
+ geompy.addToStudy(fused_face, "fused_face")
+
+ logging.info('Creating of a new geometry from the source brep...')
+ progress_emitter.emit()
+
+ sleep(5)
+
+ # Fusion des deux faces au sein de la boite + nettoyage de la boite
+ points = [geompy.GetVertexNearPoint(source_solid, geompy.MakeVertex(-298, 29, 250)),
+ geompy.GetVertexNearPoint(source_solid, geompy.MakeVertex(178, 29, 250)),
+ geompy.GetVertexNearPoint(source_solid, geompy.MakeVertex(178, -282, 250)),
+ geompy.GetVertexNearPoint(source_solid, geompy.MakeVertex(-298, -282, 250)),
+ geompy.GetVertexNearPoint(source_solid, geompy.MakeVertex(-298, 29, 0)),
+ geompy.GetVertexNearPoint(source_solid, geompy.MakeVertex(178, 29, 0)),
+ geompy.GetVertexNearPoint(source_solid, geompy.MakeVertex(178, -282, 0)),
+ geompy.GetVertexNearPoint(source_solid, geompy.MakeVertex(-298, -282, 0))]
+ # ### Fusion des deux faces
+ wire = geompy.MakePolyline(points[:4],True)
+ faces = [geompy.MakeFaceWires([wire], True)]
+
+ logging.info('Cleaning of the new geometry...')
+ progress_emitter.emit()
+
+ sleep(5)
+
+ # Uncomment to simulate exception handling in a thread worker class
+ # raise Exception
+
+ # ### Nettoyage des 4 faces latérales
+ wire = geompy.MakePolyline([points[3], points[2], points[6], points[7]],True)
+ faces.append(geompy.MakeFaceWires([wire], True))
+ wire = geompy.MakePolyline([points[0], points[3], points[7], points[4]],True)
+ faces.append(geompy.MakeFaceWires([wire], True))
+ wire = geompy.MakePolyline([points[1], points[0], points[4], points[5]],True)
+ faces.append(geompy.MakeFaceWires([wire], True))
+ wire = geompy.MakePolyline([points[2], points[1], points[5], points[6]],True)
+ faces.append(geompy.MakeFaceWires([wire], True))
+
+ # ### Récupération de la dernière face
+ faces.append(geompy.GetFaceNearPoint(source_solid, geompy.MakeVertex(-59, -127, 0)))
+
+ logging.info('Creating a solid...')
+ progress_emitter.emit()
+
+ sleep(5)
+
+ # ### Création du solide
+ shell = geompy.MakeShell(faces)
+ solid = geompy.MakeSolid(shell)
+
+ geompy.addToStudy(solid, result_name)
+
+ logging.info('Merge Faces algorithm was completed successfully.')
+ progress_emitter.emit()
+
+ return True
+
+
+def test():
+ """
+ Tests execution of repair algo script.
+ """
+
+ cube_file, _ = QFileDialog.getOpenFileName(None, 'Open brep', '/home', 'Brep Files (*.brep)')
+ if not cube_file:
+ return
+
+ # cube_file = "PartitionCube.brep"
+ source_solid = geompy.ImportBREP(cube_file)
+
+ # Récupération des faces à fusionner
+ face_a = geompy.GetFaceNearPoint(source_solid, geompy.MakeVertex(-143, -127, 250))
+ face_b = geompy.GetFaceNearPoint(source_solid, geompy.MakeVertex(49,-127,250))
+
+ geompy.addToStudy(source_solid, "source_solid")
+ geompy.addToStudyInFather(source_solid, face_a, "face_a")
+ geompy.addToStudyInFather(source_solid, face_b, "face_b")
+
+
+ args_dict = {
+ 'source_solid': source_solid,
+ 'face_a': face_a,
+ 'face_b': face_b,
+ 'result_name': 'MergeFaces_result'
+ }
+
+ # Dummy emitter
+ # TODO: doesn't work
+ progress_emitter = pyqtSignal()
+
+ run(args_dict, progress_emitter)
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ test()
+ sys.exit(app.exec_())
--- /dev/null
+# -*- 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
+
+from salome.geom.geomrepairadv.basedlg import BaseDlg
+import GEOM
+
+class UnionEdgesDlg(BaseDlg):
+ """
+ Dialog for Union Edges plugin that unifies edges of selected face.
+ """
+ def __init__(self, selection_level = GEOM.COMPOUND):
+ # Implement widget's content here
+ main_widget = QFrame()
+ layout = QGridLayout(main_widget)
+ layout.setContentsMargins(0, 0, 0, 0)
+
+ BaseDlg.__init__(
+ self, main_widget, 'Union Edges', 'union_edges_algo.py', False, selection_level)
+
+
+# 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.union_edges
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+
+ dlg = UnionEdgesDlg(None)
+ dlg.show()
+
+ sys.exit(app.exec_())