mirror of
https://git.salome-platform.org/gitpub/modules/smesh.git
synced 2024-11-11 16:19:16 +05:00
23619: EDF 18055 - Detection of sharp edges
This commit is contained in:
parent
baf83bed41
commit
7b4c10fd0e
16
doc/salome/examples/grouping_elements_ex09.py
Normal file
16
doc/salome/examples/grouping_elements_ex09.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Creating groups of faces separated by sharp edges
|
||||
|
||||
import salome
|
||||
salome.salome_init()
|
||||
from salome.geom import geomBuilder
|
||||
from salome.smesh import smeshBuilder
|
||||
geompy = geomBuilder.New()
|
||||
smesh = smeshBuilder.New()
|
||||
|
||||
# create a mesh on a box
|
||||
box = geompy.MakeBoxDXDYDZ( 10,10,10, theName="Box" )
|
||||
mesh = smesh.Mesh(box,"Mesh")
|
||||
mesh.AutomaticHexahedralization()
|
||||
|
||||
# create groups of faces of each side of the box
|
||||
groups = mesh.FaceGroupsSeparatedByEdges( 89 )
|
@ -112,6 +112,7 @@ SET(GOOD_TESTS
|
||||
grouping_elements_ex06.py
|
||||
grouping_elements_ex07.py
|
||||
grouping_elements_ex08.py
|
||||
grouping_elements_ex09.py
|
||||
measurements_ex01.py
|
||||
measurements_ex02.py
|
||||
modifying_meshes_ex01.py
|
||||
|
BIN
doc/salome/gui/SMESH/images/Nut_sharp_edges.png
Normal file
BIN
doc/salome/gui/SMESH/images/Nut_sharp_edges.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
BIN
doc/salome/gui/SMESH/images/groups_by_sharp_edges_dlg.png
Normal file
BIN
doc/salome/gui/SMESH/images/groups_by_sharp_edges_dlg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -9,8 +9,6 @@ This operation allows creating groups on geometry on all selected shapes. Only t
|
||||
The type of each new group is defined automatically by the nature of the **Geometry**.
|
||||
The group names will be the same as the names of geometrical objects.
|
||||
|
||||
.. warning:: It's impossible to create a group of *0D elements* or *ball elements* with this operation. For this, it is necessary to use :ref:`Create group <creating_groups_page>` operation.
|
||||
|
||||
To use this operation, select in the **Mesh** menu or in the contextual menu in the Object browser **Create Groups from Geometry** item.
|
||||
|
||||
.. image:: ../images/create_groups_from_geometry.png
|
||||
|
29
doc/salome/gui/SMESH/input/face_groups_by_sharp_edges.rst
Normal file
29
doc/salome/gui/SMESH/input/face_groups_by_sharp_edges.rst
Normal file
@ -0,0 +1,29 @@
|
||||
************************************
|
||||
Face Groups Separated By Sharp Edges
|
||||
************************************
|
||||
|
||||
**Face groups separated by sharp edges** operation distributes all faces of the mesh between groups using sharp edges and optionally existing 1D elements as group boundaries. Edges where more than two faces meet are always considered as a group boundary. The operation is available in **Mesh** menu.
|
||||
|
||||
The operation dialog looks as follows:
|
||||
|
||||
.. image:: ../images/groups_by_sharp_edges_dlg.png
|
||||
:align: center
|
||||
|
||||
In this dialog box specify
|
||||
|
||||
* **Mesh** including the faces to distribute between groups.
|
||||
* **Sharp angle** in degrees, by which edges used as group boundaries are detected. An edge is considered as a group boundary if an angle between normals of adjacent faces is more than this angle.
|
||||
* Activate **Create edges** option if you wish that 1D elements to be created (if not yet exist) on the edges that served as group boundaries.
|
||||
* Activate **Use existing edges** option if you wish that existing 1D elements to be used as group boundaries.
|
||||
* Activate **Preview** to see the edges that will be used as group boundaries highlighted in the Viewer.
|
||||
|
||||
|
||||
.. image:: ../images/Nut_sharp_edges.png
|
||||
:align: center
|
||||
|
||||
.. centered::
|
||||
**Preview of boundary edges detected at Sharp Angle = 10 degrees**
|
||||
|
||||
**See Also** a sample TUI Script of a :ref:`tui_groups_by_sharp_edges` operation.
|
||||
|
||||
|
@ -27,6 +27,7 @@ The following ways of group creation are possible:
|
||||
|
||||
* :ref:`Create group <creating_groups_page>` dialog allows creation of a group of any type: :ref:`Standalone group<standalone_group>`, :ref:`Group on geometry <group_on_geom>` and :ref:`Group on filter <group_on_filter>` using dedicated tabs.
|
||||
* :ref:`Create Groups from Geometry <create_groups_from_geometry_page>` dialog allows creation of several groups on geometry at once.
|
||||
* :doc:`face_groups_by_sharp_edges` operation distributes all faces of the mesh between groups using sharp edges and/or existing 1D elements as group boundaries.
|
||||
* Standalone groups of all nodes and elements of the chosen sub-mesh (type of elements depends on dimension of sub-mesh geometry) can be created using **Mesh -> Construct Group** menu item (available from the context menu as well).
|
||||
* Standalone groups of any element type can be created basing on nodes of other groups - using :ref:`Group based on nodes of other groups <group_of_underlying_elements_page>` dialog.
|
||||
* Standalone groups can be created by applying :ref:`Boolean operations <using_operations_on_groups_page>` to other groups.
|
||||
@ -49,6 +50,9 @@ In the Object Browser, if an item contains more than one child group, it is poss
|
||||
|
||||
An important tool, providing filters for creation of standalone groups and groups on filter is :ref:`selection_filter_library_page`.
|
||||
|
||||
**See Also** sample TUI Scripts of :doc:`tui_grouping_elements` operations.
|
||||
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
.. toctree::
|
||||
@ -56,6 +60,7 @@ An important tool, providing filters for creation of standalone groups and group
|
||||
|
||||
creating_groups.rst
|
||||
create_groups_from_geometry.rst
|
||||
face_groups_by_sharp_edges.rst
|
||||
group_of_underlying_elements.rst
|
||||
using_operations_on_groups.rst
|
||||
editing_groups.rst
|
||||
|
@ -1,5 +1,3 @@
|
||||
.. _tui_grouping_elements_page:
|
||||
|
||||
*****************
|
||||
Grouping Elements
|
||||
*****************
|
||||
@ -110,8 +108,11 @@ Creating groups of entities basing on nodes of other groups
|
||||
.. image:: ../images/dimgroup_tui1.png
|
||||
:align: center
|
||||
|
||||
Creating face groups separated by sharp edges
|
||||
=============================================
|
||||
|
||||
.. literalinclude:: ../../../examples/grouping_elements_ex09.py
|
||||
:language: python
|
||||
|
||||
|
||||
|
||||
:download:`Download this script <../../../examples/grouping_elements_ex09.py>`
|
||||
|
||||
|
@ -516,6 +516,20 @@ module SMESH
|
||||
in boolean underlyingOnly )
|
||||
raises (SALOME::SALOME_Exception);
|
||||
|
||||
/*!
|
||||
* Distribute all faces of the mesh between groups using sharp edges and optionally
|
||||
* existing 1D elements as group boundaries.
|
||||
* \param [in] sharpAngle - edge is considered sharp if an angle between normals of
|
||||
* adjacent faces is more than \a sharpAngle in degrees.
|
||||
* \param [in] createEdges - to create 1D elements for detected sharp edges.
|
||||
* \param [in] useExistingEdges - to use existing edges as group boundaries
|
||||
* \return ListOfGroups - the created groups
|
||||
*/
|
||||
ListOfGroups FaceGroupsSeparatedByEdges( in double sharpAngle,
|
||||
in boolean createEdges,
|
||||
in boolean useExistingEdges )
|
||||
raises (SALOME::SALOME_Exception);
|
||||
|
||||
/*!
|
||||
* Convert group on geometry or on filter into standalone group
|
||||
*/
|
||||
|
@ -80,9 +80,19 @@ module SMESH
|
||||
};
|
||||
typedef sequence<PolySegment> ListOfPolySegments;
|
||||
|
||||
// face edge defined by two nodes + optional medium node
|
||||
struct FaceEdge
|
||||
{
|
||||
long node1;
|
||||
long node2;
|
||||
long medium;
|
||||
};
|
||||
typedef sequence<FaceEdge> ListOfEdges;
|
||||
|
||||
|
||||
/*!
|
||||
* This interface makes modifications on the Mesh - removing elements and nodes etc.
|
||||
* Also provides some analysis functions.
|
||||
*/
|
||||
interface SMESH_MeshEditor
|
||||
{
|
||||
@ -812,6 +822,13 @@ module SMESH
|
||||
boolean IsCoherentOrientation2D()
|
||||
raises (SALOME::SALOME_Exception);
|
||||
|
||||
/*!
|
||||
* Return sharp edges of faces and non-manifold ones.
|
||||
* Optionally add existing edges. Angle is in degrees.
|
||||
*/
|
||||
ListOfEdges FindSharpEdges(in double angle, in boolean addExistingEdges)
|
||||
raises (SALOME::SALOME_Exception);
|
||||
|
||||
/*!
|
||||
* Returns all or only closed FreeBorder's.
|
||||
*/
|
||||
|
@ -231,6 +231,7 @@ SET(SMESH_RESOURCES_FILES
|
||||
mesh_hide.png
|
||||
mesh_deflection.png
|
||||
mesh_offset.png
|
||||
mesh_face_groups_by_edges.png
|
||||
)
|
||||
|
||||
INSTALL(FILES ${SMESH_RESOURCES_FILES} DESTINATION ${SALOME_SMESH_INSTALL_RES_DATA})
|
||||
|
BIN
resources/mesh_face_groups_by_edges.png
Normal file
BIN
resources/mesh_face_groups_by_edges.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 635 B |
@ -103,6 +103,7 @@ SET(_moc_HEADERS
|
||||
SMESHGUI_MultiEditDlg.h
|
||||
SMESHGUI_DeleteGroupDlg.h
|
||||
SMESHGUI_GroupOpDlg.h
|
||||
SMESHGUI_FaceGroupsSeparatedByEdgesDlg.h
|
||||
SMESHGUI_SmoothingDlg.h
|
||||
SMESHGUI_RenumberingDlg.h
|
||||
SMESHGUI_ExtrusionDlg.h
|
||||
@ -198,6 +199,7 @@ SET(_other_SOURCES
|
||||
SMESHGUI_MultiEditDlg.cxx
|
||||
SMESHGUI_DeleteGroupDlg.cxx
|
||||
SMESHGUI_GroupOpDlg.cxx
|
||||
SMESHGUI_FaceGroupsSeparatedByEdgesDlg.cxx
|
||||
SMESHGUI_SmoothingDlg.cxx
|
||||
SMESHGUI_RenumberingDlg.cxx
|
||||
SMESHGUI_ExtrusionDlg.cxx
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include "SMESHGUI_DuplicateNodesDlg.h"
|
||||
#include "SMESHGUI_ExtrusionAlongPathDlg.h"
|
||||
#include "SMESHGUI_ExtrusionDlg.h"
|
||||
#include "SMESHGUI_FaceGroupsSeparatedByEdgesDlg.h"
|
||||
#include "SMESHGUI_FieldSelectorWdg.h"
|
||||
#include "SMESHGUI_FileInfoDlg.h"
|
||||
#include "SMESHGUI_FileValidator.h"
|
||||
@ -779,8 +780,9 @@ namespace
|
||||
zTolLayout->setMargin( 0 );
|
||||
zTolSpin->RangeStepAndValidator( 0, 1e+100, 1., "length_precision" );
|
||||
zTolSpin->setValue( zTol );
|
||||
//QObject::connect( zTolCheck, SIGNAL( stateChanged(int)), zTolSpin, SLOT( setEnabled(bool)));
|
||||
QObject::connect( zTolCheck, SIGNAL( toggled(bool)), zTolSpin, SLOT( setEnabled(bool)));
|
||||
zTolCheck->setChecked( resMgr->booleanValue( "SMESH", "enable_ztolerance", false ));
|
||||
zTolSpin ->setEnabled( zTolCheck->isChecked() );
|
||||
wdgList.append( zTolWdg );
|
||||
|
||||
SalomeApp_CheckFileDlg* fd =
|
||||
@ -3132,6 +3134,18 @@ bool SMESHGUI::OnGUIEvent( int theCommandID )
|
||||
break;
|
||||
}
|
||||
|
||||
case SMESHOp::OpFaceGroupsByEdges: // Create face groups separated by sharp edges
|
||||
{
|
||||
if ( isStudyLocked() )
|
||||
break;
|
||||
|
||||
EmitSignalDeactivateDialog();
|
||||
SMESHGUI_FaceGroupsSeparatedByEdgesDlg* aDlg = new SMESHGUI_FaceGroupsSeparatedByEdgesDlg( this );
|
||||
aDlg->show();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SMESHOp::OpDeleteGroup: // Delete groups with their contents
|
||||
{
|
||||
if ( !vtkwnd )
|
||||
@ -3909,6 +3923,7 @@ void SMESHGUI::initialize( CAM_Application* app )
|
||||
createSMESHAction( SMESHOp::OpIntersectGroups, "INT_GROUP", "ICON_INTERSECT" );
|
||||
createSMESHAction( SMESHOp::OpCutGroups, "CUT_GROUP", "ICON_CUT" );
|
||||
createSMESHAction( SMESHOp::OpGroupUnderlyingElem, "UNDERLYING_ELEMS", "ICON_UNDERLYING_ELEMS" );
|
||||
createSMESHAction( SMESHOp::OpFaceGroupsByEdges, "FACE_GROUPS_BY_EDGES", "ICON_FACE_GROUPS_BY_EDGES" );
|
||||
createSMESHAction( SMESHOp::OpAddElemGroupPopup, "ADD_TO_GROUP" );
|
||||
createSMESHAction( SMESHOp::OpRemoveElemGroupPopup, "REMOVE_FROM_GROUP" );
|
||||
createSMESHAction( SMESHOp::OpDeleteGroup, "DEL_GROUP", "ICON_DEL_GROUP" );
|
||||
@ -4146,6 +4161,7 @@ void SMESHGUI::initialize( CAM_Application* app )
|
||||
createMenu( SMESHOp::OpCutGroups, meshId, -1 );
|
||||
createMenu( separator(), meshId, -1 );
|
||||
createMenu( SMESHOp::OpGroupUnderlyingElem, meshId, -1 );
|
||||
createMenu( SMESHOp::OpFaceGroupsByEdges, meshId, -1 );
|
||||
createMenu( separator(), meshId, -1 );
|
||||
createMenu( SMESHOp::OpMeshInformation, meshId, -1 );
|
||||
//createMenu( SMESHOp::OpStdInfo, meshId, -1 );
|
||||
|
376
src/SMESHGUI/SMESHGUI_FaceGroupsSeparatedByEdgesDlg.cxx
Normal file
376
src/SMESHGUI/SMESHGUI_FaceGroupsSeparatedByEdgesDlg.cxx
Normal file
@ -0,0 +1,376 @@
|
||||
// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// File : SMESHGUI_FaceGroupsSeparatedByEdges.cxx
|
||||
|
||||
#include "SMESHGUI_FaceGroupsSeparatedByEdgesDlg.h"
|
||||
|
||||
#include "SMESHGUI.h"
|
||||
#include "SMESHGUI_SpinBox.h"
|
||||
#include "SMESHGUI_MeshEditPreview.h"
|
||||
#include "SMESHGUI_Utils.h"
|
||||
|
||||
// SALOME GUI includes
|
||||
#include <LightApp_Application.h>
|
||||
#include <LightApp_SelectionMgr.h>
|
||||
#include <SALOME_Actor.h>
|
||||
#include <SALOME_ListIO.hxx>
|
||||
#include <SUIT_MessageBox.h>
|
||||
#include <SUIT_OverrideCursor.h>
|
||||
#include <SUIT_ResourceMgr.h>
|
||||
#include <SUIT_Session.h>
|
||||
#include <SalomeApp_Tools.h>
|
||||
|
||||
#include <SALOMEDS_SObject.hxx>
|
||||
|
||||
// IDL includes
|
||||
#include <SALOMEconfig.h>
|
||||
#include CORBA_SERVER_HEADER(SMESH_Group)
|
||||
|
||||
// Qt includes
|
||||
#include <QCheckBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QToolButton>
|
||||
|
||||
#include <Standard_ErrorHandler.hxx>
|
||||
|
||||
#define SPACING 6
|
||||
#define MARGIN 11
|
||||
|
||||
/*!
|
||||
\class SMESHGUI_FaceGroupsSeparatedByEdgesDlg
|
||||
\brief Dialog to create face groups divided by sharp edges
|
||||
*/
|
||||
|
||||
SMESHGUI_FaceGroupsSeparatedByEdgesDlg::
|
||||
SMESHGUI_FaceGroupsSeparatedByEdgesDlg( SMESHGUI* theModule ) :
|
||||
SMESHGUI_PreviewDlg( theModule ),
|
||||
mySelectionMgr( SMESH::GetSelectionMgr( theModule ))
|
||||
{
|
||||
setModal(false);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setWindowTitle(tr("CAPTION"));
|
||||
setSizeGripEnabled(true);
|
||||
|
||||
QVBoxLayout* dlgLayout = new QVBoxLayout(this);
|
||||
dlgLayout->setSpacing(SPACING);
|
||||
dlgLayout->setMargin(MARGIN);
|
||||
|
||||
/***************************************************************/
|
||||
GroupArgs = new QGroupBox(tr("SMESH_ARGUMENTS"), this);
|
||||
QGridLayout* GroupArgsLayout = new QGridLayout(GroupArgs);
|
||||
GroupArgsLayout->setSpacing(SPACING);
|
||||
GroupArgsLayout->setMargin(MARGIN);
|
||||
|
||||
// mesh
|
||||
QLabel* meshNameLabel = new QLabel(tr("SMESH_MESH"), GroupArgs);
|
||||
myMeshName = new QLineEdit( GroupArgs );
|
||||
myMeshName->setReadOnly( true );
|
||||
|
||||
// angle
|
||||
QLabel* angleLabel = new QLabel( tr("SHARP_ANGLE"), GroupArgs );
|
||||
myAngle = new SMESHGUI_SpinBox( GroupArgs );
|
||||
myAngle->RangeStepAndValidator( 0, 180.0, 10.0, "angle_precision");
|
||||
|
||||
// check-boxes
|
||||
myCreateEdgesCheck = new QCheckBox( tr("CREATE_EDGES" ), GroupArgs);
|
||||
myUseExistingCheck = new QCheckBox( tr("USE_EXISTING_EDGES"), GroupArgs);
|
||||
myPreviewCheckBox = new QCheckBox(tr("PREVIEW"), GroupArgs);
|
||||
|
||||
// set previous values
|
||||
if ( SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr() )
|
||||
{
|
||||
myAngle->setValue( resMgr->doubleValue( "SMESH", "prev_sharp_angle", 30. ));
|
||||
myCreateEdgesCheck->setChecked( resMgr->booleanValue( "SMESH", "prev_create_edges", false ));
|
||||
myUseExistingCheck->setChecked( resMgr->booleanValue( "SMESH", "prev_use_existing_edges", false ));
|
||||
}
|
||||
|
||||
// layout
|
||||
GroupArgsLayout->addWidget( meshNameLabel, 0, 0);
|
||||
GroupArgsLayout->addWidget( myMeshName, 0, 1);
|
||||
GroupArgsLayout->addWidget( angleLabel, 1, 0);
|
||||
GroupArgsLayout->addWidget( myAngle, 1, 1);
|
||||
GroupArgsLayout->addWidget( myCreateEdgesCheck, 2, 0);
|
||||
GroupArgsLayout->addWidget( myUseExistingCheck, 3, 0);
|
||||
GroupArgsLayout->addWidget( myPreviewCheckBox, 4, 0);
|
||||
|
||||
/***************************************************************/
|
||||
GroupButtons = new QGroupBox(this);
|
||||
QHBoxLayout* GroupButtonsLayout = new QHBoxLayout(GroupButtons);
|
||||
GroupButtonsLayout->setSpacing(SPACING);
|
||||
GroupButtonsLayout->setMargin(MARGIN);
|
||||
|
||||
buttonOk = new QPushButton(tr("SMESH_BUT_APPLY_AND_CLOSE"), GroupButtons);
|
||||
buttonOk->setAutoDefault(true);
|
||||
buttonOk->setDefault(true);
|
||||
buttonApply = new QPushButton(tr("SMESH_BUT_APPLY"), GroupButtons);
|
||||
buttonApply->setAutoDefault(true);
|
||||
buttonCancel = new QPushButton(tr("SMESH_BUT_CLOSE"), GroupButtons);
|
||||
buttonCancel->setAutoDefault(true);
|
||||
buttonHelp = new QPushButton(tr("SMESH_BUT_HELP"), GroupButtons);
|
||||
buttonHelp->setAutoDefault(true);
|
||||
|
||||
GroupButtonsLayout->addWidget(buttonOk);
|
||||
GroupButtonsLayout->addSpacing(10);
|
||||
GroupButtonsLayout->addWidget(buttonApply);
|
||||
GroupButtonsLayout->addSpacing(10);
|
||||
GroupButtonsLayout->addStretch();
|
||||
GroupButtonsLayout->addWidget(buttonCancel);
|
||||
GroupButtonsLayout->addWidget(buttonHelp);
|
||||
|
||||
/***************************************************************/
|
||||
dlgLayout->addWidget(GroupArgs);
|
||||
dlgLayout->addWidget(GroupButtons);
|
||||
|
||||
mySMESHGUI->SetActiveDialogBox(this);
|
||||
|
||||
/* signals and slots connections */
|
||||
connect(buttonOk, SIGNAL(clicked()), this, SLOT(ClickOnOk()));
|
||||
connect(buttonCancel, SIGNAL(clicked()), this, SLOT(reject()));
|
||||
connect(buttonApply, SIGNAL(clicked()), this, SLOT(ClickOnApply()));
|
||||
connect(buttonHelp, SIGNAL(clicked()), this, SLOT(ClickOnHelp()));
|
||||
|
||||
connect(mySMESHGUI, SIGNAL (SignalDeactivateActiveDialog()), this, SLOT(DeactivateActiveDialog()));
|
||||
connect(mySelectionMgr, SIGNAL(currentSelectionChanged()), this, SLOT(SelectionIntoArgument()));
|
||||
/* to close dialog if study change */
|
||||
connect(mySMESHGUI, SIGNAL(SignalCloseAllDialogs()), this, SLOT(reject()));
|
||||
connect(mySMESHGUI, SIGNAL(SignalActivatedViewManager()), this, SLOT(onOpenView()));
|
||||
connect(mySMESHGUI, SIGNAL(SignalCloseView()), this, SLOT(onCloseView()));
|
||||
|
||||
connect( myAngle, SIGNAL(valueChanged(double)), this, SLOT( onArgChange() ));
|
||||
connect( myCreateEdgesCheck, SIGNAL(toggled(bool)), this, SLOT( onArgChange() ));
|
||||
connect( myUseExistingCheck, SIGNAL(toggled(bool)), this, SLOT( onArgChange() ));
|
||||
|
||||
connectPreviewControl();
|
||||
mySimulation->GetActor()->GetProperty()->SetLineWidth( 5 );
|
||||
|
||||
SelectionIntoArgument();
|
||||
}
|
||||
|
||||
SMESHGUI_FaceGroupsSeparatedByEdgesDlg::~SMESHGUI_FaceGroupsSeparatedByEdgesDlg()
|
||||
{
|
||||
}
|
||||
|
||||
//=================================================================================
|
||||
// function : ClickOnApply()
|
||||
// purpose :
|
||||
//=================================================================================
|
||||
|
||||
bool SMESHGUI_FaceGroupsSeparatedByEdgesDlg::ClickOnApply()
|
||||
{
|
||||
if ( mySMESHGUI->isStudyLocked() )
|
||||
return false;
|
||||
|
||||
SUIT_OverrideCursor aWaitCursor;
|
||||
|
||||
QStringList aParameters, anEntryList;
|
||||
aParameters << myAngle->text();
|
||||
|
||||
try
|
||||
{
|
||||
SMESH::ListOfGroups_var groups =
|
||||
myMesh->FaceGroupsSeparatedByEdges( myAngle->value(),
|
||||
myCreateEdgesCheck->isChecked(),
|
||||
myUseExistingCheck->isChecked());
|
||||
|
||||
for ( CORBA::ULong i = 0; i < groups->length(); ++i )
|
||||
if( _PTR(SObject) aSObject = SMESH::ObjectToSObject( groups[i].in() ))
|
||||
anEntryList.append( aSObject->GetID().c_str() );
|
||||
|
||||
SUIT_MessageBox::information(this, tr("SMESH_INFORMATION"),
|
||||
tr("NB_GROUPS_CREATED").arg( groups->length() ));
|
||||
}
|
||||
catch ( const SALOME::SALOME_Exception& S_ex ) {
|
||||
SalomeApp_Tools::QtCatchCorbaException( S_ex );
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
|
||||
mySMESHGUI->updateObjBrowser(true); // new groups may appear
|
||||
if( LightApp_Application* anApp =
|
||||
dynamic_cast<LightApp_Application*>( SUIT_Session::session()->activeApplication() ) )
|
||||
{
|
||||
anApp->browseObjects( anEntryList, isApplyAndClose() );
|
||||
}
|
||||
|
||||
SMESHGUI::Modified();
|
||||
|
||||
return true;
|
||||
}
|
||||
//=================================================================================
|
||||
// function : ClickOnOk()
|
||||
// purpose :
|
||||
//=================================================================================
|
||||
|
||||
void SMESHGUI_FaceGroupsSeparatedByEdgesDlg::ClickOnOk()
|
||||
{
|
||||
setIsApplyAndClose( true );
|
||||
if( ClickOnApply() )
|
||||
reject();
|
||||
}
|
||||
|
||||
//=================================================================================
|
||||
// function : reject()
|
||||
// purpose :
|
||||
//=================================================================================
|
||||
void SMESHGUI_FaceGroupsSeparatedByEdgesDlg::reject()
|
||||
{
|
||||
disconnect(mySelectionMgr, 0, this, 0);
|
||||
mySMESHGUI->ResetState();
|
||||
QDialog::reject();
|
||||
}
|
||||
//=================================================================================
|
||||
// function : ClickOnHelp()
|
||||
// purpose :
|
||||
//=================================================================================
|
||||
|
||||
void SMESHGUI_FaceGroupsSeparatedByEdgesDlg::ClickOnHelp()
|
||||
{
|
||||
static QString myHelpFileName( "face_groups_by_sharp_edges.html" );
|
||||
|
||||
LightApp_Application* app = (LightApp_Application*)(SUIT_Session::session()->activeApplication());
|
||||
if (app)
|
||||
app->onHelpContextModule(mySMESHGUI ? app->moduleName(mySMESHGUI->moduleName()) : QString(""), myHelpFileName);
|
||||
else {
|
||||
QString platform;
|
||||
#ifdef WIN32
|
||||
platform = "winapplication";
|
||||
#else
|
||||
platform = "application";
|
||||
#endif
|
||||
SUIT_MessageBox::warning(this, tr("WRN_WARNING"),
|
||||
tr("EXTERNAL_BROWSER_CANNOT_SHOW_PAGE").
|
||||
arg(app->resourceMgr()->stringValue("ExternalBrowser",
|
||||
platform)).
|
||||
arg(myHelpFileName));
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================================
|
||||
// function : SelectionIntoArgument()
|
||||
// purpose : Called when selection as changed or other case
|
||||
//=================================================================================
|
||||
|
||||
void SMESHGUI_FaceGroupsSeparatedByEdgesDlg::SelectionIntoArgument()
|
||||
{
|
||||
if (!GroupButtons->isEnabled()) // inactive
|
||||
return;
|
||||
|
||||
// get selected mesh
|
||||
SALOME_ListIO aList;
|
||||
mySelectionMgr->selectedObjects(aList);
|
||||
|
||||
for ( SALOME_ListIteratorOfListIO it( aList ); it.More(); it.Next() )
|
||||
{
|
||||
Handle(SALOME_InteractiveObject) IO = it.Value();
|
||||
CORBA::Object_var anObj = SMESH::IObjectToObject( IO );
|
||||
SMESH::SMESH_Mesh_var aMesh = SMESH::SMESH_Mesh::_narrow( anObj );
|
||||
if ( aMesh->_is_nil() || aMesh->NbFaces() == 0 )
|
||||
continue;
|
||||
|
||||
if ( !aMesh->_is_equivalent( myMesh ))
|
||||
onDisplaySimulation( false );
|
||||
|
||||
myMesh = aMesh;
|
||||
myMeshName->setText( IO->getName() );
|
||||
}
|
||||
|
||||
bool ok = !myMesh->_is_nil();
|
||||
|
||||
buttonOk->setEnabled( ok );
|
||||
buttonApply->setEnabled( ok );
|
||||
|
||||
onDisplaySimulation( ok );
|
||||
}
|
||||
|
||||
//=================================================================================
|
||||
// function : DeactivateActiveDialog()
|
||||
// purpose :
|
||||
//=================================================================================
|
||||
|
||||
void SMESHGUI_FaceGroupsSeparatedByEdgesDlg::DeactivateActiveDialog()
|
||||
{
|
||||
if (GroupArgs->isEnabled())
|
||||
{
|
||||
GroupArgs->setEnabled(false);
|
||||
GroupButtons->setEnabled(false);
|
||||
mySMESHGUI->ResetState();
|
||||
mySMESHGUI->SetActiveDialogBox(0);
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================================
|
||||
// function : ActivateThisDialog()
|
||||
// purpose :
|
||||
//=================================================================================
|
||||
|
||||
void SMESHGUI_FaceGroupsSeparatedByEdgesDlg::ActivateThisDialog()
|
||||
{
|
||||
/* Emit a signal to deactivate the active dialog */
|
||||
mySMESHGUI->EmitSignalDeactivateDialog();
|
||||
GroupArgs->setEnabled(true);
|
||||
GroupButtons->setEnabled(true);
|
||||
|
||||
mySMESHGUI->SetActiveDialogBox((QDialog*)this);
|
||||
|
||||
SelectionIntoArgument();
|
||||
}
|
||||
|
||||
//=================================================================================
|
||||
// function : onDisplaySimulation
|
||||
// purpose : Show/Hide preview
|
||||
//=================================================================================
|
||||
|
||||
void SMESHGUI_FaceGroupsSeparatedByEdgesDlg::onDisplaySimulation( bool toDisplayPreview )
|
||||
{
|
||||
SUIT_OverrideCursor aWaitCursor;
|
||||
|
||||
// save current values
|
||||
if ( SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr() )
|
||||
{
|
||||
resMgr->setValue( "SMESH", "prev_sharp_angle", myAngle->value() );
|
||||
resMgr->setValue( "SMESH", "prev_create_edges", myCreateEdgesCheck->isChecked() );
|
||||
resMgr->setValue( "SMESH", "prev_use_existing_edges", myUseExistingCheck->isChecked() );
|
||||
}
|
||||
|
||||
if ( myPreviewCheckBox->isChecked() && toDisplayPreview && !myMesh->_is_nil() )
|
||||
{
|
||||
try
|
||||
{
|
||||
SMESH::SMESH_MeshEditor_var aMeshEditor = myMesh->GetMeshEditPreviewer();
|
||||
SMESH::ListOfEdges_var edges = aMeshEditor->FindSharpEdges( myAngle->value(),
|
||||
myUseExistingCheck->isChecked());
|
||||
SMESH::MeshPreviewStruct_var previewData = aMeshEditor->GetPreviewData();
|
||||
|
||||
mySimulation->SetData( previewData.in() );
|
||||
showPreview();
|
||||
|
||||
} catch (...) {
|
||||
hidePreview();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hidePreview();
|
||||
}
|
||||
}
|
81
src/SMESHGUI/SMESHGUI_FaceGroupsSeparatedByEdgesDlg.h
Normal file
81
src/SMESHGUI/SMESHGUI_FaceGroupsSeparatedByEdgesDlg.h
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE
|
||||
//
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef SMESHGUI_FaceGroupsSeparatedByEdges_H
|
||||
#define SMESHGUI_FaceGroupsSeparatedByEdges_H
|
||||
|
||||
// SMESH includes
|
||||
#include "SMESH_SMESHGUI.hxx"
|
||||
#include "SMESHGUI_PreviewDlg.h"
|
||||
|
||||
#include <SALOMEconfig.h>
|
||||
#include CORBA_SERVER_HEADER(SMESH_MeshEditor)
|
||||
#include CORBA_SERVER_HEADER(SMESH_Mesh)
|
||||
|
||||
class LightApp_SelectionMgr;
|
||||
class QCheckBox;
|
||||
class QLineEdit;
|
||||
class QWidget;
|
||||
class QPushButton;
|
||||
class SMESHGUI_SpinBox;
|
||||
|
||||
/*!
|
||||
* \brief Dialog to create face groups divided by sharp edges
|
||||
*/
|
||||
|
||||
class SMESHGUI_EXPORT SMESHGUI_FaceGroupsSeparatedByEdgesDlg : public SMESHGUI_PreviewDlg
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
SMESHGUI_FaceGroupsSeparatedByEdgesDlg( SMESHGUI* theModule );
|
||||
virtual ~SMESHGUI_FaceGroupsSeparatedByEdgesDlg();
|
||||
|
||||
private slots:
|
||||
void ClickOnOk();
|
||||
bool ClickOnApply();
|
||||
void ClickOnHelp();
|
||||
void SelectionIntoArgument();
|
||||
void DeactivateActiveDialog();
|
||||
void ActivateThisDialog();
|
||||
void reject();
|
||||
void onArgChange() { onDisplaySimulation( true ); }
|
||||
virtual void onDisplaySimulation( bool = true );
|
||||
|
||||
private:
|
||||
|
||||
LightApp_SelectionMgr* mySelectionMgr;
|
||||
QWidget* GroupArgs;
|
||||
QWidget* GroupButtons;
|
||||
|
||||
QLineEdit* myMeshName;
|
||||
SMESHGUI_SpinBox* myAngle;
|
||||
QCheckBox* myCreateEdgesCheck;
|
||||
QCheckBox* myUseExistingCheck;
|
||||
|
||||
QPushButton* buttonOk;
|
||||
QPushButton* buttonCancel;
|
||||
QPushButton* buttonApply;
|
||||
QPushButton* buttonHelp;
|
||||
|
||||
SMESH::SMESH_Mesh_var myMesh;
|
||||
};
|
||||
|
||||
#endif // SMESHGUI_FaceGroupsSeparatedByEdges_H
|
@ -86,6 +86,7 @@ namespace SMESHOp {
|
||||
OpIntersectGroups = 2061, // MENU MESH - INTERSECT GROUPS
|
||||
OpCutGroups = 2062, // MENU MESH - CUT GROUPS
|
||||
OpGroupUnderlyingElem = 2070, // MENU MESH - GROUP OF UNDERLYING ENTITIES
|
||||
OpFaceGroupsByEdges = 2071, // MENU MESH - FACE GROUPS SEPARATED by EDGES
|
||||
OpEditGroupPopup = 2080, // POPUP MENU - EDIT GROUP
|
||||
OpAddElemGroupPopup = 2081, // POPUP MENU - ADD ELEMENTS TO GROUP
|
||||
OpRemoveElemGroupPopup = 2082, // POPUP MENU - REMOVE ELEMENTS FROM GROUP
|
||||
|
@ -611,6 +611,10 @@
|
||||
<source>ICON_UNDERLYING_ELEMS</source>
|
||||
<translation>mesh_extractGroup.png</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ICON_FACE_GROUPS_BY_EDGES</source>
|
||||
<translation>mesh_face_groups_by_edges.png</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ICON_2D_FROM_3D</source>
|
||||
<translation>mesh_2d_from_3d.png</translation>
|
||||
|
@ -1144,6 +1144,10 @@
|
||||
<source>MEN_UNDERLYING_ELEMS</source>
|
||||
<translation>Group based on nodes of other groups</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MEN_FACE_GROUPS_BY_EDGES</source>
|
||||
<translation>Face groups separated by sharp edges</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MEN_UPDATE</source>
|
||||
<translation>Update</translation>
|
||||
@ -3612,6 +3616,10 @@ Use Display Entity menu command to show them.
|
||||
<source>STB_UNDERLYING_ELEMS</source>
|
||||
<translation>Create groups of entities basing on nodes of other groups</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>STB_UNDERLYING_ELEMS</source>
|
||||
<translation>Create face groups separated by sharp edges</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>STB_UPDATE</source>
|
||||
<translation>Update</translation>
|
||||
@ -4296,6 +4304,10 @@ Use Display Entity menu command to show them.
|
||||
<source>TOP_UNDERLYING_ELEMS</source>
|
||||
<translation>Create groups of entities basing on nodes of other groups</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TOP_FACE_GROUPS_BY_EDGES</source>
|
||||
<translation>Create groups of faces separated by sharp edges</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TOP_UPDATE</source>
|
||||
<translation>Update</translation>
|
||||
@ -4531,6 +4543,29 @@ It can't be deleted </translation>
|
||||
<translation>Export Fields</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SMESHGUI_FaceGroupsSeparatedByEdgesDlg</name>
|
||||
<message>
|
||||
<source>CAPTION</source>
|
||||
<translation>Face groups separated by sharp edges</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SHARP_ANGLE</source>
|
||||
<translation>Sharp angle</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CREATE_EDGES</source>
|
||||
<translation>Create edges</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>USE_EXISTING_EDGES</source>
|
||||
<translation>Use existing edges</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>NB_GROUPS_CREATED</source>
|
||||
<translation>%1 groups of faces created</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SMESHGUI_OffsetDlg</name>
|
||||
<message>
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include <IntAna_Quadric.hxx>
|
||||
#include <gp_Lin.hxx>
|
||||
#include <gp_Pln.hxx>
|
||||
#include <NCollection_DataMap.hxx>
|
||||
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
@ -1875,6 +1876,222 @@ SMESH_MeshAlgos::FindFaceInSet(const SMDS_MeshNode* n1,
|
||||
return face;
|
||||
}
|
||||
|
||||
//================================================================================
|
||||
/*!
|
||||
* Return sharp edges of faces and non-manifold ones. Optionally adds existing edges.
|
||||
*/
|
||||
//================================================================================
|
||||
|
||||
std::vector< SMESH_MeshAlgos::Edge >
|
||||
SMESH_MeshAlgos::FindSharpEdges( SMDS_Mesh* theMesh,
|
||||
double theAngle,
|
||||
bool theAddExisting )
|
||||
{
|
||||
std::vector< Edge > resultEdges;
|
||||
if ( !theMesh ) return resultEdges;
|
||||
|
||||
typedef std::pair< bool, const SMDS_MeshNode* > TIsSharpAndMedium;
|
||||
typedef NCollection_DataMap< SMESH_TLink, TIsSharpAndMedium, SMESH_TLink > TLinkSharpMap;
|
||||
|
||||
TLinkSharpMap linkIsSharp( theMesh->NbFaces() );
|
||||
TIsSharpAndMedium sharpMedium( true, 0 );
|
||||
bool & isSharp = sharpMedium.first;
|
||||
const SMDS_MeshNode* & nMedium = sharpMedium.second;
|
||||
|
||||
if ( theAddExisting )
|
||||
{
|
||||
for ( SMDS_EdgeIteratorPtr edgeIt = theMesh->edgesIterator(); edgeIt->more(); )
|
||||
{
|
||||
const SMDS_MeshElement* edge = edgeIt->next();
|
||||
nMedium = ( edge->IsQuadratic() ) ? edge->GetNode(2) : 0;
|
||||
linkIsSharp.Bind( SMESH_TLink( edge->GetNode(0), edge->GetNode(1)), sharpMedium );
|
||||
}
|
||||
}
|
||||
|
||||
// check angles between face normals
|
||||
|
||||
const double angleCos = Cos( theAngle * M_PI / 180. ), angleCos2 = angleCos * angleCos;
|
||||
gp_XYZ norm1, norm2;
|
||||
std::vector< const SMDS_MeshNode* > faceNodes, linkNodes(2);
|
||||
std::vector<const SMDS_MeshElement *> linkFaces;
|
||||
|
||||
int nbSharp = linkIsSharp.Extent();
|
||||
for ( SMDS_FaceIteratorPtr faceIt = theMesh->facesIterator(); faceIt->more(); )
|
||||
{
|
||||
const SMDS_MeshElement* face = faceIt->next();
|
||||
size_t nbCorners = face->NbCornerNodes();
|
||||
|
||||
faceNodes.assign( face->begin_nodes(), face->end_nodes() );
|
||||
if ( faceNodes.size() == nbCorners )
|
||||
faceNodes.resize( nbCorners * 2, 0 );
|
||||
|
||||
const SMDS_MeshNode* nPrev = faceNodes[ nbCorners-1 ];
|
||||
for ( size_t i = 0; i < nbCorners; ++i )
|
||||
{
|
||||
SMESH_TLink link( nPrev, faceNodes[i] );
|
||||
if ( !linkIsSharp.IsBound( link ))
|
||||
{
|
||||
linkNodes[0] = link.node1();
|
||||
linkNodes[1] = link.node2();
|
||||
linkFaces.clear();
|
||||
theMesh->GetElementsByNodes( linkNodes, linkFaces, SMDSAbs_Face );
|
||||
|
||||
isSharp = false;
|
||||
if ( linkFaces.size() > 2 )
|
||||
{
|
||||
isSharp = true;
|
||||
}
|
||||
else if ( linkFaces.size() == 2 &&
|
||||
FaceNormal( linkFaces[0], norm1, /*normalize=*/false ) &&
|
||||
FaceNormal( linkFaces[1], norm2, /*normalize=*/false ))
|
||||
{
|
||||
double dot = norm1 * norm2; // == cos * |norm1| * |norm2|
|
||||
if (( dot < 0 ) == ( angleCos < 0 ))
|
||||
{
|
||||
double cos2 = dot * dot / norm1.SquareModulus() / norm2.SquareModulus();
|
||||
isSharp = ( angleCos < 0 ) ? ( cos2 > angleCos2 ) : ( cos2 < angleCos2 );
|
||||
}
|
||||
else
|
||||
{
|
||||
isSharp = ( angleCos > 0 );
|
||||
}
|
||||
}
|
||||
nMedium = faceNodes[( i-1+nbCorners ) % nbCorners + nbCorners ];
|
||||
|
||||
linkIsSharp.Bind( link, sharpMedium );
|
||||
nbSharp += isSharp;
|
||||
}
|
||||
|
||||
nPrev = faceNodes[i];
|
||||
}
|
||||
}
|
||||
|
||||
resultEdges.resize( nbSharp );
|
||||
TLinkSharpMap::Iterator linkIsSharpIter( linkIsSharp );
|
||||
for ( int i = 0; linkIsSharpIter.More() && i < nbSharp; linkIsSharpIter.Next() )
|
||||
{
|
||||
const SMESH_TLink& link = linkIsSharpIter.Key();
|
||||
const TIsSharpAndMedium& isSharpMedium = linkIsSharpIter.Value();
|
||||
if ( isSharpMedium.first )
|
||||
{
|
||||
Edge & edge = resultEdges[ i++ ];
|
||||
edge._node1 = link.node1();
|
||||
edge._node2 = link.node2();
|
||||
edge._medium = isSharpMedium.second;
|
||||
}
|
||||
}
|
||||
|
||||
return resultEdges;
|
||||
}
|
||||
|
||||
//================================================================================
|
||||
/*!
|
||||
* Distribute all faces of the mesh between groups using given edges as group boundaries
|
||||
*/
|
||||
//================================================================================
|
||||
|
||||
std::vector< std::vector< const SMDS_MeshElement* > >
|
||||
SMESH_MeshAlgos::SeparateFacesByEdges( SMDS_Mesh* theMesh, const std::vector< Edge >& theEdges )
|
||||
{
|
||||
std::vector< std::vector< const SMDS_MeshElement* > > groups;
|
||||
if ( !theMesh ) return groups;
|
||||
|
||||
// build map of face edges (SMESH_TLink) and their faces
|
||||
|
||||
typedef std::vector< const SMDS_MeshElement* > TFaceVec;
|
||||
typedef NCollection_DataMap< SMESH_TLink, TFaceVec, SMESH_TLink > TFacesByLinks;
|
||||
TFacesByLinks facesByLink( theMesh->NbFaces() );
|
||||
|
||||
std::vector< const SMDS_MeshNode* > faceNodes;
|
||||
for ( SMDS_FaceIteratorPtr faceIt = theMesh->facesIterator(); faceIt->more(); )
|
||||
{
|
||||
const SMDS_MeshElement* face = faceIt->next();
|
||||
size_t nbCorners = face->NbCornerNodes();
|
||||
|
||||
faceNodes.assign( face->begin_nodes(), face->end_nodes() );
|
||||
faceNodes.resize( nbCorners + 1 );
|
||||
faceNodes[ nbCorners ] = faceNodes[0];
|
||||
|
||||
face->setIsMarked( false );
|
||||
|
||||
for ( size_t i = 0; i < nbCorners; ++i )
|
||||
{
|
||||
SMESH_TLink link( faceNodes[i], faceNodes[i+1] );
|
||||
TFaceVec* linkFaces = facesByLink.ChangeSeek( link );
|
||||
if ( !linkFaces )
|
||||
{
|
||||
linkFaces = facesByLink.Bound( link, TFaceVec() );
|
||||
linkFaces->reserve(2);
|
||||
}
|
||||
linkFaces->push_back( face );
|
||||
}
|
||||
}
|
||||
|
||||
// remove the given edges from facesByLink map
|
||||
|
||||
for ( size_t i = 0; i < theEdges.size(); ++i )
|
||||
{
|
||||
SMESH_TLink link( theEdges[i]._node1, theEdges[i]._node2 );
|
||||
facesByLink.UnBind( link );
|
||||
}
|
||||
|
||||
// faces connected via links of facesByLink map form a group
|
||||
|
||||
while ( !facesByLink.IsEmpty() )
|
||||
{
|
||||
groups.push_back( TFaceVec() );
|
||||
TFaceVec & group = groups.back();
|
||||
|
||||
group.push_back( TFacesByLinks::Iterator( facesByLink ).Value()[0] );
|
||||
group.back()->setIsMarked( true );
|
||||
|
||||
for ( size_t iF = 0; iF < group.size(); ++iF )
|
||||
{
|
||||
const SMDS_MeshElement* face = group[iF];
|
||||
size_t nbCorners = face->NbCornerNodes();
|
||||
faceNodes.assign( face->begin_nodes(), face->end_nodes() );
|
||||
faceNodes.resize( nbCorners + 1 );
|
||||
faceNodes[ nbCorners ] = faceNodes[0];
|
||||
|
||||
for ( size_t iN = 0; iN < nbCorners; ++iN )
|
||||
{
|
||||
SMESH_TLink link( faceNodes[iN], faceNodes[iN+1] );
|
||||
if ( const TFaceVec* faces = facesByLink.Seek( link ))
|
||||
{
|
||||
const TFaceVec& faceNeighbors = *faces;
|
||||
for ( size_t i = 0; i < faceNeighbors.size(); ++i )
|
||||
if ( !faceNeighbors[i]->isMarked() )
|
||||
{
|
||||
group.push_back( faceNeighbors[i] );
|
||||
faceNeighbors[i]->setIsMarked( true );
|
||||
}
|
||||
facesByLink.UnBind( link );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find faces that are alone in its group; they were not in facesByLink
|
||||
|
||||
int nbInGroups = 0;
|
||||
for ( size_t i = 0; i < groups.size(); ++i )
|
||||
nbInGroups += groups[i].size();
|
||||
if ( nbInGroups < theMesh->NbFaces() )
|
||||
{
|
||||
for ( SMDS_FaceIteratorPtr faceIt = theMesh->facesIterator(); faceIt->more(); )
|
||||
{
|
||||
const SMDS_MeshElement* face = faceIt->next();
|
||||
if ( !face->isMarked() )
|
||||
{
|
||||
groups.push_back( TFaceVec() );
|
||||
groups.back().push_back( face );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
//================================================================================
|
||||
/*!
|
||||
* \brief Calculate normal of a mesh face
|
||||
|
@ -198,7 +198,6 @@ namespace SMESH_MeshAlgos
|
||||
bool IsRightOrder( const SMDS_MeshElement* face,
|
||||
const SMDS_MeshNode* node0,
|
||||
const SMDS_MeshNode* node1 );
|
||||
|
||||
/*!
|
||||
* \brief Mark elements given by SMDS_Iterator
|
||||
*/
|
||||
@ -247,6 +246,27 @@ namespace SMESH_MeshAlgos
|
||||
MarkElems( (*it)->nodesIterator(), isMarked );
|
||||
}
|
||||
|
||||
// 2 nodes + optional medium node
|
||||
struct Edge
|
||||
{
|
||||
const SMDS_MeshNode* _node1;
|
||||
const SMDS_MeshNode* _node2;
|
||||
const SMDS_MeshNode* _medium;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Return sharp edges of faces and non-manifold ones.
|
||||
* Optionally adds existing edges to the result. Angle is in degrees.
|
||||
*/
|
||||
std::vector< Edge > FindSharpEdges( SMDS_Mesh* mesh,
|
||||
double angle,
|
||||
bool addExisting );
|
||||
|
||||
/*!
|
||||
* Distribute all faces of the mesh between groups using given edges.
|
||||
*/
|
||||
std::vector< std::vector< const SMDS_MeshElement* > >
|
||||
SeparateFacesByEdges( SMDS_Mesh* mesh, const std::vector< Edge >& edges );
|
||||
|
||||
|
||||
typedef std::vector<const SMDS_MeshNode*> TFreeBorder;
|
||||
|
@ -4779,6 +4779,58 @@ CORBA::Boolean SMESH_MeshEditor_i::IsCoherentOrientation2D()
|
||||
return isGoodOri;
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
//function : FindSharpEdges
|
||||
//purpose : Return sharp edges of faces and non-manifold ones. Optionally add existing edges.
|
||||
//=======================================================================
|
||||
|
||||
SMESH::ListOfEdges* SMESH_MeshEditor_i::FindSharpEdges(CORBA::Double theAngle,
|
||||
CORBA::Boolean theAddExisting)
|
||||
throw (SALOME::SALOME_Exception)
|
||||
{
|
||||
SMESH::ListOfEdges_var resultEdges = new SMESH::ListOfEdges;
|
||||
SMESH_TRY;
|
||||
|
||||
initData();
|
||||
|
||||
std::vector< SMESH_MeshAlgos::Edge > edges =
|
||||
SMESH_MeshAlgos::FindSharpEdges( getMeshDS(), theAngle, theAddExisting );
|
||||
|
||||
if ( myIsPreviewMode ) // fill a preview mesh with edges
|
||||
{
|
||||
TPreviewMesh* mesh = getPreviewMesh( SMDSAbs_Edge );
|
||||
SMDS_Mesh* meshDS = mesh->GetMeshDS();
|
||||
for ( size_t i = 0; i < edges.size(); ++i )
|
||||
{
|
||||
SMESH_NodeXYZ xyz1( edges[i]._node1), xyz2( edges[i]._node2);
|
||||
SMDS_MeshNode* n1 = meshDS->AddNode( xyz1.X(), xyz1.Y(), xyz1.Z() );
|
||||
SMDS_MeshNode* n2 = meshDS->AddNode( xyz2.X(), xyz2.Y(), xyz2.Z() );
|
||||
if ( edges[i]._medium )
|
||||
{
|
||||
xyz1.Set( edges[i]._medium );
|
||||
SMDS_MeshNode* nm = meshDS->AddNode( xyz1.X(), xyz1.Y(), xyz1.Z() );
|
||||
mesh->GetMeshDS()->AddEdge( n1, n2, nm );
|
||||
}
|
||||
else
|
||||
{
|
||||
mesh->GetMeshDS()->AddEdge( n1, n2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
resultEdges->length( edges.size() );
|
||||
for ( size_t i = 0; i < edges.size(); ++i )
|
||||
{
|
||||
resultEdges[ i ].node1 = edges[i]._node1->GetID();
|
||||
resultEdges[ i ].node2 = edges[i]._node2->GetID();
|
||||
resultEdges[ i ].medium = edges[i]._medium ? edges[i]._medium->GetID() : 0;
|
||||
}
|
||||
}
|
||||
SMESH_CATCH( SMESH::throwCorbaException );
|
||||
return resultEdges._retn();
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
//function : FindFreeBorders
|
||||
//purpose : Returns all or only closed FreeBorder's.
|
||||
@ -5624,7 +5676,7 @@ void SMESH_MeshEditor_i::dumpGroupsList(TPythonDump & theDumpPytho
|
||||
*/
|
||||
//================================================================================
|
||||
|
||||
std::string SMESH_MeshEditor_i::generateGroupName(const std::string& thePrefix)
|
||||
std::string SMESH_MeshEditor_i::GenerateGroupName(const std::string& thePrefix)
|
||||
{
|
||||
SMESH::ListOfGroups_var groups = myMesh_i->GetGroups();
|
||||
set<std::string> groupNames;
|
||||
@ -5946,7 +5998,7 @@ SMESH_MeshEditor_i::DoubleNodeGroupNew( SMESH::SMESH_GroupBase_ptr theNodes,
|
||||
SMESH::long_array_var anIds = GetLastCreatedNodes();
|
||||
if (anIds->length() > 0) {
|
||||
std::string anUnindexedName (theNodes->GetName());
|
||||
std::string aNewName = generateGroupName(anUnindexedName + "_double");
|
||||
std::string aNewName = GenerateGroupName(anUnindexedName + "_double");
|
||||
aNewGroup = myMesh_i->CreateGroup(SMESH::NODE, aNewName.c_str());
|
||||
aNewGroup->Add(anIds);
|
||||
pyDump << aNewGroup << " = ";
|
||||
@ -6045,7 +6097,7 @@ SMESH_MeshEditor_i::DoubleNodeGroupsNew( const SMESH::ListOfGroups& theNodes,
|
||||
SMESH::long_array_var anIds = GetLastCreatedNodes();
|
||||
if (anIds->length() > 0) {
|
||||
std::string anUnindexedName (theNodes[0]->GetName());
|
||||
std::string aNewName = generateGroupName(anUnindexedName + "_double");
|
||||
std::string aNewName = GenerateGroupName(anUnindexedName + "_double");
|
||||
aNewGroup = myMesh_i->CreateGroup(SMESH::NODE, aNewName.c_str());
|
||||
aNewGroup->Add(anIds);
|
||||
pyDump << aNewGroup << " = ";
|
||||
@ -6269,7 +6321,7 @@ SMESH_MeshEditor_i::DoubleNodeElemGroup2New(SMESH::SMESH_GroupBase_ptr theElems,
|
||||
{
|
||||
// Create group with newly created elements
|
||||
CORBA::String_var elemGroupName = theElems->GetName();
|
||||
std::string aNewName = generateGroupName( std::string(elemGroupName.in()) + "_double");
|
||||
std::string aNewName = GenerateGroupName( std::string(elemGroupName.in()) + "_double");
|
||||
if ( !getEditor().GetLastCreatedElems().empty() && theElemGroupNeeded )
|
||||
{
|
||||
SMESH::long_array_var anIds = GetLastCreatedElems();
|
||||
@ -6501,7 +6553,7 @@ SMESH_MeshEditor_i::DoubleNodeElemGroups2New(const SMESH::ListOfGroups& theElems
|
||||
{
|
||||
// Create group with newly created elements
|
||||
CORBA::String_var elemGroupName = theElems[0]->GetName();
|
||||
std::string aNewName = generateGroupName( std::string(elemGroupName.in()) + "_double");
|
||||
std::string aNewName = GenerateGroupName( std::string(elemGroupName.in()) + "_double");
|
||||
if ( !getEditor().GetLastCreatedElems().empty() && theElemGroupNeeded )
|
||||
{
|
||||
SMESH::long_array_var anIds = GetLastCreatedElems();
|
||||
@ -6657,7 +6709,7 @@ SMESH_MeshEditor_i::AffectedElemGroupsInRegion( const SMESH::ListOfGroups& theEl
|
||||
if ( ivol > 0 )
|
||||
{
|
||||
aNewVolumeGroup = myMesh_i->CreateGroup(SMESH::VOLUME,
|
||||
generateGroupName("affectedVolumes").c_str());
|
||||
GenerateGroupName("affectedVolumes").c_str());
|
||||
aNewVolumeGroup->Add(volumeIds);
|
||||
aListOfGroups->length( nbGroups+1 );
|
||||
aListOfGroups[ nbGroups++ ] = aNewVolumeGroup._retn();
|
||||
@ -6665,7 +6717,7 @@ SMESH_MeshEditor_i::AffectedElemGroupsInRegion( const SMESH::ListOfGroups& theEl
|
||||
if ( iface > 0 )
|
||||
{
|
||||
aNewFaceGroup = myMesh_i->CreateGroup(SMESH::FACE,
|
||||
generateGroupName("affectedFaces").c_str());
|
||||
GenerateGroupName("affectedFaces").c_str());
|
||||
aNewFaceGroup->Add(faceIds);
|
||||
aListOfGroups->length( nbGroups+1 );
|
||||
aListOfGroups[ nbGroups++ ] = aNewFaceGroup._retn();
|
||||
@ -6673,7 +6725,7 @@ SMESH_MeshEditor_i::AffectedElemGroupsInRegion( const SMESH::ListOfGroups& theEl
|
||||
if ( iedge > 0 )
|
||||
{
|
||||
aNewEdgeGroup = myMesh_i->CreateGroup(SMESH::EDGE,
|
||||
generateGroupName("affectedEdges").c_str());
|
||||
GenerateGroupName("affectedEdges").c_str());
|
||||
aNewEdgeGroup->Add(edgeIds);
|
||||
aListOfGroups->length( nbGroups+1 );
|
||||
aListOfGroups[ nbGroups++ ] = aNewEdgeGroup._retn();
|
||||
|
@ -95,6 +95,11 @@ public:
|
||||
static bool IsTemporaryIDSource( SMESH::SMESH_IDSource_ptr& idSource );
|
||||
static CORBA::Long* GetTemporaryIDs( SMESH::SMESH_IDSource_ptr& idSource, int& nbIds );
|
||||
|
||||
/*!
|
||||
* \brief Generates the unique group name
|
||||
*/
|
||||
std::string GenerateGroupName(const std::string& thePrefix);
|
||||
|
||||
CORBA::Boolean RemoveElements(const SMESH::long_array & IDsOfElements)
|
||||
throw (SALOME::SALOME_Exception);
|
||||
CORBA::Boolean RemoveNodes (const SMESH::long_array & IDsOfNodes)
|
||||
@ -579,6 +584,12 @@ public:
|
||||
CORBA::Boolean IsCoherentOrientation2D()
|
||||
throw (SALOME::SALOME_Exception);
|
||||
|
||||
/*!
|
||||
* Return sharp edges of faces and non-manifold ones. Optionally adds existing edges.
|
||||
*/
|
||||
SMESH::ListOfEdges* FindSharpEdges(CORBA::Double angle, CORBA::Boolean addExisting)
|
||||
throw (SALOME::SALOME_Exception);
|
||||
|
||||
/*!
|
||||
* Returns all or only closed FreeBorder's.
|
||||
*/
|
||||
@ -973,8 +984,6 @@ public:
|
||||
void dumpGroupsList(SMESH::TPythonDump & theDumpPython,
|
||||
const SMESH::ListOfGroups * theGroupList);
|
||||
|
||||
std::string generateGroupName(const std::string& thePrefix);
|
||||
|
||||
void prepareIdSource(SMESH::SMESH_IDSource_ptr theObject);
|
||||
|
||||
|
||||
|
@ -96,6 +96,7 @@ static int MYDEBUG = 0;
|
||||
|
||||
using namespace std;
|
||||
using SMESH::TPythonDump;
|
||||
using SMESH::TVar;
|
||||
|
||||
int SMESH_Mesh_i::_idGenerator = 0;
|
||||
|
||||
@ -1868,6 +1869,89 @@ SMESH_Mesh_i::CreateDimGroup(const SMESH::ListOfIDSources& theGroups,
|
||||
return aResGrp._retn();
|
||||
}
|
||||
|
||||
//================================================================================
|
||||
/*!
|
||||
* \brief Distribute all faces of the mesh between groups using sharp edges and optionally
|
||||
* existing 1D elements as group boundaries.
|
||||
* \param [in] theSharpAngle - edge is considered sharp if an angle between normals of
|
||||
* adjacent faces is more than \a sharpAngle in degrees.
|
||||
* \param [in] theCreateEdges - to create 1D elements for detected sharp edges.
|
||||
* \param [in] theUseExistingEdges - to use existing edges as group boundaries
|
||||
* \return ListOfGroups - the created groups
|
||||
*/
|
||||
//================================================================================
|
||||
|
||||
SMESH::ListOfGroups*
|
||||
SMESH_Mesh_i::FaceGroupsSeparatedByEdges( CORBA::Double theSharpAngle,
|
||||
CORBA::Boolean theCreateEdges,
|
||||
CORBA::Boolean theUseExistingEdges )
|
||||
throw (SALOME::SALOME_Exception)
|
||||
{
|
||||
if ( theSharpAngle < 0 || theSharpAngle > 180 )
|
||||
THROW_SALOME_CORBA_EXCEPTION("Invalid sharp angle, it must be between 0 and 180 degrees",
|
||||
SALOME::BAD_PARAM);
|
||||
|
||||
SMESH::ListOfGroups_var resultGroups = new SMESH::ListOfGroups;
|
||||
|
||||
TPythonDump pyDump;
|
||||
|
||||
SMESH_TRY;
|
||||
if ( _preMeshInfo )
|
||||
_preMeshInfo->FullLoadFromFile();
|
||||
|
||||
SMESHDS_Mesh* meshDS = _impl->GetMeshDS();
|
||||
|
||||
std::vector< SMESH_MeshAlgos::Edge > edges =
|
||||
SMESH_MeshAlgos::FindSharpEdges( meshDS, theSharpAngle, theUseExistingEdges );
|
||||
|
||||
if ( theCreateEdges )
|
||||
{
|
||||
std::vector<const SMDS_MeshNode *> nodes(2);
|
||||
for ( size_t i = 0; i < edges.size(); ++i )
|
||||
{
|
||||
nodes[0] = edges[i]._node1;
|
||||
nodes[1] = edges[i]._node2;
|
||||
if ( meshDS->FindElement( nodes, SMDSAbs_Edge ))
|
||||
continue;
|
||||
if ( edges[i]._medium )
|
||||
meshDS->AddEdge( edges[i]._node1, edges[i]._node2, edges[i]._medium );
|
||||
else
|
||||
meshDS->AddEdge( edges[i]._node1, edges[i]._node2 );
|
||||
}
|
||||
}
|
||||
|
||||
std::vector< std::vector< const SMDS_MeshElement* > > faceGroups =
|
||||
SMESH_MeshAlgos::SeparateFacesByEdges( meshDS, edges );
|
||||
|
||||
SMESH::SMESH_MeshEditor_var( GetMeshEditor() ); // create _editor
|
||||
|
||||
resultGroups->length( faceGroups.size() );
|
||||
for ( size_t iG = 0; iG < faceGroups.size(); ++iG )
|
||||
{
|
||||
SMESH::SMESH_Group_var group = CreateGroup( SMESH::FACE,
|
||||
_editor->GenerateGroupName("Group").c_str());
|
||||
resultGroups[iG] = SMESH::SMESH_Group::_duplicate( group );
|
||||
|
||||
SMESHDS_GroupBase* groupBaseDS =
|
||||
SMESH::DownCast<SMESH_GroupBase_i*>( group )->GetGroupDS();
|
||||
SMDS_MeshGroup& groupCore = static_cast< SMESHDS_Group* >( groupBaseDS )->SMDSGroup();
|
||||
|
||||
std::vector< const SMDS_MeshElement* >& faces = faceGroups[ iG ];
|
||||
for ( size_t i = 0; i < faces.size(); ++i )
|
||||
groupCore.Add( faces[i] );
|
||||
}
|
||||
|
||||
pyDump << resultGroups << " = " << SMESH::SMESH_Mesh_var(_this())
|
||||
<< ".FaceGroupsSeparatedByEdges( "
|
||||
<< TVar( theSharpAngle ) << ", "
|
||||
<< theCreateEdges << ", "
|
||||
<< theUseExistingEdges << " )";
|
||||
|
||||
SMESH_CATCH( SMESH::throwCorbaException );
|
||||
return resultGroups._retn();
|
||||
|
||||
}
|
||||
|
||||
//================================================================================
|
||||
/*!
|
||||
* \brief Remember GEOM group data
|
||||
@ -3316,7 +3400,7 @@ void SMESH_Mesh_i::ExportPartToMED(SMESH::SMESH_IDSource_ptr meshPart,
|
||||
<< autoDimension << ", "
|
||||
<< goList << ", '"
|
||||
<< ( geomAssocFields ? geomAssocFields : "" ) << "',"
|
||||
<< ZTolerance
|
||||
<< TVar( ZTolerance )
|
||||
<< " )";
|
||||
|
||||
SMESH_CATCH( SMESH::throwCorbaException );
|
||||
|
@ -167,6 +167,10 @@ public:
|
||||
CORBA::Boolean theUnderlyingOnly )
|
||||
throw (SALOME::SALOME_Exception);
|
||||
|
||||
SMESH::ListOfGroups* FaceGroupsSeparatedByEdges( CORBA::Double theSharpAngle,
|
||||
CORBA::Boolean theCreateEdges,
|
||||
CORBA::Boolean theUseExistingEdges )
|
||||
throw (SALOME::SALOME_Exception);
|
||||
|
||||
SMESH::SMESH_Group_ptr ConvertToStandalone( SMESH::SMESH_GroupBase_ptr theGroupOn )
|
||||
throw (SALOME::SALOME_Exception);
|
||||
|
@ -2257,12 +2257,17 @@ class Mesh(metaclass = MeshMeta):
|
||||
fields = kwargs.get("fields", fields)
|
||||
geomAssocFields = kwargs.get("geomAssocFields", geomAssocFields)
|
||||
z_tolerance = kwargs.get("zTolerance", z_tolerance)
|
||||
|
||||
# invoke engine's function
|
||||
if meshPart or fields or geomAssocFields or z_tolerance > 0:
|
||||
unRegister = genObjUnRegister()
|
||||
if isinstance( meshPart, list ):
|
||||
meshPart = self.GetIDSource( meshPart, SMESH.ALL )
|
||||
unRegister.set( meshPart )
|
||||
|
||||
z_tolerance,Parameters,hasVars = ParseParameters(z_tolerance)
|
||||
self.mesh.SetParameters(Parameters)
|
||||
|
||||
self.mesh.ExportPartToMED( meshPart, fileName, auto_groups, minor, overwrite, autoDimension,
|
||||
fields, geomAssocFields, z_tolerance)
|
||||
else:
|
||||
@ -2880,6 +2885,22 @@ class Mesh(metaclass = MeshMeta):
|
||||
groups = [groups]
|
||||
return self.mesh.CreateDimGroup(groups, elemType, name, nbCommonNodes, underlyingOnly)
|
||||
|
||||
def FaceGroupsSeparatedByEdges( self, sharpAngle, createEdges=False, useExistingEdges=False ):
|
||||
"""
|
||||
Distribute all faces of the mesh between groups using sharp edges and optionally
|
||||
existing 1D elements as group boundaries.
|
||||
|
||||
Parameters:
|
||||
sharpAngle: edge is considered sharp if an angle between normals of
|
||||
adjacent faces is more than \a sharpAngle in degrees.
|
||||
createEdges (boolean): to create 1D elements for detected sharp edges.
|
||||
useExistingEdges (boolean): to use existing edges as group boundaries
|
||||
Returns:
|
||||
ListOfGroups - the created groups
|
||||
"""
|
||||
sharpAngle,Parameters,hasVars = ParseParameters( sharpAngle )
|
||||
self.mesh.SetParameters(Parameters)
|
||||
return self.mesh.FaceGroupsSeparatedByEdges( sharpAngle, createEdges, useExistingEdges );
|
||||
|
||||
def ConvertToStandalone(self, group):
|
||||
"""
|
||||
@ -4269,6 +4290,21 @@ class Mesh(metaclass = MeshMeta):
|
||||
|
||||
return self.editor.IsCoherentOrientation2D()
|
||||
|
||||
def FindSharpEdges( self, angle, addExisting=False ):
|
||||
"""
|
||||
Return sharp edges of faces and non-manifold ones.
|
||||
Optionally add existing edges.
|
||||
|
||||
Parameters:
|
||||
angle: angle (in degrees) between normals of adjacent faces to detect sharp edges
|
||||
addExisting: to return existing edges (1D elements) as well
|
||||
|
||||
Returns:
|
||||
list of FaceEdge structures
|
||||
"""
|
||||
angle = ParseParameters( angle )[0]
|
||||
return self.editor.FindSharpEdges( angle, addExisting )
|
||||
|
||||
def MeshToPassThroughAPoint(self, x, y, z):
|
||||
"""
|
||||
Find the node closest to a point and moves it to a point location
|
||||
|
Loading…
Reference in New Issue
Block a user