mirror of
https://git.salome-platform.org/gitpub/modules/geom.git
synced 2024-11-11 16:19:17 +05:00
[bos #38044][EDF] (2023-T3) Support for automatic reparation. Added execution chain and generic GUI using merge faces plugin as an example.
This commit is contained in:
parent
5f12362860
commit
5995197696
@ -25,6 +25,7 @@ SET(SUBDIRS_COMMON
|
||||
GEOMImpl GEOM_I GEOMClient GEOM_I_Superv GEOM_SWIG GEOM_PY
|
||||
AdvancedEngine
|
||||
STLPlugin BREPPlugin STEPPlugin IGESPlugin XAOPlugin Tools
|
||||
RepairGUIAdv
|
||||
)
|
||||
|
||||
##
|
||||
|
70
src/RepairGUIAdv/CMakeLists.txt
Normal file
70
src/RepairGUIAdv/CMakeLists.txt
Normal file
@ -0,0 +1,70 @@
|
||||
# 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()
|
81
src/RepairGUIAdv/DlgRef_1Spin_QTD.ui
Normal file
81
src/RepairGUIAdv/DlgRef_1Spin_QTD.ui
Normal file
@ -0,0 +1,81 @@
|
||||
<?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>
|
332
src/RepairGUIAdv/basedlg.py
Normal file
332
src/RepairGUIAdv/basedlg.py
Normal file
@ -0,0 +1,332 @@
|
||||
# -*- 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_())
|
111
src/RepairGUIAdv/basedlg.ui
Normal file
111
src/RepairGUIAdv/basedlg.ui
Normal file
@ -0,0 +1,111 @@
|
||||
<?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>
|
59
src/RepairGUIAdv/geomrepairadv_common.py
Normal file
59
src/RepairGUIAdv/geomrepairadv_common.py
Normal file
@ -0,0 +1,59 @@
|
||||
# -*- 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)
|
146
src/RepairGUIAdv/geomrepairadv_execute.py
Normal file
146
src/RepairGUIAdv/geomrepairadv_execute.py
Normal file
@ -0,0 +1,146 @@
|
||||
# -*- 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_())
|
63
src/RepairGUIAdv/geomrepairadv_logger.py
Normal file
63
src/RepairGUIAdv/geomrepairadv_logger.py
Normal file
@ -0,0 +1,63 @@
|
||||
# -*- 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
|
69
src/RepairGUIAdv/geomrepairadv_plugins.py
Normal file
69
src/RepairGUIAdv/geomrepairadv_plugins.py
Normal file
@ -0,0 +1,69 @@
|
||||
# -*- 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)
|
207
src/RepairGUIAdv/geomrepairadv_progress.py
Normal file
207
src/RepairGUIAdv/geomrepairadv_progress.py
Normal file
@ -0,0 +1,207 @@
|
||||
# -*- 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_())
|
119
src/RepairGUIAdv/geomrepairadv_worker.py
Normal file
119
src/RepairGUIAdv/geomrepairadv_worker.py
Normal file
@ -0,0 +1,119 @@
|
||||
# -*- 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))
|
55
src/RepairGUIAdv/locate_subshapes.py
Normal file
55
src/RepairGUIAdv/locate_subshapes.py
Normal file
@ -0,0 +1,55 @@
|
||||
# -*- 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_())
|
114
src/RepairGUIAdv/merge_faces.py
Normal file
114
src/RepairGUIAdv/merge_faces.py
Normal file
@ -0,0 +1,114 @@
|
||||
# -*- 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_())
|
175
src/RepairGUIAdv/merge_faces_algo.py
Executable file
175
src/RepairGUIAdv/merge_faces_algo.py
Executable file
@ -0,0 +1,175 @@
|
||||
# -*- 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_())
|
54
src/RepairGUIAdv/union_edges.py
Normal file
54
src/RepairGUIAdv/union_edges.py
Normal file
@ -0,0 +1,54 @@
|
||||
# -*- 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_())
|
Loading…
Reference in New Issue
Block a user