From 09bc0414c91ebabb67c7fe200549044a1854e199 Mon Sep 17 00:00:00 2001 From: eap Date: Thu, 17 Jan 2019 15:53:49 +0300 Subject: [PATCH] 23627: [IMACS] ASERIS: project point to the mesh and create a slot 1) Enable appending to an existing mesh via smesh.Concatenate() (compound mesh) 2) Enable filtering a mesh part: Filter::GetElementsIdFromParts( ListOfIDSources ) 3) Add ElementType arg to SMESH_Mesh::GetNodeInverseElements() 4) Add Mesh.Get1DBranches( edgeIDs ) 5) Define a default Z med tolerance 6) Update ElementsOnSurface upon SetTolerance() 7) Change group management to have group ID persistent 8) Extract SMESH_PolyLine.cxx from SMESH_MeshEditor.cxx 9) Enable Min Distance measure for node-element and node-object 10) Fix SMESH_MeshAlgos::GetDistance( XYZ, face ) 11) Extract SMESH_MeshAlgos::Intersector from SMESH_Offset.cxx 12) Enable optimization in SMESH_MeshAlgos::Triangulate 13) Add mestods Mesh.GetEngine() and Mesh.GetGeomEngine() --- doc/salome/examples/creating_meshes_ex07.py | 10 +- doc/salome/gui/SMESH/images/buildcompound.png | Bin 23672 -> 29847 bytes .../gui/SMESH/input/building_compounds.rst | 17 +- idl/SMESH_Filter.idl | 1 + idl/SMESH_Gen.idl | 12 +- idl/SMESH_Mesh.idl | 2 +- idl/SMESH_MeshEditor.idl | 25 +- resources/SalomeApp.xml.in | 1 + src/Controls/SMESH_Controls.cxx | 40 +- src/Controls/SMESH_ControlsDef.hxx | 12 +- src/SMDS/SMDS_MeshCell.cxx | 3 + src/SMESH/SMESH_Group.cxx | 11 + src/SMESH/SMESH_Group.hxx | 2 + src/SMESH/SMESH_Mesh.cxx | 29 +- src/SMESH/SMESH_Mesh.hxx | 8 +- src/SMESH/SMESH_MeshEditor.cxx | 604 +-------------- src/SMESH/SMESH_MeshEditor.hxx | 43 -- src/SMESHGUI/SMESHGUI_BuildCompoundDlg.cxx | 171 +++-- src/SMESHGUI/SMESHGUI_BuildCompoundDlg.h | 24 +- src/SMESHGUI/SMESHGUI_Measurements.cxx | 10 +- src/SMESHGUI/SMESH_msg_en.ts | 10 +- src/SMESHUtils/CMakeLists.txt | 2 + src/SMESHUtils/SMESH_MeshAlgos.cxx | 330 ++++++-- src/SMESHUtils/SMESH_MeshAlgos.hxx | 161 +++- src/SMESHUtils/SMESH_Offset.cxx | 453 ++++++++--- src/SMESHUtils/SMESH_PolyLine.cxx | 595 +++++++++++++++ src/SMESHUtils/SMESH_Slot.cxx | 720 ++++++++++++++++++ src/SMESHUtils/SMESH_Triangulate.cxx | 232 +++++- src/SMESHUtils/SMESH_Triangulate.hxx | 51 -- src/SMESHUtils/SMESH_TypeDefs.hxx | 15 + src/SMESH_I/SMESH_Filter_i.cxx | 30 + src/SMESH_I/SMESH_Filter_i.hxx | 6 +- src/SMESH_I/SMESH_Gen_i.cxx | 53 +- src/SMESH_I/SMESH_Gen_i.hxx | 11 +- src/SMESH_I/SMESH_Measurements_i.cxx | 43 +- src/SMESH_I/SMESH_MeshEditor_i.cxx | 210 ++++- src/SMESH_I/SMESH_MeshEditor_i.hxx | 26 +- src/SMESH_I/SMESH_Mesh_i.cxx | 57 +- src/SMESH_I/SMESH_Mesh_i.hxx | 4 +- src/SMESH_SWIG/smeshBuilder.py | 135 +++- src/StdMeshers/StdMeshers_Import_1D.cxx | 2 +- 41 files changed, 3099 insertions(+), 1072 deletions(-) create mode 100644 src/SMESHUtils/SMESH_PolyLine.cxx create mode 100644 src/SMESHUtils/SMESH_Slot.cxx delete mode 100644 src/SMESHUtils/SMESH_Triangulate.hxx diff --git a/doc/salome/examples/creating_meshes_ex07.py b/doc/salome/examples/creating_meshes_ex07.py index db825507b..669c9b088 100644 --- a/doc/salome/examples/creating_meshes_ex07.py +++ b/doc/salome/examples/creating_meshes_ex07.py @@ -2,11 +2,8 @@ import salome salome.salome_init() -import GEOM from salome.geom import geomBuilder geompy = geomBuilder.New() - -import SMESH, SALOMEDS from salome.smesh import smeshBuilder smesh = smeshBuilder.New() @@ -77,5 +74,12 @@ Compound1 = smesh.Concatenate([Mesh_inf, Mesh_sup], 0, 1, 1e-05, Compound2 = smesh.Concatenate([Mesh_inf, Mesh_sup], 1, 0, 1e-05, True, name='Compound with UniteGrps and GrpsOfAllElems') +# copy Gsup1 into a separate mesh and translate it +groupMesh = Mesh_inf.TranslateObjectMakeMesh( Gsup1, [300,0,0] ) + +# add Ginf2 to groupMesh +smesh.Concatenate([Ginf2], False, meshToAppendTo = groupMesh ) + + if salome.sg.hasDesktop(): salome.sg.updateObjBrowser() diff --git a/doc/salome/gui/SMESH/images/buildcompound.png b/doc/salome/gui/SMESH/images/buildcompound.png index d3534fe53e631086c20e03cef70046c139cb3d27..d1f4801d7774cb72c3bbbce7d537b5b243beee3d 100644 GIT binary patch literal 29847 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 G`+oq@H}9YT literal 23672 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 diff --git a/doc/salome/gui/SMESH/input/building_compounds.rst b/doc/salome/gui/SMESH/input/building_compounds.rst index 8501ab7c6..f7d41d188 100644 --- a/doc/salome/gui/SMESH/input/building_compounds.rst +++ b/doc/salome/gui/SMESH/input/building_compounds.rst @@ -4,9 +4,14 @@ Building Compound Meshes ************************ -Compound Mesh is a combination of several meshes. All elements and groups present in input meshes are present in the compound mesh. However, it does not use geometry or hypotheses of the initial meshes. +Compound Mesh is a combination of several mesh objects (meshes, groups, submeshes). All elements and groups present in input meshes are present in the compound mesh. However, it does not use geometry or hypotheses of the initial mesh objects. The links between the input meshes and the compound mesh are not supported, consequently the modification of an input mesh does not lead to the update of the compound mesh. +There are two modes of building a compound: + +* joining selected mesh objects into a new mesh. +* appending selected mesh objects to an existing mesh. + *To Build a compound mesh:* .. |img| image:: ../images/image161.png @@ -16,9 +21,13 @@ From the **Mesh** menu select **Build Compound** or click *"Build Compound Mesh" .. image:: ../images/buildcompound.png :align: center -* **Name** - allows selecting the name of the resulting **Compound** mesh. -* **Meshes, sub-meshes, groups** - allows selecting the meshes, sub-meshes and groups to be concatenated. They can be chosen in the Object Browser while holding **Ctrl** button. -* **Processing identical groups** - allows selecting the method of processing the namesake groups existing in the input meshes. They can be either +* **Result** group allows selecting a mode of operation + + * Activating **Create new mesh named** enables typing the name of the resulting compound mesh. + * Activating **Append to mesh** enables selection of a mesh to append other selected objects to. + +* **Meshes, sub-meshes, groups** allows selecting the meshes, sub-meshes and groups to be concatenated. They can be chosen in the Object Browser while holding *Ctrl* button. +* **Processing identical groups** allows selecting the method of processing the namesake groups existing in the input meshes. They can be either * **United** - all elements of *Group1* of *Mesh_1* and *Group1* of *Mesh_2* become the elements of *Group1* of the *Compound_Mesh*, or * **Renamed** - *Group1* of *Mesh_1* becomes *Group1_1* and *Group1* of *Mesh_2* becomes *Group1_2*. diff --git a/idl/SMESH_Filter.idl b/idl/SMESH_Filter.idl index 2784e4591..85ae6dc27 100644 --- a/idl/SMESH_Filter.idl +++ b/idl/SMESH_Filter.idl @@ -523,6 +523,7 @@ module SMESH void SetMesh( in SMESH_Mesh theMesh ); long_array GetElementsId( in SMESH_Mesh theMesh ); + long_array GetElementsIdFromParts( in ListOfIDSources theParts ); ElementType GetElementType(); Predicate GetPredicate(); diff --git a/idl/SMESH_Gen.idl b/idl/SMESH_Gen.idl index 29158a831..e7254a8d9 100644 --- a/idl/SMESH_Gen.idl +++ b/idl/SMESH_Gen.idl @@ -312,7 +312,8 @@ module SMESH raises ( SALOME::SALOME_Exception ); /*! - * Concatenate the given meshes or groups into one mesh. + * Concatenate the given meshes or groups into one mesh, + * optionally to theMeshToAppendTo. * Union groups with the same name and type if * theUniteIdenticalGroups flag is true. * Merge coincident nodes and elements if @@ -321,11 +322,13 @@ module SMESH SMESH_Mesh Concatenate(in ListOfIDSources theMeshesArray, in boolean theUniteIdenticalGroups, in boolean theMergeNodesAndElements, - in double theMergeTolerance) + in double theMergeTolerance, + in SMESH_Mesh theMeshToAppendTo) raises ( SALOME::SALOME_Exception ); /*! - * Concatenate the given meshes into one mesh. + * Concatenate the given meshes into one mesh, + * optionally to theMeshToAppendTo. * Union groups with the same name and type if * theUniteIdenticalGroups flag is true. * Merge coincident nodes and elements if @@ -335,7 +338,8 @@ module SMESH SMESH_Mesh ConcatenateWithGroups(in ListOfIDSources theMeshesArray, in boolean theUniteIdenticalGroups, in boolean theMergeNodesAndElements, - in double theMergeTolerance) + in double theMergeTolerance, + in SMESH_Mesh theMeshToAppendTo) raises ( SALOME::SALOME_Exception ); /*! diff --git a/idl/SMESH_Mesh.idl b/idl/SMESH_Mesh.idl index c8c7bd786..8d21b5f77 100644 --- a/idl/SMESH_Mesh.idl +++ b/idl/SMESH_Mesh.idl @@ -902,7 +902,7 @@ module SMESH * For given node returns list of IDs of inverse elements * If there is not node for given ID - returns empty list */ - long_array GetNodeInverseElements(in long id); + long_array GetNodeInverseElements(in long id, in ElementType elemType); /*! * \brief Return position of a node on shape diff --git a/idl/SMESH_MeshEditor.idl b/idl/SMESH_MeshEditor.idl index 85ba8b73e..9491b9214 100644 --- a/idl/SMESH_MeshEditor.idl +++ b/idl/SMESH_MeshEditor.idl @@ -798,8 +798,8 @@ module SMESH long ProjectPoint(in double x, in double y, in double z, - in SMESH_IDSource meshObject, in ElementType type, + in SMESH_IDSource meshObject, out double_array projecton) raises (SALOME::SALOME_Exception); @@ -822,6 +822,18 @@ module SMESH boolean IsCoherentOrientation2D() raises (SALOME::SALOME_Exception); + /*! + * Partition given 1D elements into groups of contiguous edges. + * A node where number of meeting edges != 2 is a group end. + * An optional startNode is used to orient groups it belongs to. + * \return a list of edge groups and a list of corresponding node groups. + * If a group is closed, the first and last nodes of the group are same. + */ + array_of_long_array Get1DBranches( in SMESH_IDSource edges, + in long startNode, + out array_of_long_array nodeGroups) + raises (SALOME::SALOME_Exception); + /*! * Return sharp edges of faces and non-manifold ones. * Optionally add existing edges. Angle is in degrees. @@ -1295,6 +1307,17 @@ module SMESH void MakePolyLine(inout ListOfPolySegments segments, in string groupName) raises (SALOME::SALOME_Exception); + + /*! + * \brief Create a slot of given width around given 1D elements lying on a triangle mesh. + * The slot is consrtucted by cutting faces by cylindrical surfaces made + * around each segment. Segments are expected to be created by MakePolyLine(). + * \return Edges located at the slot boundary + */ + ListOfEdges MakeSlot( in SMESH_GroupBase segments, + in double width ) + raises (SALOME::SALOME_Exception); + }; }; diff --git a/resources/SalomeApp.xml.in b/resources/SalomeApp.xml.in index efbe065a3..85ad3de9a 100644 --- a/resources/SalomeApp.xml.in +++ b/resources/SalomeApp.xml.in @@ -86,6 +86,7 @@ + diff --git a/src/Controls/SMESH_Controls.cxx b/src/Controls/SMESH_Controls.cxx index f71565018..719cd6430 100644 --- a/src/Controls/SMESH_Controls.cxx +++ b/src/Controls/SMESH_Controls.cxx @@ -1888,7 +1888,7 @@ void Length2D::GetValues(TValues& theValues) //================================================================================ /* Class : Deflection2D - Description : Functor for calculating number of faces conneted to the edge + Description : computes distance between a face center and an underlying surface */ //================================================================================ @@ -3589,9 +3589,10 @@ void Filter::SetPredicate( PredicatePtr thePredicate ) myPredicate = thePredicate; } -void Filter::GetElementsId( const SMDS_Mesh* theMesh, - PredicatePtr thePredicate, - TIdSequence& theSequence ) +void Filter::GetElementsId( const SMDS_Mesh* theMesh, + PredicatePtr thePredicate, + TIdSequence& theSequence, + SMDS_ElemIteratorPtr theElements ) { theSequence.clear(); @@ -3600,21 +3601,28 @@ void Filter::GetElementsId( const SMDS_Mesh* theMesh, thePredicate->SetMesh( theMesh ); - SMDS_ElemIteratorPtr elemIt = theMesh->elementsIterator( thePredicate->GetType() ); - if ( elemIt ) { - while ( elemIt->more() ) { - const SMDS_MeshElement* anElem = elemIt->next(); - long anId = anElem->GetID(); - if ( thePredicate->IsSatisfy( anId ) ) - theSequence.push_back( anId ); + if ( !theElements ) + theElements = theMesh->elementsIterator( thePredicate->GetType() ); + + if ( theElements ) { + while ( theElements->more() ) { + const SMDS_MeshElement* anElem = theElements->next(); + if ( thePredicate->GetType() == SMDSAbs_All || + thePredicate->GetType() == anElem->GetType() ) + { + long anId = anElem->GetID(); + if ( thePredicate->IsSatisfy( anId ) ) + theSequence.push_back( anId ); + } } } } void Filter::GetElementsId( const SMDS_Mesh* theMesh, - Filter::TIdSequence& theSequence ) + Filter::TIdSequence& theSequence, + SMDS_ElemIteratorPtr theElements ) { - GetElementsId(theMesh,myPredicate,theSequence); + GetElementsId(theMesh,myPredicate,theSequence,theElements); } /* @@ -4042,8 +4050,10 @@ SMDSAbs_ElementType ElementsOnSurface::GetType() const void ElementsOnSurface::SetTolerance( const double theToler ) { if ( myToler != theToler ) - myIds.Clear(); - myToler = theToler; + { + myToler = theToler; + process(); + } } double ElementsOnSurface::GetTolerance() const diff --git a/src/Controls/SMESH_ControlsDef.hxx b/src/Controls/SMESH_ControlsDef.hxx index 34c3453ef..4f717d3aa 100644 --- a/src/Controls/SMESH_ControlsDef.hxx +++ b/src/Controls/SMESH_ControlsDef.hxx @@ -1178,14 +1178,16 @@ namespace SMESH{ virtual void - GetElementsId( const SMDS_Mesh* theMesh, - TIdSequence& theSequence ); + GetElementsId( const SMDS_Mesh* theMesh, + TIdSequence& theSequence, + SMDS_ElemIteratorPtr theElements=0); static void - GetElementsId( const SMDS_Mesh* theMesh, - PredicatePtr thePredicate, - TIdSequence& theSequence ); + GetElementsId( const SMDS_Mesh* theMesh, + PredicatePtr thePredicate, + TIdSequence& theSequence, + SMDS_ElemIteratorPtr theElements=0 ); protected: PredicatePtr myPredicate; diff --git a/src/SMDS/SMDS_MeshCell.cxx b/src/SMDS/SMDS_MeshCell.cxx index 25c2f367a..9b3635f14 100644 --- a/src/SMDS/SMDS_MeshCell.cxx +++ b/src/SMDS/SMDS_MeshCell.cxx @@ -639,6 +639,9 @@ const SMDS_MeshNode* SMDS_MeshCell::GetNode(const int ind) const int SMDS_MeshCell::GetNodeIndex( const SMDS_MeshNode* node ) const { + if ( !node || node->IsNull() ) + return -1; + if ( GetVtkType() == VTK_POLYHEDRON ) return static_cast< const SMDS_MeshVolume* >( this )->SMDS_MeshVolume::GetNodeIndex( node ); diff --git a/src/SMESH/SMESH_Group.cxx b/src/SMESH/SMESH_Group.cxx index 23a4955c2..35c7192ee 100644 --- a/src/SMESH/SMESH_Group.cxx +++ b/src/SMESH/SMESH_Group.cxx @@ -96,3 +96,14 @@ void SMESH_Group::SetName (const char* theName) myName = theName; myGroupDS->SetStoreName( theName ); } + +//================================================================================ +/*! + * \brief Return group ID. It is negative if no SMESHDS_GroupBase exist + */ +//================================================================================ + +int SMESH_Group::GetID() const +{ + return myGroupDS ? myGroupDS->GetID() : -1; +} diff --git a/src/SMESH/SMESH_Group.hxx b/src/SMESH/SMESH_Group.hxx index 2842b9a51..efb6cf9d6 100644 --- a/src/SMESH/SMESH_Group.hxx +++ b/src/SMESH/SMESH_Group.hxx @@ -58,6 +58,8 @@ class SMESH_EXPORT SMESH_Group SMESHDS_GroupBase * GetGroupDS () { return myGroupDS; } + int GetID() const; + private: SMESH_Group (const SMESH_Group& theOther); // prohibited copy constructor diff --git a/src/SMESH/SMESH_Mesh.cxx b/src/SMESH/SMESH_Mesh.cxx index c03eaa650..c4b6c65d1 100644 --- a/src/SMESH/SMESH_Mesh.cxx +++ b/src/SMESH/SMESH_Mesh.cxx @@ -521,11 +521,10 @@ int SMESH_Mesh::MEDToMesh(const char* theFileName, const char* theMeshName) // Reading groups (sub-meshes are out of scope of MED import functionality) std::list aGroupNames = myReader.GetGroupNamesAndTypes(); - int anId; std::list::iterator name_type = aGroupNames.begin(); for ( ; name_type != aGroupNames.end(); name_type++ ) { - SMESH_Group* aGroup = AddGroup( name_type->second, name_type->first.c_str(), anId ); + SMESH_Group* aGroup = AddGroup( name_type->second, name_type->first.c_str() ); if ( aGroup ) { SMESHDS_Group* aGroupDS = dynamic_cast( aGroup->GetGroupDS() ); if ( aGroupDS ) { @@ -2029,16 +2028,18 @@ bool SMESH_Mesh::IsMainShape(const TopoDS_Shape& theShape) const SMESH_Group* SMESH_Mesh::AddGroup (const SMDSAbs_ElementType theType, const char* theName, - int& theId, + const int theId, const TopoDS_Shape& theShape, const SMESH_PredicatePtr& thePredicate) { - if (_mapGroup.count(_groupId)) + if ( _mapGroup.count( theId )) return NULL; - theId = _groupId; - SMESH_Group* aGroup = new SMESH_Group (theId, this, theType, theName, theShape, thePredicate); + int id = ( theId < 0 ) ? _groupId : theId; + SMESH_Group* aGroup = new SMESH_Group ( id, this, theType, theName, theShape, thePredicate ); GetMeshDS()->AddGroup( aGroup->GetGroupDS() ); - _mapGroup[_groupId++] = aGroup; + _mapGroup[ id ] = aGroup; + while ( _mapGroup.count( _groupId )) + ++_groupId; return aGroup; } @@ -2065,7 +2066,8 @@ SMESH_Group* SMESH_Mesh::AddGroup (SMESHDS_GroupBase* groupDS) throw(SALOME_Exce _mapGroup[ groupDS->GetID() ] = aGroup; GetMeshDS()->AddGroup( aGroup->GetGroupDS() ); - _groupId = 1 + _mapGroup.rbegin()->first; + while ( _mapGroup.count( _groupId )) + ++_groupId; return aGroup; } @@ -2091,8 +2093,8 @@ bool SMESH_Mesh::SynchronizeGroups() if ( !_mapGroup.count( _groupId )) _mapGroup[_groupId] = new SMESH_Group( groupDS ); } - if ( !_mapGroup.empty() ) - _groupId = _mapGroup.rbegin()->first + 1; + while ( _mapGroup.count( _groupId )) + ++_groupId; return nbGroups < _mapGroup.size(); } @@ -2115,11 +2117,12 @@ SMESH_Mesh::GroupIteratorPtr SMESH_Mesh::GetGroups() const */ //============================================================================= -SMESH_Group* SMESH_Mesh::GetGroup (const int theGroupID) +SMESH_Group* SMESH_Mesh::GetGroup (const int theGroupID) const { - if (_mapGroup.find(theGroupID) == _mapGroup.end()) + std::map ::const_iterator id_grp = _mapGroup.find( theGroupID ); + if ( id_grp == _mapGroup.end() ) return NULL; - return _mapGroup[theGroupID]; + return id_grp->second; } diff --git a/src/SMESH/SMESH_Mesh.hxx b/src/SMESH/SMESH_Mesh.hxx index 54889dedc..11d435eed 100644 --- a/src/SMESH/SMESH_Mesh.hxx +++ b/src/SMESH/SMESH_Mesh.hxx @@ -315,9 +315,9 @@ class SMESH_EXPORT SMESH_Mesh SMESH_Group* AddGroup (const SMDSAbs_ElementType theType, const char* theName, - int& theId, - const TopoDS_Shape& theShape=TopoDS_Shape(), - const SMESH_PredicatePtr& thePredicate=SMESH_PredicatePtr()); + const int theId = -1, + const TopoDS_Shape& theShape = TopoDS_Shape(), + const SMESH_PredicatePtr& thePredicate = SMESH_PredicatePtr()); SMESH_Group* AddGroup (SMESHDS_GroupBase* groupDS) throw(SALOME_Exception); @@ -326,7 +326,7 @@ class SMESH_EXPORT SMESH_Mesh std::list GetGroupIds() const; - SMESH_Group* GetGroup (const int theGroupID); + SMESH_Group* GetGroup (const int theGroupID) const; bool RemoveGroup (const int theGroupID); diff --git a/src/SMESH/SMESH_MeshEditor.cxx b/src/SMESH/SMESH_MeshEditor.cxx index 495370151..6ec53053c 100644 --- a/src/SMESH/SMESH_MeshEditor.cxx +++ b/src/SMESH/SMESH_MeshEditor.cxx @@ -97,7 +97,6 @@ #include #include -#include #include "SMESH_TryCatch.hxx" // include after OCCT headers! @@ -6752,8 +6751,8 @@ SMESH_MeshEditor::PGroupIDs SMESH_MeshEditor::Offset( TIDSortedElemSet & theElem if ( theElements.empty() ) eIt = meshDS->elementsIterator( SMDSAbs_Face ); else eIt = SMESHUtils::elemSetIterator( theElements ); - SMESH_MeshAlgos::TEPairVec new2OldFaces; - SMESH_MeshAlgos::TNPairVec new2OldNodes; + SMESH_MeshAlgos::TElemIntPairVec new2OldFaces; + SMESH_MeshAlgos::TNodeIntPairVec new2OldNodes; std::unique_ptr< SMDS_Mesh > offsetMesh ( SMESH_MeshAlgos::MakeOffset( eIt, *meshDS, theValue, theFixSelfIntersection, @@ -6796,7 +6795,7 @@ SMESH_MeshEditor::PGroupIDs SMESH_MeshEditor::Offset( TIDSortedElemSet & theElem const SMDS_MeshNode* n2 = tgtMeshDS->AddNodeWithID( n->X(), n->Y(), n->Z(), idShift + n->GetID() ); myLastCreatedNodes.push_back( n2 ); - srcNodes.push_back( new2OldNodes[ i ].second ); + srcNodes.push_back( meshDS->FindNode( new2OldNodes[ i ].second )); } } @@ -6812,7 +6811,7 @@ SMESH_MeshEditor::PGroupIDs SMESH_MeshEditor::Offset( TIDSortedElemSet & theElem elemType.myNodes.push_back( tgtMeshDS->FindNode( idShift + n2->GetID() )); } tgtEditor.AddElement( elemType.myNodes, elemType ); - srcElems.push_back( new2OldFaces[ i ].second ); + srcElems.push_back( meshDS->FindElement( new2OldFaces[ i ].second )); } myLastCreatedElems.swap( tgtEditor.myLastCreatedElems ); @@ -8501,6 +8500,7 @@ SMESH_MeshEditor::SewFreeBorder (const SMDS_MeshNode* theBordFirstNode, { const SMDS_MeshElement* elem = (*insertMapIt).first; list & nodeList = (*insertMapIt).second; + if ( nodeList.size() < 3 ) continue; const SMDS_MeshNode* n1 = nodeList.front(); nodeList.pop_front(); const SMDS_MeshNode* n2 = nodeList.front(); nodeList.pop_front(); @@ -11750,12 +11750,11 @@ bool SMESH_MeshEditor::DoubleNodesOnGroupBoundaries( const std::vectormyMesh->AddGroup(SMDSAbs_Face, joints2DName.c_str(), idg); + mapOfJunctionGroups[joints2DName] = this->myMesh->AddGroup(SMDSAbs_Face, joints2DName.c_str()); SMESHDS_Group *joints2DGrp = dynamic_cast(mapOfJunctionGroups[joints2DName]->GetGroupDS()); string joints3DName = "joints3D"; - mapOfJunctionGroups[joints3DName] = this->myMesh->AddGroup(SMDSAbs_Volume, joints3DName.c_str(), idg); + mapOfJunctionGroups[joints3DName] = this->myMesh->AddGroup(SMDSAbs_Volume, joints3DName.c_str()); SMESHDS_Group *joints3DGrp = dynamic_cast(mapOfJunctionGroups[joints3DName]->GetGroupDS()); itface = faceDomains.begin(); @@ -11783,7 +11782,7 @@ bool SMESH_MeshEditor::DoubleNodesOnGroupBoundaries( const std::vectormyMesh->AddGroup(vol->GetType(), namegrp.c_str(), idg); + mapOfJunctionGroups[namegrp] = this->myMesh->AddGroup(vol->GetType(), namegrp.c_str()); SMESHDS_Group *sgrp = dynamic_cast(mapOfJunctionGroups[namegrp]->GetGroupDS()); if (sgrp) sgrp->Add(vol->GetID()); @@ -11816,10 +11815,9 @@ bool SMESH_MeshEditor::DoubleNodesOnGroupBoundaries( const std::vectormyMesh->AddGroup(SMDSAbs_Face, namegrp.c_str(), idg); + mapOfJunctionGroups[namegrp] = this->myMesh->AddGroup(SMDSAbs_Face, namegrp.c_str()); SMESHDS_Group *sgrp = dynamic_cast(mapOfJunctionGroups[namegrp]->GetGroupDS()); if (sgrp) sgrp->Add(face->GetID()); @@ -11845,10 +11843,9 @@ bool SMESH_MeshEditor::DoubleNodesOnGroupBoundaries( const std::vectorGetMeshDS()->AddVolumeFromVtkIds(orderedNodes); - int idg; string namegrp = "jointsMultiples"; if (!mapOfJunctionGroups.count(namegrp)) - mapOfJunctionGroups[namegrp] = this->myMesh->AddGroup(SMDSAbs_Volume, namegrp.c_str(), idg); + mapOfJunctionGroups[namegrp] = this->myMesh->AddGroup(SMDSAbs_Volume, namegrp.c_str()); SMESHDS_Group *sgrp = dynamic_cast(mapOfJunctionGroups[namegrp]->GetGroupDS()); if (sgrp) sgrp->Add(vol->GetID()); @@ -12106,10 +12103,9 @@ bool SMESH_MeshEditor::CreateFlatElementsOnFacesGroups(const std::vectormyMesh->AddGroup(SMDSAbs_Volume, namegrp.c_str(), idg); + mapOfJunctionGroups[namegrp] = this->myMesh->AddGroup(SMDSAbs_Volume, namegrp.c_str()); SMESHDS_Group *sgrp = dynamic_cast(mapOfJunctionGroups[namegrp]->GetGroupDS()); if (sgrp) sgrp->Add(vol->GetID()); @@ -12180,10 +12176,10 @@ void SMESH_MeshEditor::CreateHoleSkin(double radius, // --- define groups to build - int idg; // --- group of SMDS volumes + // --- group of SMDS volumes string grpvName = groupName; grpvName += "_vol"; - SMESH_Group *grp = this->myMesh->AddGroup(SMDSAbs_Volume, grpvName.c_str(), idg); + SMESH_Group *grp = this->myMesh->AddGroup(SMDSAbs_Volume, grpvName.c_str()); if (!grp) { MESSAGE("group not created " << grpvName); @@ -12191,10 +12187,10 @@ void SMESH_MeshEditor::CreateHoleSkin(double radius, } SMESHDS_Group *sgrp = dynamic_cast(grp->GetGroupDS()); - int idgs; // --- group of SMDS faces on the skin + // --- group of SMDS faces on the skin string grpsName = groupName; grpsName += "_skin"; - SMESH_Group *grps = this->myMesh->AddGroup(SMDSAbs_Face, grpsName.c_str(), idgs); + SMESH_Group *grps = this->myMesh->AddGroup(SMDSAbs_Face, grpsName.c_str()); if (!grps) { MESSAGE("group not created " << grpsName); @@ -12202,10 +12198,10 @@ void SMESH_MeshEditor::CreateHoleSkin(double radius, } SMESHDS_Group *sgrps = dynamic_cast(grps->GetGroupDS()); - int idgi; // --- group of SMDS faces internal (several shapes) + // --- group of SMDS faces internal (several shapes) string grpiName = groupName; grpiName += "_internalFaces"; - SMESH_Group *grpi = this->myMesh->AddGroup(SMDSAbs_Face, grpiName.c_str(), idgi); + SMESH_Group *grpi = this->myMesh->AddGroup(SMDSAbs_Face, grpiName.c_str()); if (!grpi) { MESSAGE("group not created " << grpiName); @@ -12213,10 +12209,10 @@ void SMESH_MeshEditor::CreateHoleSkin(double radius, } SMESHDS_Group *sgrpi = dynamic_cast(grpi->GetGroupDS()); - int idgei; // --- group of SMDS faces internal (several shapes) + // --- group of SMDS faces internal (several shapes) string grpeiName = groupName; grpeiName += "_internalEdges"; - SMESH_Group *grpei = this->myMesh->AddGroup(SMDSAbs_Edge, grpeiName.c_str(), idgei); + SMESH_Group *grpei = this->myMesh->AddGroup(SMDSAbs_Edge, grpeiName.c_str()); if (!grpei) { MESSAGE("group not created " << grpeiName); @@ -13040,565 +13036,3 @@ void SMESH_MeshEditor::copyPosition( const SMDS_MeshNode* from, default:; } } - -namespace // utils for MakePolyLine -{ - //================================================================================ - /*! - * \brief Sequence of found points and a current point data - */ - struct Path - { - std::vector< gp_XYZ > myPoints; - double myLength; - - const SMDS_MeshElement* myFace; - SMESH_NodeXYZ myNode1; // nodes of the edge the path entered myFace - SMESH_NodeXYZ myNode2; - int myNodeInd1; - int myNodeInd2; - double myDot1; - double myDot2; - - int mySrcPntInd; //!< start point index - TIDSortedElemSet myElemSet, myAvoidSet; - - Path(): myLength(0.0), myFace(0) {} - - bool SetCutAtCorner( const SMESH_NodeXYZ& cornerNode, - const SMDS_MeshElement* face, - const gp_XYZ& plnNorm, - const gp_XYZ& plnOrig ); - - void AddPoint( const gp_XYZ& p ); - - bool Extend( const gp_XYZ& plnNorm, const gp_XYZ& plnOrig ); - - bool ReachSamePoint( const Path& other ); - - static void Remove( std::vector< Path > & paths, size_t& i ); - }; - - //================================================================================ - /*! - * \brief Return true if this Path meats another - */ - //================================================================================ - - bool Path::ReachSamePoint( const Path& other ) - { - return ( mySrcPntInd != other.mySrcPntInd && - myFace == other.myFace ); - } - - //================================================================================ - /*! - * \brief Remove a path from a vector - */ - //================================================================================ - - void Path::Remove( std::vector< Path > & paths, size_t& i ) - { - if ( paths.size() > 1 ) - { - size_t j = paths.size() - 1; // last item to be removed - if ( i < j ) - { - paths[ i ].myPoints.swap( paths[ j ].myPoints ); - paths[ i ].myLength = paths[ j ].myLength; - paths[ i ].mySrcPntInd = paths[ j ].mySrcPntInd; - paths[ i ].myFace = paths[ j ].myFace; - paths[ i ].myNode1 = paths[ j ].myNode1; - paths[ i ].myNode2 = paths[ j ].myNode2; - paths[ i ].myNodeInd1 = paths[ j ].myNodeInd1; - paths[ i ].myNodeInd2 = paths[ j ].myNodeInd2; - paths[ i ].myDot1 = paths[ j ].myDot1; - paths[ i ].myDot2 = paths[ j ].myDot2; - } - } - paths.pop_back(); - if ( i > 0 ) - --i; - } - - //================================================================================ - /*! - * \brief Store a point that is at a node of a face if the face is intersected by plane. - * Return false if the node is a sole intersection point of the face and the plane - */ - //================================================================================ - - bool Path::SetCutAtCorner( const SMESH_NodeXYZ& cornerNode, - const SMDS_MeshElement* face, - const gp_XYZ& plnNorm, - const gp_XYZ& plnOrig ) - { - if ( face == myFace ) - return false; - myNodeInd1 = face->GetNodeIndex( cornerNode._node ); - myNodeInd2 = ( myNodeInd1 + 1 ) % face->NbCornerNodes(); - int ind3 = ( myNodeInd1 + 2 ) % face->NbCornerNodes(); - myNode1.Set( face->GetNode( ind3 )); - myNode2.Set( face->GetNode( myNodeInd2 )); - - myDot1 = plnNorm * ( myNode1 - plnOrig ); - myDot2 = plnNorm * ( myNode2 - plnOrig ); - - bool ok = ( myDot1 * myDot2 < 0 ); - if ( !ok && myDot1 * myDot2 == 0 ) - { - ok = ( myDot1 != myDot2 ); - if ( ok && myFace ) - ok = ( myFace->GetNodeIndex(( myDot1 == 0 ? myNode1 : myNode2 )._node ) < 0 ); - } - if ( ok ) - { - myFace = face; - myDot1 = 0; - AddPoint( cornerNode ); - } - return ok; - } - - //================================================================================ - /*! - * \brief Store a point and update myLength - */ - //================================================================================ - - void Path::AddPoint( const gp_XYZ& p ) - { - if ( !myPoints.empty() ) - myLength += ( p - myPoints.back() ).Modulus(); - else - myLength = 0; - myPoints.push_back( p ); - } - - //================================================================================ - /*! - * \brief Try to find the next point - * \param [in] plnNorm - cutting plane normal - * \param [in] plnOrig - cutting plane origin - */ - //================================================================================ - - bool Path::Extend( const gp_XYZ& plnNorm, const gp_XYZ& plnOrig ) - { - int nodeInd3 = ( myNodeInd1 + 1 ) % myFace->NbCornerNodes(); - if ( myNodeInd2 == nodeInd3 ) - nodeInd3 = ( myNodeInd1 + 2 ) % myFace->NbCornerNodes(); - - SMESH_NodeXYZ node3 = myFace->GetNode( nodeInd3 ); - double dot3 = plnNorm * ( node3 - plnOrig ); - - if ( dot3 * myDot1 < 0. ) - { - myNode2 = node3; - myNodeInd2 = nodeInd3; - myDot2 = dot3; - } - else if ( dot3 * myDot2 < 0. ) - { - myNode1 = node3; - myNodeInd1 = nodeInd3; - myDot1 = dot3; - } - else if ( dot3 == 0. ) - { - SMDS_ElemIteratorPtr fIt = node3._node->GetInverseElementIterator(SMDSAbs_Face); - while ( fIt->more() ) - if ( SetCutAtCorner( node3, fIt->next(), plnNorm, plnOrig )) - return true; - return false; - } - else if ( myDot2 == 0. ) - { - SMESH_NodeXYZ node2 = myNode2; // copy as myNode2 changes in SetCutAtCorner() - SMDS_ElemIteratorPtr fIt = node2._node->GetInverseElementIterator(SMDSAbs_Face); - while ( fIt->more() ) - if ( SetCutAtCorner( node2, fIt->next(), plnNorm, plnOrig )) - return true; - return false; - } - - double r = Abs( myDot1 / ( myDot2 - myDot1 )); - AddPoint( myNode1 * ( 1 - r ) + myNode2 * r ); - - myAvoidSet.clear(); - myAvoidSet.insert( myFace ); - myFace = SMESH_MeshAlgos::FindFaceInSet( myNode1._node, myNode2._node, - myElemSet, myAvoidSet, - &myNodeInd1, &myNodeInd2 ); - return myFace; - } - - //================================================================================ - /*! - * \brief Compute a path between two points of PolySegment - */ - struct PolyPathCompute - { - SMESH_MeshEditor::TListOfPolySegments& mySegments; //!< inout PolySegment's - std::vector< Path >& myPaths; //!< path of each of segments to compute - SMESH_Mesh* myMesh; - mutable std::vector< std::string > myErrors; - - PolyPathCompute( SMESH_MeshEditor::TListOfPolySegments& theSegments, - std::vector< Path >& thePaths, - SMESH_Mesh* theMesh): - mySegments( theSegments ), - myPaths( thePaths ), - myMesh( theMesh ), - myErrors( theSegments.size() ) - { - } - -#undef SMESH_CAUGHT -#define SMESH_CAUGHT myErrors[i] = - void operator() ( const int i ) const - { - SMESH_TRY; - const_cast< PolyPathCompute* >( this )->Compute( i ); - SMESH_CATCH( SMESH::returnError ); - } -#undef SMESH_CAUGHT - - //================================================================================ - /*! - * \brief Compute a path of a given segment - */ - //================================================================================ - - void Compute( const int iSeg ) - { - SMESH_MeshEditor::PolySegment& polySeg = mySegments[ iSeg ]; - - // the cutting plane - gp_XYZ plnNorm = ( polySeg.myXYZ[0] - polySeg.myXYZ[1] ) ^ polySeg.myVector.XYZ(); - gp_XYZ plnOrig = polySeg.myXYZ[1]; - - // Find paths connecting the 2 end points of polySeg - - std::vector< Path > paths; paths.reserve(10); - - // 1) initialize paths; two paths starts at each end point - - for ( int iP = 0; iP < 2; ++iP ) // loop on the polySeg end points - { - Path path; - path.mySrcPntInd = iP; - size_t nbPaths = paths.size(); - - if ( polySeg.myFace[ iP ]) // the end point lies on polySeg.myFace[ iP ] - { - // check coincidence of polySeg.myXYZ[ iP ] with nodes - const double tol = 1e-20; - SMESH_NodeXYZ nodes[4]; - for ( int i = 0; i < 3 && !polySeg.myNode1[ iP ]; ++i ) - { - nodes[ i ] = polySeg.myFace[ iP ]->GetNode( i ); - if (( nodes[ i ] - polySeg.myXYZ[ iP ]).SquareModulus() < tol*tol ) - polySeg.myNode1[ iP ] = nodes[ i ].Node(); - } - nodes[ 3 ] = nodes[ 0 ]; - - // check coincidence of polySeg.myXYZ[ iP ] with edges - for ( int i = 0; i < 3 && !polySeg.myNode1[ iP ]; ++i ) - { - SMDS_LinearEdge edge( nodes[i].Node(), nodes[i+1].Node() ); - if ( SMESH_MeshAlgos::GetDistance( &edge, polySeg.myXYZ[ iP ]) < tol ) - { - polySeg.myNode1[ iP ] = nodes[ i ].Node(); - polySeg.myNode2[ iP ] = nodes[ i + 1 ].Node(); - } - } - - if ( !polySeg.myNode1[ iP ] ) // polySeg.myXYZ[ iP ] is within polySeg.myFace[ iP ] - { - double dot[ 4 ]; - for ( int i = 0; i < 3; ++i ) - dot[ i ] = plnNorm * ( nodes[ i ] - plnOrig ); - dot[ 3 ] = dot[ 0 ]; - - int iCut = 0; // index of a cut edge - if ( dot[ 1 ] * dot[ 2 ] < 0. ) iCut = 1; - else if ( dot[ 2 ] * dot[ 3 ] < 0. ) iCut = 2; - - // initialize path so as if it entered the face via iCut-th edge - path.myFace = polySeg.myFace[ iP ]; - path.myNodeInd1 = iCut; - path.myNodeInd2 = iCut + 1; - path.myNode1.Set( nodes[ iCut ].Node() ); - path.myNode2.Set( nodes[ iCut + 1 ].Node() ); - path.myDot1 = dot[ iCut ]; - path.myDot2 = dot[ iCut + 1 ]; - path.myPoints.clear(); - path.AddPoint( polySeg.myXYZ[ iP ]); - paths.push_back( path ); - - path.Extend( plnNorm, plnOrig ); // to get another edge cut - path.myFace = polySeg.myFace[ iP ]; - if ( path.myDot1 == 0. ) // cut at a node - { - path.myNodeInd1 = ( iCut + 2 ) % 3; - path.myNodeInd2 = ( iCut + 3 ) % 3; - path.myNode2.Set( path.myFace->GetNode( path.myNodeInd2 )); - path.myDot2 = dot[ path.myNodeInd2 ]; - } - else - { - path.myNodeInd1 = path.myFace->GetNodeIndex( path.myNode1.Node() ); - path.myNodeInd2 = path.myFace->GetNodeIndex( path.myNode2.Node() ); - } - path.myPoints.clear(); - path.AddPoint( polySeg.myXYZ[ iP ]); - paths.push_back( path ); - } - } - - if ( polySeg.myNode2[ iP ] && polySeg.myNode2[ iP ] != polySeg.myNode1[ iP ] ) - { - // the end point is on an edge - while (( path.myFace = SMESH_MeshAlgos::FindFaceInSet( polySeg.myNode1[ iP ], - polySeg.myNode2[ iP ], - path.myElemSet, - path.myAvoidSet, - &path.myNodeInd1, - &path.myNodeInd2 ))) - { - path.myNode1.Set( polySeg.myNode1[ iP ]); - path.myNode2.Set( polySeg.myNode2[ iP ]); - path.myDot1 = plnNorm * ( path.myNode1 - plnOrig ); - path.myDot2 = plnNorm * ( path.myNode2 - plnOrig ); - path.myPoints.clear(); - path.AddPoint( polySeg.myXYZ[ iP ]); - path.myAvoidSet.insert( path.myFace ); - paths.push_back( path ); - } - if ( nbPaths == paths.size() ) - throw SALOME_Exception ( SMESH_Comment("No face edge found by point ") << iP+1 - << " in a PolySegment " << iSeg ); - } - else if ( polySeg.myNode1[ iP ] ) // the end point is at a node - { - std::set nodes; - SMDS_ElemIteratorPtr fIt = polySeg.myNode1[ iP ]->GetInverseElementIterator(SMDSAbs_Face); - while ( fIt->more() ) - { - path.myPoints.clear(); - if ( path.SetCutAtCorner( polySeg.myNode1[ iP ], fIt->next(), plnNorm, plnOrig )) - { - if (( path.myDot1 * path.myDot2 != 0 ) || - ( nodes.insert( path.myDot1 == 0 ? path.myNode1._node : path.myNode2._node ).second )) - paths.push_back( path ); - } - } - } - - // look for a one-segment path - for ( size_t i = 0; i < nbPaths; ++i ) - for ( size_t j = nbPaths; j < paths.size(); ++j ) - if ( paths[i].myFace == paths[j].myFace ) - { - myPaths[ iSeg ].myPoints.push_back( paths[i].myPoints[0] ); - myPaths[ iSeg ].myPoints.push_back( paths[j].myPoints[0] ); - paths.clear(); - } - } - - // 2) extend paths and compose the shortest one connecting the two points - - myPaths[ iSeg ].myLength = 1e100; - - while ( paths.size() >= 2 ) - { - for ( size_t i = 0; i < paths.size(); ++i ) - { - Path& path = paths[ i ]; - if ( !path.Extend( plnNorm, plnOrig ) || // path reached a mesh boundary - path.myLength > myPaths[ iSeg ].myLength ) // path is longer than others - { - Path::Remove( paths, i ); - continue; - } - - // join paths that reach same point - for ( size_t j = 0; j < paths.size(); ++j ) - { - if ( i != j && paths[i].ReachSamePoint( paths[j] )) - { - double distLast = ( paths[i].myPoints.back() - paths[j].myPoints.back() ).Modulus(); - double fullLength = ( paths[i].myLength + paths[j].myLength + distLast ); - if ( fullLength < myPaths[ iSeg ].myLength ) - { - myPaths[ iSeg ].myLength = fullLength; - std::vector< gp_XYZ > & allPoints = myPaths[ iSeg ].myPoints; - allPoints.swap( paths[i].myPoints ); - allPoints.insert( allPoints.end(), - paths[j].myPoints.rbegin(), - paths[j].myPoints.rend() ); - } - Path::Remove( paths, i ); - Path::Remove( paths, j ); - } - } - } - if ( !paths.empty() && (int) paths[0].myPoints.size() > myMesh->NbFaces() ) - throw SALOME_Exception(LOCALIZED( "Infinite loop in MakePolyLine()")); - } - - if ( myPaths[ iSeg ].myPoints.empty() ) - throw SALOME_Exception( SMESH_Comment("Can't find a full path for PolySegment #") << iSeg ); - - // reverse the path - double d00 = ( polySeg.myXYZ[0] - myPaths[ iSeg ].myPoints.front() ).SquareModulus(); - double d01 = ( polySeg.myXYZ[0] - myPaths[ iSeg ].myPoints.back() ).SquareModulus(); - if ( d00 > d01 ) - std::reverse( myPaths[ iSeg ].myPoints.begin(), myPaths[ iSeg ].myPoints.end() ); - - } // PolyPathCompute::Compute() - - }; // struct PolyPathCompute - -} // namespace - -//======================================================================= -//function : MakePolyLine -//purpose : Create a polyline consisting of 1D mesh elements each lying on a 2D element of -// the initial mesh -//======================================================================= - -void SMESH_MeshEditor::MakePolyLine( TListOfPolySegments& theSegments, - SMESHDS_Group* theGroup, - SMESH_ElementSearcher* theSearcher) -{ - std::vector< Path > segPaths( theSegments.size() ); // path of each of segments - - SMESH_ElementSearcher* searcher = theSearcher; - SMESHUtils::Deleter delSearcher; - if ( !searcher ) - { - searcher = SMESH_MeshAlgos::GetElementSearcher( *GetMeshDS() ); - delSearcher._obj = searcher; - } - - // get cutting planes - - std::vector< bool > isVectorOK( theSegments.size(), true ); - const double planarCoef = 0.333; // plane height in planar case - - for ( size_t iSeg = 0; iSeg < theSegments.size(); ++iSeg ) - { - PolySegment& polySeg = theSegments[ iSeg ]; - - gp_XYZ p1 = SMESH_NodeXYZ( polySeg.myNode1[0] ); - gp_XYZ p2 = SMESH_NodeXYZ( polySeg.myNode1[1] ); - if ( polySeg.myNode2[0] ) p1 = 0.5 * ( p1 + SMESH_NodeXYZ( polySeg.myNode2[0] )); - if ( polySeg.myNode2[1] ) p2 = 0.5 * ( p2 + SMESH_NodeXYZ( polySeg.myNode2[1] )); - - polySeg.myFace[0] = polySeg.myFace[1] = 0; - if ( !polySeg.myNode1[0] && !polySeg.myNode2[0] ) - { - p1 = searcher->Project( polySeg.myXYZ[0], SMDSAbs_Face, &polySeg.myFace[0] ); - } - if ( !polySeg.myNode1[1] && !polySeg.myNode2[1] ) - { - p2 = searcher->Project( polySeg.myXYZ[1], SMDSAbs_Face, &polySeg.myFace[1] ); - } - polySeg.myXYZ[0] = p1; - polySeg.myXYZ[1] = p2; - - gp_XYZ plnNorm = ( p1 - p2 ) ^ polySeg.myVector.XYZ(); - - isVectorOK[ iSeg ] = ( plnNorm.Modulus() > std::numeric_limits::min() ); - if ( !isVectorOK[ iSeg ]) - { - gp_XYZ pMid = 0.5 * ( p1 + p2 ); - const SMDS_MeshElement* face; - polySeg.myMidProjPoint = searcher->Project( pMid, SMDSAbs_Face, &face ); - polySeg.myVector = polySeg.myMidProjPoint.XYZ() - pMid; - - gp_XYZ faceNorm; - SMESH_MeshAlgos::FaceNormal( face, faceNorm ); - - if ( polySeg.myVector.Magnitude() < Precision::Confusion() || - polySeg.myVector * faceNorm < Precision::Confusion() ) - { - polySeg.myVector = faceNorm; - polySeg.myMidProjPoint = pMid + faceNorm * ( p1 - p2 ).Modulus() * planarCoef; - } - } - else - { - polySeg.myVector = plnNorm ^ ( p1 - p2 ); - } - } - - // assure that inverse elements are constructed, avoid their concurrent building in threads - GetMeshDS()->nodesIterator()->next()->NbInverseElements(); - - // find paths - - PolyPathCompute algo( theSegments, segPaths, myMesh ); - OSD_Parallel::For( 0, theSegments.size(), algo, theSegments.size() == 1 ); - - for ( size_t iSeg = 0; iSeg < theSegments.size(); ++iSeg ) - if ( !algo.myErrors[ iSeg ].empty() ) - throw SALOME_Exception( algo.myErrors[ iSeg ].c_str() ); - - // create an 1D mesh - - const SMDS_MeshNode *n, *nPrev = 0; - SMESHDS_Mesh* mesh = GetMeshDS(); - - for ( size_t iSeg = 0; iSeg < theSegments.size(); ++iSeg ) - { - const Path& path = segPaths[iSeg]; - if ( path.myPoints.size() < 2 ) - continue; - - double tol = path.myLength / path.myPoints.size() / 1000.; - if ( !nPrev || ( SMESH_NodeXYZ( nPrev ) - path.myPoints[0] ).SquareModulus() > tol*tol ) - { - nPrev = mesh->AddNode( path.myPoints[0].X(), path.myPoints[0].Y(), path.myPoints[0].Z() ); - myLastCreatedNodes.push_back( nPrev ); - } - for ( size_t iP = 1; iP < path.myPoints.size(); ++iP ) - { - n = mesh->AddNode( path.myPoints[iP].X(), path.myPoints[iP].Y(), path.myPoints[iP].Z() ); - myLastCreatedNodes.push_back( n ); - - const SMDS_MeshElement* elem = mesh->AddEdge( nPrev, n ); - myLastCreatedElems.push_back( elem ); - if ( theGroup ) - theGroup->Add( elem ); - - nPrev = n; - } - - // return a vector - - gp_XYZ pMid = 0.5 * ( path.myPoints[0] + path.myPoints.back() ); - if ( isVectorOK[ iSeg ]) - { - // find the most distance point of a path - double maxDist = 0; - for ( size_t iP = 1; iP < path.myPoints.size(); ++iP ) - { - double dist = Abs( theSegments[iSeg].myVector * ( path.myPoints[iP] - path.myPoints[0] )); - if ( dist > maxDist ) - { - maxDist = dist; - theSegments[iSeg].myMidProjPoint = path.myPoints[iP]; - } - } - if ( maxDist < Precision::Confusion() ) // planar case - theSegments[iSeg].myMidProjPoint = - pMid + theSegments[iSeg].myVector.XYZ().Normalized() * path.myLength * planarCoef; - } - theSegments[iSeg].myVector = gp_Vec( pMid, theSegments[iSeg].myMidProjPoint ); - } - - return; -} diff --git a/src/SMESH/SMESH_MeshEditor.hxx b/src/SMESH/SMESH_MeshEditor.hxx index e5e558bee..221a78d46 100644 --- a/src/SMESH/SMESH_MeshEditor.hxx +++ b/src/SMESH/SMESH_MeshEditor.hxx @@ -717,49 +717,6 @@ public: bool toAddExistingBondary = false, bool aroundElements = false); - - // structure used in MakePolyLine() to define a cutting plane - struct PolySegment - { - // 2 points, each defined as follows: - // ( myNode1 && myNode2 ) ==> point is in the middle of an edge defined by two nodes - // ( myNode1 && !myNode2 ) ==> point is at myNode1 - // else ==> point is at myXYZ - const SMDS_MeshNode* myNode1[2]; - const SMDS_MeshNode* myNode2[2]; - gp_XYZ myXYZ [2]; - - // face on which myXYZ projects (found by MakePolyLine()) - const SMDS_MeshElement* myFace [2]; - - // vector on the plane; to use a default plane set vector = (0,0,0) - gp_Vec myVector; - - // point to return coordinates of a middle of the two points, projected to mesh - gp_Pnt myMidProjPoint; - }; - typedef std::vector TListOfPolySegments; - - /*! - * \brief Create a polyline consisting of 1D mesh elements each lying on a 2D element of - * the initial mesh. Positions of new nodes are found by cutting the mesh by the - * plane passing through pairs of points specified by each PolySegment structure. - * If there are several paths connecting a pair of points, the shortest path is - * selected by the module. Position of the cutting plane is defined by the two - * points and an optional vector lying on the plane specified by a PolySegment. - * By default the vector is defined by Mesh module as following. A middle point - * of the two given points is computed. The middle point is projected to the mesh. - * The vector goes from the middle point to the projection point. In case of planar - * mesh, the vector is normal to the mesh. - * \param [inout] segments - PolySegment's defining positions of cutting planes. - * Return the used vector and position of the middle point. - * \param [in] group - an optional group where created mesh segments will - * be added. - */ - void MakePolyLine( TListOfPolySegments& segments, - SMESHDS_Group* group=0, - SMESH_ElementSearcher* searcher=0); - private: /*! diff --git a/src/SMESHGUI/SMESHGUI_BuildCompoundDlg.cxx b/src/SMESHGUI/SMESHGUI_BuildCompoundDlg.cxx index 3159231f8..2e16075c0 100644 --- a/src/SMESHGUI/SMESHGUI_BuildCompoundDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_BuildCompoundDlg.cxx @@ -65,6 +65,8 @@ #define SPACING 6 #define MARGIN 11 +enum { NEW_MESH_ID, APPEND_TO_ID }; + //================================================================================= // name : SMESHGUI_BuildCompoundDlg // Purpose : @@ -103,16 +105,28 @@ SMESHGUI_BuildCompoundDlg::SMESHGUI_BuildCompoundDlg( SMESHGUI* theModule ) ButtonGroup->addButton(Constructor1, 0); /***************************************************************/ - GroupName = new QGroupBox(tr("RESULT_NAME"), this); - QHBoxLayout* GroupNameLayout = new QHBoxLayout(GroupName); - GroupNameLayout->setSpacing(SPACING); - GroupNameLayout->setMargin(MARGIN); - - TextLabelName = new QLabel(tr("SMESH_NAME"), GroupName); - LineEditName = new QLineEdit(GroupName); - - GroupNameLayout->addWidget(TextLabelName); - GroupNameLayout->addWidget(LineEditName); + GroupResult = new QGroupBox(tr("RESULT_NAME"), this); + QGridLayout* GroupResultLayout = new QGridLayout(GroupResult); + GroupResultLayout->setSpacing(SPACING); + GroupResultLayout->setMargin(MARGIN); + + QRadioButton* newMeshRadioBtn = new QRadioButton( tr("NEW_MESH_NAME"), GroupResult ); + QRadioButton* appendToRadioBtn = new QRadioButton( tr("MESH_APPEND_TO"), GroupResult ); + LineEditNewName = new QLineEdit(GroupResult); + LineEditAppendTo = new QLineEdit(GroupResult); + SelectButtonAppendTo = new QPushButton(GroupResult); + SelectButtonAppendTo->setIcon(image1); + SelectButtonAppendTo->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + ResultButtonGroup = new QButtonGroup( GroupResult ); + ResultButtonGroup->addButton( newMeshRadioBtn, NEW_MESH_ID ); + ResultButtonGroup->addButton( appendToRadioBtn, APPEND_TO_ID ); + newMeshRadioBtn->setChecked( true ); + + GroupResultLayout->addWidget( newMeshRadioBtn, 0, 0, 1, 2 ); + GroupResultLayout->addWidget( LineEditNewName, 0, 2 ); + GroupResultLayout->addWidget( appendToRadioBtn, 1, 0 ); + GroupResultLayout->addWidget( SelectButtonAppendTo, 1, 1 ); + GroupResultLayout->addWidget( LineEditAppendTo, 1, 2 ); /***************************************************************/ GroupArgs = new QGroupBox(tr("SMESH_ARGUMENTS"), this); @@ -175,7 +189,7 @@ SMESHGUI_BuildCompoundDlg::SMESHGUI_BuildCompoundDlg( SMESHGUI* theModule ) /***************************************************************/ aTopLayout->addWidget(GroupConstructors); - aTopLayout->addWidget(GroupName); + aTopLayout->addWidget(GroupResult); aTopLayout->addWidget(GroupArgs); aTopLayout->addWidget(GroupButtons); @@ -200,11 +214,11 @@ void SMESHGUI_BuildCompoundDlg::Init() { mySMESHGUI->SetActiveDialogBox((QDialog*)this); - myMesh = SMESH::SMESH_IDSource::_nil(); + myMeshToAppendTo = SMESH::SMESH_Mesh::_nil(); + myMeshArray = new SMESH::ListOfIDSources(); - myMeshFilter = new SMESH_TypeFilter (SMESH::IDSOURCE); - - myMeshArray = new SMESH::ListOfIDSources(); + myMeshFilter = new SMESH_TypeFilter (SMESH::IDSOURCE); + myAppendToFilter = new SMESH_TypeFilter (SMESH::MESH); // signals and slots connections connect(buttonOk, SIGNAL(clicked()), this, SLOT(ClickOnOk())); @@ -212,7 +226,10 @@ void SMESHGUI_BuildCompoundDlg::Init() connect(buttonApply, SIGNAL(clicked()), this, SLOT(ClickOnApply())); connect(buttonHelp, SIGNAL(clicked()), this, SLOT(ClickOnHelp())); - connect(SelectButton, SIGNAL(clicked()), this, SLOT(SelectionIntoArgument())); + connect(ResultButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(onResultTypeChange(int))); + + connect(SelectButtonAppendTo, SIGNAL(clicked()), this, SLOT(onSelectionButton())); + connect(SelectButton, SIGNAL(clicked()), this, SLOT(onSelectionButton())); connect(CheckBoxMerge, SIGNAL(toggled(bool)), this, SLOT(onSelectMerge(bool))); @@ -221,8 +238,7 @@ void SMESHGUI_BuildCompoundDlg::Init() connect(mySMESHGUI, SIGNAL(SignalDeactivateActiveDialog()), this, SLOT(DeactivateActiveDialog())); connect(mySMESHGUI, SIGNAL(SignalCloseAllDialogs()), this, SLOT(reject())); - LineEditName->setText(GetDefaultName(tr("COMPOUND_MESH"))); - LineEditMeshes->setFocus(); + LineEditNewName->setText(GetDefaultName(tr("COMPOUND_MESH"))); ComboBoxUnion->addItem(tr("UNITE")); ComboBoxUnion->addItem(tr("RENAME")); @@ -235,10 +251,9 @@ void SMESHGUI_BuildCompoundDlg::Init() SpinBoxTol->setEnabled(CheckBoxMerge->isChecked()); - mySelectionMgr->clearFilters(); - mySelectionMgr->installFilter(myMeshFilter); + onResultTypeChange( ResultButtonGroup->checkedId() ); - SelectionIntoArgument(); + onSelectionButton(); } //================================================================================= @@ -290,7 +305,9 @@ bool SMESHGUI_BuildCompoundDlg::ClickOnApply() SMESH::SMESH_Mesh_var aMesh; - if (!myMesh->_is_nil()) + int nbMeshes = myMeshArray->length() + ( !myMeshToAppendTo->_is_nil() ); + + if ( nbMeshes > 1 ) { QStringList aParameters; aParameters << (CheckBoxMerge->isChecked() ? SpinBoxTol->text() : QString(" ")); @@ -307,16 +324,19 @@ bool SMESHGUI_BuildCompoundDlg::ClickOnApply() aMesh = aSMESHGen->ConcatenateWithGroups(myMeshArray, !(ComboBoxUnion->currentIndex()), CheckBoxMerge->isChecked(), - SpinBoxTol->GetValue()); + SpinBoxTol->GetValue(), + myMeshToAppendTo); else aMesh = aSMESHGen->Concatenate(myMeshArray, !(ComboBoxUnion->currentIndex()), CheckBoxMerge->isChecked(), - SpinBoxTol->GetValue()); + SpinBoxTol->GetValue(), + myMeshToAppendTo); _PTR(SObject) aSO = SMESH::FindSObject( aMesh ); if( aSO ) { - SMESH::SetName( aSO, LineEditName->text() ); + if ( myMeshToAppendTo->_is_nil() ) + SMESH::SetName( aSO, LineEditNewName->text() ); anEntryList.append( aSO->GetID().c_str() ); } mySMESHGUI->updateObjBrowser(); @@ -324,7 +344,7 @@ bool SMESHGUI_BuildCompoundDlg::ClickOnApply() return false; } - LineEditName->setText(GetDefaultName(tr("COMPOUND_MESH"))); + LineEditNewName->setText(GetDefaultName(tr("COMPOUND_MESH"))); // IPAL21468 Compound is hidden after creation. if ( SMESHGUI::automaticUpdate() ) { @@ -401,30 +421,41 @@ void SMESHGUI_BuildCompoundDlg::SelectionIntoArgument() return; QString aString = ""; - SALOME_ListIO aList; + mySelectionMgr->selectedObjects(aList); int nbSel = SMESH::GetNameOfSelectedIObjects(mySelectionMgr, aString); - if (nbSel != 0) { - myMeshArray->length(nbSel); - for (int i = 0; nbSel != 0; i++, nbSel--) { - Handle(SALOME_InteractiveObject) IO = aList.First(); + bool toAppend = ( CurrentLineEdit == LineEditAppendTo ); + bool isOk = toAppend ? ( nbSel == 1 ) : ( nbSel > 0 ); + if ( !isOk ) + aString = ""; + + if ( toAppend ) + { + myMeshToAppendTo = SMESH::SMESH_Mesh::_nil(); + if ( isOk ) + myMeshToAppendTo = SMESH::IObjectToInterface( aList.First() ); + } + else + { + myMeshArray->length( nbSel ); + for ( int i = 0; !aList.IsEmpty(); i++ ) { + myMeshArray[i] = SMESH::IObjectToInterface(aList.First()); aList.RemoveFirst(); - myMesh = SMESH::IObjectToInterface(IO); - myMeshArray[i] = myMesh; } } - else { - myMesh = SMESH::SMESH_IDSource::_nil(); - aString = ""; - } - - LineEditMeshes->setText(aString); - - bool isEnabled = (!myMesh->_is_nil()); - buttonOk->setEnabled(isEnabled); - buttonApply->setEnabled(isEnabled); + CurrentLineEdit->setText(aString); + + bool isEnabled; + if ( ResultButtonGroup->checkedId() == NEW_MESH_ID ) + isEnabled = ( myMeshArray->length() > 1 ); + else + isEnabled = ( myMeshArray->length() > 0 && + !myMeshToAppendTo->_is_nil() && + LineEditAppendTo->text() != LineEditMeshes->text() ); + buttonOk ->setEnabled( isEnabled ); + buttonApply->setEnabled( isEnabled ); } //================================================================================= @@ -435,7 +466,7 @@ void SMESHGUI_BuildCompoundDlg::DeactivateActiveDialog() { if (GroupConstructors->isEnabled()) { GroupConstructors->setEnabled(false); - GroupName->setEnabled(false); + GroupResult->setEnabled(false); GroupArgs->setEnabled(false); GroupButtons->setEnabled(false); mySMESHGUI->ResetState(); @@ -452,7 +483,7 @@ void SMESHGUI_BuildCompoundDlg::ActivateThisDialog() /* Emit a signal to deactivate the active dialog */ mySMESHGUI->EmitSignalDeactivateDialog(); GroupConstructors->setEnabled(true); - GroupName->setEnabled(true); + GroupResult->setEnabled(true); GroupArgs->setEnabled(true); GroupButtons->setEnabled(true); @@ -500,6 +531,56 @@ void SMESHGUI_BuildCompoundDlg::onSelectMerge(bool toMerge) SpinBoxTol->SetValue(1e-05); } +//======================================================================= +//function : onResultTypeChange +//purpose : +//======================================================================= + +void SMESHGUI_BuildCompoundDlg::onResultTypeChange( int buttonID ) +{ + LineEditNewName ->setEnabled( buttonID == NEW_MESH_ID ); + SelectButtonAppendTo->setEnabled( buttonID == APPEND_TO_ID ); + LineEditAppendTo ->setEnabled( buttonID == APPEND_TO_ID ); + + if ( CurrentLineEdit == LineEditAppendTo && buttonID == NEW_MESH_ID ) + onSelectionButton(); // to select into myMeshArray + + if ( buttonID == NEW_MESH_ID ) + { + myMeshToAppendTo = SMESH::SMESH_Mesh::_nil(); + LineEditAppendTo->setText(""); + } + else + { + // activate selection of myMeshToAppendTo + SelectButtonAppendTo->click(); + LineEditAppendTo->setFocus(); + } +} + +//======================================================================= +//function : onSelectionButton +//purpose : +//======================================================================= + +void SMESHGUI_BuildCompoundDlg::onSelectionButton() +{ + mySelectionMgr->clearFilters(); + if ( sender() == SelectButtonAppendTo ) + { + mySelectionMgr->installFilter( myAppendToFilter ); + CurrentLineEdit = LineEditAppendTo; + } + else + { + mySelectionMgr->installFilter( myMeshFilter ); + CurrentLineEdit = LineEditMeshes; + } + CurrentLineEdit->setFocus(); + + SelectionIntoArgument(); +} + //================================================================================= // function : isValid // purpose : diff --git a/src/SMESHGUI/SMESHGUI_BuildCompoundDlg.h b/src/SMESHGUI/SMESHGUI_BuildCompoundDlg.h index f54e59eda..b401d2caa 100644 --- a/src/SMESHGUI/SMESHGUI_BuildCompoundDlg.h +++ b/src/SMESHGUI/SMESHGUI_BuildCompoundDlg.h @@ -38,16 +38,17 @@ #include CORBA_SERVER_HEADER(SMESH_Gen) #include CORBA_SERVER_HEADER(SMESH_Mesh) +class LightApp_SelectionMgr; +class QButtonGroup; +class QCheckBox; +class QComboBox; class QGroupBox; class QLabel; class QLineEdit; class QPushButton; class QRadioButton; -class QCheckBox; -class QComboBox; class SMESHGUI; class SMESHGUI_SpinBox; -class LightApp_SelectionMgr; class SUIT_SelectionFilter; //================================================================================= @@ -80,9 +81,10 @@ private: SMESHGUI* mySMESHGUI; /* Current SMESHGUI object */ LightApp_SelectionMgr* mySelectionMgr; /* User shape selection */ - SMESH::SMESH_IDSource_var myMesh; - SUIT_SelectionFilter* myMeshFilter; + SMESH::SMESH_Mesh_var myMeshToAppendTo; SMESH::ListOfIDSources_var myMeshArray; + SUIT_SelectionFilter* myMeshFilter; + SUIT_SelectionFilter* myAppendToFilter; // Widgets QGroupBox* GroupConstructors; @@ -94,9 +96,11 @@ private: QPushButton* buttonApply; QPushButton* buttonHelp; - QGroupBox* GroupName; - QLabel* TextLabelName; - QLineEdit* LineEditName; + QGroupBox* GroupResult; + QButtonGroup* ResultButtonGroup; + QLineEdit* LineEditNewName; + QPushButton* SelectButtonAppendTo; + QLineEdit* LineEditAppendTo; QGroupBox* GroupArgs; QLabel* TextLabelMeshes; @@ -109,6 +113,8 @@ private: QLabel* TextLabelTol; SMESHGUI_SpinBox* SpinBoxTol; + QLineEdit* CurrentLineEdit; + QString myHelpFileName; bool myIsApplyAndClose; @@ -124,6 +130,8 @@ private slots: void DeactivateActiveDialog(); void ActivateThisDialog(); void onSelectMerge( bool ); + void onResultTypeChange( int ); + void onSelectionButton(); }; #endif // SMESHGUI_BUILDCOMPOUNDDLG_H diff --git a/src/SMESHGUI/SMESHGUI_Measurements.cxx b/src/SMESHGUI/SMESHGUI_Measurements.cxx index 8e19cddca..852bca21e 100644 --- a/src/SMESHGUI/SMESHGUI_Measurements.cxx +++ b/src/SMESHGUI/SMESHGUI_Measurements.cxx @@ -173,11 +173,11 @@ SMESHGUI_MinDistance::SMESHGUI_MinDistance( QWidget* parent ) aSOrigin->setChecked( true ); #ifndef MINDIST_ENABLE_ELEMENT aFElem->setEnabled( false ); // NOT AVAILABLE YET - aSElem->setEnabled( false ); // NOT AVAILABLE YET + //aSElem->setEnabled( false ); // NOT AVAILABLE YET #endif #ifndef MINDIST_ENABLE_OBJECT aFObject->setEnabled( false ); // NOT AVAILABLE YET - aSObject->setEnabled( false ); // NOT AVAILABLE YET + //aSObject->setEnabled( false ); // NOT AVAILABLE YET #endif myDX->setReadOnly( true ); myDY->setReadOnly( true ); @@ -595,10 +595,14 @@ void SMESHGUI_MinDistance::compute() if ( isOrigin ) { x2 = y2 = z2 = 0.; } - else { + else if ( mySecond->checkedId() == NodeTgt ) { coord = s2->GetMesh()->GetNodeXYZ( result.node2 ); x2 = coord[0]; y2 = coord[1]; z2 = coord[2]; } + else + { + x2 = result.maxX; y2 = result.maxY; z2 = result.maxZ; + } createPreview( x1, y1, z1, x2, y2, z2 ); displayPreview(); } diff --git a/src/SMESHGUI/SMESH_msg_en.ts b/src/SMESHGUI/SMESH_msg_en.ts index 41d27f5c0..c4ffdde6f 100644 --- a/src/SMESHGUI/SMESH_msg_en.ts +++ b/src/SMESHGUI/SMESH_msg_en.ts @@ -5118,6 +5118,14 @@ Please, create VTK viewer and try again MESHES Meshes, sub-meshes, groups + + NEW_MESH_NAME + Create new mesh named + + + MESH_APPEND_TO + Append to mesh + PROCESSING_IDENTICAL_GROUPS Processing identical groups @@ -5128,7 +5136,7 @@ Please, create VTK viewer and try again RESULT_NAME - Result name + Result UNITE diff --git a/src/SMESHUtils/CMakeLists.txt b/src/SMESHUtils/CMakeLists.txt index ae011b71a..d1f527b1d 100644 --- a/src/SMESHUtils/CMakeLists.txt +++ b/src/SMESHUtils/CMakeLists.txt @@ -82,6 +82,8 @@ SET(SMESHUtils_SOURCES SMESH_FillHole.cxx SMESH_Triangulate.cxx SMESH_Offset.cxx + SMESH_Slot.cxx + SMESH_PolyLine.cxx ) # --- rules --- diff --git a/src/SMESHUtils/SMESH_MeshAlgos.cxx b/src/SMESHUtils/SMESH_MeshAlgos.cxx index 019f57762..6cd891ccc 100644 --- a/src/SMESHUtils/SMESH_MeshAlgos.cxx +++ b/src/SMESHUtils/SMESH_MeshAlgos.cxx @@ -23,7 +23,7 @@ // Created : Tue Apr 30 18:00:36 2013 // Author : Edward AGAPOV (eap) -// This file holds some low level algorithms extracted from SMESH_MeshEditor +// Initially this file held some low level algorithms extracted from SMESH_MeshEditor // to make them accessible from Controls package #include "SMESH_MeshAlgos.hxx" @@ -285,6 +285,11 @@ namespace // Utils used in SMESH_ElementSearcherImpl::FindElementsByPoint() TElementBoxPool& elBoPool = getLimitAndPool()->_elBoPool; +#ifdef _DEBUG_ + if ( theElemIt && !theElemIt->more() ) + std::cout << "WARNING: ElementBndBoxTree constructed on empty iterator!" << std::endl; +#endif + SMDS_ElemIteratorPtr elemIt = theElemIt ? theElemIt : mesh.elementsIterator( elemType ); while ( elemIt->more() ) { @@ -874,7 +879,7 @@ SMESH_ElementSearcherImpl::FindClosestTo( const gp_Pnt& point, radius = point.Distance( boxCenter ) - 0.5 * ebbTree->maxSize(); if ( radius < 0 ) radius = ebbTree->maxSize() / pow( 2., getTreeHeight()) / 2; - while ( suspectElems.empty() ) + while ( suspectElems.empty() && radius < 1e100 ) { ebbTree->getElementsInSphere( point.XYZ(), radius, suspectElems ); radius *= 1.1; @@ -1253,7 +1258,7 @@ gp_XYZ SMESH_ElementSearcherImpl::Project(const gp_Pnt& point, ElementBndBoxTree::TElemSeq elems; ebbTree->getElementsInSphere( p, radius, elems ); - while ( elems.empty() ) + while ( elems.empty() && radius < 1e100 ) { radius *= 1.5; ebbTree->getElementsInSphere( p, radius, elems ); @@ -1264,7 +1269,7 @@ gp_XYZ SMESH_ElementSearcherImpl::Project(const gp_Pnt& point, ElementBndBoxTree::TElemSeq::iterator e = elems.begin(); for ( ; e != elems.end(); ++e ) { - double d = SMESH_MeshAlgos::GetDistance( *e, p, &proj ); + double d = SMESH_MeshAlgos::GetDistance( *e, point, &proj ); if ( d < minDist ) { bestProj = proj; @@ -1460,7 +1465,8 @@ namespace // . RIGHT . // . . enum PositionName { POS_LEFT = 1, POS_VERTEX = 2, POS_RIGHT = 4, //POS_ON = 8, - POS_ALL = POS_LEFT | POS_RIGHT | POS_VERTEX }; + POS_ALL = POS_LEFT | POS_RIGHT | POS_VERTEX, + POS_MAX = POS_RIGHT }; struct PointPos { PositionName _name; @@ -1558,10 +1564,35 @@ double SMESH_MeshAlgos::GetDistance( const SMDS_MeshFace* face, const double badDistance = -1; if ( !face ) return badDistance; + int nbCorners = face->NbCornerNodes(); + if ( nbCorners > 3 ) + { + std::vector< const SMDS_MeshNode* > nodes; + int nbTria = SMESH_MeshAlgos::Triangulate().GetTriangles( face, nodes ); + + double minDist = Precision::Infinite(); + gp_XYZ cp; + for ( int i = 0; i < 3 * nbTria; i += 3 ) + { + SMDS_FaceOfNodes triangle( nodes[i], nodes[i+1], nodes[i+2] ); + double dist = GetDistance( &triangle, point, closestPnt ); + if ( dist < minDist ) + { + minDist = dist; + if ( closestPnt ) + cp = *closestPnt; + } + } + + if ( closestPnt ) + *closestPnt = cp; + return minDist; + } + // coordinates of nodes (medium nodes, if any, ignored) typedef SMDS_StdIterator< SMESH_TNodeXYZ, SMDS_ElemIteratorPtr > TXyzIterator; std::vector xyz( TXyzIterator( face->nodesIterator()), TXyzIterator() ); - xyz.resize( face->NbCornerNodes()+1 ); + xyz.resize( 4 ); // transformation to get xyz[0] lies on the origin, xyz[1] lies on the Z axis, // and xyz[2] lies in the XZ plane. This is to pass to 2D space on XZ plane. @@ -1585,7 +1616,7 @@ double SMESH_MeshAlgos::GetDistance( const SMDS_MeshFace* face, // move all the nodes to 2D std::vector xy( xyz.size() ); - for ( size_t i = 0;i < xyz.size()-1; ++i ) + for ( size_t i = 0; i < 3; ++i ) { gp_XYZ p3d = xyz[i]; trsf.Transforms( p3d ); @@ -1600,71 +1631,63 @@ double SMESH_MeshAlgos::GetDistance( const SMDS_MeshFace* face, gp_XY point2D( tmpPnt.X(), tmpPnt.Z() ); // loop on edges of the face to analyze point position ralative to the face - std::set< PointPos > pntPosSet; + std::vector< PointPos > pntPosByType[ POS_MAX + 1 ]; for ( size_t i = 1; i < xy.size(); ++i ) { PointPos pos = getPointPosition( point2D, &xy[0], i-1 ); - pntPosSet.insert( pos ); + pntPosByType[ pos._name ].push_back( pos ); } // compute distance - double minDist2 = Precision::Infinite(); - for ( std::set< PointPos >::iterator posIt = pntPosSet.begin(); posIt != pntPosSet.end(); ++posIt) + double dist = badDistance; + + if ( pntPosByType[ POS_LEFT ].size() > 0 ) // point is most close to an edge { - PointPos pos = *posIt; - if ( pos._name != pntPosSet.begin()->_name ) - break; - switch ( pos._name ) - { - case POS_LEFT: // point is most close to an edge - { - gp_Vec edge( xyz[ pos._index ], xyz[ pos._index+1 ]); - gp_Vec n1p ( xyz[ pos._index ], point ); - double u = ( edge * n1p ) / edge.SquareMagnitude(); // param [0,1] on the edge - // projection of the point on the edge - gp_XYZ proj = xyz[ pos._index ] + u * edge.XYZ(); - double dist2 = point.SquareDistance( proj ); - if ( dist2 < minDist2 ) - { - if ( closestPnt ) *closestPnt = proj; - minDist2 = dist2; - } - break; - } + PointPos& pos = pntPosByType[ POS_LEFT ][0]; + + gp_Vec edge( xyz[ pos._index ], xyz[ pos._index+1 ]); + gp_Vec n1p ( xyz[ pos._index ], point ); + double u = ( edge * n1p ) / edge.SquareMagnitude(); // param [0,1] on the edge + gp_XYZ proj = xyz[ pos._index ] + u * edge.XYZ(); // projection on the edge + dist = point.Distance( proj ); + if ( closestPnt ) *closestPnt = proj; + } - case POS_RIGHT: // point is inside the face + else if ( pntPosByType[ POS_RIGHT ].size() >= 2 ) // point is inside the face + { + dist = Abs( tmpPnt.Y() ); + if ( closestPnt ) { - double distToFacePlane = Abs( tmpPnt.Y() ); - if ( closestPnt ) - { - if ( distToFacePlane < std::numeric_limits::min() ) { - *closestPnt = point.XYZ(); - } - else { - tmpPnt.SetY( 0 ); - trsf.Inverted().Transforms( tmpPnt ); - *closestPnt = tmpPnt; - } + if ( dist < std::numeric_limits::min() ) { + *closestPnt = point.XYZ(); + } + else { + tmpPnt.SetY( 0 ); + trsf.Inverted().Transforms( tmpPnt ); + *closestPnt = tmpPnt; } - return distToFacePlane; } + } - case POS_VERTEX: // point is most close to a node + else if ( pntPosByType[ POS_VERTEX ].size() > 0 ) // point is most close to a node + { + double minDist2 = Precision::Infinite(); + for ( size_t i = 0; i < pntPosByType[ POS_VERTEX ].size(); ++i ) { - double dist2 = point.SquareDistance( xyz[ pos._index ]); - if ( dist2 < minDist2 ) + PointPos& pos = pntPosByType[ POS_VERTEX ][i]; + + double d2 = point.SquareDistance( xyz[ pos._index ]); + if ( minDist2 > d2 ) { if ( closestPnt ) *closestPnt = xyz[ pos._index ]; - minDist2 = dist2; + minDist2 = d2; } - break; - } - default:; - return badDistance; } + dist = Sqrt( minDist2 ); } - return Sqrt( minDist2 ); + + return dist; } //======================================================================= @@ -2166,6 +2189,207 @@ bool SMESH_MeshAlgos::IsRightOrder( const SMDS_MeshElement* face, return ( diff == 1 ) || ( diff == -face->NbNodes()+1 ); } +//======================================================================= +/*! + * \brief Partition given 1D elements into groups of contiguous edges. + * A node where number of meeting edges != 2 is a group end. + * An optional startNode is used to orient groups it belongs to. + * \return a list of edge groups and a list of corresponding node groups. + * If a group is closed, the first and last nodes of the group are same. + */ +//======================================================================= + +void SMESH_MeshAlgos::Get1DBranches( SMDS_ElemIteratorPtr theEdgeIt, + TElemGroupVector& theEdgeGroups, + TNodeGroupVector& theNodeGroups, + const SMDS_MeshNode* theStartNode ) +{ + if ( !theEdgeIt ) + return; + + // build map of nodes and their adjacent edges + + typedef std::vector< const SMDS_MeshNode* > TNodeVec; + typedef std::vector< const SMDS_MeshElement* > TEdgeVec; + typedef NCollection_DataMap< const SMDS_MeshNode*, TEdgeVec, SMESH_Hasher > TEdgesByNodeMap; + TEdgesByNodeMap edgesByNode; + + while ( theEdgeIt->more() ) + { + const SMDS_MeshElement* edge = theEdgeIt->next(); + if ( edge->GetType() != SMDSAbs_Edge ) + continue; + + const SMDS_MeshNode* nodes[2] = { edge->GetNode(0), edge->GetNode(1) }; + for ( int i = 0; i < 2; ++i ) + { + TEdgeVec* nodeEdges = edgesByNode.ChangeSeek( nodes[i] ); + if ( !nodeEdges ) + { + nodeEdges = edgesByNode.Bound( nodes[i], TEdgeVec() ); + nodeEdges->reserve(2); + } + nodeEdges->push_back( edge ); + } + } + + if ( edgesByNode.IsEmpty() ) + return; + + + // build edge branches + + TElemGroupVector branches(2); + TNodeGroupVector nodeBranches(2); + + while ( !edgesByNode.IsEmpty() ) + { + if ( !theStartNode || !edgesByNode.IsBound( theStartNode )) + { + theStartNode = TEdgesByNodeMap::Iterator( edgesByNode ).Key(); + } + + size_t nbBranches = 0; + bool startIsBranchEnd = false; + + while ( edgesByNode.IsBound( theStartNode )) + { + // initialize a new branch + + ++nbBranches; + if ( branches.size() < nbBranches ) + { + branches.push_back ( TEdgeVec() ); + nodeBranches.push_back( TNodeVec() ); + } + TEdgeVec & branch = branches [ nbBranches - 1 ]; + TNodeVec & nodeBranch = nodeBranches[ nbBranches - 1 ]; + branch.clear(); + nodeBranch.clear(); + { + TEdgeVec& edges = edgesByNode( theStartNode ); + startIsBranchEnd = ( edges.size() != 2 ); + + int nbEdges = 0; + const SMDS_MeshElement* startEdge = 0; + for ( size_t i = 0; i < edges.size(); ++i ) + { + if ( !startEdge && edges[i] ) + { + startEdge = edges[i]; + edges[i] = 0; + } + nbEdges += bool( edges[i] ); + } + if ( nbEdges == 0 ) + edgesByNode.UnBind( theStartNode ); + if ( !startEdge ) + continue; + + branch.push_back( startEdge ); + + nodeBranch.push_back( theStartNode ); + nodeBranch.push_back( branch.back()->GetNode(0) ); + if ( nodeBranch.back() == theStartNode ) + nodeBranch.back() = branch.back()->GetNode(1); + } + + // fill the branch + + bool isBranchEnd = false; + TEdgeVec* edgesPtr; + + while (( !isBranchEnd ) && ( edgesPtr = edgesByNode.ChangeSeek( nodeBranch.back() ))) + { + TEdgeVec& edges = *edgesPtr; + + isBranchEnd = ( edges.size() != 2 ); + + const SMDS_MeshNode* lastNode = nodeBranch.back(); + + switch ( edges.size() ) + { + case 1: + edgesByNode.UnBind( lastNode ); + break; + + case 2: + { + if ( const SMDS_MeshElement* nextEdge = edges[ edges[0] == branch.back() ]) + { + branch.push_back( nextEdge ); + + const SMDS_MeshNode* nextNode = nextEdge->GetNode(0); + if ( nodeBranch.back() == nextNode ) + nextNode = nextEdge->GetNode(1); + nodeBranch.push_back( nextNode ); + } + edgesByNode.UnBind( lastNode ); + break; + } + + default: + int nbEdges = 0; + for ( size_t i = 0; i < edges.size(); ++i ) + { + if ( edges[i] == branch.back() ) + edges[i] = 0; + nbEdges += bool( edges[i] ); + } + if ( nbEdges == 0 ) + edgesByNode.UnBind( lastNode ); + } + } + } // while ( edgesByNode.IsBound( theStartNode )) + + + // put the found branches to the result + + if ( nbBranches == 2 && !startIsBranchEnd ) // join two branches starting at the same node + { + if ( nodeBranches[0].back() == nodeBranches[1].back() ) + { + // it is a closed branch, keep theStartNode first + nodeBranches[0].pop_back(); + nodeBranches[0].reserve( nodeBranches[0].size() + nodeBranches[1].size() ); + nodeBranches[0].insert( nodeBranches[0].end(), + nodeBranches[1].rbegin(), nodeBranches[1].rend() ); + branches[0].reserve( branches[0].size() + branches[1].size() ); + branches[0].insert( branches[0].end(), branches[1].rbegin(), branches[1].rend() ); + } + else + { + std::reverse( nodeBranches[0].begin(), nodeBranches[0].end() ); + nodeBranches[0].pop_back(); + nodeBranches[0].reserve( nodeBranches[0].size() + nodeBranches[1].size() ); + nodeBranches[0].insert( nodeBranches[0].end(), + nodeBranches[1].begin(), nodeBranches[1].end() ); + + std::reverse( branches[0].begin(), branches[0].end() ); + branches[0].reserve( branches[0].size() + branches[1].size() ); + branches[0].insert( branches[0].end(), branches[1].begin(), branches[1].end() ); + } + nodeBranches[1].clear(); + branches[1].clear(); + } + + for ( size_t i = 0; i < nbBranches; ++i ) + { + if ( branches[i].empty() ) + continue; + + theEdgeGroups.push_back( TEdgeVec() ); + theEdgeGroups.back().swap( branches[i] ); + + theNodeGroups.push_back( TNodeVec() ); + theNodeGroups.back().swap( nodeBranches[i] ); + } + + } // while ( !edgesByNode.IsEmpty() ) + + return; +} + //======================================================================= /*! * \brief Return SMESH_NodeSearcher diff --git a/src/SMESHUtils/SMESH_MeshAlgos.hxx b/src/SMESHUtils/SMESH_MeshAlgos.hxx index 6a507f1b1..c6965050d 100644 --- a/src/SMESHUtils/SMESH_MeshAlgos.hxx +++ b/src/SMESHUtils/SMESH_MeshAlgos.hxx @@ -23,7 +23,7 @@ // Created : Tue Apr 30 18:00:36 2013 // Author : Edward AGAPOV (eap) -// This file holds some low level algorithms extracted from SMESH_MeshEditor +// Initially this file held some low level algorithms extracted from SMESH_MeshEditor // to make them accessible from Controls package, and more @@ -37,14 +37,17 @@ #include "SMESH_TypeDefs.hxx" #include +#include +#include + #include -class gp_Pnt; -class gp_Ax1; class Bnd_B3d; -class SMDS_MeshNode; -class SMDS_MeshElement; +class gp_Ax1; class SMDS_Mesh; +class SMDS_MeshElement; +class SMDS_MeshGroup; +class SMDS_MeshNode; //======================================================================= /*! @@ -198,6 +201,22 @@ namespace SMESH_MeshAlgos bool IsRightOrder( const SMDS_MeshElement* face, const SMDS_MeshNode* node0, const SMDS_MeshNode* node1 ); + + typedef std::vector< std::vector< const SMDS_MeshElement* > > TElemGroupVector; + typedef std::vector< std::vector< const SMDS_MeshNode* > > TNodeGroupVector; + /*! + * \brief Partition given 1D elements into groups of contiguous edges. + * A node where number of meeting edges != 2 is a group end. + * An optional startNode is used to orient groups it belongs to. + * \return a list of edge groups and a list of corresponding node groups. + * If a group is closed, the first and last nodes of the group are same. + */ + SMESHUtils_EXPORT + void Get1DBranches( SMDS_ElemIteratorPtr edgeIt, + TElemGroupVector& edgeGroups, + TNodeGroupVector& nodeGroups, + const SMDS_MeshNode* startNode = 0 ); + /*! * \brief Mark elements given by SMDS_Iterator */ @@ -320,7 +339,6 @@ namespace SMESH_MeshAlgos std::vector& newFaces); // Implemented in ./SMESH_FillHole.cxx - /*! * \brief Find nodes whose merge makes the element invalid */ @@ -331,8 +349,8 @@ namespace SMESH_MeshAlgos // Implemented in SMESH_DeMerge.cxx - typedef std::vector< std::pair< const SMDS_MeshElement*, const SMDS_MeshElement* > > TEPairVec; - typedef std::vector< std::pair< const SMDS_MeshNode*, const SMDS_MeshNode* > > TNPairVec; + typedef std::vector< std::pair< const SMDS_MeshElement*, int > > TElemIntPairVec; + typedef std::vector< std::pair< const SMDS_MeshNode*, int > > TNodeIntPairVec; /*! * \brief Create an offset mesh of given faces * \param [in] faceIt - the input faces @@ -346,20 +364,77 @@ namespace SMESH_MeshAlgos SMDS_Mesh& mesh, const double offset, const bool theFixIntersections, - TEPairVec& new2OldFaces, - TNPairVec& new2OldNodes ); + TElemIntPairVec& new2OldFaces, + TNodeIntPairVec& new2OldNodes ); // Implemented in ./SMESH_Offset.cxx + //======================================================================= + /*! + * \brief Cut faces of a triangular mesh. + * Usage work-flow: 1) call Cut() methods as many times as needed + * 2) call MakeNewFaces() to really modify the mesh faces + */ + //======================================================================= + // implemented in SMESH_Offset.cxx + + class SMESHUtils_EXPORT Intersector + { + public: + Intersector( SMDS_Mesh* mesh, double tol, const std::vector< gp_XYZ >& normals ); + ~Intersector(); + + //! Compute cut of two faces of the mesh + void Cut( const SMDS_MeshElement* face1, + const SMDS_MeshElement* face2, + const int nbCommonNodes = -1 ); + + //! Store a face cut by a line given by its ends lying either on face edges or inside the face. + // Line ends are accompanied by indices of intersected face edges. + // Edge index is <0 if a line end is inside the face. + void Cut( const SMDS_MeshElement* face, + SMESH_NodeXYZ& lineEnd1, + int edgeIndex1, + SMESH_NodeXYZ& lineEnd2, + int edgeIndex2 ); + + //! Split all faces intersected by Cut() methods. + // theSign = (-1|1) is used to choose which part of a face cut by another one to remove. + // 1 means to remove a part opposite to face normal. + // Optionally optimize quality of split faces by edge swapping. + void MakeNewFaces( SMESH_MeshAlgos::TElemIntPairVec& theNew2OldFaces, + SMESH_MeshAlgos::TNodeIntPairVec& theNew2OldNodes, + const double theSign = 1., + const bool theOptimize = false ); + + typedef std::vector< SMESH_NodeXYZ > TFace; + + //! Cut a face by planes, whose normals point to parts to keep. + // Return true if the whole face is cut off + static bool CutByPlanes(const SMDS_MeshElement* face, + const std::vector< gp_Ax1 > & planes, + const double tol, + std::vector< TFace > & newFaceConnectivity ); + + private: + struct Algo; + Algo* myAlgo; + }; + + //======================================================================= /*! * \brief Divide a mesh face into triangles */ + //======================================================================= // Implemented in ./SMESH_Triangulate.cxx class SMESHUtils_EXPORT Triangulate { public: + Triangulate(bool optimize=false); + ~Triangulate(); + static int GetNbTriangles( const SMDS_MeshElement* face ); int GetTriangles( const SMDS_MeshElement* face, @@ -374,19 +449,81 @@ namespace SMESH_MeshAlgos struct PolyVertex { SMESH_NodeXYZ _nxyz; + size_t _index; gp_XY _xy; PolyVertex* _prev; PolyVertex* _next; - void SetNodeAndNext( const SMDS_MeshNode* n, PolyVertex& v ); - void GetTriaNodes( const SMDS_MeshNode** nodes) const; + void SetNodeAndNext( const SMDS_MeshNode* n, PolyVertex& v, size_t index ); + void GetTriaNodes( const SMDS_MeshNode** nodes, size_t* nodeIndices) const; double TriaArea() const; bool IsInsideTria( const PolyVertex* v ); PolyVertex* Delete(); }; + struct Optimizer; + std::vector< PolyVertex > _pv; + std::vector< size_t > _nodeIndex; + Optimizer* _optimizer; }; + // structure used in MakePolyLine() to define a cutting plane + struct PolySegment + { + // 2 points, each defined as follows: + // ( myNode1 && myNode2 ) ==> point is in the middle of an edge defined by two nodes + // ( myNode1 && !myNode2 ) ==> point is at myNode1 of a some face + // else ==> point is at myXYZ + const SMDS_MeshNode* myNode1[2]; + const SMDS_MeshNode* myNode2[2]; + gp_XYZ myXYZ [2]; + + // face on which myXYZ projects (found by MakePolyLine()) + const SMDS_MeshElement* myFace [2]; + + // vector on the plane; to use a default plane set vector = (0,0,0) + gp_Vec myVector; + + // point returning coordinates of a middle of the two points, projected to mesh + gp_Pnt myMidProjPoint; + }; + typedef std::vector TListOfPolySegments; + + /*! + * \brief Create a polyline consisting of 1D mesh elements each lying on a 2D element of + * the initial mesh. Positions of new nodes are found by cutting the mesh by the + * plane passing through pairs of points specified by each PolySegment structure. + * If there are several paths connecting a pair of points, the shortest path is + * selected by the module. Position of the cutting plane is defined by the two + * points and an optional vector lying on the plane specified by a PolySegment. + * By default the vector is defined by Mesh module as following. A middle point + * of the two given points is computed. The middle point is projected to the mesh. + * The vector goes from the middle point to the projection point. In case of planar + * mesh, the vector is normal to the mesh. + * \param [inout] segments - PolySegment's defining positions of cutting planes. + * Return the used vector and position of the middle point. + * \param [in] group - an optional group where created mesh segments will + * be added. + */ + // Implemented in ./SMESH_PolyLine.cxx + SMESHUtils_EXPORT + void MakePolyLine( SMDS_Mesh* mesh, + TListOfPolySegments& segments, + std::vector& newEdges, + std::vector& newNodes, + SMDS_MeshGroup* group=0, + SMESH_ElementSearcher* searcher=0); + + /*! + * Create a slot of given width around given 1D elements lying on a triangle mesh. + * The slot is consrtucted by cutting faces by cylindrical surfaces made around each segment. + * \return Edges located at the slot boundary + */ + // Implemented in ./SMESH_Slot.cxx + SMESHUtils_EXPORT + std::vector< Edge > MakeSlot( SMDS_ElemIteratorPtr segmentIt, + double width, + SMDS_Mesh* mesh); } // namespace SMESH_MeshAlgos diff --git a/src/SMESHUtils/SMESH_Offset.cxx b/src/SMESHUtils/SMESH_Offset.cxx index 33d5b1c78..29aed9537 100644 --- a/src/SMESHUtils/SMESH_Offset.cxx +++ b/src/SMESHUtils/SMESH_Offset.cxx @@ -42,8 +42,8 @@ namespace { const int theMaxNbFaces = 256; // max number of faces sharing a node - typedef NCollection_DataMap< Standard_Address, const SMDS_MeshNode* > TNNMap; - typedef NCollection_Map< SMESH_Link, SMESH_Link > TLinkMap; + typedef NCollection_DataMap< const SMDS_MeshNode*, const SMDS_MeshNode*, SMESH_Hasher > TNNMap; + typedef NCollection_Map< SMESH_Link, SMESH_Link > TLinkMap; //-------------------------------------------------------------------------------- /*! @@ -53,7 +53,7 @@ namespace struct CutLink { bool myReverse; - const SMDS_MeshNode* myNode[2]; // side nodes + const SMDS_MeshNode* myNode[2]; // side nodes. WARNING: don't set them directly, use Set() mutable SMESH_NodeXYZ myIntNode; // intersection node const SMDS_MeshElement* myFace; // cutter face int myIndex; // index of a node on the same link @@ -694,11 +694,15 @@ namespace return useOneNormal; } +} // namespace + +namespace SMESH_MeshAlgos +{ //-------------------------------------------------------------------------------- /*! * \brief Intersect faces of a mesh */ - struct Intersector + struct Intersector::Algo { SMDS_Mesh* myMesh; double myTol, myEps; @@ -716,7 +720,7 @@ namespace int myNbOnPlane1, myNbOnPlane2; TIntPointSet myIntPointSet; - Intersector( SMDS_Mesh* mesh, double tol, const std::vector< gp_XYZ >& normals ) + Algo( SMDS_Mesh* mesh, double tol, const std::vector< gp_XYZ >& normals ) : myMesh( mesh ), myTol( tol ), myEps( 1e-100 ), @@ -727,9 +731,20 @@ namespace void Cut( const SMDS_MeshElement* face1, const SMDS_MeshElement* face2, const int nbCommonNodes ); - void MakeNewFaces( SMESH_MeshAlgos::TEPairVec& theNew2OldFaces, - SMESH_MeshAlgos::TNPairVec& theNew2OldNodes, - const double theSign ); + void Cut( const SMDS_MeshElement* face, + SMESH_NodeXYZ& lineEnd1, + int edgeIndex1, + SMESH_NodeXYZ& lineEnd2, + int edgeIndex2 ); + void MakeNewFaces( TElemIntPairVec& theNew2OldFaces, + TNodeIntPairVec& theNew2OldNodes, + const double theSign, + const bool theOptimize ); + + //! Cut a face by planes, whose normals point to parts to keep + bool CutByPlanes(const SMDS_MeshElement* face, + const std::vector< gp_Ax1 > & planes, + std::vector< SMESH_NodeXYZ > & newConnectivity ); private: @@ -805,7 +820,7 @@ namespace */ //================================================================================ - const SMDS_MeshNode* Intersector::createNode( const gp_XYZ& p ) + const SMDS_MeshNode* Intersector::Algo::createNode( const gp_XYZ& p ) { const SMDS_MeshNode* n = myMesh->AddNode( p.X(), p.Y(), p.Z() ); n->setIsMarked( true ); // cut nodes are marked @@ -818,7 +833,7 @@ namespace */ //================================================================================ - void Intersector::addLink( CutLink& link ) + void Intersector::Algo::addLink( CutLink& link ) { link.myIndex = 0; const CutLink* added = & myCutLinks.Added( link ); @@ -844,7 +859,7 @@ namespace */ //================================================================================ - bool Intersector::findLink( CutLink& link ) + bool Intersector::Algo::findLink( CutLink& link ) { link.myIndex = 0; while ( myCutLinks.Contains( link )) @@ -872,12 +887,12 @@ namespace */ //================================================================================ - bool Intersector::isPlaneIntersected( const gp_XYZ& n2, - const double d2, - const std::vector< SMESH_NodeXYZ >& nodes1, - std::vector< double > & dist1, - int & nbOnPlane1, - int & iNotOnPlane1) + bool Intersector::Algo::isPlaneIntersected( const gp_XYZ& n2, + const double d2, + const std::vector< SMESH_NodeXYZ >& nodes1, + std::vector< double > & dist1, + int & nbOnPlane1, + int & iNotOnPlane1) { iNotOnPlane1 = nbOnPlane1 = 0; dist1.resize( nodes1.size() ); @@ -915,12 +930,12 @@ namespace */ //================================================================================ - void Intersector::computeIntervals( const std::vector< SMESH_NodeXYZ >& nodes, - const std::vector< double >& dist, - const int nbOnPln, - const int iMaxCoo, - double * u, - int* iE) + void Intersector::Algo::computeIntervals( const std::vector< SMESH_NodeXYZ >& nodes, + const std::vector< double >& dist, + const int nbOnPln, + const int iMaxCoo, + double * u, + int* iE) { if ( nbOnPln == 3 ) { @@ -961,9 +976,9 @@ namespace */ //================================================================================ - void Intersector::findIntPointOnPlane( const std::vector< SMESH_NodeXYZ >& nodes, - const std::vector< double > & dist, - CutLink& link ) + void Intersector::Algo::findIntPointOnPlane( const std::vector< SMESH_NodeXYZ >& nodes, + const std::vector< double > & dist, + CutLink& link ) { int i1 = ( dist[0] == 0 ? 0 : 1 ), i2 = ( dist[2] == 0 ? 2 : 1 ); CutLink link2 = link; @@ -978,11 +993,11 @@ namespace */ //================================================================================ - void Intersector::intersectLink( const std::vector< SMESH_NodeXYZ >& nodes1, - const std::vector< double > & dist1, - const int iEdge1, - const SMDS_MeshElement* face2, - CutLink& link1) + void Intersector::Algo::intersectLink( const std::vector< SMESH_NodeXYZ >& nodes1, + const std::vector< double > & dist1, + const int iEdge1, + const SMDS_MeshElement* face2, + CutLink& link1) { const int iEdge2 = ( iEdge1 + 1 ) % nodes1.size(); const SMESH_NodeXYZ& p1 = nodes1[ iEdge1 ]; @@ -1028,15 +1043,15 @@ namespace */ //================================================================================ - void Intersector::replaceIntNode( const SMDS_MeshNode* nToKeep, - const SMDS_MeshNode* nToRemove ) + void Intersector::Algo::replaceIntNode( const SMDS_MeshNode* nToKeep, + const SMDS_MeshNode* nToRemove ) { if ( nToKeep == nToRemove ) return; if ( nToRemove->GetID() < nToKeep->GetID() ) // keep node with lower ID - myRemove2KeepNodes.Bind((void*) nToKeep, nToRemove ); + myRemove2KeepNodes.Bind( nToKeep, nToRemove ); else - myRemove2KeepNodes.Bind((void*) nToRemove, nToKeep ); + myRemove2KeepNodes.Bind( nToRemove, nToKeep ); } //================================================================================ @@ -1053,13 +1068,13 @@ namespace */ //================================================================================ - void Intersector::computeIntPoint( const double u1, - const double u2, - const int iE1, - const int iE2, - CutLink & link, - const SMDS_MeshNode* & node1, - const SMDS_MeshNode* & node2) + void Intersector::Algo::computeIntPoint( const double u1, + const double u2, + const int iE1, + const int iE2, + CutLink & link, + const SMDS_MeshNode* & node1, + const SMDS_MeshNode* & node2) { if ( u1 > u2 + myTol ) { @@ -1100,11 +1115,11 @@ namespace */ //================================================================================ - void Intersector::cutCollinearLink( const int iNotOnPlane1, - const std::vector< SMESH_NodeXYZ >& nodes1, - const SMDS_MeshElement* face2, - const CutLink& link1, - const CutLink& link2) + void Intersector::Algo::cutCollinearLink( const int iNotOnPlane1, + const std::vector< SMESH_NodeXYZ >& nodes1, + const SMDS_MeshElement* face2, + const CutLink& link1, + const CutLink& link2) { int iN1 = ( iNotOnPlane1 + 1 ) % 3; @@ -1128,7 +1143,7 @@ namespace */ //================================================================================ - void Intersector::setPlaneIndices( const gp_XYZ& planeNorm ) + void Intersector::Algo::setPlaneIndices( const gp_XYZ& planeNorm ) { switch ( MaxIndex( planeNorm )) { case 1: myInd1 = 2; myInd2 = 3; break; @@ -1143,9 +1158,9 @@ namespace */ //================================================================================ - void Intersector::Cut( const SMDS_MeshElement* face1, - const SMDS_MeshElement* face2, - const int nbCommonNodes) + void Intersector::Algo::Cut( const SMDS_MeshElement* face1, + const SMDS_MeshElement* face2, + const int nbCommonNodes) { myFace1 = face1; myFace2 = face2; @@ -1241,16 +1256,83 @@ namespace return; } + //================================================================================ + /*! + * \brief Store a face cut by a line given by its ends + * accompanied by indices of intersected face edges. + * Edge index is <0 if a line end is inside the face. + * \param [in] face - a face to cut + * \param [inout] lineEnd1 - line end coordinates + optional node existing at this point + * \param [in] edgeIndex1 - index of face edge cut by lineEnd1 + * \param [inout] lineEnd2 - line end coordinates + optional node existing at this point + * \param [in] edgeIndex2 - index of face edge cut by lineEnd2 + */ + //================================================================================ + + void Intersector::Algo::Cut( const SMDS_MeshElement* face, + SMESH_NodeXYZ& lineEnd1, + int edgeIndex1, + SMESH_NodeXYZ& lineEnd2, + int edgeIndex2 ) + { + if ( lineEnd1.Node() && lineEnd2.Node() && + face->GetNodeIndex( lineEnd1.Node() ) >= 0 && + face->GetNodeIndex( lineEnd2.Node() ) >= 0 ) + return; // intersection at a face node or edge + + if ((int) myNormals.size() <= face->GetID() ) + const_cast< std::vector< gp_XYZ >& >( myNormals ).resize( face->GetID() + 1 ); + + const CutFace& cf = myCutFaces.Added( CutFace( face )); + cf.InitLinks(); + + // look for intersection nodes coincident with line ends + CutLink links[2]; + for ( int is2nd = 0; is2nd < 2; ++is2nd ) + { + SMESH_NodeXYZ& lineEnd = is2nd ? lineEnd2 : lineEnd1; + int edgeIndex = is2nd ? edgeIndex2 : edgeIndex1; + CutLink & link = links[ is2nd ]; + + link.myIntNode = lineEnd; + + for ( size_t i = ( edgeIndex < 0 ? 3 : 0 ); i < cf.myLinks.size(); ++i ) + if ( coincide( lineEnd, SMESH_NodeXYZ( cf.myLinks[i].myNode1 ), myTol )) + { + link.myIntNode = cf.myLinks[i].myNode1; + break; + } + + if ( edgeIndex >= 0 ) + { + link.Set( face->GetNode ( edgeIndex ), + face->GetNodeWrap( edgeIndex + 1 ), + /*cuttingFace=*/0); + findLink( link ); + } + + if ( !link.myIntNode ) + link.myIntNode.Set( createNode( lineEnd )); + + lineEnd._node = link.IntNode(); + + if ( link.myNode[0] ) + addLink( link ); + } + + cf.AddEdge( links[0], links[1], /*face=*/0, /*nbOnPlane=*/0, /*iNotOnPlane=*/-1 ); + } + //================================================================================ /*! * \brief Intersect two 2D line segments */ //================================================================================ - bool Intersector::intersectEdgeEdge( const gp_XY s1p0, const gp_XY s1p1, - const gp_XY s2p0, const gp_XY s2p1, - double & t1, double & t2, - bool & isCollinear ) + bool Intersector::Algo::intersectEdgeEdge( const gp_XY s1p0, const gp_XY s1p1, + const gp_XY s2p0, const gp_XY s2p1, + double & t1, double & t2, + bool & isCollinear ) { gp_XY u = s1p1 - s1p0; gp_XY v = s2p1 - s2p0; @@ -1306,7 +1388,7 @@ namespace */ //================================================================================ - bool Intersector::intersectEdgeEdge( int iE1, int iE2, IntPoint2D& intPoint ) + bool Intersector::Algo::intersectEdgeEdge( int iE1, int iE2, IntPoint2D& intPoint ) { int i01 = iE1, i11 = ( iE1 + 1 ) % 3; int i02 = iE2, i12 = ( iE2 + 1 ) % 3; @@ -1422,7 +1504,7 @@ namespace */ //================================================================================ - bool Intersector::isPointInTriangle( const gp_XYZ& p, const std::vector< SMESH_NodeXYZ >& nodes ) + bool Intersector::Algo::isPointInTriangle( const gp_XYZ& p, const std::vector< SMESH_NodeXYZ >& nodes ) { double bc1, bc2; SMESH_MeshAlgos::GetBarycentricCoords( p2D( p ), @@ -1437,7 +1519,7 @@ namespace */ //================================================================================ - void Intersector::cutCoplanar() + void Intersector::Algo::cutCoplanar() { // find intersections of edges @@ -1554,7 +1636,7 @@ namespace } return; - } // Intersector::cutCoplanar() + } // Intersector::Algo::cutCoplanar() //================================================================================ /*! @@ -1562,13 +1644,19 @@ namespace */ //================================================================================ - void Intersector::intersectNewEdges( const CutFace& cf ) + void Intersector::Algo::intersectNewEdges( const CutFace& cf ) { IntPoint2D intPoint; if ( cf.NbInternalEdges() < 2 ) return; + if ( myNodes1.empty() ) + { + myNodes1.resize(2); + myNodes2.resize(2); + } + const gp_XYZ& faceNorm = myNormals[ cf.myInitFace->GetID() ]; setPlaneIndices( faceNorm ); // choose indices on an axis-aligned plane @@ -1753,7 +1841,7 @@ namespace } } if ( cf.myLinks.size() >= limit ) - throw SALOME_Exception( "Infinite loop in Intersector::intersectNewEdges()" ); + throw SALOME_Exception( "Infinite loop in Intersector::Algo::intersectNewEdges()" ); } ++i1; // each internal edge encounters twice } @@ -1766,10 +1854,23 @@ namespace */ //================================================================================ - void Intersector::MakeNewFaces( SMESH_MeshAlgos::TEPairVec& theNew2OldFaces, - SMESH_MeshAlgos::TNPairVec& theNew2OldNodes, - const double theSign) + void Intersector::Algo::MakeNewFaces( SMESH_MeshAlgos::TElemIntPairVec& theNew2OldFaces, + SMESH_MeshAlgos::TNodeIntPairVec& theNew2OldNodes, + const double theSign, + const bool theOptimize) { + // fill theNew2OldFaces if empty + TCutFaceMap::const_iterator cutFacesIt = myCutFaces.cbegin(); + if ( theNew2OldFaces.empty() ) + for ( ; cutFacesIt != myCutFaces.cend(); ++cutFacesIt ) + { + const CutFace& cf = *cutFacesIt; + int index = cf.myInitFace->GetID(); // index in theNew2OldFaces + if ((int) theNew2OldFaces.size() <= index ) + theNew2OldFaces.resize( index + 1 ); + theNew2OldFaces[ index ] = std::make_pair( cf.myInitFace, index ); + } + // unmark all nodes except intersection ones for ( SMDS_NodeIteratorPtr nIt = myMesh->nodesIterator(); nIt->more(); ) @@ -1790,8 +1891,7 @@ namespace // intersect edges added to myCutFaces - TCutFaceMap::const_iterator cutFacesIt = myCutFaces.cbegin(); - for ( ; cutFacesIt != myCutFaces.cend(); ++cutFacesIt ) + for ( cutFacesIt = myCutFaces.cbegin(); cutFacesIt != myCutFaces.cend(); ++cutFacesIt ) { const CutFace& cf = *cutFacesIt; cf.ReplaceNodes( myRemove2KeepNodes ); @@ -1801,11 +1901,11 @@ namespace // make new faces EdgeLoopSet loopSet; - SMESH_MeshAlgos::Triangulate triangulator; + SMESH_MeshAlgos::Triangulate triangulator( theOptimize ); std::vector< EdgePart > cutOffLinks; TLinkMap cutOffCoplanarLinks; std::vector< const CutFace* > touchedFaces; - SMESH_MeshAlgos::TEPairVec::value_type new2OldTria; + SMESH_MeshAlgos::TElemIntPairVec::value_type new2OldTria; CutFace cutFace(0); std::vector< const SMDS_MeshNode* > nodes; std::vector faces; @@ -1877,13 +1977,14 @@ namespace // remove split faces for ( size_t id = 1; id < theNew2OldFaces.size(); ++id ) { - if ( theNew2OldFaces[id].first ) + if ( theNew2OldFaces[id].first || + theNew2OldFaces[id].second == 0 ) continue; if ( const SMDS_MeshElement* f = myMesh->FindElement( id )) myMesh->RemoveFreeElement( f ); } - // remove face connected to cut off parts of cf.myInitFace + // remove faces connected to cut off parts of cf.myInitFace nodes.resize(2); for ( size_t i = 0; i < cutOffLinks.size(); ++i ) @@ -1954,8 +2055,8 @@ namespace // add used new nodes to theNew2OldNodes - SMESH_MeshAlgos::TNPairVec::value_type new2OldNode; - new2OldNode.second = NULL; + SMESH_MeshAlgos::TNodeIntPairVec::value_type new2OldNode; + new2OldNode.second = 0; for ( cutLinksIt = myCutLinks.cbegin(); cutLinksIt != myCutLinks.cend(); ++cutLinksIt ) { const CutLink& link = *cutLinksIt; @@ -1969,6 +2070,185 @@ namespace return; } + //================================================================================ + Intersector::Intersector( SMDS_Mesh* mesh, double tol, const std::vector< gp_XYZ >& normals ) + { + myAlgo = new Algo( mesh, tol, normals ); + } + //================================================================================ + Intersector::~Intersector() + { + delete myAlgo; + } + //================================================================================ + //! compute cut of two faces of the mesh + void Intersector::Cut( const SMDS_MeshElement* face1, + const SMDS_MeshElement* face2, + const int nbCommonNodes ) + { + myAlgo->Cut( face1, face2, nbCommonNodes ); + } + //================================================================================ + //! store a face cut by a line given by its ends + // accompanied by indices of intersected face edges. + // Edge index is <0 if a line end is inside the face. + void Intersector::Cut( const SMDS_MeshElement* face, + SMESH_NodeXYZ& lineEnd1, + int edgeIndex1, + SMESH_NodeXYZ& lineEnd2, + int edgeIndex2 ) + { + myAlgo->Cut( face, lineEnd1, edgeIndex1, lineEnd2, edgeIndex2 ); + } + //================================================================================ + //! split all face intersected by Cut() methods + void Intersector::MakeNewFaces( SMESH_MeshAlgos::TElemIntPairVec& theNew2OldFaces, + SMESH_MeshAlgos::TNodeIntPairVec& theNew2OldNodes, + const double theSign, + const bool theOptimize ) + { + myAlgo->MakeNewFaces( theNew2OldFaces, theNew2OldNodes, theSign, theOptimize ); + } + //================================================================================ + //! Cut a face by planes, whose normals point to parts to keep + bool Intersector::CutByPlanes(const SMDS_MeshElement* theFace, + const std::vector< gp_Ax1 > & thePlanes, + const double theTol, + std::vector< TFace > & theNewFaceConnectivity ) + { + theNewFaceConnectivity.clear(); + + // check if theFace is wholly cut off + std::vector< SMESH_NodeXYZ > facePoints( theFace->begin_nodes(), theFace->end_nodes() ); + facePoints.resize( theFace->NbCornerNodes() ); + for ( size_t iP = 0; iP < thePlanes.size(); ++iP ) + { + size_t nbOut = 0; + const gp_Pnt& O = thePlanes[iP].Location(); + for ( size_t i = 0; i < facePoints.size(); ++i ) + { + gp_Vec Op( O, facePoints[i] ); + nbOut += ( Op * thePlanes[iP].Direction() <= 0 ); + } + if ( nbOut == facePoints.size() ) + return true; + } + + // copy theFace into a temporary mesh + SMDS_Mesh mesh; + Bnd_B3d faceBox; + std::vector< const SMDS_MeshNode* > faceNodes; + faceNodes.resize( facePoints.size() ); + for ( size_t i = 0; i < facePoints.size(); ++i ) + { + const SMESH_NodeXYZ& n = facePoints[i]; + faceNodes[i] = mesh.AddNode( n.X(), n.Y(), n.Z() ); + faceBox.Add( n ); + } + const SMDS_MeshElement* faceToCut = 0; + switch ( theFace->NbCornerNodes() ) + { + case 3: + faceToCut = mesh.AddFace( faceNodes[0], faceNodes[1], faceNodes[2] ); + break; + case 4: + faceToCut = mesh.AddFace( faceNodes[0], faceNodes[1], faceNodes[2], faceNodes[3] ); + break; + default: + faceToCut = mesh.AddPolygonalFace( faceNodes ); + } + + std::vector< gp_XYZ > normals( 2 + thePlanes.size() ); + SMESH_MeshAlgos::FaceNormal( faceToCut, normals[ faceToCut->GetID() ]); + + // add faces corresponding to thePlanes + std::vector< const SMDS_MeshElement* > planeFaces; + double faceSize = Sqrt( faceBox.SquareExtent() ); + gp_XYZ center = 0.5 * ( faceBox.CornerMin() + faceBox.CornerMax() ); + for ( size_t i = 0; i < thePlanes.size(); ++i ) + { + gp_Ax2 plnAx( thePlanes[i].Location(), thePlanes[i].Direction() ); + gp_XYZ O = plnAx.Location().XYZ(); + gp_XYZ X = plnAx.XDirection().XYZ(); + gp_XYZ Y = plnAx.YDirection().XYZ(); + gp_XYZ Z = plnAx.Direction().XYZ(); + + double dot = ( O - center ) * Z; + gp_XYZ o = center + Z * dot; // center projected to a plane + + gp_XYZ p1 = o + X * faceSize * 2; + gp_XYZ p2 = o + Y * faceSize * 2; + gp_XYZ p3 = o - (X + Y ) * faceSize * 2; + + const SMDS_MeshNode* n1 = mesh.AddNode( p1.X(), p1.Y(), p1.Z() ); + const SMDS_MeshNode* n2 = mesh.AddNode( p2.X(), p2.Y(), p2.Z() ); + const SMDS_MeshNode* n3 = mesh.AddNode( p3.X(), p3.Y(), p3.Z() ); + planeFaces.push_back( mesh.AddFace( n1, n2, n3 )); + + normals[ planeFaces.back()->GetID() ] = thePlanes[i].Direction().XYZ(); + } + + // cut theFace + Algo algo ( &mesh, theTol, normals ); + for ( size_t i = 0; i < planeFaces.size(); ++i ) + { + algo.Cut( faceToCut, planeFaces[i], 0 ); + } + + // retrieve a result + SMESH_MeshAlgos::TElemIntPairVec new2OldFaces; + SMESH_MeshAlgos::TNodeIntPairVec new2OldNodes; + TCutFaceMap::const_iterator cutFacesIt= algo.myCutFaces.cbegin(); + for ( ; cutFacesIt != algo.myCutFaces.cend(); ++cutFacesIt ) + { + const CutFace& cf = *cutFacesIt; + if ( cf.myInitFace != faceToCut ) + continue; + + if ( !cf.IsCut() ) + { + theNewFaceConnectivity.push_back( facePoints ); + break; + } + // form loops of new faces + EdgeLoopSet loopSet; + cf.MakeLoops( loopSet, normals[ faceToCut->GetID() ]); + + // erase loops that are cut off by thePlanes + const double sign = 1; + std::vector< EdgePart > cutOffLinks; + TLinkMap cutOffCoplanarLinks; + cf.CutOffLoops( loopSet, sign, normals, cutOffLinks, cutOffCoplanarLinks ); + + for ( size_t iL = 0; iL < loopSet.myNbLoops; ++iL ) + { + EdgeLoop& loop = loopSet.myLoops[ iL ]; + if ( loop.myLinks.size() > 0 ) + { + facePoints.clear(); + for ( SMDS_NodeIteratorPtr nIt = loop.nodeIterator(); nIt->more(); ) + { + const SMDS_MeshNode* n = nIt->next(); + facePoints.push_back( n ); + int iN = faceToCut->GetNodeIndex( n ); + if ( iN < 0 ) + facePoints.back()._node = 0; // an intersection point + else + facePoints.back()._node = theFace->GetNode( iN ); + } + theNewFaceConnectivity.push_back( facePoints ); + } + } + break; + } + + return theNewFaceConnectivity.empty(); + } + +} // namespace SMESH_MeshAlgos + +namespace +{ //================================================================================ /*! * \brief Debug @@ -2190,11 +2470,11 @@ namespace bool replaced = false; for ( size_t i = 0; i < myLinks.size(); ++i ) { - while ( theRm2KeepMap.IsBound((Standard_Address) myLinks[i].myNode1 )) - replaced = ( myLinks[i].myNode1 = theRm2KeepMap((Standard_Address) myLinks[i].myNode1 )); + while ( theRm2KeepMap.IsBound( myLinks[i].myNode1 )) + replaced = ( myLinks[i].myNode1 = theRm2KeepMap( myLinks[i].myNode1 )); - while ( theRm2KeepMap.IsBound((Standard_Address) myLinks[i].myNode2 )) - replaced = ( myLinks[i].myNode2 = theRm2KeepMap((Standard_Address) myLinks[i].myNode2 )); + while ( theRm2KeepMap.IsBound( myLinks[i].myNode2 )) + replaced = ( myLinks[i].myNode2 = theRm2KeepMap( myLinks[i].myNode2 )); } //if ( replaced ) // remove equal links @@ -2615,7 +2895,7 @@ namespace //================================================================================ /*! - * \brief Replace _COPLANAR cut edge by _INTERNAL oe vice versa + * \brief Replace _COPLANAR cut edge by _INTERNAL or vice versa */ //================================================================================ @@ -2643,8 +2923,8 @@ namespace /*! * \brief Create an offsetMesh of given faces * \param [in] faceIt - the input faces - * \param [out] new2OldFaces - history of faces - * \param [out] new2OldNodes - history of nodes + * \param [out] new2OldFaces - history of faces (new face -> old face ID) + * \param [out] new2OldNodes - history of nodes (new node -> old node ID) * \return SMDS_Mesh* - the new offset mesh, a caller should delete */ //================================================================================ @@ -2653,8 +2933,8 @@ SMDS_Mesh* SMESH_MeshAlgos::MakeOffset( SMDS_ElemIteratorPtr theFaceIt, SMDS_Mesh& theSrcMesh, const double theOffset, const bool theFixIntersections, - TEPairVec& theNew2OldFaces, - TNPairVec& theNew2OldNodes) + TElemIntPairVec& theNew2OldFaces, + TNodeIntPairVec& theNew2OldNodes) { if ( theSrcMesh.GetMeshInfo().NbFaces( ORDER_QUADRATIC ) > 0 ) throw SALOME_Exception( "Offset of quadratic mesh not supported" ); @@ -2665,8 +2945,7 @@ SMDS_Mesh* SMESH_MeshAlgos::MakeOffset( SMDS_ElemIteratorPtr theFaceIt, theNew2OldFaces.clear(); theNew2OldNodes.clear(); theNew2OldFaces.push_back - ( std::make_pair(( const SMDS_MeshElement*) 0, - ( const SMDS_MeshElement*) 0)); // to have index == face->GetID() + ( std::make_pair(( const SMDS_MeshElement*) 0, 0)); // to have index == face->GetID() // copy input faces to the newMesh keeping IDs of nodes @@ -2687,7 +2966,7 @@ SMDS_Mesh* SMESH_MeshAlgos::MakeOffset( SMDS_ElemIteratorPtr theFaceIt, { SMESH_NodeXYZ xyz( nodes[i] ); newNode = newMesh->AddNodeWithID( xyz.X(), xyz.Y(), xyz.Z(), nodes[i]->GetID() ); - theNew2OldNodes.push_back( std::make_pair( newNode, nodes[i] )); + theNew2OldNodes.push_back( std::make_pair( newNode, nodes[i]->GetID() )); nodes[i] = newNode; } } @@ -2725,7 +3004,7 @@ SMDS_Mesh* SMESH_MeshAlgos::MakeOffset( SMDS_ElemIteratorPtr theFaceIt, default: continue; } - theNew2OldFaces.push_back( std::make_pair( newFace, face )); + theNew2OldFaces.push_back( std::make_pair( newFace, face->GetID() )); SMESH_NodeXYZ pPrev = nodes.back(), p; for ( size_t i = 0; i < nodes.size(); ++i ) @@ -2743,7 +3022,7 @@ SMDS_Mesh* SMESH_MeshAlgos::MakeOffset( SMDS_ElemIteratorPtr theFaceIt, std::vector< gp_XYZ > normals( theNew2OldFaces.size() ); for ( size_t i = 1; i < normals.size(); ++i ) { - if ( !SMESH_MeshAlgos::FaceNormal( theNew2OldFaces[i].second, normals[i] )) + if ( !SMESH_MeshAlgos::FaceNormal( theNew2OldFaces[i].first, normals[i] )) normals[i].SetCoord( 0,0,0 ); // TODO find norm by neighbors } @@ -2792,7 +3071,7 @@ SMDS_Mesh* SMESH_MeshAlgos::MakeOffset( SMDS_ElemIteratorPtr theFaceIt, { newNode = newMesh->AddNode( newXYZ.X(), newXYZ.Y(), newXYZ.Z() ); newNode->setIsMarked( true ); - theNew2OldNodes.push_back( std::make_pair( newNode, theNew2OldNodes[i].second )); + theNew2OldNodes.push_back( std::make_pair( newNode, 0 )); multiPos.emplace_back( newNode ); } } diff --git a/src/SMESHUtils/SMESH_PolyLine.cxx b/src/SMESHUtils/SMESH_PolyLine.cxx new file mode 100644 index 000000000..4916e7c19 --- /dev/null +++ b/src/SMESHUtils/SMESH_PolyLine.cxx @@ -0,0 +1,595 @@ +// Copyright (C) 2018 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 : SMESH_PolyLine.cxx +// Created : Thu Dec 6 17:33:26 2018 +// Author : Edward AGAPOV (eap) + +#include "SMESH_MeshAlgos.hxx" + +#include "SMDS_MeshGroup.hxx" +#include "SMDS_LinearEdge.hxx" +#include "SMDS_Mesh.hxx" +#include "SMESH_TryCatch.hxx" + +#include +#include + +namespace +{ + //================================================================================ + /*! + * \brief Sequence of found points and a current point data + */ + struct Path + { + std::vector< gp_XYZ > myPoints; + double myLength; + + const SMDS_MeshElement* myFace; + SMESH_NodeXYZ myNode1; // nodes of the edge the path entered myFace + SMESH_NodeXYZ myNode2; + int myNodeInd1; + int myNodeInd2; + double myDot1; + double myDot2; + + int mySrcPntInd; //!< start point index + TIDSortedElemSet myElemSet, myAvoidSet; + + Path(): myLength(0.0), myFace(0) {} + + bool SetCutAtCorner( const SMESH_NodeXYZ& cornerNode, + const SMDS_MeshElement* face, + const gp_XYZ& plnNorm, + const gp_XYZ& plnOrig ); + + void AddPoint( const gp_XYZ& p ); + + bool Extend( const gp_XYZ& plnNorm, const gp_XYZ& plnOrig ); + + bool ReachSamePoint( const Path& other ); + + static void Remove( std::vector< Path > & paths, size_t& i ); + }; + + //================================================================================ + /*! + * \brief Return true if this Path meats another + */ + //================================================================================ + + bool Path::ReachSamePoint( const Path& other ) + { + return ( mySrcPntInd != other.mySrcPntInd && + myFace == other.myFace ); + } + + //================================================================================ + /*! + * \brief Remove a path from a vector + */ + //================================================================================ + + void Path::Remove( std::vector< Path > & paths, size_t& i ) + { + if ( paths.size() > 1 ) + { + size_t j = paths.size() - 1; // last item to be removed + if ( i < j ) + { + paths[ i ].myPoints.swap( paths[ j ].myPoints ); + paths[ i ].myLength = paths[ j ].myLength; + paths[ i ].mySrcPntInd = paths[ j ].mySrcPntInd; + paths[ i ].myFace = paths[ j ].myFace; + paths[ i ].myNode1 = paths[ j ].myNode1; + paths[ i ].myNode2 = paths[ j ].myNode2; + paths[ i ].myNodeInd1 = paths[ j ].myNodeInd1; + paths[ i ].myNodeInd2 = paths[ j ].myNodeInd2; + paths[ i ].myDot1 = paths[ j ].myDot1; + paths[ i ].myDot2 = paths[ j ].myDot2; + } + } + paths.pop_back(); + if ( i > 0 ) + --i; + } + + //================================================================================ + /*! + * \brief Store a point that is at a node of a face if the face is intersected by plane. + * Return false if the node is a sole intersection point of the face and the plane + */ + //================================================================================ + + bool Path::SetCutAtCorner( const SMESH_NodeXYZ& cornerNode, + const SMDS_MeshElement* face, + const gp_XYZ& plnNorm, + const gp_XYZ& plnOrig ) + { + if ( face == myFace ) + return false; + myNodeInd1 = face->GetNodeIndex( cornerNode._node ); + myNodeInd2 = ( myNodeInd1 + 1 ) % face->NbCornerNodes(); + int ind3 = ( myNodeInd1 + 2 ) % face->NbCornerNodes(); + myNode1.Set( face->GetNode( ind3 )); + myNode2.Set( face->GetNode( myNodeInd2 )); + + myDot1 = plnNorm * ( myNode1 - plnOrig ); + myDot2 = plnNorm * ( myNode2 - plnOrig ); + + bool ok = ( myDot1 * myDot2 < 0 ); + if ( !ok && myDot1 * myDot2 == 0 ) + { + ok = ( myDot1 != myDot2 ); + if ( ok && myFace ) + ok = ( myFace->GetNodeIndex(( myDot1 == 0 ? myNode1 : myNode2 )._node ) < 0 ); + } + if ( ok ) + { + myFace = face; + myDot1 = 0; + AddPoint( cornerNode ); + } + return ok; + } + + //================================================================================ + /*! + * \brief Store a point and update myLength + */ + //================================================================================ + + void Path::AddPoint( const gp_XYZ& p ) + { + if ( !myPoints.empty() ) + myLength += ( p - myPoints.back() ).Modulus(); + else + myLength = 0; + myPoints.push_back( p ); + } + + //================================================================================ + /*! + * \brief Try to find the next point + * \param [in] plnNorm - cutting plane normal + * \param [in] plnOrig - cutting plane origin + */ + //================================================================================ + + bool Path::Extend( const gp_XYZ& plnNorm, const gp_XYZ& plnOrig ) + { + int nodeInd3 = ( myNodeInd1 + 1 ) % myFace->NbCornerNodes(); + if ( myNodeInd2 == nodeInd3 ) + nodeInd3 = ( myNodeInd1 + 2 ) % myFace->NbCornerNodes(); + + SMESH_NodeXYZ node3 = myFace->GetNode( nodeInd3 ); + double dot3 = plnNorm * ( node3 - plnOrig ); + + if ( dot3 * myDot1 < 0. ) + { + myNode2 = node3; + myNodeInd2 = nodeInd3; + myDot2 = dot3; + } + else if ( dot3 * myDot2 < 0. ) + { + myNode1 = node3; + myNodeInd1 = nodeInd3; + myDot1 = dot3; + } + else if ( dot3 == 0. ) + { + SMDS_ElemIteratorPtr fIt = node3._node->GetInverseElementIterator(SMDSAbs_Face); + while ( fIt->more() ) + if ( SetCutAtCorner( node3, fIt->next(), plnNorm, plnOrig )) + return true; + return false; + } + else if ( myDot2 == 0. ) + { + SMESH_NodeXYZ node2 = myNode2; // copy as myNode2 changes in SetCutAtCorner() + SMDS_ElemIteratorPtr fIt = node2._node->GetInverseElementIterator(SMDSAbs_Face); + while ( fIt->more() ) + if ( SetCutAtCorner( node2, fIt->next(), plnNorm, plnOrig )) + return true; + return false; + } + + double r = Abs( myDot1 / ( myDot2 - myDot1 )); + AddPoint( myNode1 * ( 1 - r ) + myNode2 * r ); + + myAvoidSet.clear(); + myAvoidSet.insert( myFace ); + myFace = SMESH_MeshAlgos::FindFaceInSet( myNode1._node, myNode2._node, + myElemSet, myAvoidSet, + &myNodeInd1, &myNodeInd2 ); + return myFace; + } + + //================================================================================ + /*! + * \brief Compute a path between two points of PolySegment + */ + struct PolyPathCompute + { + SMESH_MeshAlgos::TListOfPolySegments& mySegments; //!< inout PolySegment's + std::vector< Path >& myPaths; //!< path of each of segments to compute + SMDS_Mesh* myMesh; + mutable std::vector< std::string > myErrors; + + PolyPathCompute( SMESH_MeshAlgos::TListOfPolySegments& theSegments, + std::vector< Path >& thePaths, + SMDS_Mesh* theMesh): + mySegments( theSegments ), + myPaths( thePaths ), + myMesh( theMesh ), + myErrors( theSegments.size() ) + { + } + +#undef SMESH_CAUGHT +#define SMESH_CAUGHT myErrors[i] = + void operator() ( const int i ) const + { + SMESH_TRY; + const_cast< PolyPathCompute* >( this )->Compute( i ); + SMESH_CATCH( SMESH::returnError ); + } +#undef SMESH_CAUGHT + + //================================================================================ + /*! + * \brief Compute a path of a given segment + */ + //================================================================================ + + void Compute( const int iSeg ) + { + SMESH_MeshAlgos::PolySegment& polySeg = mySegments[ iSeg ]; + + // the cutting plane + gp_XYZ plnNorm = ( polySeg.myXYZ[0] - polySeg.myXYZ[1] ) ^ polySeg.myVector.XYZ(); + gp_XYZ plnOrig = polySeg.myXYZ[1]; + + // Find paths connecting the 2 end points of polySeg + + std::vector< Path > paths; paths.reserve(10); + + // 1) initialize paths; two paths starts at each end point + + for ( int iP = 0; iP < 2; ++iP ) // loop on the polySeg end points + { + Path path; + path.mySrcPntInd = iP; + size_t nbPaths = paths.size(); + + if ( polySeg.myFace[ iP ]) // the end point lies on polySeg.myFace[ iP ] + { + // check coincidence of polySeg.myXYZ[ iP ] with nodes + const double tol = 1e-20; + SMESH_NodeXYZ nodes[4]; + for ( int i = 0; i < 3 && !polySeg.myNode1[ iP ]; ++i ) + { + nodes[ i ] = polySeg.myFace[ iP ]->GetNode( i ); + if (( nodes[ i ] - polySeg.myXYZ[ iP ]).SquareModulus() < tol*tol ) + polySeg.myNode1[ iP ] = nodes[ i ].Node(); + } + nodes[ 3 ] = nodes[ 0 ]; + + // check coincidence of polySeg.myXYZ[ iP ] with edges + for ( int i = 0; i < 3 && !polySeg.myNode1[ iP ]; ++i ) + { + SMDS_LinearEdge edge( nodes[i].Node(), nodes[i+1].Node() ); + if ( SMESH_MeshAlgos::GetDistance( &edge, polySeg.myXYZ[ iP ]) < tol ) + { + polySeg.myNode1[ iP ] = nodes[ i ].Node(); + polySeg.myNode2[ iP ] = nodes[ i + 1 ].Node(); + } + } + + if ( !polySeg.myNode1[ iP ] ) // polySeg.myXYZ[ iP ] is within polySeg.myFace[ iP ] + { + double dot[ 4 ]; + for ( int i = 0; i < 3; ++i ) + dot[ i ] = plnNorm * ( nodes[ i ] - plnOrig ); + dot[ 3 ] = dot[ 0 ]; + + int iCut = 0; // index of a cut edge + if ( dot[ 1 ] * dot[ 2 ] < 0. ) iCut = 1; + else if ( dot[ 2 ] * dot[ 3 ] < 0. ) iCut = 2; + + // initialize path so as if it entered the face via iCut-th edge + path.myFace = polySeg.myFace[ iP ]; + path.myNodeInd1 = iCut; + path.myNodeInd2 = iCut + 1; + path.myNode1.Set( nodes[ iCut ].Node() ); + path.myNode2.Set( nodes[ iCut + 1 ].Node() ); + path.myDot1 = dot[ iCut ]; + path.myDot2 = dot[ iCut + 1 ]; + path.myPoints.clear(); + path.AddPoint( polySeg.myXYZ[ iP ]); + paths.push_back( path ); + + path.Extend( plnNorm, plnOrig ); // to get another edge cut + path.myFace = polySeg.myFace[ iP ]; + if ( path.myDot1 == 0. ) // cut at a node + { + path.myNodeInd1 = ( iCut + 2 ) % 3; + path.myNodeInd2 = ( iCut + 3 ) % 3; + path.myNode2.Set( path.myFace->GetNode( path.myNodeInd2 )); + path.myDot2 = dot[ path.myNodeInd2 ]; + } + else + { + path.myNodeInd1 = path.myFace->GetNodeIndex( path.myNode1.Node() ); + path.myNodeInd2 = path.myFace->GetNodeIndex( path.myNode2.Node() ); + } + path.myPoints.clear(); + path.AddPoint( polySeg.myXYZ[ iP ]); + paths.push_back( path ); + } + } + + if ( polySeg.myNode2[ iP ] && polySeg.myNode2[ iP ] != polySeg.myNode1[ iP ] ) + { + // the end point is on an edge + while (( path.myFace = SMESH_MeshAlgos::FindFaceInSet( polySeg.myNode1[ iP ], + polySeg.myNode2[ iP ], + path.myElemSet, + path.myAvoidSet, + &path.myNodeInd1, + &path.myNodeInd2 ))) + { + path.myNode1.Set( polySeg.myNode1[ iP ]); + path.myNode2.Set( polySeg.myNode2[ iP ]); + path.myDot1 = plnNorm * ( path.myNode1 - plnOrig ); + path.myDot2 = plnNorm * ( path.myNode2 - plnOrig ); + path.myPoints.clear(); + path.AddPoint( polySeg.myXYZ[ iP ]); + path.myAvoidSet.insert( path.myFace ); + paths.push_back( path ); + } + if ( nbPaths == paths.size() ) + throw SALOME_Exception ( SMESH_Comment("No face edge found by point ") << iP+1 + << " in a PolySegment " << iSeg ); + } + else if ( polySeg.myNode1[ iP ] ) // the end point is at a node + { + std::set nodes; + SMDS_ElemIteratorPtr fIt = polySeg.myNode1[ iP ]->GetInverseElementIterator(SMDSAbs_Face); + while ( fIt->more() ) + { + path.myPoints.clear(); + if ( path.SetCutAtCorner( polySeg.myNode1[ iP ], fIt->next(), plnNorm, plnOrig )) + { + if (( path.myDot1 * path.myDot2 != 0 ) || + ( nodes.insert( path.myDot1 == 0 ? path.myNode1._node : path.myNode2._node ).second )) + paths.push_back( path ); + } + } + } + + // look for a one-segment path + for ( size_t i = 0; i < nbPaths; ++i ) + for ( size_t j = nbPaths; j < paths.size(); ++j ) + if ( paths[i].myFace == paths[j].myFace ) + { + myPaths[ iSeg ].myPoints.push_back( paths[i].myPoints[0] ); + myPaths[ iSeg ].myPoints.push_back( paths[j].myPoints[0] ); + paths.clear(); + } + } + + // 2) extend paths and compose the shortest one connecting the two points + + myPaths[ iSeg ].myLength = 1e100; + + while ( paths.size() >= 2 ) + { + for ( size_t i = 0; i < paths.size(); ++i ) + { + Path& path = paths[ i ]; + if ( !path.Extend( plnNorm, plnOrig ) || // path reached a mesh boundary + path.myLength > myPaths[ iSeg ].myLength ) // path is longer than others + { + Path::Remove( paths, i ); + continue; + } + + // join paths that reach same point + for ( size_t j = 0; j < paths.size(); ++j ) + { + if ( i != j && paths[i].ReachSamePoint( paths[j] )) + { + double distLast = ( paths[i].myPoints.back() - paths[j].myPoints.back() ).Modulus(); + double fullLength = ( paths[i].myLength + paths[j].myLength + distLast ); + if ( fullLength < myPaths[ iSeg ].myLength ) + { + myPaths[ iSeg ].myLength = fullLength; + std::vector< gp_XYZ > & allPoints = myPaths[ iSeg ].myPoints; + allPoints.swap( paths[i].myPoints ); + allPoints.insert( allPoints.end(), + paths[j].myPoints.rbegin(), + paths[j].myPoints.rend() ); + } + Path::Remove( paths, i ); + Path::Remove( paths, j ); + } + } + } + if ( !paths.empty() && (int) paths[0].myPoints.size() > myMesh->NbFaces() ) + throw SALOME_Exception(LOCALIZED( "Infinite loop in MakePolyLine()")); + } + + if ( myPaths[ iSeg ].myPoints.empty() ) + throw SALOME_Exception( SMESH_Comment("Can't find a full path for PolySegment #") << iSeg ); + + // reverse the path + double d00 = ( polySeg.myXYZ[0] - myPaths[ iSeg ].myPoints.front() ).SquareModulus(); + double d01 = ( polySeg.myXYZ[0] - myPaths[ iSeg ].myPoints.back() ).SquareModulus(); + if ( d00 > d01 ) + std::reverse( myPaths[ iSeg ].myPoints.begin(), myPaths[ iSeg ].myPoints.end() ); + + } // PolyPathCompute::Compute() + + }; // struct PolyPathCompute + +} // namespace + +//======================================================================= +//function : MakePolyLine +//purpose : Create a polyline consisting of 1D mesh elements each lying on a 2D element of +// the initial mesh +//======================================================================= + +void SMESH_MeshAlgos::MakePolyLine( SMDS_Mesh* theMesh, + TListOfPolySegments& theSegments, + std::vector& theNewEdges, + std::vector< const SMDS_MeshNode* >& theNewNodes, + SMDS_MeshGroup* theGroup, + SMESH_ElementSearcher* theSearcher) +{ + std::vector< Path > segPaths( theSegments.size() ); // path of each of segments + + SMESH_ElementSearcher* searcher = theSearcher; + SMESHUtils::Deleter delSearcher; + if ( !searcher ) + { + searcher = SMESH_MeshAlgos::GetElementSearcher( *theMesh ); + delSearcher._obj = searcher; + } + + // get cutting planes + + std::vector< bool > isVectorOK( theSegments.size(), true ); + const double planarCoef = 0.333; // plane height in planar case + + for ( size_t iSeg = 0; iSeg < theSegments.size(); ++iSeg ) + { + PolySegment& polySeg = theSegments[ iSeg ]; + + gp_XYZ p1 = SMESH_NodeXYZ( polySeg.myNode1[0] ); + gp_XYZ p2 = SMESH_NodeXYZ( polySeg.myNode1[1] ); + if ( polySeg.myNode2[0] ) p1 = 0.5 * ( p1 + SMESH_NodeXYZ( polySeg.myNode2[0] )); + if ( polySeg.myNode2[1] ) p2 = 0.5 * ( p2 + SMESH_NodeXYZ( polySeg.myNode2[1] )); + + polySeg.myFace[0] = polySeg.myFace[1] = 0; + if ( !polySeg.myNode1[0] && !polySeg.myNode2[0] ) + { + p1 = searcher->Project( polySeg.myXYZ[0], SMDSAbs_Face, &polySeg.myFace[0] ); + } + if ( !polySeg.myNode1[1] && !polySeg.myNode2[1] ) + { + p2 = searcher->Project( polySeg.myXYZ[1], SMDSAbs_Face, &polySeg.myFace[1] ); + } + polySeg.myXYZ[0] = p1; + polySeg.myXYZ[1] = p2; + + gp_XYZ plnNorm = ( p1 - p2 ) ^ polySeg.myVector.XYZ(); + + isVectorOK[ iSeg ] = ( plnNorm.Modulus() > std::numeric_limits::min() ); + if ( !isVectorOK[ iSeg ]) + { + gp_XYZ pMid = 0.5 * ( p1 + p2 ); + const SMDS_MeshElement* face; + polySeg.myMidProjPoint = searcher->Project( pMid, SMDSAbs_Face, &face ); + polySeg.myVector = polySeg.myMidProjPoint.XYZ() - pMid; + + gp_XYZ faceNorm; + SMESH_MeshAlgos::FaceNormal( face, faceNorm ); + + if ( polySeg.myVector.Magnitude() < Precision::Confusion() || + polySeg.myVector * faceNorm < Precision::Confusion() ) + { + polySeg.myVector = faceNorm; + polySeg.myMidProjPoint = pMid + faceNorm * ( p1 - p2 ).Modulus() * planarCoef; + } + } + else + { + polySeg.myVector = plnNorm ^ ( p1 - p2 ); + } + } + + // assure that inverse elements are constructed, avoid their concurrent building in threads + theMesh->nodesIterator()->next()->NbInverseElements(); + + // find paths + + PolyPathCompute algo( theSegments, segPaths, theMesh ); + OSD_Parallel::For( 0, theSegments.size(), algo, theSegments.size() == 1 ); + + for ( size_t iSeg = 0; iSeg < theSegments.size(); ++iSeg ) + if ( !algo.myErrors[ iSeg ].empty() ) + throw SALOME_Exception( algo.myErrors[ iSeg ].c_str() ); + + // create an 1D mesh + + const SMDS_MeshNode *n, *nPrev = 0; + + for ( size_t iSeg = 0; iSeg < theSegments.size(); ++iSeg ) + { + const Path& path = segPaths[iSeg]; + if ( path.myPoints.size() < 2 ) + continue; + + double tol = path.myLength / path.myPoints.size() / 1000.; + if ( !nPrev || ( SMESH_NodeXYZ( nPrev ) - path.myPoints[0] ).SquareModulus() > tol*tol ) + { + nPrev = theMesh->AddNode( path.myPoints[0].X(), path.myPoints[0].Y(), path.myPoints[0].Z() ); + theNewNodes.push_back( nPrev ); + } + for ( size_t iP = 1; iP < path.myPoints.size(); ++iP ) + { + n = theMesh->AddNode( path.myPoints[iP].X(), path.myPoints[iP].Y(), path.myPoints[iP].Z() ); + theNewNodes.push_back( n ); + + const SMDS_MeshElement* elem = theMesh->AddEdge( nPrev, n ); + theNewEdges.push_back( elem ); + if ( theGroup ) + theGroup->Add( elem ); + + nPrev = n; + } + + // return a vector + + gp_XYZ pMid = 0.5 * ( path.myPoints[0] + path.myPoints.back() ); + if ( isVectorOK[ iSeg ]) + { + // find the most distant point of a path + double maxDist = 0; + for ( size_t iP = 1; iP < path.myPoints.size(); ++iP ) + { + double dist = Abs( theSegments[iSeg].myVector * ( path.myPoints[iP] - path.myPoints[0] )); + if ( dist > maxDist ) + { + maxDist = dist; + theSegments[iSeg].myMidProjPoint = path.myPoints[iP]; + } + } + if ( maxDist < Precision::Confusion() ) // planar case + theSegments[iSeg].myMidProjPoint = + pMid + theSegments[iSeg].myVector.XYZ().Normalized() * path.myLength * planarCoef; + } + theSegments[iSeg].myVector = gp_Vec( pMid, theSegments[iSeg].myMidProjPoint ); + } + + return; +} diff --git a/src/SMESHUtils/SMESH_Slot.cxx b/src/SMESHUtils/SMESH_Slot.cxx new file mode 100644 index 000000000..0ceca4af8 --- /dev/null +++ b/src/SMESHUtils/SMESH_Slot.cxx @@ -0,0 +1,720 @@ +// Copyright (C) 2018 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 : SMESH_Slot.cxx +// Created : Fri Nov 30 15:58:37 2018 +// Author : Edward AGAPOV (eap) + +#include "SMESH_MeshAlgos.hxx" + +#include "ObjectPool.hxx" +#include "SMDS_LinearEdge.hxx" +#include "SMDS_Mesh.hxx" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace +{ + typedef SMESH_MeshAlgos::Edge TEdge; + + //================================================================================ + //! point of intersection of a face edge with the cylinder + struct IntPoint + { + SMESH_NodeXYZ myNode; // point and a node + int myEdgeIndex; // face edge index + bool myIsOutPln[2]; // isOut of two planes + }; + + //================================================================================ + //! poly-line segment + struct Segment + { + typedef boost::container::flat_set< const SMDS_MeshNode* > TNodeSet; + //typedef std::list< TEdge > TEdgeList; + + const SMDS_MeshElement* myEdge; + TNodeSet myEndNodes; // ends of cut edges + //TEdgeList myCutEdges[2]; + + + // return its axis + gp_Ax1 Ax1( bool reversed = false ) const + { + SMESH_NodeXYZ n1 = myEdge->GetNode( reversed ); + SMESH_NodeXYZ n2 = myEdge->GetNode( !reversed ); + return gp_Ax1( n1, gp_Dir( n2 - n1 )); + } + // return a node + const SMDS_MeshNode* Node(int i) const + { + return myEdge->GetNode( i % 2 ); + } + // store an intersection edge forming the slot border + void AddEdge( TEdge& e, double tol ) + { + const SMDS_MeshNode** nodes = & e._node1; + for ( int i = 0; i < 2; ++i ) + { + std::pair< TNodeSet::iterator, bool > nItAdded = myEndNodes.insert( nodes[ i ]); + if ( !nItAdded.second ) + myEndNodes.erase( nItAdded.first ); + } + } + // { -- PREV version + // int i = myCutEdges[0].empty() ? 0 : 1; + // std::insert_iterator< TEdgeList > where = inserter( myCutEdges[i], myCutEdges[i].begin() ); + + // //double minDist = 1e100; + // SMESH_NodeXYZ nNew[2] = { e._node1, e._node2 }; + // int iNewMin = 0, iCurMin = 1; + // for ( i = 0; i < 2; ++i ) + // { + // if ( myCutEdges[i].empty() ) + // continue; + // SMESH_NodeXYZ nCur[2] = { myCutEdges[i].front()._node1, + // myCutEdges[i].back()._node2 }; + // for ( int iN = 0; iN < 2; ++iN ) + // for ( int iC = 0; iC < 2; ++iC ) + // { + // if (( nCur[iC].Node() && nCur[iC] == nNew[iN] ) || + // ( nCur[iC] - nNew[iN] ).SquareModulus() < tol * tol ) + // { + // where = inserter( myCutEdges[i], iC ? myCutEdges[i].end() : myCutEdges[i].begin() ); + // iNewMin = iN; + // iCurMin = iC; + // //minDist = dist; + // iN = 2; + // break; + // } + // } + // } + // if ( iNewMin == iCurMin ) + // std::swap( e._node1, e._node2 ); + + // where = e; + // } + Segment( const SMDS_MeshElement* e = 0 ): myEdge(e) { myEndNodes.reserve( 4 ); } + }; + typedef ObjectPoolIterator TSegmentIterator; + + + //================================================================================ + /*! + * \brief Intersect a face edge given by its nodes with a cylinder. + */ + //================================================================================ + + void intersectEdge( const gp_Cylinder& cyl, + const SMESH_NodeXYZ& n1, + const SMESH_NodeXYZ& n2, + const double tol, + std::vector< IntPoint >& intPoints ) + { + gp_Lin line( gp_Ax1( n1, gp_Dir( n2 - n1 ))); + IntAna_IntConicQuad intersection( line, IntAna_Quadric( cyl )); + + if ( !intersection.IsDone() || + intersection.IsParallel() || + intersection.IsInQuadric() || + intersection.NbPoints() == 0 ) + return; + + gp_Vec edge( n1, n2 ); + + size_t oldNbPnts = intPoints.size(); + for ( int iP = 1; iP <= intersection.NbPoints(); ++iP ) + { + const gp_Pnt& p = intersection.Point( iP ); + + gp_Vec n1p ( n1, p ); + const SMDS_MeshNode* n = 0; + + double u = ( edge * n1p ) / edge.SquareMagnitude(); // param [0,1] on the edge + if ( u <= 0. ) { + if ( p.SquareDistance( n1 ) < tol * tol ) + n = n1.Node(); + else + continue; + } + else if ( u >= 1. ) { + if ( p.SquareDistance( n2 ) < tol * tol ) + n = n2.Node(); + else + continue; + } + else { + if ( p.SquareDistance( n1 ) < tol * tol ) + n = n1.Node(); + else if ( p.SquareDistance( n2 ) < tol * tol ) + n = n2.Node(); + } + + intPoints.push_back( IntPoint() ); + if ( n ) + intPoints.back().myNode.Set( n ); + else + intPoints.back().myNode.SetCoord( p.X(),p.Y(),p.Z() ); + } + + // set points order along an edge + if ( intPoints.size() - oldNbPnts == 2 && + intersection.ParamOnConic( 1 ) > intersection.ParamOnConic( 2 )) + { + int i = intPoints.size() - 1; + std::swap( intPoints[ i ], intPoints[ i - 1 ]); + } + + return; + } + + //================================================================================ + /*! + * \brief Return signed distance between a point and a plane + */ + //================================================================================ + + double signedDist( const gp_Pnt& p, const gp_Ax1& planeNormal ) + { + const gp_Pnt& O = planeNormal.Location(); + gp_Vec Op( O, p ); + return Op * planeNormal.Direction(); + } + + //================================================================================ + /*! + * \brief Check if a point is outside a segment domain bound by two planes + */ + //================================================================================ + + bool isOut( const gp_Pnt& p, const gp_Ax1* planeNormal, bool* isOutPtr ) + { + isOutPtr[0] = isOutPtr[1] = false; + + for ( int i = 0; i < 2; ++i ) + { + isOutPtr[i] = ( signedDist( p, planeNormal[i] ) <= 0. ); + } + return ( isOutPtr[0] && isOutPtr[1] ); + } + + //================================================================================ + /*! + * \brief Check if a segment between two points is outside a segment domain bound by two planes + */ + //================================================================================ + + bool isSegmentOut( bool* isOutPtr1, bool* isOutPtr2 ) + { + return (( isOutPtr1[0] && isOutPtr2[0] ) || + ( isOutPtr1[1] && isOutPtr2[1] )); + } + + //================================================================================ + /*! + * \brief cut off ip1 from edge (ip1 - ip2) by a plane + */ + //================================================================================ + + void cutOff( IntPoint & ip1, const IntPoint & ip2, const gp_Ax1& planeNormal, double tol ) + { + gp_Lin lin( ip1.myNode, ( ip2.myNode - ip1.myNode )); + gp_Pln pln( planeNormal.Location(), planeNormal.Direction() ); + + IntAna_IntConicQuad intersection( lin, pln, Precision::Angular/*Tolerance*/() ); + if ( intersection.IsDone() && + !intersection.IsParallel() && + !intersection.IsInQuadric() && + intersection.NbPoints() == 1 ) + { + if ( intersection.Point( 1 ).SquareDistance( ip1.myNode ) > tol * tol ) + { + static_cast< gp_XYZ& >( ip1.myNode ) = intersection.Point( 1 ).XYZ(); + ip1.myNode._node = 0; + ip1.myEdgeIndex = -1; + } + } + } + + //================================================================================ + /*! + * \brief Assure that face normal is computed in faceNormals vector + */ + //================================================================================ + + const gp_XYZ& computeNormal( const SMDS_MeshElement* face, + std::vector< gp_XYZ >& faceNormals ) + { + bool toCompute; + if ((int) faceNormals.size() <= face->GetID() ) + { + toCompute = true; + faceNormals.resize( face->GetID() + 1 ); + } + else + { + toCompute = faceNormals[ face->GetID() ].SquareModulus() == 0.; + } + if ( toCompute ) + SMESH_MeshAlgos::FaceNormal( face, faceNormals[ face->GetID() ], /*normalized=*/false ); + + return faceNormals[ face->GetID() ]; + } +} + +//================================================================================ +/*! + * \brief Create a slot of given width around given 1D elements lying on a triangle mesh. + * The slot is consrtucted by cutting faces by cylindrical surfaces made around each segment. + * \return Edges located at the slot boundary + */ +//================================================================================ + +std::vector< SMESH_MeshAlgos::Edge > +SMESH_MeshAlgos::MakeSlot( SMDS_ElemIteratorPtr theSegmentIt, + double theWidth, + SMDS_Mesh* theMesh) +{ + std::vector< Edge > bndEdges; + + if ( !theSegmentIt || !theSegmentIt->more() || !theMesh || theWidth == 0.) + return bndEdges; + + // put the input segments to a data map in order to be able finding neighboring ones + + typedef std::vector< Segment* > TSegmentVec; + typedef NCollection_DataMap< const SMDS_MeshNode*, TSegmentVec, SMESH_Hasher > TSegmentsOfNode; + TSegmentsOfNode segmentsOfNode; + ObjectPool< Segment > segmentPool; + + while( theSegmentIt->more() ) + { + const SMDS_MeshElement* edge = theSegmentIt->next(); + if ( edge->GetType() != SMDSAbs_Edge ) + throw SALOME_Exception( "A segment is not a mesh edge"); + + Segment* segment = segmentPool.getNew(); + segment->myEdge = edge; + + for ( SMDS_NodeIteratorPtr nIt = edge->nodeIterator(); nIt->more(); ) + { + const SMDS_MeshNode* n = nIt->next(); + TSegmentVec* segVec = segmentsOfNode.ChangeSeek( n ); + if ( !segVec ) + segVec = segmentsOfNode.Bound( n, TSegmentVec() ); + segVec->reserve(2); + segVec->push_back( segment ); + } + } + + // Cut the mesh around the segments + + const double tol = Precision::Confusion(); + std::vector< gp_XYZ > faceNormals; + SMESH_MeshAlgos::Intersector meshIntersector( theMesh, tol, faceNormals ); + std::unique_ptr< SMESH_ElementSearcher> faceSearcher; + + std::vector< NLink > startEdges; + std::vector< const SMDS_MeshNode* > faceNodes(4), edgeNodes(2); + std::vector faces(2); + NCollection_Map checkedFaces; + std::vector< IntPoint > intPoints, p(2); + std::vector< SMESH_NodeXYZ > facePoints(4); + std::vector< Intersector::TFace > cutFacePoints; + + std::vector< gp_Ax1 > planeNormalVec(2); + gp_Ax1 * planeNormal = & planeNormalVec[0]; + + for ( TSegmentIterator segIt( segmentPool ); segIt.more(); ) // loop on all segments + { + Segment* segment = const_cast< Segment* >( segIt.next() ); + + gp_Lin segLine( segment->Ax1() ); + gp_Ax3 cylAxis( segLine.Location(), segLine.Direction() ); + gp_Cylinder segCylinder( cylAxis, 0.5 * theWidth ); + double radius2( segCylinder.Radius() * segCylinder.Radius() ); + + // get normals of planes separating domains of neighboring segments + for ( int i = 0; i < 2; ++i ) // loop on 2 segment ends + { + planeNormal[i] = segment->Ax1(i); + + const SMDS_MeshNode* n = segment->Node( i ); + const TSegmentVec& segVec = segmentsOfNode( n ); + for ( size_t iS = 0; iS < segVec.size(); ++iS ) + { + if ( segVec[iS] == segment ) + continue; + + gp_Ax1 axis2 = segVec[iS]->Ax1(); + if ( n != segVec[iS]->Node( 1 )) + axis2.Reverse(); // along a wire + + planeNormal[i].SetDirection( planeNormal[i].Direction().XYZ() + axis2.Direction().XYZ() ); + } + } + + // we explore faces around a segment starting from face edges; + // initialize a list of starting edges + startEdges.clear(); + { + // get a face to start searching intersected faces from + const SMDS_MeshNode* n0 = segment->Node( 0 ); + SMDS_ElemIteratorPtr fIt = n0->GetInverseElementIterator( SMDSAbs_Face ); + const SMDS_MeshElement* face = ( fIt->more() ) ? fIt->next() : 0; + if ( !theMesh->Contains( face )) + { + if ( !faceSearcher ) + faceSearcher.reset( SMESH_MeshAlgos::GetElementSearcher( *theMesh )); + face = faceSearcher->FindClosestTo( SMESH_NodeXYZ( n0 ), SMDSAbs_Face ); + } + // collect face edges + int nbNodes = face->NbCornerNodes(); + faceNodes.assign( face->begin_nodes(), face->end_nodes() ); + faceNodes.resize( nbNodes + 1 ); + faceNodes[ nbNodes ] = faceNodes[ 0 ]; + for ( int i = 0; i < nbNodes; ++i ) + startEdges.push_back( NLink( faceNodes[i], faceNodes[i+1] )); + } + + // intersect faces located around a segment + checkedFaces.Clear(); + while ( !startEdges.empty() ) + { + edgeNodes[0] = startEdges[0].first; + edgeNodes[1] = startEdges[0].second; + + theMesh->GetElementsByNodes( edgeNodes, faces, SMDSAbs_Face ); + for ( size_t iF = 0; iF < faces.size(); ++iF ) // loop on faces sharing a start edge + { + const SMDS_MeshElement* face = faces[iF]; + if ( !checkedFaces.Add( face )) + continue; + + int nbNodes = face->NbCornerNodes(); + if ( nbNodes != 3 ) + throw SALOME_Exception( "MakeSlot() accepts triangles only" ); + facePoints.assign( face->begin_nodes(), face->end_nodes() ); + facePoints.resize( nbNodes + 1 ); + facePoints[ nbNodes ] = facePoints[ 0 ]; + + // check if cylinder axis || face + const gp_XYZ& faceNorm = computeNormal( face, faceNormals ); + bool isCylinderOnFace = ( Abs( faceNorm * cylAxis.Direction().XYZ() ) < tol ); + + if ( !isCylinderOnFace ) + { + if ( Intersector::CutByPlanes( face, planeNormalVec, tol, cutFacePoints )) + continue; // whole face cut off + facePoints.swap( cutFacePoints[0] ); + facePoints.push_back( facePoints[0] ); + } + + // find intersection points on face edges + intPoints.clear(); + int nbPoints = facePoints.size()-1; + int nbFarPoints = 0; + for ( int i = 0; i < nbPoints; ++i ) + { + const SMESH_NodeXYZ& n1 = facePoints[i]; + const SMESH_NodeXYZ& n2 = facePoints[i+1]; + + size_t iP = intPoints.size(); + intersectEdge( segCylinder, n1, n2, tol, intPoints ); + + // save edge index + if ( isCylinderOnFace ) + for ( ; iP < intPoints.size(); ++iP ) + intPoints[ iP ].myEdgeIndex = i; + else + for ( ; iP < intPoints.size(); ++iP ) + if ( n1.Node() && n2.Node() ) + intPoints[ iP ].myEdgeIndex = face->GetNodeIndex( n1.Node() ); + else + intPoints[ iP ].myEdgeIndex = -(i+1); + + nbFarPoints += ( segLine.SquareDistance( n1 ) > radius2 ); + } + + // feed startEdges + if ( nbFarPoints < nbPoints || !intPoints.empty() ) + for ( int i = 0; i < nbPoints; ++i ) + { + const SMESH_NodeXYZ& n1 = facePoints[i]; + const SMESH_NodeXYZ& n2 = facePoints[i+1]; + if ( n1.Node() && n2.Node() ) + { + isOut( n1, planeNormal, p[0].myIsOutPln ); + isOut( n2, planeNormal, p[1].myIsOutPln ); + if ( !isSegmentOut( p[0].myIsOutPln, p[1].myIsOutPln )) + { + startEdges.push_back( NLink( n1.Node(), n2.Node() )); + } + } + } + + if ( intPoints.size() < 2 ) + continue; + + // classify intPoints by planes + for ( size_t i = 0; i < intPoints.size(); ++i ) + isOut( intPoints[i].myNode, planeNormal, intPoints[i].myIsOutPln ); + + // cut the face + + if ( intPoints.size() > 2 ) + intPoints.push_back( intPoints[0] ); + + for ( size_t iE = 1; iE < intPoints.size(); ++iE ) // 2 <= intPoints.size() <= 5 + { + if (( intPoints[iE].myIsOutPln[0] && intPoints[iE].myIsOutPln[1] ) || + ( isSegmentOut( intPoints[iE].myIsOutPln, intPoints[iE-1].myIsOutPln ))) + continue; // intPoint is out of domain + + // check if a cutting edge connecting two intPoints is on cylinder surface + if ( intPoints[iE].myEdgeIndex == intPoints[iE-1].myEdgeIndex ) + continue; // on same edge + if ( intPoints[iE].myNode.Node() && + intPoints[iE].myNode == intPoints[iE-1].myNode ) // coincide + continue; + + gp_XYZ edegDir = intPoints[iE].myNode - intPoints[iE-1].myNode; + + bool toCut; // = edegDir.SquareModulus() > tol * tol; + if ( intPoints.size() == 2 ) + toCut = true; + else if ( isCylinderOnFace ) + toCut = cylAxis.Direction().IsParallel( edegDir, tol ); + else + { + SMESH_NodeXYZ nBetween; + int eInd = intPoints[iE-1].myEdgeIndex; + if ( eInd < 0 ) + nBetween = facePoints[( 1 - (eInd-1)) % nbPoints ]; + else + nBetween = faceNodes[( 1 + eInd ) % nbNodes ]; + toCut = ( segLine.SquareDistance( nBetween ) > radius2 ); + } + if ( !toCut ) + continue; + + // limit the edge by planes + if ( intPoints[iE].myIsOutPln[0] || + intPoints[iE].myIsOutPln[1] ) + cutOff( intPoints[iE], intPoints[iE-1], + planeNormal[ intPoints[iE].myIsOutPln[1] ], tol ); + + if ( intPoints[iE-1].myIsOutPln[0] || + intPoints[iE-1].myIsOutPln[1] ) + cutOff( intPoints[iE-1], intPoints[iE], + planeNormal[ intPoints[iE-1].myIsOutPln[1] ], tol ); + + edegDir = intPoints[iE].myNode - intPoints[iE-1].myNode; + if ( edegDir.SquareModulus() < tol * tol ) + continue; // fully cut off + + // face cut + meshIntersector.Cut( face, + intPoints[iE-1].myNode, intPoints[iE-1].myEdgeIndex, + intPoints[iE ].myNode, intPoints[iE ].myEdgeIndex ); + + Edge e = { intPoints[iE].myNode.Node(), intPoints[iE-1].myNode.Node(), 0 }; + segment->AddEdge( e, tol ); + bndEdges.push_back( e ); + } + } // loop on faces sharing an edge + + startEdges[0] = startEdges.back(); + startEdges.pop_back(); + + } // loop on startEdges + } // loop on all input segments + + + // Make cut at the end of group of segments + + std::vector polySegments; + + for ( TSegmentsOfNode::Iterator nSegsIt( segmentsOfNode ); nSegsIt.More(); nSegsIt.Next() ) + { + const TSegmentVec& segVec = nSegsIt.Value(); + if ( segVec.size() != 1 ) + continue; + + const Segment* segment = segVec[0]; + const SMDS_MeshNode* segNode = nSegsIt.Key(); + + // find two end nodes of cut edges to make a cut between + if ( segment->myEndNodes.size() != 4 ) + throw SALOME_Exception( "MakeSlot(): too short end edge?" ); + SMESH_MeshAlgos::PolySegment linkNodes; + gp_Ax1 planeNorm = segment->Ax1( segNode != segment->Node(0) ); + double minDist[2] = { 1e100, 1e100 }; + Segment::TNodeSet::const_iterator nIt = segment->myEndNodes.begin(); + for ( ; nIt != segment->myEndNodes.end(); ++nIt ) + { + SMESH_NodeXYZ n = *nIt; + double d = Abs( signedDist( n, planeNorm )); + double diff1 = minDist[0] - d, diff2 = minDist[1] - d; + int i; + if ( diff1 > 0 && diff2 > 0 ) + { + i = ( diff1 < diff2 ); + } + else if ( diff1 > 0 ) + { + i = 0; + } + else if ( diff2 > 0 ) + { + i = 1; + } + else + { + continue; + } + linkNodes.myXYZ[ i ] = n; + minDist [ i ] = d; + } + // for ( int iSide = 0; iSide < 2; ++iSide ) + // { + // if ( segment->myCutEdges[ iSide ].empty() ) + // throw SALOME_Exception( "MakeSlot(): too short end edge?" ); + // SMESH_NodeXYZ n1 = segment->myCutEdges[ iSide ].front()._node1; + // SMESH_NodeXYZ n2 = segment->myCutEdges[ iSide ].back ()._node2; + // double d1 = Abs( signedDist( n1, planeNorm )); + // double d2 = Abs( signedDist( n2, planeNorm )); + // linkNodes.myXYZ [ iSide ] = ( d1 < d2 ) ? n1 : n2; + // linkNodes.myNode1[ iSide ] = linkNodes.myNode2[ iSide ] = 0; + // } + linkNodes.myVector = planeNorm.Direction() ^ (linkNodes.myXYZ[0] - linkNodes.myXYZ[1]); + linkNodes.myNode1[ 0 ] = linkNodes.myNode2[ 0 ] = 0; + linkNodes.myNode1[ 1 ] = linkNodes.myNode2[ 1 ] = 0; + + // create segments connecting linkNodes + std::vector newSegments; + std::vector newNodes; + SMESH_MeshAlgos::TListOfPolySegments polySegs(1, linkNodes); + SMESH_MeshAlgos::MakePolyLine( theMesh, polySegs, newSegments, newNodes, + /*group=*/0, faceSearcher.get() ); + // cut faces by newSegments + intPoints.resize(2); + for ( size_t i = 0; i < newSegments.size(); ++i ) + { + intPoints[0].myNode = edgeNodes[0] = newSegments[i]->GetNode(0); + intPoints[1].myNode = edgeNodes[1] = newSegments[i]->GetNode(1); + + // find an underlying face + gp_XYZ middle = 0.5 * ( intPoints[0].myNode + intPoints[1].myNode ); + const SMDS_MeshElement* face = faceSearcher->FindClosestTo( middle, SMDSAbs_Face ); + + // find intersected edges of the face + int nbNodes = face->NbCornerNodes(); + faceNodes.assign( face->begin_nodes(), face->end_nodes() ); + faceNodes.resize( nbNodes + 1 ); + faceNodes[ nbNodes ] = faceNodes[ 0 ]; + for ( int iP = 0; iP < 2; ++iP ) + { + intPoints[iP].myEdgeIndex = -1; + for ( int iN = 0; iN < nbNodes && intPoints[iP].myEdgeIndex < 0; ++iN ) + { + SMDS_LinearEdge edge( faceNodes[iN], faceNodes[iN+1] ); + if ( SMESH_MeshAlgos::GetDistance( &edge, intPoints[iP].myNode) < tol ) + intPoints[iP].myEdgeIndex = iN; + } + } + + + // face cut + computeNormal( face, faceNormals ); + meshIntersector.Cut( face, + intPoints[0].myNode, intPoints[0].myEdgeIndex, + intPoints[1].myNode, intPoints[1].myEdgeIndex ); + + Edge e = { intPoints[0].myNode.Node(), intPoints[1].myNode.Node(), 0 }; + bndEdges.push_back( e ); + + // add cut points to an adjacent face at ends of poly-line + // if they fall onto face edges + if (( i == 0 && intPoints[0].myEdgeIndex >= 0 ) || + ( i == newSegments.size() - 1 && intPoints[1].myEdgeIndex >= 0 )) + { + for ( int iE = 0; iE < 2; ++iE ) // loop on ends of a new segment + { + if ( iE ? ( i != newSegments.size() - 1 ) : ( i != 0 )) + continue; + int iEdge = intPoints[ iE ].myEdgeIndex; + edgeNodes[0] = faceNodes[ iEdge ]; + edgeNodes[1] = faceNodes[ iEdge+1 ]; + theMesh->GetElementsByNodes( edgeNodes, faces, SMDSAbs_Face ); + for ( size_t iF = 0; iF < faces.size(); ++iF ) + if ( faces[iF] != face ) + { + int iN1 = faces[iF]->GetNodeIndex( edgeNodes[0] ); + int iN2 = faces[iF]->GetNodeIndex( edgeNodes[1] ); + intPoints[ iE ].myEdgeIndex = Abs( iN1 - iN2 ) == 1 ? Min( iN1, iN2 ) : 2; + computeNormal( faces[iF], faceNormals ); + meshIntersector.Cut( faces[iF], + intPoints[iE].myNode, intPoints[iE].myEdgeIndex, + intPoints[iE].myNode, intPoints[iE].myEdgeIndex ); + } + } + } + + } // loop on newSegments + + polySegments.insert( polySegments.end(), newSegments.begin(), newSegments.end() ); + + } // loop on map of input segments + + // actual mesh splitting + TElemIntPairVec new2OldFaces; + TNodeIntPairVec new2OldNodes; + meshIntersector.MakeNewFaces( new2OldFaces, new2OldNodes, /*sign=*/1, /*optimize=*/true ); + + // remove poly-line edges + for ( size_t i = 0; i < polySegments.size(); ++i ) + { + edgeNodes[0] = polySegments[i]->GetNode(0); + edgeNodes[1] = polySegments[i]->GetNode(1); + + theMesh->RemoveFreeElement( polySegments[i] ); + + if ( edgeNodes[0]->NbInverseElements() == 0 ) + theMesh->RemoveNode( edgeNodes[0] ); + if ( edgeNodes[1]->NbInverseElements() == 0 ) + theMesh->RemoveNode( edgeNodes[1] ); + } + + return bndEdges; +} diff --git a/src/SMESHUtils/SMESH_Triangulate.cxx b/src/SMESHUtils/SMESH_Triangulate.cxx index 6652aeae1..b391ababe 100644 --- a/src/SMESHUtils/SMESH_Triangulate.cxx +++ b/src/SMESHUtils/SMESH_Triangulate.cxx @@ -31,8 +31,183 @@ #include #include +#include + using namespace SMESH_MeshAlgos; +namespace +{ + struct Node // node of a triangle + { + size_t _triaIndex; // triangle index == index of the 1st triangle node in triangulation array + size_t _nodeIndex; // node index within triangle [0-2] + + //! return node index within the node array + size_t Index() const { return _triaIndex + _nodeIndex; } + + //! return local 3-d index [0-2] + static size_t ThirdIndex( size_t i1, size_t i2 ) + { + size_t i3 = ( i2 + 1 ) % 3; + if ( i3 == i1 ) + i3 = ( i2 + 2 ) % 3; + return i3; + } + //! return 3-d node index within the node array + static size_t ThirdIndex( const Node& n1, const Node& n2 ) + { + return n1._triaIndex + ThirdIndex( n1._nodeIndex, n2._nodeIndex ); + } + bool operator<(const Node& other) const { return _triaIndex < other._triaIndex; } + }; + typedef boost::container::flat_set< Node > TNodeSet; + +} + +struct Triangulate::Optimizer +{ + std::vector< TNodeSet > _nodeUsage; // inclusions of a node in triangles + + //================================================================================ + /*! + * \brief Optimize triangles by edge swapping + * \param [inout] nodes - polygon triangulation, i.e. connectivity of all triangles to optimize + * \param [in] points - coordinates of nodes of the input polygon + * \param [in] nodeIndices - indices of triangulation nodes within the input polygon + */ + //================================================================================ + + void optimize( std::vector< const SMDS_MeshNode*>& nodes, + std::vector< PolyVertex > & points, + std::vector< size_t > & nodeIndices) + { + // for each node of the polygon, remember triangles using it + _nodeUsage.resize( points.size() ); + for ( size_t i = 0; i < points.size(); ++i ) // clear old data + { + _nodeUsage[ i ].clear(); + } + for ( size_t i = 0, iTria = 0; i < nodeIndices.size(); ++iTria ) + { + _nodeUsage[ nodeIndices[ i++ ]].insert({ iTria * 3, 0 }); + _nodeUsage[ nodeIndices[ i++ ]].insert({ iTria * 3, 1 }); + _nodeUsage[ nodeIndices[ i++ ]].insert({ iTria * 3, 2 }); + } + + // optimization + for ( size_t iTria = 0; iTria < nodeIndices.size(); iTria += 3 ) + { + double badness1 = computeBadness( nodeIndices[ iTria + 0 ], + nodeIndices[ iTria + 1 ], + nodeIndices[ iTria + 2 ], + points ); + for ( size_t i = 0; i < 3; ++i ) // loop on triangle edges to find a neighbor triangle + { + size_t i1 = iTria + i; // node index in nodeIndices + size_t i2 = iTria + ( i + 1 ) % 3; + size_t ind1 = nodeIndices[ i1 ]; // node index in points + size_t ind2 = nodeIndices[ i2 ]; + TNodeSet & usage1 = _nodeUsage[ ind1 ]; // triangles using a node + TNodeSet & usage2 = _nodeUsage[ ind2 ]; + if ( usage1.size() < 2 || + usage2.size() < 2 ) + continue; + + // look for another triangle using two nodes + TNodeSet::iterator usIt1 = usage1.begin(); + for ( ; usIt1 != usage1.end(); ++usIt1 ) + { + if ( usIt1->_triaIndex == iTria ) + continue; // current triangle + TNodeSet::iterator usIt2 = usage2.find( *usIt1 ); + if ( usIt2 == usage2.end() ) + continue; // no common _triaIndex in two usages + + size_t i3 = iTria + ( i + 2 ) % 3; + size_t i4 = Node::ThirdIndex( *usIt1, *usIt2 ); // 4th node of quadrangle + size_t ind3 = nodeIndices[ i3 ]; + size_t ind4 = nodeIndices[ i4 ]; + + double badness2 = computeBadness( ind2, ind1, ind4, points ); + double badness3 = computeBadness( ind1, ind4, ind3, points, /*checkArea=*/true ); + double badness4 = computeBadness( ind2, ind3, ind4, points, /*checkArea=*/true ); + + if ( Max( badness1, badness2 ) < Max( badness3, badness4 )) + continue; + + // swap edge by modifying nodeIndices + + nodeIndices[ i2 ] = ind4; + _nodeUsage[ ind2 ].erase ({ iTria, i2 - iTria }); + _nodeUsage[ ind4 ].insert({ iTria, i2 - iTria }); + + i1 = usIt1->Index(); + nodeIndices[ i1 ] = ind3; + _nodeUsage[ ind1 ].erase ( *usIt1 ); + _nodeUsage[ ind3 ].insert( *usIt1 ); + + --i; // to re-check a current edge + badness1 = badness3; + break; + } + } + } + + // update nodes by updated nodeIndices + for ( size_t i = 0; i < nodeIndices.size(); ++i ) + nodes[ i ] = points[ nodeIndices[ i ]]._nxyz.Node(); + + return; + } + + //================================================================================ + /*! + * \brief Return 1./area. Initially: max cos^2 of triangle angles + */ + //================================================================================ + + double computeBadness( size_t i1, size_t i2, size_t i3, + std::vector< PolyVertex > & points, + bool checkArea = false ) + { + //if ( checkArea ) + { + points[ i2 ]._prev = & points[ i1 ]; + points[ i2 ]._next = & points[ i3 ]; + double a = points[ i2 ].TriaArea(); + if ( a < 0 ) + return std::numeric_limits::max(); + return 1. / a; + + if ( points[ i2 ].TriaArea() < 0 ) + return 2; + } + const gp_XY & p1 = points[ i1 ]._xy; + const gp_XY & p2 = points[ i2 ]._xy; + const gp_XY & p3 = points[ i3 ]._xy; + gp_XY vec[3] = { p2 - p1, + p3 - p2, + p1 - p3 }; + double len[3] = { vec[0].SquareModulus(), + vec[1].SquareModulus(), + vec[2].SquareModulus() }; + if ( len[0] < gp::Resolution() || + len[1] < gp::Resolution() || + len[2] < gp::Resolution() ) + return 2; + + double maxCos2 = 0; + for ( int i = 0; i < 3; ++i ) + { + int i2 = ( i+1 ) % 3; + double dot = -vec[ i ] * vec[ i2 ]; + if ( dot > 0 ) + maxCos2 = Max( maxCos2, dot * dot / len[ i ] / len[ i2 ] ); + } + return maxCos2; + } +}; + //================================================================================ /*! * \brief Initialization @@ -40,11 +215,13 @@ using namespace SMESH_MeshAlgos; //================================================================================ void Triangulate::PolyVertex::SetNodeAndNext( const SMDS_MeshNode* n, - PolyVertex& v ) + PolyVertex& v, + size_t index ) { _nxyz.Set( n ); _next = &v; v._prev = this; + _index = index; } //================================================================================ /*! @@ -65,11 +242,15 @@ Triangulate::PolyVertex* Triangulate::PolyVertex::Delete() */ //================================================================================ -void Triangulate::PolyVertex::GetTriaNodes( const SMDS_MeshNode** nodes) const + void Triangulate::PolyVertex::GetTriaNodes( const SMDS_MeshNode** nodes, + size_t* nodeIndices) const { nodes[0] = _prev->_nxyz._node; nodes[1] = this->_nxyz._node; nodes[2] = _next->_nxyz._node; + nodeIndices[0] = _prev->_index; + nodeIndices[1] = this->_index; + nodeIndices[2] = _next->_index; } //================================================================================ @@ -112,7 +293,7 @@ bool Triangulate::PolyVertex::IsInsideTria( const PolyVertex* v ) gp_XY p = _prev->_xy - v->_xy; gp_XY t = this->_xy - v->_xy; gp_XY n = _next->_xy - v->_xy; - const double tol = -1e-12; + const double tol = -1e-7; return (( p ^ t ) >= tol && ( t ^ n ) >= tol && ( n ^ p ) >= tol ); @@ -128,13 +309,13 @@ bool Triangulate::PolyVertex::IsInsideTria( const PolyVertex* v ) //================================================================================ bool Triangulate::triangulate( std::vector< const SMDS_MeshNode*>& nodes, - const size_t nbNodes ) + const size_t nbNodes) { // connect nodes into a ring _pv.resize( nbNodes ); for ( size_t i = 1; i < nbNodes; ++i ) - _pv[i-1].SetNodeAndNext( nodes[i-1], _pv[i] ); - _pv[ nbNodes-1 ].SetNodeAndNext( nodes[ nbNodes-1 ], _pv[0] ); + _pv[i-1].SetNodeAndNext( nodes[i-1], _pv[i], i-1 ); + _pv[ nbNodes-1 ].SetNodeAndNext( nodes[ nbNodes-1 ], _pv[0], nbNodes-1 ); // get a polygon normal gp_XYZ normal(0,0,0), p0,v01,v02; @@ -163,7 +344,8 @@ bool Triangulate::triangulate( std::vector< const SMDS_MeshNode*>& nodes, // in a loop, find triangles with positive area and having no vertices inside int iN = 0, nbTria = nbNodes - 2; - nodes.reserve( nbTria * 3 ); + nodes.resize( nbTria * 3 ); + _nodeIndex.resize( nbTria * 3 ); const double minArea = 1e-6; PolyVertex* v = &_pv[0], *vi; int nbVertices = nbNodes, nbBadTria = 0, isGoodTria; @@ -182,13 +364,15 @@ bool Triangulate::triangulate( std::vector< const SMDS_MeshNode*>& nodes, } if ( isGoodTria ) { - v->GetTriaNodes( &nodes[ iN ] ); + v->GetTriaNodes( &nodes[ iN ], &_nodeIndex[ iN ] ); iN += 3; v = v->Delete(); if ( --nbVertices == 3 ) { // last triangle remains - v->GetTriaNodes( &nodes[ iN ] ); + v->GetTriaNodes( &nodes[ iN ], &_nodeIndex[ iN ] ); + if ( _optimizer ) + _optimizer->optimize( nodes, _pv, _nodeIndex ); return true; } nbBadTria = 0; @@ -207,13 +391,13 @@ bool Triangulate::triangulate( std::vector< const SMDS_MeshNode*>& nodes, isGoodTria = v->TriaArea() > minArea; if ( isGoodTria ) { - v->GetTriaNodes( &nodes[ iN ] ); + v->GetTriaNodes( &nodes[ iN ], &_nodeIndex[ iN ] ); iN += 3; v = v->Delete(); if ( --nbVertices == 3 ) { // last triangle remains - v->GetTriaNodes( &nodes[ iN ] ); + v->GetTriaNodes( &nodes[ iN ], &_nodeIndex[ iN ] ); return true; } nbBadTria = 0; @@ -228,7 +412,7 @@ bool Triangulate::triangulate( std::vector< const SMDS_MeshNode*>& nodes, // add all the rest triangles while ( nbVertices >= 3 ) { - v->GetTriaNodes( &nodes[ iN ] ); + v->GetTriaNodes( &nodes[ iN ], &_nodeIndex[ iN ] ); iN += 3; v = v->Delete(); --nbVertices; @@ -238,6 +422,30 @@ bool Triangulate::triangulate( std::vector< const SMDS_MeshNode*>& nodes, } // triangulate() +//================================================================================ +/*! + * \brief Constructor + */ +//================================================================================ + +Triangulate::Triangulate( bool optimize ): _optimizer(0) +{ + if ( optimize ) + _optimizer = new Optimizer; +} + +//================================================================================ +/*! + * \brief Destructor + */ +//================================================================================ + +Triangulate::~Triangulate() +{ + delete _optimizer; + _optimizer = 0; +} + //================================================================================ /*! * \brief Return nb triangles in a decomposed mesh face diff --git a/src/SMESHUtils/SMESH_Triangulate.hxx b/src/SMESHUtils/SMESH_Triangulate.hxx deleted file mode 100644 index 8734c4b7a..000000000 --- a/src/SMESHUtils/SMESH_Triangulate.hxx +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2007-2016 CEA/DEN, EDF R&D, OPEN CASCADE -// -// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, -// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS -// -// 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 : SMESH_Triangulate.hxx -// Created : Thu Jan 18 17:51:34 2018 -// Author : Edward AGAPOV (eap) - - -#ifndef __SMESH_Triangulate_HXX__ -#define __SMESH_Triangulate_HXX__ - -/*! - * \brief Divide a mesh face into triangles - */ -class SMESHUtils_EXPORT SMESH_Triangulate -{ - public: - - static int GetNbTriangles( const SMDS_MeshElement* face ); - - int GetTriangles( const SMDS_MeshElement* face, - std::vector< const SMDS_MeshNode*>& nodes); - - private: - - bool triangulate( std::vector< const SMDS_MeshNode*>& nodes, const size_t nbNodes ); - - struct PolyVertex; - std::vector< PolyVertex > _pv; -}; - - -#endif diff --git a/src/SMESHUtils/SMESH_TypeDefs.hxx b/src/SMESHUtils/SMESH_TypeDefs.hxx index 9fbc5b076..f523fdd53 100644 --- a/src/SMESHUtils/SMESH_TypeDefs.hxx +++ b/src/SMESHUtils/SMESH_TypeDefs.hxx @@ -185,6 +185,21 @@ struct SMESH_TNodeXYZ : public gp_XYZ }; typedef SMESH_TNodeXYZ SMESH_NodeXYZ; +// -------------------------------------------------------------------------------- +// SMESH_Hasher provide methods needed to put mesh data to NCollection maps + +struct SMESH_Hasher +{ + static Standard_Integer HashCode(const SMDS_MeshElement* e, const Standard_Integer upper) + { + return ::HashCode( e->GetID(), upper ); + } + static Standard_Boolean IsEqual( const SMDS_MeshElement* e1, const SMDS_MeshElement* e2 ) + { + return ( e1 == e2 ); + } +}; + //-------------------------------------------------- /*! * \brief Data of a node generated on FACE boundary diff --git a/src/SMESH_I/SMESH_Filter_i.cxx b/src/SMESH_I/SMESH_Filter_i.cxx index 435080b56..7a9b20bd3 100644 --- a/src/SMESH_I/SMESH_Filter_i.cxx +++ b/src/SMESH_I/SMESH_Filter_i.cxx @@ -2577,6 +2577,36 @@ GetElementsId( SMESH_Mesh_ptr theMesh ) return anArray._retn(); } +SMESH::long_array* +Filter_i:: +GetElementsIdFromParts( const ListOfIDSources& theParts ) +{ + SMESH::long_array_var array = new SMESH::long_array; + if ( theParts.length() > 0 && myPredicate ) + { + SMESH_Mesh_ptr mesh = theParts[0]->GetMesh(); + mesh->Load(); + const SMDS_Mesh* meshDS = MeshPtr2SMDSMesh( mesh ); + Controls::Filter::TIdSequence totalSequence; + for ( CORBA::ULong i = 0; i < theParts.length(); ++i ) + { + if ( SMESH::Filter_i* filter = SMESH::DownCast( theParts[i] )) + filter->SetMesh( mesh ); + SMDS_ElemIteratorPtr iter = SMESH_Mesh_i::GetElements( theParts[i], GetElementType() ); + if ( iter && meshDS ) + { + Controls::Filter::TIdSequence sequence; + Controls::Filter::GetElementsId( meshDS, myPredicate->GetPredicate(), sequence, iter ); + totalSequence.insert( totalSequence.end(), sequence.begin(), sequence.end() ); + } + } + array->length( totalSequence.size() ); + for ( size_t i = 0; i < totalSequence.size(); ++i ) + array[ i ] = totalSequence[ i ]; + } + return array._retn(); +} + //============================================================================= /*! * \brief Returns number of mesh elements per each \a EntityType diff --git a/src/SMESH_I/SMESH_Filter_i.hxx b/src/SMESH_I/SMESH_Filter_i.hxx index 3643bd743..9d976ef08 100644 --- a/src/SMESH_I/SMESH_Filter_i.hxx +++ b/src/SMESH_I/SMESH_Filter_i.hxx @@ -996,7 +996,11 @@ namespace SMESH GetElementsId( SMESH_Mesh_ptr ); virtual - ElementType + long_array* + GetElementsIdFromParts( const ListOfIDSources& theParts ); + + virtual + ElementType GetElementType(); virtual diff --git a/src/SMESH_I/SMESH_Gen_i.cxx b/src/SMESH_I/SMESH_Gen_i.cxx index 86f9daa3b..2e902f532 100644 --- a/src/SMESH_I/SMESH_Gen_i.cxx +++ b/src/SMESH_I/SMESH_Gen_i.cxx @@ -2431,14 +2431,16 @@ SMESH::SMESH_Mesh_ptr SMESH_Gen_i::Concatenate(const SMESH::ListOfIDSources& theMeshesArray, CORBA::Boolean theUniteIdenticalGroups, CORBA::Boolean theMergeNodesAndElements, - CORBA::Double theMergeTolerance) + CORBA::Double theMergeTolerance, + SMESH::SMESH_Mesh_ptr theMeshToAppendTo) throw ( SALOME::SALOME_Exception ) { return ConcatenateCommon(theMeshesArray, theUniteIdenticalGroups, theMergeNodesAndElements, theMergeTolerance, - false); + false, + theMeshToAppendTo); } //================================================================================ @@ -2454,14 +2456,16 @@ SMESH::SMESH_Mesh_ptr SMESH_Gen_i::ConcatenateWithGroups(const SMESH::ListOfIDSources& theMeshesArray, CORBA::Boolean theUniteIdenticalGroups, CORBA::Boolean theMergeNodesAndElements, - CORBA::Double theMergeTolerance) + CORBA::Double theMergeTolerance, + SMESH::SMESH_Mesh_ptr theMeshToAppendTo) throw ( SALOME::SALOME_Exception ) { return ConcatenateCommon(theMeshesArray, theUniteIdenticalGroups, theMergeNodesAndElements, theMergeTolerance, - true); + true, + theMeshToAppendTo); } //================================================================================ @@ -2477,16 +2481,22 @@ SMESH_Gen_i::ConcatenateCommon(const SMESH::ListOfIDSources& theMeshesArray, CORBA::Boolean theUniteIdenticalGroups, CORBA::Boolean theMergeNodesAndElements, CORBA::Double theMergeTolerance, - CORBA::Boolean theCommonGroups) + CORBA::Boolean theCommonGroups, + SMESH::SMESH_Mesh_ptr theMeshToAppendTo) throw ( SALOME::SALOME_Exception ) { std::unique_ptr< TPythonDump > pPythonDump( new TPythonDump ); TPythonDump& pythonDump = *pPythonDump; // prevent dump of called methods - // create mesh - SMESH::SMESH_Mesh_var newMesh = CreateEmptyMesh(); - SMESH_Mesh_i* newImpl = SMESH::DownCast( newMesh ); + // create mesh if theMeshToAppendTo not provided + SMESH::SMESH_Mesh_var newMesh; + if ( CORBA::is_nil( theMeshToAppendTo )) + newMesh = CreateEmptyMesh(); + else + newMesh = SMESH::SMESH_Mesh::_duplicate( theMeshToAppendTo ); + SMESH_Mesh_i* newImpl = SMESH::DownCast( newMesh ); if ( !newImpl ) return newMesh._retn(); + newImpl->Load(); ::SMESH_Mesh& locMesh = newImpl->GetImpl(); SMESHDS_Mesh* newMeshDS = locMesh.GetMeshDS(); @@ -2507,6 +2517,8 @@ SMESH_Gen_i::ConcatenateCommon(const SMESH::ListOfIDSources& theMeshesArray, SMESH::SMESH_Mesh_var initMesh = theMeshesArray[i]->GetMesh(); SMESH_Mesh_i* initImpl = SMESH::DownCast( initMesh ); if ( !initImpl ) continue; + if ( initMesh->_is_equivalent( theMeshToAppendTo )) + continue; initImpl->Load(); // assure that IDs increments by one during iteration @@ -2693,11 +2705,12 @@ SMESH_Gen_i::ConcatenateCommon(const SMESH::ListOfIDSources& theMeshesArray, // Update Python script pythonDump << newMesh << " = " << this - << "." << ( theCommonGroups ? "ConcatenateWithGroups" : "Concatenate" ) << "(" + << "." << ( theCommonGroups ? "ConcatenateWithGroups" : "Concatenate" ) << "( " << theMeshesArray << ", " << theUniteIdenticalGroups << ", " << theMergeNodesAndElements << ", " - << TVar( theMergeTolerance ) << ")"; + << TVar( theMergeTolerance ) << ", " + << theMeshToAppendTo << " )"; pPythonDump.reset(); // enable python dump from GetGroups() @@ -4011,7 +4024,7 @@ SALOMEDS::TMPFile* SMESH_Gen_i::Save( SALOMEDS::SComponent_ptr theComponent, CORBA::String_var objStr = GetORB()->object_to_string( grImpl->_this() ); int anId = myStudyContext->findId( string( objStr.in() ) ); char grpName[ 30 ]; - sprintf( grpName, "Group %d", anId ); + sprintf( grpName, "Group %d %d", anId, grImpl->GetLocalID() ); SMESHDS_GroupBase* aGrpBaseDS = grImpl->GetGroupDS(); aGrpBaseDS->SetStoreName( grpName ); } @@ -5334,7 +5347,7 @@ bool SMESH_Gen_i::Load( SALOMEDS::SComponent_ptr theComponent, // get mesh old id CORBA::String_var iorString = GetORB()->object_to_string( myNewMeshImpl->_this() ); int newId = myStudyContext->findId( iorString.in() ); - int id = myStudyContext->getOldId( newId ); + int meshOldId = myStudyContext->getOldId( newId ); // try to find mesh data dataset if ( aTopGroup->ExistInternalObject( "Has data" ) ) { @@ -5346,10 +5359,6 @@ bool SMESH_Gen_i::Load( SALOMEDS::SComponent_ptr theComponent, aDataset->ReadFromDisk( strHasData ); aDataset->CloseOnDisk(); if ( strcmp( strHasData, "1") == 0 ) { - // read mesh data from MED file - // myReader.SetMesh( mySMESHDSMesh ); - // myReader.SetMeshId( id ); - // myReader.Perform(); hasData = true; } delete [] strHasData; @@ -5616,9 +5625,13 @@ bool SMESH_Gen_i::Load( SALOMEDS::SComponent_ptr theComponent, // check if it is a group if ( name_dataset.substr( 0, 5 ) == "Group" ) { // --> get group id - int subid = atoi( name_dataset.substr( 5 ).c_str() ); + char * endptr; + int subid = strtol( name_dataset.data() + 5, &endptr, 10 ); if ( subid <= 0 ) continue; + int groupID = -1; // group local ID (also persistent) + if ( *endptr ) + groupID = atoi( endptr + 1 ); aDataset = new HDFdataset( name_dataset.c_str(), aGroup ); aDataset->OpenOnDisk(); @@ -5675,7 +5688,7 @@ bool SMESH_Gen_i::Load( SALOMEDS::SComponent_ptr theComponent, // Create group servant SMESH::ElementType type = (SMESH::ElementType)(ii - GetNodeGroupsTag() + 1); SMESH::SMESH_GroupBase_var aNewGroup = SMESH::SMESH_GroupBase::_duplicate - ( myNewMeshImpl->createGroup( type, nameFromFile, aShape, predicate ) ); + ( myNewMeshImpl->createGroup( type, nameFromFile, groupID, aShape, predicate ) ); delete [] nameFromFile; // Obtain a SMESHDS_Group object if ( aNewGroup->_is_nil() ) @@ -5724,10 +5737,10 @@ bool SMESH_Gen_i::Load( SALOMEDS::SComponent_ptr theComponent, } // reading GROUPs // instead of reading mesh data, we read only brief information of all - // objects: mesh, groups, sub-meshes (issue 0021208 ) + // objects: mesh, groups, sub-meshes (issue 0021208) if ( hasData ) { - SMESH_PreMeshInfo::LoadFromFile( myNewMeshImpl, id, + SMESH_PreMeshInfo::LoadFromFile( myNewMeshImpl, meshOldId, meshfile.ToCString(), filename.ToCString(), !isMultiFile ); } diff --git a/src/SMESH_I/SMESH_Gen_i.hxx b/src/SMESH_I/SMESH_Gen_i.hxx index 1209ec7af..f713e8479 100644 --- a/src/SMESH_I/SMESH_Gen_i.hxx +++ b/src/SMESH_I/SMESH_Gen_i.hxx @@ -307,7 +307,7 @@ public: // Returns errors of hypotheses definition SMESH::algo_error_array* GetAlgoState( SMESH::SMESH_Mesh_ptr theMesh, GEOM::GEOM_Object_ptr theSubObject ) - throw ( SALOME::SALOME_Exception ); + throw ( SALOME::SALOME_Exception ); // Return mesh elements preventing computation of a subshape SMESH::MeshPreviewStruct* GetBadInputElements( SMESH::SMESH_Mesh_ptr theMesh, @@ -341,14 +341,16 @@ public: CORBA::Boolean uniteIdenticalGroups, CORBA::Boolean mergeNodesAndElements, CORBA::Double mergeTolerance, - CORBA::Boolean commonGroups) + CORBA::Boolean commonGroups, + SMESH::SMESH_Mesh_ptr meshToAppendTo) throw ( SALOME::SALOME_Exception ); // Concatenate the given meshes into one mesh SMESH::SMESH_Mesh_ptr Concatenate(const SMESH::ListOfIDSources& meshesArray, CORBA::Boolean uniteIdenticalGroups, CORBA::Boolean mergeNodesAndElements, - CORBA::Double mergeTolerance) + CORBA::Double mergeTolerance, + SMESH::SMESH_Mesh_ptr meshToAppendTo) throw ( SALOME::SALOME_Exception ); // Concatenate the given meshes into one mesh @@ -356,7 +358,8 @@ public: SMESH::SMESH_Mesh_ptr ConcatenateWithGroups(const SMESH::ListOfIDSources& meshesArray, CORBA::Boolean uniteIdenticalGroups, CORBA::Boolean mergeNodesAndElements, - CORBA::Double mergeTolerance) + CORBA::Double mergeTolerance, + SMESH::SMESH_Mesh_ptr meshToAppendTo) throw ( SALOME::SALOME_Exception ); // Get version of MED format being used. diff --git a/src/SMESH_I/SMESH_Measurements_i.cxx b/src/SMESH_I/SMESH_Measurements_i.cxx index e6e5493fa..4d573980a 100644 --- a/src/SMESH_I/SMESH_Measurements_i.cxx +++ b/src/SMESH_I/SMESH_Measurements_i.cxx @@ -120,6 +120,33 @@ static bool getNodeNodeDistance (SMESH::Measure& theMeasure, return true; } +static bool getNodeElemDistance (SMESH::Measure& theMeasure, + const SMDS_MeshNode* theNode, + SMESH_ElementSearcher* theElemSearcher) +{ + if ( !theNode || !theElemSearcher ) + return false; + + const SMDS_MeshElement* closestElement = 0; + gp_Pnt point = SMESH_NodeXYZ( theNode ); + gp_Pnt closestPoint = theElemSearcher->Project( point, SMDSAbs_All, &closestElement ); + + if ( closestElement ) + { + theMeasure.value = point.Distance( closestPoint ); + theMeasure.node1 = theNode->GetID(); + theMeasure.elem2 = closestElement->GetID(); + theMeasure.maxX = closestPoint.X(); + theMeasure.maxY = closestPoint.Y(); + theMeasure.maxZ = closestPoint.Z(); + theMeasure.minX = closestPoint.X() - point.X(); + theMeasure.minY = closestPoint.Y() - point.Y(); + theMeasure.minZ = closestPoint.Z() - point.Z(); + } + + return closestElement; +} + static SMESHDS_Mesh* getMesh(SMESH::SMESH_IDSource_ptr theSource) { if (!CORBA::is_nil( theSource )) @@ -183,7 +210,6 @@ SMESH::Measure Measurements_i::MinDistance SMESH::long_array_var aElementsId1 = theSource1->GetIDs(); SMESH::long_array_var aElementsId2; - if ( !isOrigin ) aElementsId2 = theSource2->GetIDs(); // compute distance between two entities /* NOTE: currently only node-to-node case is implemented @@ -196,10 +222,25 @@ SMESH::Measure Measurements_i::MinDistance // node - node const SMESHDS_Mesh* aMesh1 = getMesh( theSource1 ); const SMESHDS_Mesh* aMesh2 = isOrigin ? 0 : getMesh( theSource2 ); + if ( !isOrigin ) aElementsId2 = theSource2->GetIDs(); const SMDS_MeshNode* theNode1 = aMesh1 ? aMesh1->FindNode( aElementsId1[0] ) : 0; const SMDS_MeshNode* theNode2 = aMesh2 ? aMesh2->FindNode( aElementsId2[0] ) : 0; getNodeNodeDistance( aMeasure, theNode1, theNode2 ); } + if (isNode1 && !isNode2 && aElementsId1->length() == 1 ) + { + // node - elements + SMESHDS_Mesh* aMesh1 = getMesh( theSource1 ); + SMESHDS_Mesh* aMesh2 = getMesh( theSource2 ); + if ( aMesh1 && aMesh2 ) + { + const SMDS_MeshNode* aNode = aMesh1->FindNode( aElementsId1[0] ); + SMDS_ElemIteratorPtr anElemIt = SMESH_Mesh_i::GetElements( theSource2, SMESH::ALL ); + std::unique_ptr< SMESH_ElementSearcher > aSearcher + ( SMESH_MeshAlgos::GetElementSearcher( *aMesh2, anElemIt )); + getNodeElemDistance( aMeasure, aNode, aSearcher.get() ); + } + } else { // NOT_IMPLEMENTED diff --git a/src/SMESH_I/SMESH_MeshEditor_i.cxx b/src/SMESH_I/SMESH_MeshEditor_i.cxx index 2e8fda89b..f1e40ec43 100644 --- a/src/SMESH_I/SMESH_MeshEditor_i.cxx +++ b/src/SMESH_I/SMESH_MeshEditor_i.cxx @@ -383,6 +383,8 @@ namespace MeshEditor_I { string getPartIOR( SMESH::SMESH_IDSource_ptr theMeshPart, SMESH::ElementType type = SMESH::ALL ) { + if ( SMESH::DownCast( theMeshPart )) + return ""; string partIOR = SMESH_Gen_i::GetORB()->object_to_string( theMeshPart ); if ( SMESH_Group_i* group_i = SMESH::DownCast( theMeshPart )) // take into account passible group modification @@ -2066,9 +2068,10 @@ void SMESH_MeshEditor_i::SplitVolumesIntoTetra (SMESH::SMESH_IDSource_ptr elems, ::SMESH_MeshEditor::TFacetOfElem elemSet; const int noneFacet = -1; - SMDS_ElemIteratorPtr volIt = myMesh_i->GetElements( elems, SMESH::VOLUME ); - while( volIt->more() ) - elemSet.insert( elemSet.end(), make_pair( volIt->next(), noneFacet )); + prepareIdSource( elems ); + if ( SMDS_ElemIteratorPtr volIt = myMesh_i->GetElements( elems, SMESH::VOLUME )) + while ( volIt->more() ) + elemSet.insert( elemSet.end(), make_pair( volIt->next(), noneFacet )); getEditor().SplitVolumes( elemSet, int( methodFlags )); declareMeshModified( /*isReComputeSafe=*/true ); // it does not influence Compute() @@ -2109,6 +2112,7 @@ void SMESH_MeshEditor_i::SplitHexahedraIntoPrisms( SMESH::SMESH_IDSource_ptr el facetToSplitNormal.PS.y, facetToSplitNormal.PS.z )); TIDSortedElemSet elemSet; + prepareIdSource( elems ); SMESH::long_array_var anElementsId = elems->GetIDs(); SMDS_MeshElement::GeomFilter filter( SMDSGeom_HEXA ); arrayToSet( anElementsId, getMeshDS(), elemSet, SMDSAbs_Volume, &filter ); @@ -2423,8 +2427,8 @@ SMESH_MeshEditor_i::RotationSweepObjects(const SMESH::ListOfIDSources & theNodes TIDSortedElemSet elemsNodes[2]; for ( int i = 0, nb = theNodes.length(); i < nb; ++i ) { - SMDS_ElemIteratorPtr nIt = myMesh_i->GetElements( theNodes[i], SMESH::NODE ); - while ( nIt->more() ) elemsNodes[1].insert( nIt->next() ); + if ( SMDS_ElemIteratorPtr nIt = myMesh_i->GetElements( theNodes[i], SMESH::NODE )) + while ( nIt->more() ) elemsNodes[1].insert( nIt->next() ); } for ( int i = 0, nb = theEdges.length(); i < nb; ++i ) idSourceToSet( theEdges[i], getMeshDS(), elemsNodes[0], SMDSAbs_Edge ); @@ -2621,8 +2625,8 @@ SMESH_MeshEditor_i::ExtrusionSweepObjects(const SMESH::ListOfIDSources & theNode TIDSortedElemSet elemsNodes[2]; for ( int i = 0, nb = theNodes.length(); i < nb; ++i ) { - SMDS_ElemIteratorPtr nIt = myMesh_i->GetElements( theNodes[i], SMESH::NODE ); - while ( nIt->more() ) elemsNodes[1].insert( nIt->next() ); + if ( SMDS_ElemIteratorPtr nIt = myMesh_i->GetElements( theNodes[i], SMESH::NODE )) + while ( nIt->more() ) elemsNodes[1].insert( nIt->next() ); } for ( int i = 0, nb = theEdges.length(); i < nb; ++i ) idSourceToSet( theEdges[i], getMeshDS(), elemsNodes[0], SMDSAbs_Edge ); @@ -2900,8 +2904,8 @@ SMESH_MeshEditor_i::ExtrusionAlongPathObjects(const SMESH::ListOfIDSources & the TIDSortedElemSet elemsNodes[2]; for ( int i = 0, nb = theNodes.length(); i < nb; ++i ) { - SMDS_ElemIteratorPtr nIt = myMesh_i->GetElements( theNodes[i], SMESH::NODE ); - while ( nIt->more() ) elemsNodes[1].insert( nIt->next() ); + if ( SMDS_ElemIteratorPtr nIt = myMesh_i->GetElements( theNodes[i], SMESH::NODE )) + while ( nIt->more() ) elemsNodes[1].insert( nIt->next() ); } for ( int i = 0, nb = theEdges.length(); i < nb; ++i ) idSourceToSet( theEdges[i], getMeshDS(), elemsNodes[0], SMDSAbs_Edge ); @@ -4180,6 +4184,7 @@ FindCoincidentNodesOnPart(SMESH::SMESH_IDSource_ptr theObject, initData(); TIDSortedNodeSet nodes; + prepareIdSource( theObject ); idSourceToNodeSet( theObject, getMeshDS(), nodes ); findCoincidentNodes( nodes, Tolerance, GroupsOfNodes, SeparateCornersAndMedium ); @@ -4211,14 +4216,15 @@ FindCoincidentNodesOnPartBut(SMESH::SMESH_IDSource_ptr theObject, initData(); TIDSortedNodeSet nodes; + prepareIdSource( theObject ); idSourceToNodeSet( theObject, getMeshDS(), nodes ); for ( CORBA::ULong i = 0; i < theExceptSubMeshOrGroups.length(); ++i ) { - SMDS_ElemIteratorPtr nodeIt = myMesh_i->GetElements( theExceptSubMeshOrGroups[i], - SMESH::NODE ); - while ( nodeIt->more() ) - nodes.erase( cast2Node( nodeIt->next() )); + if ( SMDS_ElemIteratorPtr nodeIt = myMesh_i->GetElements( theExceptSubMeshOrGroups[i], + SMESH::NODE )) + while ( nodeIt->more() ) + nodes.erase( cast2Node( nodeIt->next() )); } findCoincidentNodes( nodes, theTolerance, theGroupsOfNodes, theSeparateCornersAndMedium ); @@ -4253,9 +4259,9 @@ void SMESH_MeshEditor_i::MergeNodes (const SMESH::array_of_long_array& GroupsOfN for ( CORBA::ULong i = 0; i < NodesToKeep.length(); ++i ) { prepareIdSource( NodesToKeep[i] ); - SMDS_ElemIteratorPtr nodeIt = myMesh_i->GetElements( NodesToKeep[i], SMESH::NODE ); - while ( nodeIt->more() ) - setOfNodesToKeep.insert( setOfNodesToKeep.end(), cast2Node( nodeIt->next() )); + if ( SMDS_ElemIteratorPtr nodeIt = myMesh_i->GetElements( NodesToKeep[i], SMESH::NODE )) + while ( nodeIt->more() ) + setOfNodesToKeep.insert( setOfNodesToKeep.end(), cast2Node( nodeIt->next() )); } ::SMESH_MeshEditor::TListOfListOfNodes aListOfListOfNodes; @@ -4304,7 +4310,7 @@ void SMESH_MeshEditor_i::FindEqualElements(SMESH::SMESH_IDSource_ptr theObj initData(); SMESH::SMESH_GroupBase_var group = SMESH::SMESH_GroupBase::_narrow(theObject); - if ( !(!group->_is_nil() && group->GetType() == SMESH::NODE) ) + if ( !( !group->_is_nil() && group->GetType() == SMESH::NODE )) { TIDSortedElemSet elems; idSourceToSet( theObject, getMeshDS(), elems, SMDSAbs_All, /*emptyIfIsMesh=*/true); @@ -4606,6 +4612,7 @@ SMESH_MeshEditor_i::FindAmongElementsByPoint(SMESH::SMESH_IDSource_ptr elementID SMESH_TRY; SMESH::long_array_var res = new SMESH::long_array; + prepareIdSource( elementIDs ); if ( type != SMESH::NODE ) { SMESH::array_of_ElementType_var types = elementIDs->GetTypes(); @@ -4614,6 +4621,16 @@ SMESH_MeshEditor_i::FindAmongElementsByPoint(SMESH::SMESH_IDSource_ptr elementID type != types[0] ) // but search of elements of dim > 0 return res._retn(); } + + SMESH::SMESH_Mesh_var mesh = elementIDs->GetMesh(); + SMESH_Mesh_i* mesh_i = SMESH::DownCast( mesh ); + if ( mesh_i != myMesh_i ) + { + SMESH::SMESH_MeshEditor_var editor= + myIsPreviewMode ? mesh_i->GetMeshEditPreviewer() : mesh_i->GetMeshEditor(); + return editor->FindAmongElementsByPoint( elementIDs, x,y,z, type ); + } + if ( SMESH::DownCast( elementIDs )) // elementIDs is the whole mesh return FindElementsByPoint( x,y,z, type ); @@ -4623,18 +4640,15 @@ SMESH_MeshEditor_i::FindAmongElementsByPoint(SMESH::SMESH_IDSource_ptr elementID if ( !theElementSearcher ) { // create a searcher from elementIDs - SMESH::SMESH_Mesh_var mesh = elementIDs->GetMesh(); - SMESHDS_Mesh* meshDS = SMESH::DownCast( mesh )->GetImpl().GetMeshDS(); - - if ( !idSourceToSet( elementIDs, meshDS, elements, - ( type == SMESH::NODE ? SMDSAbs_All : (SMDSAbs_ElementType) type ), - /*emptyIfIsMesh=*/true)) - return res._retn(); - - typedef SMDS_SetIterator TIter; - SMDS_ElemIteratorPtr elemsIt( new TIter( elements.begin(), elements.end() )); - - theElementSearcher = SMESH_MeshAlgos::GetElementSearcher( *getMeshDS(), elemsIt ); + SMDS_ElemIteratorPtr elemIt; + if ( ! SMESH::DownCast( elementIDs )) + { + //prepareIdSource( elementIDs ); + elemIt = myMesh_i->GetElements( elementIDs, type ); + if ( !elemIt ) + return res._retn(); + } + theElementSearcher = SMESH_MeshAlgos::GetElementSearcher( *getMeshDS(), elemIt ); } vector< const SMDS_MeshElement* > foundElems; @@ -4663,8 +4677,8 @@ SMESH_MeshEditor_i::FindAmongElementsByPoint(SMESH::SMESH_IDSource_ptr elementID CORBA::Long SMESH_MeshEditor_i::ProjectPoint(CORBA::Double x, CORBA::Double y, CORBA::Double z, - SMESH::SMESH_IDSource_ptr meshObject, SMESH::ElementType type, + SMESH::SMESH_IDSource_ptr meshObject, SMESH::double_array_out projecton) throw (SALOME::SALOME_Exception) { @@ -4679,19 +4693,23 @@ CORBA::Long SMESH_MeshEditor_i::ProjectPoint(CORBA::Double x, { SMESH::SMESH_MeshEditor_var editor= myIsPreviewMode ? mesh_i->GetMeshEditPreviewer() : mesh_i->GetMeshEditor(); - return editor->ProjectPoint( x,y,z, meshObject, type, projecton ); + return editor->ProjectPoint( x,y,z, type, meshObject, projecton ); } - theSearchersDeleter.Set( myMesh, getPartIOR( meshObject )); + theSearchersDeleter.Set( myMesh, getPartIOR( meshObject, type )); if ( !theElementSearcher ) { // create a searcher from meshObject SMDS_ElemIteratorPtr elemIt; if ( ! SMESH::DownCast( meshObject )) + { + prepareIdSource( meshObject ); elemIt = myMesh_i->GetElements( meshObject, type ); - + if ( !elemIt ) + return -1; + } theElementSearcher = SMESH_MeshAlgos::GetElementSearcher( *getMeshDS(), elemIt ); } @@ -4779,6 +4797,59 @@ CORBA::Boolean SMESH_MeshEditor_i::IsCoherentOrientation2D() return isGoodOri; } +//======================================================================= +//function : Get1DBranches +//purpose : Partition given 1D elements into groups of contiguous edges. +// A node where number of meeting edges != 2 is a group end. +// An optional startNode is used to orient groups it belongs to. +//return : a list of edge groups and a list of corresponding node groups. +// If a group is closed, the first and last nodes of the group are same. +//======================================================================= + +SMESH::array_of_long_array* +SMESH_MeshEditor_i::Get1DBranches( SMESH::SMESH_IDSource_ptr theEdges, + CORBA::Long theStartNode, + SMESH::array_of_long_array_out theNodeGroups ) + throw (SALOME::SALOME_Exception) +{ + if ( CORBA::is_nil( theEdges )) + THROW_SALOME_CORBA_EXCEPTION("Get1DBranches(): NULL group given", SALOME::BAD_PARAM); + + SMESH::array_of_long_array_var edgeGroupArray = new SMESH::array_of_long_array; + theNodeGroups = new SMESH::array_of_long_array; + + SMESH_TRY; + + prepareIdSource( theEdges ); + + SMESH_MeshAlgos::TElemGroupVector edgeBranches; + SMESH_MeshAlgos::TNodeGroupVector nodeBranches; + SMESH_MeshAlgos::Get1DBranches( SMESH_Mesh_i::GetElements( theEdges, SMESH::EDGE ), + edgeBranches, + nodeBranches, + getMeshDS()->FindNode( theStartNode )); + + edgeGroupArray->length( edgeBranches.size() ); + for ( size_t iG = 0; iG < edgeBranches.size(); ++iG ) + { + edgeGroupArray[ iG ].length( edgeBranches[ iG ].size() ); + for ( size_t i = 0; i < edgeBranches[ iG ].size(); ++i ) + edgeGroupArray[ iG ][ i ] = edgeBranches[ iG ][ i ]->GetID(); + } + + theNodeGroups->length( nodeBranches.size() ); + for ( size_t iG = 0; iG < nodeBranches.size(); ++iG ) + { + theNodeGroups[ iG ].length( nodeBranches[ iG ].size() ); + for ( size_t i = 0; i < nodeBranches[ iG ].size(); ++i ) + theNodeGroups[ iG ][ i ] = nodeBranches[ iG ][ i ]->GetID(); + } + + SMESH_CATCH( SMESH::throwCorbaException ); + + return edgeGroupArray._retn(); +} + //======================================================================= //function : FindSharpEdges //purpose : Return sharp edges of faces and non-manifold ones. Optionally add existing edges. @@ -5525,8 +5596,8 @@ void SMESH_MeshEditor_i::convertToQuadratic(CORBA::Boolean theForce3d bool elemsOK; if ( !( elemsOK = CORBA::is_nil( theObject ))) { - elemsOK = idSourceToSet( theObject, getMeshDS(), elems, - SMDSAbs_All, /*emptyIfIsMesh=*/true ); + elemsOK = idSourceToSet( theObject, getMeshDS(), elems, + SMDSAbs_All, /*emptyIfIsMesh=*/true ); } if ( elemsOK ) { @@ -7087,7 +7158,7 @@ CORBA::Long SMESH_MeshEditor_i::MakeBoundaryElements(SMESH::Bnd_Dimension dim, for ( CORBA::ULong i = 0; i < groups.length(); ++i ) { SMESH::SMESH_Mesh_var m = groups[i]->GetMesh(); - if ( myMesh_i != SMESH::DownCast( m )) + if ( !m->_is_nil() && myMesh_i != SMESH::DownCast( m )) groupsOfOtherMesh[ nbGroupsOfOtherMesh++ ] = groups[i]; else groupsOfThisMesh[ nbGroups++ ] = groups[i]; @@ -7267,11 +7338,11 @@ void SMESH_MeshEditor_i::MakePolyLine(SMESH::ListOfPolySegments& theSegments, } // convert input polySegments - ::SMESH_MeshEditor::TListOfPolySegments segments( theSegments.length() ); + SMESH_MeshAlgos::TListOfPolySegments segments( theSegments.length() ); for ( CORBA::ULong i = 0; i < theSegments.length(); ++i ) { - SMESH::PolySegment& segIn = theSegments[ i ]; - ::SMESH_MeshEditor::PolySegment& segOut = segments[ i ]; + SMESH::PolySegment& segIn = theSegments[ i ]; + SMESH_MeshAlgos::PolySegment& segOut = segments[ i ]; segOut.myNode1[0] = meshDS->FindNode( segIn.node1ID1 ); segOut.myNode2[0] = meshDS->FindNode( segIn.node1ID2 ); segOut.myNode1[1] = meshDS->FindNode( segIn.node2ID1 ); @@ -7294,15 +7365,24 @@ void SMESH_MeshEditor_i::MakePolyLine(SMESH::ListOfPolySegments& theSegments, theElementSearcher = SMESH_MeshAlgos::GetElementSearcher( *getMeshDS() ); // compute - getEditor().MakePolyLine( segments, groupDS, theElementSearcher ); + std::vector newEdges; + std::vector newNodes; + SMESH_MeshAlgos::MakePolyLine( meshDS, segments, newEdges, newNodes, + groupDS ? &groupDS->SMDSGroup() : 0, + theElementSearcher ); + + const_cast< SMESH_SequenceOfElemPtr& >( getEditor().GetLastCreatedElems() ). + swap( newEdges ); + const_cast< SMESH_SequenceOfElemPtr& >( getEditor().GetLastCreatedNodes() ). + assign( newNodes.begin(), newNodes.end() ); // return vectors if ( myIsPreviewMode ) { for ( CORBA::ULong i = 0; i < theSegments.length(); ++i ) { - SMESH::PolySegment& segOut = theSegments[ i ]; - ::SMESH_MeshEditor::PolySegment& segIn = segments[ i ]; + SMESH::PolySegment& segOut = theSegments[ i ]; + SMESH_MeshAlgos::PolySegment& segIn = segments[ i ]; segOut.vector.PS.x = segIn.myVector.X(); segOut.vector.PS.y = segIn.myVector.Y(); segOut.vector.PS.z = segIn.myVector.Z(); @@ -7330,3 +7410,49 @@ void SMESH_MeshEditor_i::MakePolyLine(SMESH::ListOfPolySegments& theSegments, SMESH_CATCH( SMESH::throwCorbaException ); return; } + +//================================================================================ +/*! + * \brief Create a slot of given width around given 1D elements lying on a triangle mesh. + * The slot is consrtucted by cutting faces by cylindrical surfaces made + * around each segment. Segments are expected to be created by MakePolyLine(). + * \return Edges located at the slot boundary + */ +//================================================================================ + +SMESH::ListOfEdges* SMESH_MeshEditor_i::MakeSlot(SMESH::SMESH_GroupBase_ptr theSegments, + CORBA::Double theWidth) + throw (SALOME::SALOME_Exception) +{ + if ( CORBA::is_nil( theSegments ) || + theSegments->GetType() != SMESH::EDGE ) + THROW_SALOME_CORBA_EXCEPTION("No segments given", SALOME::BAD_PARAM ); + if ( myMesh->NbFaces() == 0 ) + THROW_SALOME_CORBA_EXCEPTION("No faces in the mesh", SALOME::BAD_PARAM ); + + SMESH::ListOfEdges_var resultEdges = new SMESH::ListOfEdges; + + SMESH_TRY; + initData(/*deleteSearchers=*/false); + + SMESHDS_Mesh* meshDS = getMeshDS(); + + std::vector< SMESH_MeshAlgos::Edge > edges = + SMESH_MeshAlgos::MakeSlot( SMESH_Mesh_i::GetElements( theSegments, SMESH::EDGE ), + theWidth, meshDS ); + + 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; + } + + meshDS->Modified(); + SMESH_CATCH( SMESH::throwCorbaException ); + + TSearchersDeleter::Delete(); // face searcher becomes invalid as some faces were removed + + return resultEdges._retn(); +} diff --git a/src/SMESH_I/SMESH_MeshEditor_i.hxx b/src/SMESH_I/SMESH_MeshEditor_i.hxx index 89c730f7b..462d42db2 100644 --- a/src/SMESH_I/SMESH_MeshEditor_i.hxx +++ b/src/SMESH_I/SMESH_MeshEditor_i.hxx @@ -560,8 +560,8 @@ public: CORBA::Long ProjectPoint(CORBA::Double x, CORBA::Double y, CORBA::Double z, - SMESH::SMESH_IDSource_ptr meshObject, SMESH::ElementType type, + SMESH::SMESH_IDSource_ptr meshObject, SMESH::double_array_out projecton) throw (SALOME::SALOME_Exception); @@ -584,6 +584,18 @@ public: CORBA::Boolean IsCoherentOrientation2D() throw (SALOME::SALOME_Exception); + /*! + * Partition given 1D elements into groups of contiguous edges. + * A node where number of meeting edges != 2 is a group end. + * An optional startNode is used to orient groups it belongs to. + * \return a list of edge groups and a list of corresponding node groups. + * If a group is closed, the first and last nodes of the group are same. + */ + SMESH::array_of_long_array* Get1DBranches( SMESH::SMESH_IDSource_ptr edges, + CORBA::Long startNode, + SMESH::array_of_long_array_out nodeGroups) + throw (SALOME::SALOME_Exception); + /*! * Return sharp edges of faces and non-manifold ones. Optionally adds existing edges. */ @@ -920,7 +932,17 @@ public: * be added. */ void MakePolyLine(SMESH::ListOfPolySegments& segments, - const char* groupName) + const char* groupName) + throw (SALOME::SALOME_Exception); + + /*! + * \brief Create a slot of given width around given 1D elements lying on a triangle mesh. + * The slot is consrtucted by cutting faces by cylindrical surfaces made + * around each segment. Segments are expected to be created by MakePolyLine(). + * \return Edges located at the slot boundary + */ + SMESH::ListOfEdges* MakeSlot(SMESH::SMESH_GroupBase_ptr segments, + CORBA::Double width) throw (SALOME::SALOME_Exception); diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 802186aab..016bf658b 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -1020,7 +1020,7 @@ SMESH_Mesh_i::CreateGroupFromGEOM (SMESH::ElementType theElemType, if ( !aShape.IsNull() ) { aNewGroup = - SMESH::SMESH_GroupOnGeom::_narrow( createGroup( theElemType, theName, aShape )); + SMESH::SMESH_GroupOnGeom::_narrow( createGroup( theElemType, theName, /*id=*/-1, aShape )); if ( _gen_i->CanPublishInStudy( aNewGroup ) ) { @@ -1064,7 +1064,7 @@ SMESH_Mesh_i::CreateGroupFromFilter(SMESH::ElementType theElemType, THROW_SALOME_CORBA_EXCEPTION("Invalid filter", SALOME::BAD_PARAM); SMESH::SMESH_GroupOnFilter_var aNewGroup = SMESH::SMESH_GroupOnFilter::_narrow - ( createGroup( theElemType, theName, TopoDS_Shape(), predicate )); + ( createGroup( theElemType, theName, /*id=*/-1, TopoDS_Shape(), predicate )); TPythonDump pd; if ( !aNewGroup->_is_nil() ) @@ -2429,10 +2429,10 @@ void SMESH_Mesh_i::CheckGeomGroupModif() SALOMEDS::SObject_wrap groupSO = _gen_i->ObjectToSObject( _mapGroups[oldID] ); CORBA::String_var name = groupSO->GetName(); // update - SMESH_GroupBase_i* group_i = SMESH::DownCast(_mapGroups[oldID] ); - int newID; - if ( group_i && _impl->AddGroup( geomType->second, name.in(), newID, geom._shape )) - group_i->changeLocalId( newID ); + if ( SMESH_GroupBase_i* group_i = SMESH::DownCast(_mapGroups[oldID])) + if ( SMESH_Group* group = _impl->AddGroup( geomType->second, name.in(), + /*id=*/-1, geom._shape )) + group_i->changeLocalId( group->GetID() ); } break; // everything has been updated @@ -2685,6 +2685,7 @@ bool SMESH_Mesh_i::removeSubMesh (SMESH::SMESH_subMesh_ptr theSubMesh, SMESH::SMESH_GroupBase_ptr SMESH_Mesh_i::createGroup (SMESH::ElementType theElemType, const char* theName, + const int theID, const TopoDS_Shape& theShape, const SMESH_PredicatePtr& thePredicate ) { @@ -2703,10 +2704,11 @@ SMESH::SMESH_GroupBase_ptr SMESH_Mesh_i::createGroup (SMESH::ElementType } while ( !presentNames.insert( newName ).second ); theName = newName.c_str(); } - int anId; SMESH::SMESH_GroupBase_var aGroup; - if ( _impl->AddGroup( (SMDSAbs_ElementType)theElemType, theName, anId, theShape, thePredicate )) + if ( SMESH_Group* g = _impl->AddGroup( (SMDSAbs_ElementType)theElemType, theName, + theID, theShape, thePredicate )) { + int anId = g->GetID(); SMESH_GroupBase_i* aGroupImpl; if ( !theShape.IsNull() ) aGroupImpl = new SMESH_GroupOnGeom_i( SMESH_Gen_i::GetPOA(), this, anId ); @@ -2722,7 +2724,7 @@ SMESH::SMESH_GroupBase_ptr SMESH_Mesh_i::createGroup (SMESH::ElementType // register CORBA object for persistence int nextId = _gen_i->RegisterObject( aGroup ); if(MYDEBUG) { MESSAGE( "Add group to map with id = "<< nextId); } - else { nextId = 0; } // avoid "unused variable" warning in release mode + else { nextId = ( nextId > 0 ); } // avoid "unused variable" warning in release mode // to track changes of GEOM groups if ( !theShape.IsNull() ) { @@ -4475,7 +4477,8 @@ SMESH::double_array* SMESH_Mesh_i::GetNodeXYZ(const CORBA::Long id) */ //============================================================================= -SMESH::long_array* SMESH_Mesh_i::GetNodeInverseElements(const CORBA::Long id) +SMESH::long_array* SMESH_Mesh_i::GetNodeInverseElements(const CORBA::Long id, + SMESH::ElementType elemType) { if ( _preMeshInfo ) _preMeshInfo->FullLoadFromFile(); @@ -4486,13 +4489,14 @@ SMESH::long_array* SMESH_Mesh_i::GetNodeInverseElements(const CORBA::Long id) return aResult._retn(); // find node - const SMDS_MeshNode* aNode = aMeshDS->FindNode(id); - if(!aNode) + const SMDS_MeshNode* aNode = aMeshDS->FindNode( id ); + if ( !aNode ) return aResult._retn(); // find inverse elements - SMDS_ElemIteratorPtr eIt = aNode->GetInverseElementIterator(); - aResult->length( aNode->NbInverseElements() ); + SMDSAbs_ElementType type = SMDSAbs_ElementType( elemType ); + SMDS_ElemIteratorPtr eIt = aNode->GetInverseElementIterator( type ); + aResult->length( aNode->NbInverseElements( type )); for( int i = 0; eIt->more(); ++i ) { const SMDS_MeshElement* elem = eIt->next(); @@ -5513,10 +5517,12 @@ namespace /* Iterators used in SMESH_Mesh_i::GetElements(SMESH::SMESH_IDSource_v SMDS_ElemIteratorPtr _elemIter; PredicatePtr _predicate; const SMDS_MeshElement* _elem; + SMDSAbs_ElementType _type; - PredicateIterator( SMDS_ElemIteratorPtr iterator, - PredicatePtr predicate): - _elemIter(iterator), _predicate(predicate) + PredicateIterator( SMDS_ElemIteratorPtr iterator, + PredicatePtr predicate, + SMDSAbs_ElementType type): + _elemIter(iterator), _predicate(predicate), _type(type) { next(); } @@ -5530,8 +5536,9 @@ namespace /* Iterators used in SMESH_Mesh_i::GetElements(SMESH::SMESH_IDSource_v _elem = 0; while ( _elemIter->more() && !_elem ) { - _elem = _elemIter->next(); - if ( _elem && ( !_predicate->IsSatisfy( _elem->GetID() ))) + if ((_elem = _elemIter->next()) && + (( _type != SMDSAbs_All && _type != _elem->GetType() ) || + ( !_predicate->IsSatisfy( _elem->GetID() )))) _elem = 0; } return res; @@ -5685,6 +5692,7 @@ SMDS_ElemIteratorPtr SMESH_Mesh_i::GetElements(SMESH::SMESH_IDSource_ptr theObje else if ( SMESH::Filter_i* filter_i = SMESH::DownCast( theObject )) { if ( filter_i->GetElementType() == theType || + filter_i->GetElementType() == SMESH::ALL || elemType == SMDSAbs_Node || elemType == SMDSAbs_All) { @@ -5693,8 +5701,10 @@ SMDS_ElemIteratorPtr SMESH_Mesh_i::GetElements(SMESH::SMESH_IDSource_ptr theObje { SMDSAbs_ElementType filterType = SMDSAbs_ElementType( filter_i->GetElementType() ); SMDS_ElemIteratorPtr allElemIt = meshDS->elementsIterator( filterType ); - elemIt = SMDS_ElemIteratorPtr( new PredicateIterator( allElemIt, pred_i->GetPredicate() )); - typeOK = ( filterType == elemType || elemType == SMDSAbs_All ); + SMDSAbs_ElementType iterType = elemType == SMDSAbs_Node ? filterType : elemType; + elemIt = SMDS_ElemIteratorPtr + ( new PredicateIterator( allElemIt, pred_i->GetPredicate(), iterType )); + typeOK = ( elemType == SMDSAbs_Node ? filterType == SMDSAbs_Node : elemIt->more() ); } } } @@ -5704,16 +5714,17 @@ SMDS_ElemIteratorPtr SMESH_Mesh_i::GetElements(SMESH::SMESH_IDSource_ptr theObje const bool isNodes = ( types->length() == 1 && types[0] == SMESH::NODE ); if ( isNodes && elemType != SMDSAbs_Node && elemType != SMDSAbs_All ) return elemIt; + SMDSAbs_ElementType iterType = isNodes ? SMDSAbs_Node : elemType; if ( SMESH_MeshEditor_i::IsTemporaryIDSource( theObject )) { int nbIds; if ( CORBA::Long* ids = SMESH_MeshEditor_i::GetTemporaryIDs( theObject, nbIds )) - elemIt = SMDS_ElemIteratorPtr( new IDSourceIterator( meshDS, ids, nbIds, elemType )); + elemIt = SMDS_ElemIteratorPtr( new IDSourceIterator( meshDS, ids, nbIds, iterType )); } else { SMESH::long_array_var ids = theObject->GetIDs(); - elemIt = SMDS_ElemIteratorPtr( new IDSourceIterator( meshDS, ids._retn(), elemType )); + elemIt = SMDS_ElemIteratorPtr( new IDSourceIterator( meshDS, ids._retn(), iterType )); } typeOK = ( isNodes == ( elemType == SMDSAbs_Node )) || ( elemType == SMDSAbs_All ); } diff --git a/src/SMESH_I/SMESH_Mesh_i.hxx b/src/SMESH_I/SMESH_Mesh_i.hxx index d06a704eb..34c7c3050 100644 --- a/src/SMESH_I/SMESH_Mesh_i.hxx +++ b/src/SMESH_I/SMESH_Mesh_i.hxx @@ -440,6 +440,7 @@ public: SMESH::SMESH_GroupBase_ptr createGroup(SMESH::ElementType theElemType, const char* theName, + const int theID = -1, const TopoDS_Shape& theShape = TopoDS_Shape(), const SMESH_PredicatePtr& thePred = SMESH_PredicatePtr()); @@ -501,7 +502,8 @@ public: * For given node returns list of IDs of inverse elements * If there is not node for given ID - returns empty list */ - SMESH::long_array* GetNodeInverseElements(CORBA::Long id); + SMESH::long_array* GetNodeInverseElements(CORBA::Long id, + SMESH::ElementType elemType); /*! * \brief Return position of a node on shape diff --git a/src/SMESH_SWIG/smeshBuilder.py b/src/SMESH_SWIG/smeshBuilder.py index 35198fe59..026858782 100755 --- a/src/SMESH_SWIG/smeshBuilder.py +++ b/src/SMESH_SWIG/smeshBuilder.py @@ -733,10 +733,10 @@ class smeshBuilder( SMESH._objref_SMESH_Gen, object ): def Concatenate( self, meshes, uniteIdenticalGroups, mergeNodesAndElements = False, mergeTolerance = 1e-5, allGroups = False, - name = ""): + name = "", meshToAppendTo = None): """ - Concatenate the given meshes into one mesh. All groups of input meshes will be - present in the new mesh. + Concatenate the given meshes into one mesh, optionally to meshToAppendTo. + All groups of input meshes will be present in the new mesh. Parameters: meshes: :class:`meshes, sub-meshes, groups or filters ` to combine into one mesh @@ -745,6 +745,7 @@ class smeshBuilder( SMESH._objref_SMESH_Gen, object ): mergeTolerance: tolerance for merging nodes allGroups: forces creation of groups corresponding to every input mesh name: name of a new mesh + meshToAppendTo a mesh to append all given meshes Returns: an instance of class :class:`Mesh` @@ -755,13 +756,21 @@ class smeshBuilder( SMESH._objref_SMESH_Gen, object ): if isinstance(m, Mesh): meshes[i] = m.GetMesh() mergeTolerance,Parameters,hasVars = ParseParameters(mergeTolerance) - meshes[0].SetParameters(Parameters) + if hasattr(meshes[0], "SetParameters"): + meshes[0].SetParameters(Parameters) + else: + meshes[0].GetMesh().SetParameters(Parameters) + if isinstance( meshToAppendTo, Mesh ): + meshToAppendTo = meshToAppendTo.GetMesh() if allGroups: aSmeshMesh = SMESH._objref_SMESH_Gen.ConcatenateWithGroups( - self,meshes,uniteIdenticalGroups,mergeNodesAndElements,mergeTolerance) + self,meshes,uniteIdenticalGroups,mergeNodesAndElements, + mergeTolerance,meshToAppendTo) else: aSmeshMesh = SMESH._objref_SMESH_Gen.Concatenate( - self,meshes,uniteIdenticalGroups,mergeNodesAndElements,mergeTolerance) + self,meshes,uniteIdenticalGroups,mergeNodesAndElements, + mergeTolerance,meshToAppendTo) + aMesh = Mesh(self, self.geompyD, aSmeshMesh, name=name) return aMesh @@ -1428,13 +1437,16 @@ class smeshBuilder( SMESH._objref_SMESH_Gen, object ): def GetGravityCenter(self, obj): """ - Get gravity center of all nodes of the mesh object. + Get gravity center of all nodes of a mesh object. Parameters: obj: :class:`mesh, sub-mesh, group or filter ` Returns: - Three components of the gravity center (x,y,z) + Three components of the gravity center (x,y,z) + + See also: + :meth:`Mesh.BaryCenter` """ if isinstance(obj, Mesh): obj = obj.mesh if isinstance(obj, Mesh_Algorithm): obj = obj.GetSubMesh() @@ -1618,6 +1630,18 @@ class Mesh(metaclass = MeshMeta): return self.mesh + def GetEngine(self): + """ + Return a smeshBuilder instance created this mesh + """ + return self.smeshpyD + + def GetGeomEngine(self): + """ + Return a geomBuilder instance + """ + return self.geompyD + def GetName(self): """ Get the name of the mesh @@ -2881,7 +2905,7 @@ class Mesh(metaclass = MeshMeta): Create a standalone group of entities basing on nodes of other groups. Parameters: - groups: list of reference :class:`sub-meshes, groups or filters `, of any type. + groups: list of :class:`sub-meshes, groups or filters `, of any type. elemType: a type of elements to include to the new group; either of (SMESH.NODE, SMESH.EDGE, SMESH.FACE, SMESH.VOLUME). name: a name of the new group. @@ -3532,16 +3556,20 @@ class Mesh(metaclass = MeshMeta): return self.mesh.GetNodeXYZ(id) - def GetNodeInverseElements(self, id): + def GetNodeInverseElements(self, id, elemType=SMESH.ALL): """ Return list of IDs of inverse elements for the given node. If there is no node for the given ID - return an empty list + Parameters: + id: node ID + elementType: :class:`type of elements ` (SMESH.EDGE, SMESH.FACE, SMESH.VOLUME, etc.) + Returns: list of integer values """ - return self.mesh.GetNodeInverseElements(id) + return self.mesh.GetNodeInverseElements(id,elemType) def GetNodePosition(self,NodeID): """ @@ -3715,26 +3743,40 @@ class Mesh(metaclass = MeshMeta): Returns: a list of three double values + + See also: + :meth:`smeshBuilder.GetGravityCenter` """ return self.mesh.BaryCenter(id) - def GetIdsFromFilter(self, theFilter): + def GetIdsFromFilter(self, filter, meshParts=[] ): """ Pass mesh elements through the given filter and return IDs of fitting elements Parameters: - theFilter: :class:`SMESH.Filter` + filter: :class:`SMESH.Filter` + meshParts: list of mesh parts (:class:`sub-mesh, group or filter `) to filter Returns: a list of ids See Also: :meth:`SMESH.Filter.GetIDs` + :meth:`SMESH.Filter.GetElementsIdFromParts` """ - theFilter.SetMesh( self.mesh ) - return theFilter.GetIDs() + filter.SetMesh( self.mesh ) + + if meshParts: + if isinstance( meshParts, Mesh ): + filter.SetMesh( meshParts.GetMesh() ) + return theFilter.GetIDs() + if isinstance( meshParts, SMESH._objref_SMESH_IDSource ): + meshParts = [ meshParts ] + return filter.GetElementsIdFromParts( meshParts ) + + return filter.GetIDs() # Get mesh measurements information: # ------------------------------------ @@ -4256,8 +4298,6 @@ class Mesh(metaclass = MeshMeta): the ID of a node """ - #preview = self.mesh.GetMeshEditPreviewer() - #return preview.MoveClosestNodeToPoint(x, y, z, -1) return self.editor.FindNodeClosestTo(x, y, z) def FindElementsByPoint(self, x, y, z, elementType = SMESH.ALL, meshPart=None): @@ -4278,16 +4318,18 @@ class Mesh(metaclass = MeshMeta): else: return self.editor.FindElementsByPoint(x, y, z, elementType) - def ProjectPoint(self, x,y,z, meshObject, elementType): + def ProjectPoint(self, x,y,z, elementType, meshObject=None): """ Project a point to a mesh object. Return ID of an element of given type where the given point is projected and coordinates of the projection point. In the case if nothing found, return -1 and [] """ - if ( isinstance( meshObject, Mesh )): + if isinstance( meshObject, Mesh ): meshObject = meshObject.GetMesh() - return self.editor.ProjectPoint( x,y,z, meshObject, elementType ) + if not meshObject: + meshObject = self.GetMesh() + return self.editor.ProjectPoint( x,y,z, elementType, meshObject ) def GetPointState(self, x, y, z): """ @@ -4312,6 +4354,25 @@ class Mesh(metaclass = MeshMeta): return self.editor.IsCoherentOrientation2D() + def Get1DBranches( self, edges, startNode = 0 ): + """ + Partition given 1D elements into groups of contiguous edges. + A node where number of meeting edges != 2 is a group end. + An optional startNode is used to orient groups it belongs to. + + Returns: + A list of edge groups and a list of corresponding node groups, + where the group is a list of IDs of edges or elements. + If a group is closed, the first and last nodes of the group are same. + """ + if isinstance( edges, Mesh ): + edges = edges.GetMesh() + unRegister = genObjUnRegister() + if isinstance( edges, list ): + edges = self.GetIDSource( edges, SMESH.EDGE ) + unRegister.set( edges ) + return self.editor.Get1DBranches( edges, startNode ) + def FindSharpEdges( self, angle, addExisting=False ): """ Return sharp edges of faces and non-manifold ones. @@ -5102,7 +5163,7 @@ class Mesh(metaclass = MeshMeta): groups: list of :class:`sub-meshes, groups or filters ` of elements to make boundary around Returns: - tuple( long, mesh, groups ) + tuple( long, mesh, group ) - long - number of added boundary elements - mesh - the :class:`Mesh` where elements were added to - group - the :class:`group ` of boundary elements or None @@ -6178,7 +6239,7 @@ class Mesh(metaclass = MeshMeta): Parameters: Tolerance: the value of tolerance - SubMeshOrGroup: :class:`sub-mesh, group or filter ` + SubMeshOrGroup: :class:`sub-mesh, group or filter ` or node IDs exceptNodes: list of either SubMeshes, Groups or node IDs to exclude from search SeparateCornerAndMediumNodes: if *True*, in quadratic mesh puts corner and medium nodes in separate groups thus preventing @@ -6191,11 +6252,16 @@ class Mesh(metaclass = MeshMeta): unRegister = genObjUnRegister() if (isinstance( SubMeshOrGroup, Mesh )): SubMeshOrGroup = SubMeshOrGroup.GetMesh() + if isinstance( SubMeshOrGroup, list ): + SubMeshOrGroup = self.GetIDSource( SubMeshOrGroup, SMESH.NODE ) + unRegister.set( SubMeshOrGroup ) + if not isinstance( exceptNodes, list ): exceptNodes = [ exceptNodes ] if exceptNodes and isinstance( exceptNodes[0], int ): exceptNodes = [ self.GetIDSource( exceptNodes, SMESH.NODE )] unRegister.set( exceptNodes ) + return self.editor.FindCoincidentNodesOnPartBut(SubMeshOrGroup, Tolerance, exceptNodes, SeparateCornerAndMediumNodes) @@ -6755,7 +6821,18 @@ class Mesh(metaclass = MeshMeta): segments[i].vector = seg.vector if isPreview: return editor.GetPreviewData() - return None + return None + + def MakeSlot(self, segmentGroup, width ): + """ + Create a slot of given width around given 1D elements lying on a triangle mesh. + The slot is consrtucted by cutting faces by cylindrical surfaces made + around each segment. Segments are expected to be created by MakePolyLine(). + + Returns: + FaceEdge's located at the slot boundary + """ + return self.editor.MakeSlot( segmentGroup, width ) def GetFunctor(self, funcType ): """ @@ -6856,11 +6933,15 @@ class Mesh(metaclass = MeshMeta): node1,node2,node3: IDs of the three nodes Returns: - Angle in radians + Angle in radians [0,PI]. -1 if failure case. """ - return self.smeshpyD.GetAngle( self.GetNodeXYZ( node1 ), - self.GetNodeXYZ( node2 ), - self.GetNodeXYZ( node3 )) + p1 = self.GetNodeXYZ( node1 ) + p2 = self.GetNodeXYZ( node2 ) + p3 = self.GetNodeXYZ( node3 ) + if p1 and p2 and p3: + return self.smeshpyD.GetAngle( p1,p2,p3 ) + return -1. + def GetMaxElementLength(self, elemId): """ diff --git a/src/StdMeshers/StdMeshers_Import_1D.cxx b/src/StdMeshers/StdMeshers_Import_1D.cxx index 20020982a..a313cd6a3 100644 --- a/src/StdMeshers/StdMeshers_Import_1D.cxx +++ b/src/StdMeshers/StdMeshers_Import_1D.cxx @@ -911,7 +911,7 @@ void StdMeshers_Import_1D::importMesh(const SMESH_Mesh* srcMesh, int nb = 1; while ( !namesByType[ srcGroupDS->GetType() ].insert( name ).second ) name = SMESH_Comment(srcGroup->GetName()) << "_imported_" << nb++; - SMESH_Group* newGroup = tgtMesh.AddGroup( srcGroupDS->GetType(), name.c_str(), nb ); + SMESH_Group* newGroup = tgtMesh.AddGroup( srcGroupDS->GetType(), name.c_str() ); SMESHDS_Group* newGroupDS = (SMESHDS_Group*)newGroup->GetGroupDS(); resultGroups.push_back( newGroup ); -- 2.30.2