From 650885c1351fd0ec14226de37a67dec62329d83f Mon Sep 17 00:00:00 2001 From: eap Date: Wed, 5 Feb 2020 14:58:45 +0300 Subject: [PATCH] #17237: Body fitting on sub-mesh, #16523: Treatment of internal faces --- doc/salome/examples/cartesian_algo.py | 27 +- .../gui/SMESH/images/cartesian3D_hyp.png | Bin 60056 -> 59740 bytes doc/salome/gui/SMESH/input/cartesian_algo.rst | 3 + idl/SMESH_BasicHypothesis.idl | 26 +- resources/StdMeshers.xml.in | 2 - src/Controls/SMESH_Controls.cxx | 23 +- src/SMESH/SMESH_MeshEditor.cxx | 3 +- src/SMESHDS/SMESHDS_SubMesh.cxx | 10 +- src/SMESHGUI/SMESHGUI_Hypotheses.cxx | 4 +- .../StdMeshers_CartesianParameters3D.cxx | 57 +- .../StdMeshers_CartesianParameters3D.hxx | 22 + src/StdMeshers/StdMeshers_Cartesian_3D.cxx | 2372 ++++++++++++++--- .../StdMeshersGUI_CartesianParamCreator.cxx | 21 +- .../StdMeshersGUI_CartesianParamCreator.h | 3 + src/StdMeshersGUI/StdMeshers_msg_en.ts | 12 + .../StdMeshers_CartesianParameters3D_i.cxx | 83 + .../StdMeshers_CartesianParameters3D_i.hxx | 27 +- 17 files changed, 2339 insertions(+), 356 deletions(-) diff --git a/doc/salome/examples/cartesian_algo.py b/doc/salome/examples/cartesian_algo.py index 26bdb8bf0..d7d64b8b7 100644 --- a/doc/salome/examples/cartesian_algo.py +++ b/doc/salome/examples/cartesian_algo.py @@ -14,10 +14,13 @@ smesh = smeshBuilder.New() # create a sphere sphere = geompy.MakeSphereR( 50 ) -geompy.addToStudy( sphere, "sphere" ) + +# cut the sphere by a box +box = geompy.MakeBoxDXDYDZ( 100, 100, 100 ) +partition = geompy.MakePartition([ sphere ], [ box ], theName="partition") # create a mesh and assign a "Body Fitting" algo -mesh = smesh.Mesh( sphere ) +mesh = smesh.Mesh( partition ) cartAlgo = mesh.BodyFitted() # define a cartesian grid using Coordinates @@ -38,7 +41,27 @@ mesh.Compute() print("nb hexahedra",mesh.NbHexas()) print("nb tetrahedra",mesh.NbTetras()) print("nb polyhedra",mesh.NbPolyhedrons()) +print("nb faces",mesh.NbFaces()) +print() + +# activate creation of faces +cartHyp.SetToCreateFaces( True ) + +mesh.Compute() +print("nb hexahedra",mesh.NbHexas()) +print("nb tetrahedra",mesh.NbTetras()) +print("nb polyhedra",mesh.NbPolyhedrons()) +print("nb faces",mesh.NbFaces()) +print() +# enable consideration of shared faces +cartHyp.SetToConsiderInternalFaces( True ) +mesh.Compute() +print("nb hexahedra",mesh.NbHexas()) +print("nb tetrahedra",mesh.NbTetras()) +print("nb polyhedra",mesh.NbPolyhedrons()) +print("nb faces",mesh.NbFaces()) +print() # define the grid by setting different spacing in 2 sub-ranges of geometry spaceFuns = ["5","10+10*t"] diff --git a/doc/salome/gui/SMESH/images/cartesian3D_hyp.png b/doc/salome/gui/SMESH/images/cartesian3D_hyp.png index c9a605fea0ca0cec4e0d8ba14290e00db423cac9..9f4c59a033bf1f2081553c72d6c564c2d25db971 100644 GIT binary patch literal 59740 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⩔JJQh7d;9zPP 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ℜ?aO6-=y3&k5*x{kP z>ankrlYucnXhM-Nppxq0cao`|qC3UyU`{KTUa6KS!SYj!K&&CcQ4 z#k#@a-EMcWI14f`lMH%K{r25#8~vZmd^8M7c1)8?n(96q@^MN(SAzo<=zM-&=AVn` z!bupD%Lf!~Ew?or@d|1 z`!glxi@Yp#%SY@dnWJ9ICao;WI+am#YG$q;UB1HfF~(?#E~k(nl#nxz?*2@0%jOoM z$xxn8%^aV&Sl+5OgJbLX{bTUpJ}lU1jDXA~LZ!P#lIrp#<$_ z;=wY2%Qn}jp3m^hLC9h@2S1dSR#3oz%!#ydJ)cqcnaZtwQnc$;IpvoG!8=gWOCApS zILtJsX$et?8Z6uJeU{%c`Hi~f`muQo3p2A2dP+c`um7XlIdByFwc@4ogMUf20da^c z@w_i*=zM5Y`OFAGFQcMDI3TY0`2_!3X>qb|N-z^D2ma$bBOAnC)q6<#A*LL_H~PRyHCIUNrd| z0w0kJ`m|j%hswky9dZmA&c#AZQoHUri2#aosR5>KiVq(q56A4sZY~ofKIr*{+3oA= z^JiLxGPy;a-hLK^#aIfjQggj?^PnEFM{?n@#W52Yw>0gUb(js3mSX|-w>C< ztIZVE9)G{)jpn)HQ@Z$Q`N?QC`Pc;N*7B@d_`_sW0q+7iA<{#q%MXLH*?nmL26+;8Xn+s_Q9v?s za)h`9**kWV_ufK9o(B#;8ucI!bA89`lllA+kcRvjQl<%_dPh-p7E^Z_Eqa+5m6oNo zkYUt`imb1z1GUkjugL26_v3Z?F|ox~tVmO1tCBB}B$ju+nvEq&;KmBwWZ%U;+XD}n z-FFNCL#lK>-HvnJX}>+N?Jyz96epMxqR*iYGI)2SA`b7CX1mmNmrHbu2L~rJ=d4#oZ-GtrT{kzkpi4-1 z2s{T>xl1jl9@MMR<*KYN4J~zJveYsN)##bo>q)BlCUSGSZ|7YLMaX+|1b*#zv>r|$ z5PwM@lRnfRU_W#R4Was_A%}VP{!)u#pbte5)}5D@a(1u}Hh#PbbmHAN#_jIX^h*xS zlp&&bup~B>}TFid$7046Hydu(-(N2vD;^tIvh>{bf^rm+x zADE^tA?q+Mc+_!wz-{|h6iAuT(ndHr?re~Vxic9CIblm=AYSO^RtV?`N@*^z*jh5e zT!0QYs2Zt*+6VN-o?>J~0n6~|mFL%WZZO`CNxu@2N;WumM*?Vy`y2C(=Va8@J8xF9 zNmj~q<{J$Cg%IBp|J)Ra;$N^EuxnMQIBqTJj#l;NXf+y_6kvCk3wOjU3XGf8{Afj_ zL3HX{QUFEAcQ6uZo@8B#E(H%V*dfjPQe$?g_IwWuZ!k-g^&NE=b>DzX$Kl2*qmc8ZB9cTtqlGn=}m@X;Tm4BuYUPr(S zRIZF4#Ln*Sk}S{9t7a8CF1OS1Z75063E^<$X{`!48hppeWYubDE6@PxmUd zspzU%f{y1dSoX0=IV~2xy3MO!@o~XizOpiFxVvJ!cW~AYRA?!nBz$WA zIR~_~L$Ivz=utx4F)dfMeI>tO9-&*m;sy$c9x6Bw+`Heoa1hUBmFH)km}u~}@|A3t zopZW-538z**kws7Y^j{~NyjUfQ)3h7x*G}o)u)S_8_%%$$q;Ip&X1ig-m)c;|EFk1 z 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 L{!Z>~gBSk;*etYI literal 60056 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 P @@ -570,7 +569,6 @@ support-submeshes="false" output ="HEXA" need-hyp ="true" - context ="GLOBAL" dim ="3"> Cartesian_3D=BodyFitted() diff --git a/src/Controls/SMESH_Controls.cxx b/src/Controls/SMESH_Controls.cxx index 95e95a2cf..fc7ba629b 100644 --- a/src/Controls/SMESH_Controls.cxx +++ b/src/Controls/SMESH_Controls.cxx @@ -4262,6 +4262,7 @@ private: bool isOutOfFace (const gp_Pnt& p); bool isOutOfEdge (const gp_Pnt& p); bool isOutOfVertex(const gp_Pnt& p); + bool isOutOfNone (const gp_Pnt& p) { return true; } bool isBox (const TopoDS_Shape& s); bool (Classifier::* myIsOutFun)(const gp_Pnt& p); @@ -4583,7 +4584,7 @@ bool ElementsOnShape::IsSatisfy (const SMDS_MeshNode* node, { isNodeOut = false; if ( okShape ) - *okShape = myWorkClassifiers[i]->Shape(); + *okShape = myClassifiers[i].Shape(); break; } } @@ -4621,17 +4622,27 @@ void ElementsOnShape::Classifier::Init( const TopoDS_Shape& theShape, { Standard_Real u1,u2,v1,v2; Handle(Geom_Surface) surf = BRep_Tool::Surface( TopoDS::Face( theShape )); - surf->Bounds( u1,u2,v1,v2 ); - myProjFace.Init(surf, u1,u2, v1,v2, myTol ); - myIsOutFun = & ElementsOnShape::Classifier::isOutOfFace; + if ( surf.IsNull() ) + myIsOutFun = & ElementsOnShape::Classifier::isOutOfNone; + else + { + surf->Bounds( u1,u2,v1,v2 ); + myProjFace.Init(surf, u1,u2, v1,v2, myTol ); + myIsOutFun = & ElementsOnShape::Classifier::isOutOfFace; + } break; } case TopAbs_EDGE: { Standard_Real u1, u2; Handle(Geom_Curve) curve = BRep_Tool::Curve( TopoDS::Edge( theShape ), u1, u2); - myProjEdge.Init(curve, u1, u2); - myIsOutFun = & ElementsOnShape::Classifier::isOutOfEdge; + if ( curve.IsNull() ) + myIsOutFun = & ElementsOnShape::Classifier::isOutOfNone; + else + { + myProjEdge.Init(curve, u1, u2); + myIsOutFun = & ElementsOnShape::Classifier::isOutOfEdge; + } break; } case TopAbs_VERTEX: diff --git a/src/SMESH/SMESH_MeshEditor.cxx b/src/SMESH/SMESH_MeshEditor.cxx index db5b6507f..2104c7920 100644 --- a/src/SMESH/SMESH_MeshEditor.cxx +++ b/src/SMESH/SMESH_MeshEditor.cxx @@ -161,8 +161,7 @@ SMESH_MeshEditor::ElemFeatures::Init( const SMDS_MeshElement* elem, bool basicOn myIsQuad = elem->IsQuadratic(); if ( myType == SMDSAbs_Volume && !basicOnly ) { - vector quant = static_cast( elem )->GetQuantities(); - myPolyhedQuantities.swap( quant ); + myPolyhedQuantities = static_cast( elem )->GetQuantities(); } } } diff --git a/src/SMESHDS/SMESHDS_SubMesh.cxx b/src/SMESHDS/SMESHDS_SubMesh.cxx index 503de16b0..8d5dee889 100644 --- a/src/SMESHDS/SMESHDS_SubMesh.cxx +++ b/src/SMESHDS/SMESHDS_SubMesh.cxx @@ -115,9 +115,12 @@ void SMESHDS_SubMesh::AddElement(const SMDS_MeshElement * elem) (LOCALIZED("add element in subshape already belonging to a subshape")); } } + else + { + ++myNbElements; + } elem->setShapeID( myIndex ); - myNbElements++; // remember element with smallest ID to optimize iteration on them add( elem ); @@ -178,8 +181,11 @@ void SMESHDS_SubMesh::AddNode(const SMDS_MeshNode * N) (LOCALIZED("a node being in sub-mesh is added to another sub-mesh")); return; // already in } + else + { + ++myNbNodes; + } N->setShapeID( myIndex ); - myNbNodes++; // remember node with smallest ID to optimize iteration on them add( N ); diff --git a/src/SMESHGUI/SMESHGUI_Hypotheses.cxx b/src/SMESHGUI/SMESHGUI_Hypotheses.cxx index b2e1e2496..03767040c 100644 --- a/src/SMESHGUI/SMESHGUI_Hypotheses.cxx +++ b/src/SMESHGUI/SMESHGUI_Hypotheses.cxx @@ -813,8 +813,8 @@ HypothesesSet::HypothesesSet( const QString& theSetName, : myUseCommonSize( useCommonSize ), myQuadDominated( isQuadDominated ), myHypoSetName( theSetName ), - myHypoList { mainHypos, altHypos, intHypos }, - myAlgoList { mainAlgos, altAlgos, intAlgos }, + myHypoList { mainHypos, altHypos, intHypos }, + myAlgoList { mainAlgos, altAlgos, intAlgos }, myIsAlgo( false ), myIsCustom( false ), myIndex( 0 ) diff --git a/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx b/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx index 21bc0bbb1..fe479bb1e 100644 --- a/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx +++ b/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx @@ -64,7 +64,10 @@ StdMeshers_CartesianParameters3D::StdMeshers_CartesianParameters3D(int h SMESH_Gen * gen) : SMESH_Hypothesis(hypId, gen), _sizeThreshold( 4.0 ), // default according to the customer specification - _toAddEdges( false ) + _toAddEdges( false ), + _toConsiderInternalFaces( false ), + _toUseThresholdForInternalFaces( false ), + _toCreateFaces( false ) { _name = "CartesianParameters3D"; // used by "Cartesian_3D" _param_algo_dim = 3; // 3D @@ -739,6 +742,48 @@ bool StdMeshers_CartesianParameters3D::GetToAddEdges() const return _toAddEdges; } +//======================================================================= +//function : SetToConsiderInternalFaces +//purpose : Enables treatment of geom faces either shared by solids or internal +//======================================================================= + +void StdMeshers_CartesianParameters3D::SetToConsiderInternalFaces(bool toTreat) +{ + if ( _toConsiderInternalFaces != toTreat ) + { + _toConsiderInternalFaces = toTreat; + NotifySubMeshesHypothesisModification(); + } +} + +//======================================================================= +//function : SetToUseThresholdForInternalFaces +//purpose : Enables applying size threshold to grid cells cut by internal geom faces. +//======================================================================= + +void StdMeshers_CartesianParameters3D::SetToUseThresholdForInternalFaces(bool toUse) +{ + if ( _toUseThresholdForInternalFaces != toUse ) + { + _toUseThresholdForInternalFaces = toUse; + NotifySubMeshesHypothesisModification(); + } +} + +//======================================================================= +//function : SetToCreateFaces +//purpose : Enables creation of mesh faces. +//======================================================================= + +void StdMeshers_CartesianParameters3D::SetToCreateFaces(bool toCreate) +{ + if ( _toCreateFaces != toCreate ) + { + _toCreateFaces = toCreate; + NotifySubMeshesHypothesisModification(); + } +} + //======================================================================= //function : IsDefined //purpose : Return true if parameters are well defined @@ -786,6 +831,10 @@ std::ostream & StdMeshers_CartesianParameters3D::SaveTo(std::ostream & save) for ( int i = 0; i < 3; ++i ) save << _fixedPoint[i] << " "; + save << " " << _toConsiderInternalFaces + << " " << _toUseThresholdForInternalFaces + << " " << _toCreateFaces; + return save; } @@ -844,6 +893,12 @@ std::istream & StdMeshers_CartesianParameters3D::LoadFrom(std::istream & load) for ( int i = 0; i < 3 && ok ; ++i ) ok = static_cast( load >> _fixedPoint[i]); + if ( load >> _toConsiderInternalFaces ) + { + load >> _toUseThresholdForInternalFaces; + load >> _toCreateFaces; + } + return load; } diff --git a/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx b/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx index 089d19f46..fc8ceff4c 100644 --- a/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx +++ b/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx @@ -139,6 +139,25 @@ public: void SetToAddEdges(bool toAdd); bool GetToAddEdges() const; + /*! + * \brief Enables treatment of geom faces either shared by solids or internal. + */ + void SetToConsiderInternalFaces(bool toTreat); + bool GetToConsiderInternalFaces() const { return _toConsiderInternalFaces; } + + /*! + * \brief Enables applying size threshold to grid cells cut by internal geom faces. + */ + void SetToUseThresholdForInternalFaces(bool toUse); + bool GetToUseThresholdForInternalFaces() const { return _toUseThresholdForInternalFaces; } + + /*! + * \brief Enables creation of mesh faces. + */ + void SetToCreateFaces(bool toCreate); + bool GetToCreateFaces() const { return _toCreateFaces; } + + /*! * \brief Return true if parameters are well defined */ @@ -171,6 +190,9 @@ public: double _sizeThreshold; bool _toAddEdges; + bool _toConsiderInternalFaces; + bool _toUseThresholdForInternalFaces; + bool _toCreateFaces; }; #endif diff --git a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx index 85a984c30..ea641c2b1 100644 --- a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx +++ b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx @@ -23,16 +23,22 @@ // Module : SMESH // #include "StdMeshers_Cartesian_3D.hxx" +#include "StdMeshers_CartesianParameters3D.hxx" +#include "ObjectPool.hxx" #include "SMDS_MeshNode.hxx" +#include "SMDS_VolumeTool.hxx" #include "SMESHDS_Mesh.hxx" #include "SMESH_Block.hxx" #include "SMESH_Comment.hxx" +#include "SMESH_ControlsDef.hxx" #include "SMESH_Mesh.hxx" +#include "SMESH_MeshAlgos.hxx" +#include "SMESH_MeshEditor.hxx" #include "SMESH_MesherHelper.hxx" #include "SMESH_subMesh.hxx" #include "SMESH_subMeshEventListener.hxx" -#include "StdMeshers_CartesianParameters3D.hxx" +#include "StdMeshers_FaceSide.hxx" #include #include @@ -89,6 +95,8 @@ #include +#include + //#undef WITH_TBB #ifdef WITH_TBB @@ -97,7 +105,6 @@ // Windows 10 = 0x0A00 #define WINVER 0x0A00 #define _WIN32_WINNT 0x0A00 - #endif #include @@ -105,6 +112,7 @@ #endif using namespace std; +using namespace SMESH; #ifdef _DEBUG_ //#define _MY_DEBUG_ @@ -161,7 +169,7 @@ bool StdMeshers_Cartesian_3D::CheckHypothesis (SMESH_Mesh& aMesh, namespace { - typedef int TGeomID; + typedef int TGeomID; // IDs of sub-shapes //============================================================================= // Definitions of internal utils @@ -170,7 +178,72 @@ namespace Trans_TANGENT = IntCurveSurface_Tangent, Trans_IN = IntCurveSurface_In, Trans_OUT = IntCurveSurface_Out, - Trans_APEX + Trans_APEX, + Trans_INTERNAL // for INTERNAL FACE + }; + // -------------------------------------------------------------------------- + /*! + * \brief Container of IDs of SOLID sub-shapes + */ + class Solid // sole SOLID contains all sub-shapes + { + TGeomID _id; // SOLID id + bool _hasInternalFaces; + public: + virtual ~Solid() {} + virtual bool Contains( TGeomID subID ) const { return true; } + virtual bool ContainsAny( const vector< TGeomID>& subIDs ) const { return true; } + virtual TopAbs_Orientation Orientation( const TopoDS_Shape& s ) const { return s.Orientation(); } + virtual bool IsOutsideOriented( TGeomID faceID ) const { return true; } + void SetID( TGeomID id ) { _id = id; } + TGeomID ID() const { return _id; } + void SetHasInternalFaces( bool has ) { _hasInternalFaces = has; } + bool HasInternalFaces() const { return _hasInternalFaces; } + }; + // -------------------------------------------------------------------------- + class OneOfSolids : public Solid + { + TColStd_MapOfInteger _subIDs; + TopTools_MapOfShape _faces; // keep FACE orientation + TColStd_MapOfInteger _outFaceIDs; // FACEs of shape_to_mesh oriented outside the SOLID + public: + void Init( const TopoDS_Shape& solid, + TopAbs_ShapeEnum subType, + const SMESHDS_Mesh* mesh ); + virtual bool Contains( TGeomID i ) const { return i == ID() || _subIDs.Contains( i ); } + virtual bool ContainsAny( const vector< TGeomID>& subIDs ) const + { + for ( size_t i = 0; i < subIDs.size(); ++i ) if ( Contains( subIDs[ i ])) return true; + return false; + } + virtual TopAbs_Orientation Orientation( const TopoDS_Shape& face ) const + { + const TopoDS_Shape& sInMap = const_cast< OneOfSolids* >(this)->_faces.Added( face ); + return sInMap.Orientation(); + } + virtual bool IsOutsideOriented( TGeomID faceID ) const + { + return faceID == 0 || _outFaceIDs.Contains( faceID ); + } + }; + // -------------------------------------------------------------------------- + /*! + * \brief Geom data + */ + struct Geometry + { + TopoDS_Shape _mainShape; + vector< vector< TGeomID > > _solidIDsByShapeID;// V/E/F ID -> SOLID IDs + Solid _soleSolid; + map< TGeomID, OneOfSolids > _solidByID; + TColStd_MapOfInteger _boundaryFaces; // FACEs on boundary of mesh->ShapeToMesh() + TColStd_MapOfInteger _strangeEdges; // EDGEs shared by strange FACEs + TGeomID _extIntFaceID; // pseudo FACE - extension of INTERNAL FACE + + Controls::ElementsOnShape _edgeClassifier; + Controls::ElementsOnShape _vertexClassifier; + + bool IsOneSolid() const { return _solidByID.size() < 2; } }; // -------------------------------------------------------------------------- /*! @@ -194,6 +267,7 @@ namespace struct F_IntersectPoint : public B_IntersectPoint { double _paramOnLine; + double _u, _v; mutable Transition _transition; mutable size_t _indexOnLine; @@ -207,7 +281,7 @@ namespace { gp_Pnt _point; double _uvw[3]; - TGeomID _shapeID; + TGeomID _shapeID; // ID of EDGE or VERTEX }; // -------------------------------------------------------------------------- /*! @@ -220,7 +294,9 @@ namespace multiset< F_IntersectPoint > _intPoints; void RemoveExcessIntPoints( const double tol ); - bool GetIsOutBefore( multiset< F_IntersectPoint >::iterator ip, bool prevIsOut ); + TGeomID GetSolidIDBefore( multiset< F_IntersectPoint >::iterator ip, + const TGeomID prevID, + const Geometry& geom); }; // -------------------------------------------------------------------------- /*! @@ -249,7 +325,7 @@ namespace { _size[0] = sz1; _size[1] = sz2; _size[2] = sz3; _curInd[0] = _curInd[1] = _curInd[2] = 0; - _iVar1 = iv1; _iVar2 = iv2; _iConst = iConst; + _iVar1 = iv1; _iVar2 = iv2; _iConst = iConst; _name1 = nv1; _name2 = nv2; _nameConst = nConst; } @@ -288,9 +364,16 @@ namespace vector< const SMDS_MeshNode* > _nodes; // mesh nodes at grid nodes vector< const F_IntersectPoint* > _gridIntP; // grid node intersection with geometry + ObjectPool< E_IntersectPoint > _edgeIntPool; // intersections with EDGEs + ObjectPool< F_IntersectPoint > _extIntPool; // intersections with extended INTERNAL FACEs + //list< E_IntersectPoint > _edgeIntP; // intersections with EDGEs - list< E_IntersectPoint > _edgeIntP; // intersections with EDGEs - TopTools_IndexedMapOfShape _shapes; + Geometry _geometry; + bool _toAddEdges; + bool _toCreateFaces; + bool _toConsiderInternalFaces; + bool _toUseThresholdForInternalFaces; + double _sizeThreshold; SMESH_MesherHelper* _helper; @@ -308,6 +391,43 @@ namespace LineIndexer GetLineIndexer(size_t iDir) const; + E_IntersectPoint* Add( const E_IntersectPoint& ip ) + { + E_IntersectPoint* eip = _edgeIntPool.getNew(); + *eip = ip; + return eip; + } + void Remove( E_IntersectPoint* eip ) { _edgeIntPool.destroy( eip ); } + + TGeomID ShapeID( const TopoDS_Shape& s ) const; + const TopoDS_Shape& Shape( TGeomID id ) const; + TopAbs_ShapeEnum ShapeType( TGeomID id ) const { return Shape(id).ShapeType(); } + void InitGeometry( const TopoDS_Shape& theShape ); + void InitClassifier( const TopoDS_Shape& mainShape, + TopAbs_ShapeEnum shapeType, + Controls::ElementsOnShape& classifier ); + void GetEdgesToImplement( map< TGeomID, vector< TGeomID > > & edge2faceMap, + const TopoDS_Shape& shape, + const vector< TopoDS_Shape >& faces ); + void SetSolidFather( const TopoDS_Shape& s, const TopoDS_Shape& theShapeToMesh ); + bool IsShared( TGeomID faceID ) const; + bool IsAnyShared( const std::vector< TGeomID >& faceIDs ) const; + bool IsInternal( TGeomID faceID ) const { + return ( faceID == PseudoIntExtFaceID() || + Shape( faceID ).Orientation() == TopAbs_INTERNAL ); } + bool IsSolid( TGeomID shapeID ) const { + if ( _geometry.IsOneSolid() ) return _geometry._soleSolid.ID() == shapeID; + else return _geometry._solidByID.count( shapeID ); } + bool IsStrangeEdge( TGeomID id ) const { return _geometry._strangeEdges.Contains( id ); } + TGeomID PseudoIntExtFaceID() const { return _geometry._extIntFaceID; } + Solid* GetSolid( TGeomID solidID = 0 ); + Solid* GetOneOfSolids( TGeomID solidID ); + const vector< TGeomID > & GetSolidIDs( TGeomID subShapeID ) const; + bool IsCorrectTransition( TGeomID faceID, const Solid* solid ); + bool IsBoundaryFace( TGeomID face ) const { return _geometry._boundaryFaces.Contains( face ); } + void SetOnShape( const SMDS_MeshNode* n, const F_IntersectPoint& ip, bool unset=false ); + bool IsToCheckNodePos() const { return !_toAddEdges && _toCreateFaces; } + void SetCoordinates(const vector& xCoords, const vector& yCoords, const vector& zCoords, @@ -317,6 +437,49 @@ namespace void ComputeNodes(SMESH_MesherHelper& helper); }; // -------------------------------------------------------------------------- + /*! + * \brief Return cells sharing a link + */ + struct CellsAroundLink + { + int _dInd[4][3]; + size_t _nbCells[3]; + int _i,_j,_k; + Grid* _grid; + + CellsAroundLink( Grid* grid, int iDir ): + _dInd{ {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} }, + _nbCells{ grid->_coords[0].size() - 1, + grid->_coords[1].size() - 1, + grid->_coords[2].size() - 1 }, + _grid( grid ) + { + const int iDirOther[3][2] = {{ 1,2 },{ 0,2 },{ 0,1 }}; + _dInd[1][ iDirOther[iDir][0] ] = -1; + _dInd[2][ iDirOther[iDir][1] ] = -1; + _dInd[3][ iDirOther[iDir][0] ] = -1; _dInd[3][ iDirOther[iDir][1] ] = -1; + } + void Init( int i, int j, int k, int link12 = 0 ) + { + int iL = link12 % 4; + _i = i - _dInd[iL][0]; + _j = j - _dInd[iL][1]; + _k = k - _dInd[iL][2]; + } + bool GetCell( int iL, int& i, int& j, int& k, int& cellIndex ) + { + i = _i + _dInd[iL][0]; + j = _j + _dInd[iL][1]; + k = _k + _dInd[iL][2]; + if ( i < 0 || i >= (int)_nbCells[0] || + j < 0 || j >= (int)_nbCells[1] || + k < 0 || k >= (int)_nbCells[2] ) + return false; + cellIndex = _grid->CellIndex( i,j,k ); + return true; + } + }; + // -------------------------------------------------------------------------- /*! * \brief Intersector of TopoDS_Face with all GridLine's */ @@ -336,7 +499,7 @@ namespace { for ( size_t i = 0; i < _intersections.size(); ++i ) { - multiset< F_IntersectPoint >::iterator ip = + multiset< F_IntersectPoint >::iterator ip = _intersections[i].first->_intPoints.insert( _intersections[i].second ); ip->_faceIDs.reserve( 1 ); ip->_faceIDs.push_back( _faceID ); @@ -368,7 +531,7 @@ namespace { double _tol; double _u, _v, _w; // params on the face and the line - Transition _transition; // transition of at intersection (see IntCurveSurface.cdl) + Transition _transition; // transition at intersection (see IntCurveSurface.cdl) Transition _transIn, _transOut; // IN and OUT transitions depending of face orientation gp_Pln _plane; @@ -406,36 +569,31 @@ namespace // -------------------------------------------------------------------------------- struct _Face; struct _Link; + enum IsInternalFlag { IS_NOT_INTERNAL, IS_INTERNAL, IS_CUT_BY_INTERNAL_FACE }; // -------------------------------------------------------------------------------- struct _Node //!< node either at a hexahedron corner or at intersection { const SMDS_MeshNode* _node; // mesh node at hexahedron corner const B_IntersectPoint* _intPoint; const _Face* _usedInFace; + char _isInternalFlags; _Node(const SMDS_MeshNode* n=0, const B_IntersectPoint* ip=0) - :_node(n), _intPoint(ip), _usedInFace(0) {} + :_node(n), _intPoint(ip), _usedInFace(0), _isInternalFlags(0) {} const SMDS_MeshNode* Node() const { return ( _intPoint && _intPoint->_node ) ? _intPoint->_node : _node; } const E_IntersectPoint* EdgeIntPnt() const { return static_cast< const E_IntersectPoint* >( _intPoint ); } + const F_IntersectPoint* FaceIntPnt() const + { return static_cast< const F_IntersectPoint* >( _intPoint ); } + const vector< TGeomID >& faces() const { return _intPoint->_faceIDs; } + TGeomID face(size_t i) const { return _intPoint->_faceIDs[ i ]; } + void SetInternal( IsInternalFlag intFlag ) { _isInternalFlags |= intFlag; } + bool IsCutByInternal() const { return _isInternalFlags & IS_CUT_BY_INTERNAL_FACE; } bool IsUsedInFace( const _Face* polygon = 0 ) { return polygon ? ( _usedInFace == polygon ) : bool( _usedInFace ); } - void Add( const E_IntersectPoint* ip ) - { - if ( !_intPoint ) { - _intPoint = ip; - } - else if ( !_intPoint->_node ) { - ip->Add( _intPoint->_faceIDs ); - _intPoint = ip; - } - else { - _intPoint->Add( ip->_faceIDs ); - } - } TGeomID IsLinked( const B_IntersectPoint* other, TGeomID avoidFace=-1 ) const // returns id of a common face { @@ -448,7 +606,7 @@ namespace gp_Pnt Point() const { if ( const SMDS_MeshNode* n = Node() ) - return SMESH_TNodeXYZ( n ); + return SMESH_NodeXYZ( n ); if ( const E_IntersectPoint* eip = dynamic_cast< const E_IntersectPoint* >( _intPoint )) return eip->_point; @@ -460,6 +618,27 @@ namespace return eip->_shapeID; return 0; } + void Add( const E_IntersectPoint* ip ) + { + // Possible cases before Add(ip): + /// 1) _node != 0 --> _Node at hex corner ( _intPoint == 0 || _intPoint._node == 0 ) + /// 2) _node == 0 && _intPoint._node != 0 --> link intersected by FACE + /// 3) _node == 0 && _intPoint._node == 0 --> _Node at EDGE intersection + // + // If ip is added in cases 1) and 2) _node position must be changed to ip._shapeID + // at creation of elements + // To recognize this case, set _intPoint._node = Node() + const SMDS_MeshNode* node = Node(); + if ( !_intPoint ) { + _intPoint = ip; + } + else { + ip->Add( _intPoint->_faceIDs ); + _intPoint = ip; + } + if ( node ) + _node = _intPoint->_node = node; + } }; // -------------------------------------------------------------------------------- struct _Link // link connecting two _Node's @@ -469,7 +648,7 @@ namespace vector< const F_IntersectPoint* > _fIntPoints; // GridLine intersections with FACEs vector< _Node* > _fIntNodes; // _Node's at _fIntPoints vector< _Link > _splits; - _Link() { _faces[0] = 0; } + _Link(): _faces{ 0, 0 } {} }; // -------------------------------------------------------------------------------- struct _OrientedLink @@ -541,6 +720,43 @@ namespace } }; // -------------------------------------------------------------------------------- + struct _SplitIterator //! set to _hexLinks splits on one side of INTERNAL FACEs + { + struct _Split // data of a link split + { + int _linkID; // hex link ID + _Node* _nodes[2]; + int _iCheckIteration; // iteration where split is tried as Hexahedron split + _Link* _checkedSplit; // split set to hex links + bool _isUsed; // used in a volume + + _Split( _Link & split, int iLink ): + _linkID( iLink ), _nodes{ split._nodes[0], split._nodes[1] }, + _iCheckIteration( 0 ), _isUsed( false ) + {} + bool IsCheckedOrUsed( bool used ) const { return used ? _isUsed : _iCheckIteration > 0; } + }; + _Link* _hexLinks; + std::vector< _Split > _splits; + int _iterationNb; + size_t _nbChecked; + size_t _nbUsed; + std::vector< _Node* > _freeNodes; // nodes reached while composing a split set + + _SplitIterator( _Link* hexLinks ): + _hexLinks( hexLinks ), _iterationNb(0), _nbChecked(0), _nbUsed(0) + { + _freeNodes.reserve( 12 ); + _splits.reserve( 24 ); + for ( int iL = 0; iL < 12; ++iL ) + for ( size_t iS = 0; iS < _hexLinks[ iL ]._splits.size(); ++iS ) + _splits.emplace_back( _hexLinks[ iL ]._splits[ iS ], iL ); + Next(); + } + bool More() const { return _nbUsed < _splits.size(); } + bool Next(); + }; + // -------------------------------------------------------------------------------- struct _Face { vector< _OrientedLink > _links; // links on GridLine's @@ -573,14 +789,37 @@ namespace // -------------------------------------------------------------------------------- struct _volumeDef // holder of nodes of a volume mesh element { - vector< _Node* > _nodes; - vector< int > _quantities; - typedef boost::shared_ptr<_volumeDef> Ptr; - void set( const vector< _Node* >& nodes, - const vector< int >& quant = vector< int >() ) - { _nodes = nodes; _quantities = quant; } - void set( _Node** nodes, int nb ) + struct _nodeDef + { + const SMDS_MeshNode* _node; // mesh node at hexahedron corner + const B_IntersectPoint* _intPoint; + + _nodeDef( _Node* n ): _node( n->_node), _intPoint( n->_intPoint ) {} + const SMDS_MeshNode* Node() const + { return ( _intPoint && _intPoint->_node ) ? _intPoint->_node : _node; } + const E_IntersectPoint* EdgeIntPnt() const + { return static_cast< const E_IntersectPoint* >( _intPoint ); } + }; + vector< _nodeDef > _nodes; + vector< int > _quantities; + _volumeDef* _next; // to store several _volumeDefs in a chain + TGeomID _solidID; + const SMDS_MeshElement* _volume; // new volume + + _volumeDef(): _next(0), _solidID(0), _volume(0) {} + ~_volumeDef() { delete _next; } + _volumeDef( _volumeDef& other ): + _next(0), _solidID( other._solidID ), _volume( other._volume ) + { _nodes.swap( other._nodes ); _quantities.swap( other._quantities ); other._volume = 0; } + + void Set( const vector< _Node* >& nodes, const vector< int >& quant = vector< int >() ) + { _nodes.assign( nodes.begin(), nodes.end() ); _quantities = quant; } + + void Set( _Node** nodes, int nb ) { _nodes.assign( nodes, nodes + nb ); } + + void SetNext( _volumeDef* vd ) + { if ( _next ) { _next->SetNext( vd ); } else { _next = vd; }} }; // topology of a hexahedron @@ -602,26 +841,33 @@ namespace vector< _Node* > _vIntNodes; // computed volume elements - //vector< _volumeDef::Ptr > _volumeDefs; _volumeDef _volumeDefs; Grid* _grid; - double _sizeThreshold, _sideLength[3]; + double _sideLength[3]; int _nbCornerNodes, _nbFaceIntNodes, _nbBndNodes; int _origNodeInd; // index of _hexNodes[0] node within the _grid size_t _i,_j,_k; + bool _hasTooSmall; + +#ifdef _DEBUG_ + int _cellID; +#endif public: - Hexahedron(const double sizeThreshold, Grid* grid); + Hexahedron(Grid* grid); int MakeElements(SMESH_MesherHelper& helper, const map< TGeomID, vector< TGeomID > >& edge2faceIDsMap); - void ComputeElements(); - void Init() { init( _i, _j, _k ); } + void ComputeElements( const Solid* solid = 0, int solidIndex = -1 ); private: - Hexahedron(const Hexahedron& other ); - void init( size_t i, size_t j, size_t k ); + Hexahedron(const Hexahedron& other, size_t i, size_t j, size_t k, int cellID ); + void init( size_t i, size_t j, size_t k, const Solid* solid=0 ); void init( size_t i ); + void setIJK( size_t i ); + bool compute( const Solid* solid, const IsInternalFlag intFlag ); + vector< TGeomID > getSolids(); + bool isCutByInternalFace( IsInternalFlag & maxFlag ); void addEdges(SMESH_MesherHelper& helper, vector< Hexahedron* >& intersectedHex, const map< TGeomID, vector< TGeomID > >& edge2faceIDsMap); @@ -629,7 +875,7 @@ namespace double proj, BRepAdaptor_Curve& curve, const gp_XYZ& axis, const gp_XYZ& origin ); int getEntity( const E_IntersectPoint* ip, int* facets, int& sub ); - bool addIntersection( const E_IntersectPoint& ip, + bool addIntersection( const E_IntersectPoint* ip, vector< Hexahedron* >& hexes, int ijk[], int dIJK[] ); bool findChain( _Node* n1, _Node* n2, _Face& quad, vector<_Node*>& chainNodes ); @@ -640,11 +886,22 @@ namespace size_t & iS, _Face& quad, vector<_Node*>& chn); - int addElements(SMESH_MesherHelper& helper); - bool isOutPoint( _Link& link, int iP, SMESH_MesherHelper& helper ) const; + int addVolumes(SMESH_MesherHelper& helper ); + void addFaces( SMESH_MesherHelper& helper, + const vector< const SMDS_MeshElement* > & boundaryVolumes ); + void addSegments( SMESH_MesherHelper& helper, + const map< TGeomID, vector< TGeomID > >& edge2faceIDsMap ); + void getVolumes( vector< const SMDS_MeshElement* > & volumes ); + void getBoundaryElems( vector< const SMDS_MeshElement* > & boundaryVolumes ); + TGeomID getAnyFace() const; + void cutByExtendedInternal( std::vector< Hexahedron* >& hexes, + const TColStd_MapOfInteger& intEdgeIDs ); + gp_Pnt mostDistantInternalPnt( int hexIndex, const gp_Pnt& p1, const gp_Pnt& p2 ); + bool isOutPoint( _Link& link, int iP, SMESH_MesherHelper& helper, const Solid* solid ) const; void sortVertexNodes(vector<_Node*>& nodes, _Node* curNode, TGeomID face); bool isInHole() const; - bool checkPolyhedronSize() const; + bool hasStrangeEdge() const; + bool checkPolyhedronSize( bool isCutByInternalFace ) const; bool addHexa (); bool addTetra(); bool addPenta(); @@ -660,8 +917,16 @@ namespace return nodes[i]; return 0; } - bool isImplementEdges() const { return !_grid->_edgeIntP.empty(); } + bool isImplementEdges() const { return _grid->_edgeIntPool.nbElements(); } bool isOutParam(const double uvw[3]) const; + + typedef boost::container::flat_map< TGeomID, size_t > TID2Nb; + static void insertAndIncrement( TGeomID id, TID2Nb& id2nbMap ) + { + TID2Nb::value_type s0( id, 0 ); + TID2Nb::iterator id2nb = id2nbMap.insert( s0 ).first; + id2nb->second++; + } }; #ifdef WITH_TBB @@ -756,29 +1021,72 @@ namespace } //================================================================================ /* - * Return "is OUT" state for nodes before the given intersection point + * Return ID of SOLID for nodes before the given intersection point */ - bool GridLine::GetIsOutBefore( multiset< F_IntersectPoint >::iterator ip, bool prevIsOut ) + TGeomID GridLine::GetSolidIDBefore( multiset< F_IntersectPoint >::iterator ip, + const TGeomID prevID, + const Geometry& geom ) { - if ( ip->_transition == Trans_IN ) - return true; - if ( ip->_transition == Trans_OUT ) - return false; - if ( ip->_transition == Trans_APEX ) + if ( ip == _intPoints.begin() ) + return 0; + + if ( geom.IsOneSolid() ) { - // singularity point (apex of a cone) - if ( _intPoints.size() == 1 || ip == _intPoints.begin() ) - return true; - multiset< F_IntersectPoint >::iterator ipBef = ip, ipAft = ++ip; - if ( ipAft == _intPoints.end() ) - return false; - --ipBef; - if ( ipBef->_transition != ipAft->_transition ) - return ( ipBef->_transition == Trans_OUT ); - return ( ipBef->_transition != Trans_OUT ); + bool isOut = true; + switch ( ip->_transition ) { + case Trans_IN: isOut = true; break; + case Trans_OUT: isOut = false; break; + case Trans_TANGENT: isOut = ( prevID == 0 ); break; + case Trans_APEX: + { + // singularity point (apex of a cone) + multiset< F_IntersectPoint >::iterator ipBef = ip, ipAft = ++ip; + if ( ipAft == _intPoints.end() ) + isOut = false; + else + { + --ipBef; + if ( ipBef->_transition != ipAft->_transition ) + isOut = ( ipBef->_transition == Trans_OUT ); + else + isOut = ( ipBef->_transition != Trans_OUT ); + } + break; + } + case Trans_INTERNAL: isOut = false; + default:; + } + return isOut ? 0 : geom._soleSolid.ID(); + } + + const vector< TGeomID >& solids = geom._solidIDsByShapeID[ ip->_faceIDs[ 0 ]]; + + --ip; + if ( ip->_transition == Trans_INTERNAL ) + return prevID; + + const vector< TGeomID >& solidsBef = geom._solidIDsByShapeID[ ip->_faceIDs[ 0 ]]; + + if ( ip->_transition == Trans_IN || + ip->_transition == Trans_OUT ) + { + if ( solidsBef.size() == 1 ) + return ( solidsBef[0] == prevID ) ? 0 : solidsBef[0]; + + return solidsBef[ solidsBef[0] == prevID ]; + } + + if ( solidsBef.size() == 1 ) + return solidsBef[0]; + + for ( size_t i = 0; i < solids.size(); ++i ) + { + vector< TGeomID >::const_iterator it = + std::find( solidsBef.begin(), solidsBef.end(), solids[i] ); + if ( it != solidsBef.end() ) + return solids[i]; } - // _transition == Trans_TANGENT - return !prevIsOut; + return 0; } //================================================================================ /* @@ -824,6 +1132,35 @@ namespace return ( it != _faceIDs.end() ); } //================================================================================ + /* + * OneOfSolids initialization + */ + void OneOfSolids::Init( const TopoDS_Shape& solid, + TopAbs_ShapeEnum subType, + const SMESHDS_Mesh* mesh ) + { + SetID( mesh->ShapeToIndex( solid )); + + if ( subType == TopAbs_FACE ) + SetHasInternalFaces( false ); + + for ( TopExp_Explorer sub( solid, subType ); sub.More(); sub.Next() ) + { + _subIDs.Add( mesh->ShapeToIndex( sub.Current() )); + if ( subType == TopAbs_FACE ) + { + _faces.Add( sub.Current() ); + if ( sub.Current().Orientation() == TopAbs_INTERNAL ) + SetHasInternalFaces( true ); + + TGeomID faceID = mesh->ShapeToIndex( sub.Current() ); + if ( sub.Current().Orientation() == TopAbs_INTERNAL || + sub.Current().Orientation() == mesh->IndexToShape( faceID ).Orientation() ) + _outFaceIDs.Add( faceID ); + } + } + } + //================================================================================ /* * Return an iterator on GridLine's in a given direction */ @@ -928,6 +1265,290 @@ namespace } } } + //================================================================================ + /* + * Return local ID of shape + */ + TGeomID Grid::ShapeID( const TopoDS_Shape& s ) const + { + return _helper->GetMeshDS()->ShapeToIndex( s ); + } + //================================================================================ + /* + * Return a shape by its local ID + */ + const TopoDS_Shape& Grid::Shape( TGeomID id ) const + { + return _helper->GetMeshDS()->IndexToShape( id ); + } + //================================================================================ + /* + * Initialize _geometry + */ + void Grid::InitGeometry( const TopoDS_Shape& theShapeToMesh ) + { + SMESH_Mesh* mesh = _helper->GetMesh(); + + _geometry._mainShape = theShapeToMesh; + _geometry._extIntFaceID = mesh->GetMeshDS()->MaxShapeIndex() * 100; + _geometry._soleSolid.SetID( 0 ); + _geometry._soleSolid.SetHasInternalFaces( false ); + + InitClassifier( theShapeToMesh, TopAbs_VERTEX, _geometry._vertexClassifier ); + InitClassifier( theShapeToMesh, TopAbs_EDGE , _geometry._edgeClassifier ); + + TopExp_Explorer solidExp( theShapeToMesh, TopAbs_SOLID ); + + bool isSeveralSolids = false; + if ( _toConsiderInternalFaces ) // check nb SOLIDs + { + solidExp.Next(); + isSeveralSolids = solidExp.More(); + _toConsiderInternalFaces = isSeveralSolids; + solidExp.ReInit(); + + if ( !isSeveralSolids ) // look for an internal FACE + { + TopExp_Explorer fExp( theShapeToMesh, TopAbs_FACE ); + for ( ; fExp.More() && !_toConsiderInternalFaces; fExp.Next() ) + _toConsiderInternalFaces = ( fExp.Current().Orientation() == TopAbs_INTERNAL ); + + _geometry._soleSolid.SetHasInternalFaces( _toConsiderInternalFaces ); + _geometry._soleSolid.SetID( ShapeID( solidExp.Current() )); + } + else // fill Geometry::_solidByID + { + for ( ; solidExp.More(); solidExp.Next() ) + { + OneOfSolids & solid = _geometry._solidByID[ ShapeID( solidExp.Current() )]; + solid.Init( solidExp.Current(), TopAbs_FACE, mesh->GetMeshDS() ); + solid.Init( solidExp.Current(), TopAbs_EDGE, mesh->GetMeshDS() ); + solid.Init( solidExp.Current(), TopAbs_VERTEX, mesh->GetMeshDS() ); + } + } + } + else + { + _geometry._soleSolid.SetID( ShapeID( solidExp.Current() )); + } + + if ( !_toCreateFaces ) + { + int nbSolidsGlobal = _helper->Count( mesh->GetShapeToMesh(), TopAbs_SOLID, false ); + int nbSolidsLocal = _helper->Count( theShapeToMesh, TopAbs_SOLID, false ); + _toCreateFaces = ( nbSolidsLocal < nbSolidsGlobal ); + } + + TopTools_IndexedMapOfShape faces; + if ( _toCreateFaces || isSeveralSolids ) + TopExp::MapShapes( theShapeToMesh, TopAbs_FACE, faces ); + + // find boundary FACEs on boundary of mesh->ShapeToMesh() + if ( _toCreateFaces ) + for ( int i = 1; i <= faces.Size(); ++i ) + if ( faces(i).Orientation() != TopAbs_INTERNAL && + _helper->NbAncestors( faces(i), *mesh, TopAbs_SOLID ) == 1 ) + { + _geometry._boundaryFaces.Add( ShapeID( faces(i) )); + } + + if ( isSeveralSolids ) + for ( int i = 1; i <= faces.Size(); ++i ) + { + SetSolidFather( faces(i), theShapeToMesh ); + for ( TopExp_Explorer eExp( faces(i), TopAbs_EDGE ); eExp.More(); eExp.Next() ) + { + const TopoDS_Edge& edge = TopoDS::Edge( eExp.Current() ); + SetSolidFather( edge, theShapeToMesh ); + SetSolidFather( _helper->IthVertex( 0, edge ), theShapeToMesh ); + SetSolidFather( _helper->IthVertex( 1, edge ), theShapeToMesh ); + } + } + return; + } + //================================================================================ + /* + * Store ID of SOLID as father of its child shape ID + */ + void Grid::SetSolidFather( const TopoDS_Shape& s, const TopoDS_Shape& theShapeToMesh ) + { + if ( _geometry._solidIDsByShapeID.empty() ) + _geometry._solidIDsByShapeID.resize( _helper->GetMeshDS()->MaxShapeIndex() + 1 ); + + vector< TGeomID > & solidIDs = _geometry._solidIDsByShapeID[ ShapeID( s )]; + if ( !solidIDs.empty() ) + return; + solidIDs.reserve(2); + PShapeIteratorPtr solidIt = _helper->GetAncestors( s, + *_helper->GetMesh(), + TopAbs_SOLID, + & theShapeToMesh ); + while ( const TopoDS_Shape* solid = solidIt->next() ) + solidIDs.push_back( ShapeID( *solid )); + } + //================================================================================ + /* + * Return IDs of solids given sub-shape belongs to + */ + const vector< TGeomID > & Grid::GetSolidIDs( TGeomID subShapeID ) const + { + return _geometry._solidIDsByShapeID[ subShapeID ]; + } + //================================================================================ + /* + * Check if a sub-shape belongs to several SOLIDs + */ + bool Grid::IsShared( TGeomID shapeID ) const + { + return !_geometry.IsOneSolid() && ( _geometry._solidIDsByShapeID[ shapeID ].size() > 1 ); + } + //================================================================================ + /* + * Check if any of FACEs belongs to several SOLIDs + */ + bool Grid::IsAnyShared( const std::vector< TGeomID >& faceIDs ) const + { + for ( size_t i = 0; i < faceIDs.size(); ++i ) + if ( IsShared( faceIDs[ i ])) + return true; + return false; + } + //================================================================================ + /* + * Return Solid by ID + */ + Solid* Grid::GetSolid( TGeomID solidID ) + { + if ( !solidID || _geometry.IsOneSolid() || _geometry._solidByID.empty() ) + return & _geometry._soleSolid; + + return & _geometry._solidByID[ solidID ]; + } + //================================================================================ + /* + * Return OneOfSolids by ID + */ + Solid* Grid::GetOneOfSolids( TGeomID solidID ) + { + map< TGeomID, OneOfSolids >::iterator is2s = _geometry._solidByID.find( solidID ); + if ( is2s != _geometry._solidByID.end() ) + return & is2s->second; + + return & _geometry._soleSolid; + } + //================================================================================ + /* + * Check if transition on given FACE is correct for a given SOLID + */ + bool Grid::IsCorrectTransition( TGeomID faceID, const Solid* solid ) + { + if ( _geometry.IsOneSolid() ) + return true; + + const vector< TGeomID >& solidIDs = _geometry._solidIDsByShapeID[ faceID ]; + return solidIDs[0] == solid->ID(); + } + + //================================================================================ + /* + * Assign to geometry a node at FACE intersection + */ + void Grid::SetOnShape( const SMDS_MeshNode* n, const F_IntersectPoint& ip, bool unset ) + { + TopoDS_Shape s; + SMESHDS_Mesh* mesh = _helper->GetMeshDS(); + if ( ip._faceIDs.size() == 1 ) + { + mesh->SetNodeOnFace( n, ip._faceIDs[0], ip._u, ip._v ); + } + else if ( _geometry._vertexClassifier.IsSatisfy( n, &s )) + { + if ( unset ) mesh->UnSetNodeOnShape( n ); + mesh->SetNodeOnVertex( n, TopoDS::Vertex( s )); + } + else if ( _geometry._edgeClassifier.IsSatisfy( n, &s )) + { + if ( unset ) mesh->UnSetNodeOnShape( n ); + mesh->SetNodeOnEdge( n, TopoDS::Edge( s )); + } + else if ( ip._faceIDs.size() > 0 ) + { + mesh->SetNodeOnFace( n, ip._faceIDs[0], ip._u, ip._v ); + } + else if ( !unset && _geometry.IsOneSolid() ) + { + mesh->SetNodeInVolume( n, _geometry._soleSolid.ID() ); + } + } + //================================================================================ + /* + * Initialize a classifier + */ + void Grid::InitClassifier( const TopoDS_Shape& mainShape, + TopAbs_ShapeEnum shapeType, + Controls::ElementsOnShape& classifier ) + { + TopTools_IndexedMapOfShape shapes; + TopExp::MapShapes( mainShape, shapeType, shapes ); + + TopoDS_Compound compound; BRep_Builder builder; + builder.MakeCompound( compound ); + for ( int i = 1; i <= shapes.Size(); ++i ) + builder.Add( compound, shapes(i) ); + + classifier.SetMesh( _helper->GetMeshDS() ); + //classifier.SetTolerance( _tol ); // _tol is not initialised + classifier.SetShape( compound, SMDSAbs_Node ); + } + + //================================================================================ + /* + * Return EDGEs with FACEs to implement into the mesh + */ + void Grid::GetEdgesToImplement( map< TGeomID, vector< TGeomID > > & edge2faceIDsMap, + const TopoDS_Shape& shape, + const vector< TopoDS_Shape >& faces ) + { + // check if there are strange EDGEs + TopTools_IndexedMapOfShape faceMap; + TopExp::MapShapes( _helper->GetMesh()->GetShapeToMesh(), TopAbs_FACE, faceMap ); + int nbFacesGlobal = faceMap.Size(); + faceMap.Clear( false ); + TopExp::MapShapes( shape, TopAbs_FACE, faceMap ); + int nbFacesLocal = faceMap.Size(); + bool hasStrangeEdges = ( nbFacesGlobal > nbFacesLocal ); + if ( !_toAddEdges && !hasStrangeEdges ) + return; // no FACEs in contact with those meshed by other algo + + for ( size_t i = 0; i < faces.size(); ++i ) + { + _helper->SetSubShape( faces[i] ); + for ( TopExp_Explorer eExp( faces[i], TopAbs_EDGE ); eExp.More(); eExp.Next() ) + { + const TopoDS_Edge& edge = TopoDS::Edge( eExp.Current() ); + if ( hasStrangeEdges ) + { + bool hasStrangeFace = false; + PShapeIteratorPtr faceIt = _helper->GetAncestors( edge, *_helper->GetMesh(), TopAbs_FACE); + while ( const TopoDS_Shape* face = faceIt->next() ) + if (( hasStrangeFace = !faceMap.Contains( *face ))) + break; + if ( !hasStrangeFace && !_toAddEdges ) + continue; + _geometry._strangeEdges.Add( ShapeID( edge )); + _geometry._strangeEdges.Add( ShapeID( _helper->IthVertex( 0, edge ))); + _geometry._strangeEdges.Add( ShapeID( _helper->IthVertex( 1, edge ))); + } + if ( !SMESH_Algo::isDegenerated( edge ) && + !_helper->IsRealSeam( edge )) + { + edge2faceIDsMap[ ShapeID( edge )].push_back( ShapeID( faces[i] )); + } + } + } + return; + } + //================================================================================ /* * Computes coordinates of a point in the grid CS @@ -945,10 +1566,13 @@ namespace { // state of each node of the grid relative to the geometry const size_t nbGridNodes = _coords[0].size() * _coords[1].size() * _coords[2].size(); - vector< bool > isNodeOut( nbGridNodes, false ); + const TGeomID undefID = 1e+9; + vector< TGeomID > shapeIDVec( nbGridNodes, undefID ); _nodes.resize( nbGridNodes, 0 ); _gridIntP.resize( nbGridNodes, NULL ); + SMESHDS_Mesh* mesh = helper.GetMeshDS(); + for ( int iDir = 0; iDir < 3; ++iDir ) // loop on 3 line directions { LineIndexer li = GetLineIndexer( iDir ); @@ -968,26 +1592,30 @@ namespace GridLine& line = _lines[ iDir ][ li.LineIndex() ]; const gp_XYZ lineLoc = line._line.Location().XYZ(); const gp_XYZ lineDir = line._line.Direction().XYZ(); + line.RemoveExcessIntPoints( _tol ); - multiset< F_IntersectPoint >& intPnts = line._intPoints; + multiset< F_IntersectPoint >& intPnts = line._intPoints; multiset< F_IntersectPoint >::iterator ip = intPnts.begin(); - bool isOut = true; + // Create mesh nodes at intersections with geometry + // and set OUT state of nodes between intersections + + TGeomID solidID = 0; const double* nodeCoord = & coords[0]; const double* coord0 = nodeCoord; const double* coordEnd = coord0 + coords.size(); double nodeParam = 0; for ( ; ip != intPnts.end(); ++ip ) { + solidID = line.GetSolidIDBefore( ip, solidID, _geometry ); + // set OUT state or just skip IN nodes before ip if ( nodeParam < ip->_paramOnLine - _tol ) { - isOut = line.GetIsOutBefore( ip, isOut ); - while ( nodeParam < ip->_paramOnLine - _tol ) { - if ( isOut ) - isNodeOut[ nIndex0 + nShift * ( nodeCoord-coord0 ) ] = isOut; + TGeomID & nodeShapeID = shapeIDVec[ nIndex0 + nShift * ( nodeCoord-coord0 ) ]; + nodeShapeID = Min( solidID, nodeShapeID ); if ( ++nodeCoord < coordEnd ) nodeParam = *nodeCoord - *coord0; else @@ -998,24 +1626,21 @@ namespace // create a mesh node on a GridLine at ip if it does not coincide with a grid node if ( nodeParam > ip->_paramOnLine + _tol ) { - // li.SetIndexOnLine( 0 ); - // double xyz[3] = { _coords[0][ li.I() ], _coords[1][ li.J() ], _coords[2][ li.K() ]}; - // xyz[ li._iConst ] += ip->_paramOnLine; gp_XYZ xyz = lineLoc + ip->_paramOnLine * lineDir; - ip->_node = helper.AddNode( xyz.X(), xyz.Y(), xyz.Z() ); + ip->_node = mesh->AddNode( xyz.X(), xyz.Y(), xyz.Z() ); ip->_indexOnLine = nodeCoord-coord0-1; + SetOnShape( ip->_node, *ip ); } - // create a mesh node at ip concident with a grid node + // create a mesh node at ip coincident with a grid node else { int nodeIndex = nIndex0 + nShift * ( nodeCoord-coord0 ); if ( !_nodes[ nodeIndex ] ) { - //li.SetIndexOnLine( nodeCoord-coord0 ); - //double xyz[3] = { _coords[0][ li.I() ], _coords[1][ li.J() ], _coords[2][ li.K() ]}; gp_XYZ xyz = lineLoc + nodeParam * lineDir; - _nodes [ nodeIndex ] = helper.AddNode( xyz.X(), xyz.Y(), xyz.Z() ); - _gridIntP[ nodeIndex ] = & * ip; + _nodes [ nodeIndex ] = mesh->AddNode( xyz.X(), xyz.Y(), xyz.Z() ); + //_gridIntP[ nodeIndex ] = & * ip; + //SetOnShape( _nodes[ nodeIndex ], *ip ); } if ( _gridIntP[ nodeIndex ] ) _gridIntP[ nodeIndex ]->Add( ip->_faceIDs ); @@ -1029,7 +1654,7 @@ namespace } // set OUT state to nodes after the last ip for ( ; nodeCoord < coordEnd; ++nodeCoord ) - isNodeOut[ nIndex0 + nShift * ( nodeCoord-coord0 ) ] = true; + shapeIDVec[ nIndex0 + nShift * ( nodeCoord-coord0 ) ] = 0; } } @@ -1040,13 +1665,19 @@ namespace for ( size_t x = 0; x < _coords[0].size(); ++x ) { size_t nodeIndex = NodeIndex( x, y, z ); - if ( !isNodeOut[ nodeIndex ] && !_nodes[ nodeIndex] ) + if ( !_nodes[ nodeIndex ] && + 0 < shapeIDVec[ nodeIndex ] && shapeIDVec[ nodeIndex ] < undefID ) { - //_nodes[ nodeIndex ] = helper.AddNode( _coords[0][x], _coords[1][y], _coords[2][z] ); gp_XYZ xyz = ( _coords[0][x] * _axes[0] + _coords[1][y] * _axes[1] + _coords[2][z] * _axes[2] ); - _nodes[ nodeIndex ] = helper.AddNode( xyz.X(), xyz.Y(), xyz.Z() ); + _nodes[ nodeIndex ] = mesh->AddNode( xyz.X(), xyz.Y(), xyz.Z() ); + mesh->SetNodeInVolume( _nodes[ nodeIndex ], shapeIDVec[ nodeIndex ]); + } + else if ( _nodes[ nodeIndex ] && _gridIntP[ nodeIndex ] /*&& + !_nodes[ nodeIndex]->GetShapeID()*/ ) + { + SetOnShape( _nodes[ nodeIndex ], *_gridIntP[ nodeIndex ]); } } @@ -1174,8 +1805,19 @@ namespace _intersections.push_back( make_pair( &gridLine, intersector._intPoints[i] )); } } - } - //================================================================================ + + if ( _face.Orientation() == TopAbs_INTERNAL ) + { + for ( size_t i = 0; i < _intersections.size(); ++i ) + if ( _intersections[i].second._transition == Trans_IN || + _intersections[i].second._transition == Trans_OUT ) + { + _intersections[i].second._transition = Trans_INTERNAL; + } + } + return; + } + //================================================================================ /* * Return true if (_u,_v) is on the face */ @@ -1194,6 +1836,8 @@ namespace { F_IntersectPoint p; p._paramOnLine = _w; + p._u = _u; + p._v = _v; p._transition = _transition; _intPoints.push_back( p ); } @@ -1428,8 +2072,8 @@ namespace /*! * \brief Creates topology of the hexahedron */ - Hexahedron::Hexahedron(const double sizeThreshold, Grid* grid) - : _grid( grid ), _sizeThreshold( sizeThreshold ), _nbFaceIntNodes(0) + Hexahedron::Hexahedron(Grid* grid) + : _grid( grid ), _nbFaceIntNodes(0), _hasTooSmall( false ) { _polygons.reserve(100); // to avoid reallocation; @@ -1491,11 +2135,12 @@ namespace /*! * \brief Copy constructor */ - Hexahedron::Hexahedron( const Hexahedron& other ) - :_grid( other._grid ), _sizeThreshold( other._sizeThreshold ), _nbFaceIntNodes(0) + Hexahedron::Hexahedron( const Hexahedron& other, size_t i, size_t j, size_t k, int cellID ) + :_grid( other._grid ), _nbFaceIntNodes(0), _i( i ), _j( j ), _k( k ), _hasTooSmall( false ) { _polygons.reserve(100); // to avoid reallocation; + // copy topology for ( int i = 0; i < 8; ++i ) _nodeShift[i] = other._nodeShift[i]; @@ -1520,23 +2165,192 @@ namespace tgtLink._link = _hexLinks + ( srcLink._link - other._hexLinks ); } } +#ifdef _DEBUG_ + _cellID = cellID; +#endif + } + + //================================================================================ + /*! + * \brief Return IDs of SOLIDs interfering with this Hexahedron + */ + vector< TGeomID > Hexahedron::getSolids() + { + // count intersection points belonging to each SOLID + TID2Nb id2NbPoints; + id2NbPoints.reserve( 3 ); + + _origNodeInd = _grid->NodeIndex( _i,_j,_k ); + for ( int iN = 0; iN < 8; ++iN ) + { + _hexNodes[iN]._node = _grid->_nodes [ _origNodeInd + _nodeShift[iN] ]; + _hexNodes[iN]._intPoint = _grid->_gridIntP[ _origNodeInd + _nodeShift[iN] ]; + + if ( _hexNodes[iN]._intPoint ) // intersection with a FACE + { + for ( size_t iF = 0; iF < _hexNodes[iN]._intPoint->_faceIDs.size(); ++iF ) + { + const vector< TGeomID > & solidIDs = + _grid->GetSolidIDs( _hexNodes[iN]._intPoint->_faceIDs[iF] ); + for ( size_t i = 0; i < solidIDs.size(); ++i ) + insertAndIncrement( solidIDs[i], id2NbPoints ); + } + } + else if ( _hexNodes[iN]._node ) // node inside a SOLID + { + insertAndIncrement( _hexNodes[iN]._node->GetShapeID(), id2NbPoints ); + } + } + + for ( int iL = 0; iL < 12; ++iL ) + { + const _Link& link = _hexLinks[ iL ]; + for ( size_t iP = 0; iP < link._fIntPoints.size(); ++iP ) + { + for ( size_t iF = 0; iF < link._fIntPoints[iP]->_faceIDs.size(); ++iF ) + { + const vector< TGeomID > & solidIDs = + _grid->GetSolidIDs( link._fIntPoints[iP]->_faceIDs[iF] ); + for ( size_t i = 0; i < solidIDs.size(); ++i ) + insertAndIncrement( solidIDs[i], id2NbPoints ); + } + } + } + + for ( size_t iP = 0; iP < _eIntPoints.size(); ++iP ) + { + const vector< TGeomID > & solidIDs = _grid->GetSolidIDs( _eIntPoints[iP]->_shapeID ); + for ( size_t i = 0; i < solidIDs.size(); ++i ) + insertAndIncrement( solidIDs[i], id2NbPoints ); + } + + vector< TGeomID > solids; solids.reserve( id2NbPoints.size() ); + for ( TID2Nb::iterator id2nb = id2NbPoints.begin(); id2nb != id2NbPoints.end(); ++id2nb ) + if ( id2nb->second >= 3 ) + solids.push_back( id2nb->first ); + + return solids; + } + + //================================================================================ + /*! + * \brief Count cuts by INTERNAL FACEs and set _Node::_isInternalFlags + */ + bool Hexahedron::isCutByInternalFace( IsInternalFlag & maxFlag ) + { + TID2Nb id2NbPoints; + id2NbPoints.reserve( 3 ); + + for ( size_t iN = 0; iN < _intNodes.size(); ++iN ) + for ( size_t iF = 0; iF < _intNodes[iN]._intPoint->_faceIDs.size(); ++iF ) + { + if ( _grid->IsInternal( _intNodes[iN]._intPoint->_faceIDs[iF])) + insertAndIncrement( _intNodes[iN]._intPoint->_faceIDs[iF], id2NbPoints ); + } + for ( size_t iN = 0; iN < 8; ++iN ) + if ( _hexNodes[iN]._intPoint ) + for ( size_t iF = 0; iF < _hexNodes[iN]._intPoint->_faceIDs.size(); ++iF ) + { + if ( _grid->IsInternal( _hexNodes[iN]._intPoint->_faceIDs[iF])) + insertAndIncrement( _hexNodes[iN]._intPoint->_faceIDs[iF], id2NbPoints ); + } + + maxFlag = IS_NOT_INTERNAL; + for ( TID2Nb::iterator id2nb = id2NbPoints.begin(); id2nb != id2NbPoints.end(); ++id2nb ) + { + TGeomID intFace = id2nb->first; + IsInternalFlag intFlag = ( id2nb->second >= 3 ? IS_CUT_BY_INTERNAL_FACE : IS_INTERNAL ); + if ( intFlag > maxFlag ) + maxFlag = intFlag; + + for ( size_t iN = 0; iN < _intNodes.size(); ++iN ) + if ( _intNodes[iN].IsOnFace( intFace )) + _intNodes[iN].SetInternal( intFlag ); + + for ( size_t iN = 0; iN < 8; ++iN ) + if ( _hexNodes[iN].IsOnFace( intFace )) + _hexNodes[iN].SetInternal( intFlag ); + } + + return maxFlag; + } + + //================================================================================ + /*! + * \brief Return any FACE interfering with this Hexahedron + */ + TGeomID Hexahedron::getAnyFace() const + { + TID2Nb id2NbPoints; + id2NbPoints.reserve( 3 ); + + for ( size_t iN = 0; iN < _intNodes.size(); ++iN ) + for ( size_t iF = 0; iF < _intNodes[iN]._intPoint->_faceIDs.size(); ++iF ) + insertAndIncrement( _intNodes[iN]._intPoint->_faceIDs[iF], id2NbPoints ); + + for ( size_t iN = 0; iN < 8; ++iN ) + if ( _hexNodes[iN]._intPoint ) + for ( size_t iF = 0; iF < _hexNodes[iN]._intPoint->_faceIDs.size(); ++iF ) + insertAndIncrement( _hexNodes[iN]._intPoint->_faceIDs[iF], id2NbPoints ); + + for ( unsigned int minNb = 3; minNb > 0; --minNb ) + for ( TID2Nb::iterator id2nb = id2NbPoints.begin(); id2nb != id2NbPoints.end(); ++id2nb ) + if ( id2nb->second >= minNb ) + return id2nb->first; + + return 0; + } + + //================================================================================ + /*! + * \brief Initializes IJK by Hexahedron index + */ + void Hexahedron::setIJK( size_t iCell ) + { + size_t iNbCell = _grid->_coords[0].size() - 1; + size_t jNbCell = _grid->_coords[1].size() - 1; + _i = iCell % iNbCell; + _j = ( iCell % ( iNbCell * jNbCell )) / iNbCell; + _k = iCell / iNbCell / jNbCell; + } + + //================================================================================ + /*! + * \brief Initializes its data by given grid cell (countered from zero) + */ + void Hexahedron::init( size_t iCell ) + { + setIJK( iCell ); + init( _i, _j, _k ); } //================================================================================ /*! - * \brief Initializes its data by given grid cell + * \brief Initializes its data by given grid cell nodes and intersections */ - void Hexahedron::init( size_t i, size_t j, size_t k ) + void Hexahedron::init( size_t i, size_t j, size_t k, const Solid* solid ) { _i = i; _j = j; _k = k; + + if ( !solid ) + solid = _grid->GetSolid(); + // set nodes of grid to nodes of the hexahedron and // count nodes at hexahedron corners located IN and ON geometry _nbCornerNodes = _nbBndNodes = 0; _origNodeInd = _grid->NodeIndex( i,j,k ); for ( int iN = 0; iN < 8; ++iN ) { + _hexNodes[iN]._isInternalFlags = 0; + _hexNodes[iN]._node = _grid->_nodes [ _origNodeInd + _nodeShift[iN] ]; _hexNodes[iN]._intPoint = _grid->_gridIntP[ _origNodeInd + _nodeShift[iN] ]; + + if ( _hexNodes[iN]._node && !solid->Contains( _hexNodes[iN]._node->GetShapeID() )) + _hexNodes[iN]._node = 0; + if ( _hexNodes[iN]._intPoint && !solid->ContainsAny( _hexNodes[iN]._intPoint->_faceIDs )) + _hexNodes[iN]._intPoint = 0; + _nbCornerNodes += bool( _hexNodes[iN]._node ); _nbBndNodes += bool( _hexNodes[iN]._intPoint ); } @@ -1547,25 +2361,28 @@ namespace _intNodes.clear(); _vIntNodes.clear(); - if ( _nbFaceIntNodes + _eIntPoints.size() > 0 && - _nbFaceIntNodes + _nbCornerNodes + _eIntPoints.size() > 3) + if ( _nbFaceIntNodes + _eIntPoints.size() > 0 && + _nbFaceIntNodes + _eIntPoints.size() + _nbCornerNodes > 3) { _intNodes.reserve( 3 * _nbBndNodes + _nbFaceIntNodes + _eIntPoints.size() ); // this method can be called in parallel, so use own helper SMESH_MesherHelper helper( *_grid->_helper->GetMesh() ); - // create sub-links (_splits) by splitting links with _fIntPoints + // Create sub-links (_Link::_splits) by splitting links with _Link::_fIntPoints + // --------------------------------------------------------------- _Link split; for ( int iLink = 0; iLink < 12; ++iLink ) { _Link& link = _hexLinks[ iLink ]; - link._fIntNodes.resize( link._fIntPoints.size() ); + link._fIntNodes.clear(); + link._fIntNodes.reserve( link._fIntPoints.size() ); for ( size_t i = 0; i < link._fIntPoints.size(); ++i ) - { - _intNodes.push_back( _Node( 0, link._fIntPoints[i] )); - link._fIntNodes[ i ] = & _intNodes.back(); - } + if ( solid->ContainsAny( link._fIntPoints[i]->_faceIDs )) + { + _intNodes.push_back( _Node( 0, link._fIntPoints[i] )); + link._fIntNodes.push_back( & _intNodes.back() ); + } link._splits.clear(); split._nodes[ 0 ] = link._nodes[0]; @@ -1590,15 +2407,21 @@ namespace } if ( checkTransition ) { - if ( link._fIntPoints[i]->_faceIDs.size() > 1 || _eIntPoints.size() > 0 ) - isOut = isOutPoint( link, i, helper ); + const vector< TGeomID >& faceIDs = link._fIntNodes[i]->_intPoint->_faceIDs; + if ( _grid->IsInternal( faceIDs.back() )) + isOut = false; + else if ( faceIDs.size() > 1 || _eIntPoints.size() > 0 ) + isOut = isOutPoint( link, i, helper, solid ); else - switch ( link._fIntPoints[i]->_transition ) { - case Trans_OUT: isOut = true; break; - case Trans_IN : isOut = false; break; + { + bool okTransi = _grid->IsCorrectTransition( faceIDs[0], solid ); + switch ( link._fIntNodes[i]->FaceIntPnt()->_transition ) { + case Trans_OUT: isOut = okTransi; break; + case Trans_IN : isOut = !okTransi; break; default: - isOut = isOutPoint( link, i, helper ); + isOut = isOutPoint( link, i, helper, solid ); } + } } } if ( link._nodes[ 1 ]->Node() && split._nodes[ 0 ]->Node() && !isOut ) @@ -1609,12 +2432,20 @@ namespace } // Create _Node's at intersections with EDGEs. - + // -------------------------------------------- + // 1) add this->_eIntPoints to _Face::_eIntNodes + // 2) fill _intNodes and _vIntNodes + // const double tol2 = _grid->_tol * _grid->_tol; int facets[3], nbFacets, subEntity; + for ( int iF = 0; iF < 6; ++iF ) + _hexQuads[ iF ]._eIntNodes.clear(); + for ( size_t iP = 0; iP < _eIntPoints.size(); ++iP ) { + if ( !solid->ContainsAny( _eIntPoints[iP]->_faceIDs )) + continue; nbFacets = getEntity( _eIntPoints[iP], facets, subEntity ); _Node* equalNode = 0; switch( nbFacets ) { @@ -1639,10 +2470,16 @@ namespace equalNode = findEqualNode( link._fIntNodes, _eIntPoints[ iP ], tol2 ); if ( equalNode ) equalNode->Add( _eIntPoints[ iP ] ); + else if ( link._splits.size() == 1 && + link._splits[0]._nodes[0] && + link._splits[0]._nodes[1] ) + link._splits.clear(); // hex edge is divided by _eIntPoints[iP] } - else + //else + if ( !equalNode ) { _intNodes.push_back( _Node( 0, _eIntPoints[ iP ])); + bool newNodeUsed = false; for ( int iF = 0; iF < 2; ++iF ) { _Face& quad = _hexQuads[ facets[iF] - SMESH_Block::ID_FirstF ]; @@ -1652,8 +2489,11 @@ namespace } else { quad._eIntNodes.push_back( & _intNodes.back() ); + newNodeUsed = true; } } + if ( !newNodeUsed ) + _intNodes.pop_back(); } break; } @@ -1685,7 +2525,7 @@ namespace } // switch( nbFacets ) if ( nbFacets == 0 || - _grid->_shapes( _eIntPoints[ iP ]->_shapeID ).ShapeType() == TopAbs_VERTEX ) + _grid->ShapeType( _eIntPoints[ iP ]->_shapeID ) == TopAbs_VERTEX ) { equalNode = findEqualNode( _vIntNodes, _eIntPoints[ iP ], tol2 ); if ( equalNode ) { @@ -1699,6 +2539,7 @@ namespace } } // loop on _eIntPoints } + else if ( 3 < _nbCornerNodes && _nbCornerNodes < 8 ) // _nbFaceIntNodes == 0 { _Link split; @@ -1715,40 +2556,76 @@ namespace } } } + return; - } - //================================================================================ - /*! - * \brief Initializes its data by given grid cell (countered from zero) - */ - void Hexahedron::init( size_t iCell ) - { - size_t iNbCell = _grid->_coords[0].size() - 1; - size_t jNbCell = _grid->_coords[1].size() - 1; - _i = iCell % iNbCell; - _j = ( iCell % ( iNbCell * jNbCell )) / iNbCell; - _k = iCell / iNbCell / jNbCell; - init( _i, _j, _k ); - } + } // init( _i, _j, _k ) //================================================================================ /*! * \brief Compute mesh volumes resulted from intersection of the Hexahedron */ - void Hexahedron::ComputeElements() + void Hexahedron::ComputeElements( const Solid* solid, int solidIndex ) { - Init(); + if ( !solid ) + { + solid = _grid->GetSolid(); + if ( !_grid->_geometry.IsOneSolid() ) + { + vector< TGeomID > solidIDs = getSolids(); + if ( solidIDs.size() > 1 ) + { + for ( size_t i = 0; i < solidIDs.size(); ++i ) + { + solid = _grid->GetSolid( solidIDs[i] ); + ComputeElements( solid, i ); + if ( !_volumeDefs._nodes.empty() && i < solidIDs.size() - 1 ) + _volumeDefs.SetNext( new _volumeDef( _volumeDefs )); + } + return; + } + solid = _grid->GetSolid( solidIDs[0] ); + } + } + + init( _i, _j, _k, solid ); // get nodes and intersections from grid nodes and split links int nbIntersections = _nbFaceIntNodes + _eIntPoints.size(); if ( _nbCornerNodes + nbIntersections < 4 ) return; if ( _nbBndNodes == _nbCornerNodes && nbIntersections == 0 && isInHole() ) - return; + return; // cell is in a hole + + IsInternalFlag intFlag = IS_NOT_INTERNAL; + if ( solid->HasInternalFaces() && this->isCutByInternalFace( intFlag )) + { + for ( _SplitIterator it( _hexLinks ); it.More(); it.Next() ) + { + if ( compute( solid, intFlag )) + _volumeDefs.SetNext( new _volumeDef( _volumeDefs )); + } + } + else + { + if ( solidIndex >= 0 ) + intFlag = IS_CUT_BY_INTERNAL_FACE; + + compute( solid, intFlag ); + } + } + //================================================================================ + /*! + * \brief Compute mesh volumes resulted from intersection of the Hexahedron + */ + bool Hexahedron::compute( const Solid* solid, const IsInternalFlag intFlag ) + { _polygons.clear(); _polygons.reserve( 20 ); + for ( int iN = 0; iN < 8; ++iN ) + _hexNodes[iN]._usedInFace = 0; + // Create polygons from quadrangles // -------------------------------- @@ -1778,14 +2655,13 @@ namespace if (( nbSplits == 1 ) && ( quad._eIntNodes.empty() || splits[0].FirstNode()->IsLinked( splits[0].LastNode()->_intPoint ))) - //( quad._eIntNodes.empty() || _nbCornerNodes + nbIntersections > 6 )) + //( quad._eIntNodes.empty() || _nbCornerNodes + nbIntersections > 6 )) nbSplits = 0; -#ifdef _DEBUG_ for ( size_t iP = 0; iP < quad._eIntNodes.size(); ++iP ) if ( quad._eIntNodes[ iP ]->IsUsedInFace( polygon )) quad._eIntNodes[ iP ]->_usedInFace = 0; -#endif + size_t nbUsedEdgeNodes = 0; _Face* prevPolyg = 0; // polygon previously created from this quad @@ -1815,16 +2691,20 @@ namespace n1 = split.FirstNode(); if ( n1 == n2 && n1->_intPoint && - n1->_intPoint->_faceIDs.size() > 1 ) + (( n1->_intPoint->_faceIDs.size() > 1 && isImplementEdges() ) || + ( n1->_isInternalFlags ))) { // n1 is at intersection with EDGE if ( findChainOnEdge( splits, polygon->_links.back(), split, iS, quad, chainNodes )) { for ( size_t i = 1; i < chainNodes.size(); ++i ) polygon->AddPolyLink( chainNodes[i-1], chainNodes[i], prevPolyg ); - prevPolyg = polygon; - n2 = chainNodes.back(); - continue; + if ( chainNodes.back() != n1 ) // not a partial cut by INTERNAL FACE + { + prevPolyg = polygon; + n2 = chainNodes.back(); + continue; + } } } else if ( n1 != n2 ) @@ -1940,7 +2820,7 @@ namespace freeLinks.push_back( & polygon._links[ iL ]); } int nbFreeLinks = freeLinks.size(); - if ( nbFreeLinks == 1 ) return; + if ( nbFreeLinks == 1 ) return false; // put not used intersection nodes to _vIntNodes int nbVertexNodes = 0; // nb not used vertex nodes @@ -2106,7 +2986,8 @@ namespace _vIntNodes[ iN ]->_usedInFace = &polygon; chainNodes.push_back( _vIntNodes[ iN ] ); } - if ( chainNodes.size() > 1 ) + if ( chainNodes.size() > 1 && + curFace != _grid->PseudoIntExtFaceID() ) /////// TODO { sortVertexNodes( chainNodes, curNode, curFace ); } @@ -2130,7 +3011,7 @@ namespace if ( polygon._links.size() < 2 || polygon._links[0].LastNode() != polygon._links.back().FirstNode() ) - return; // closed polygon not found -> invalid polyhedron + return false; // closed polygon not found -> invalid polyhedron if ( polygon._links.size() == 2 ) { @@ -2251,15 +3132,16 @@ namespace } // end of case ( polygon._links.size() > 2 ) } // while ( nbFreeLinks > 0 ) - if ( ! checkPolyhedronSize() ) - { - return; - } + // check volume size + _hasTooSmall = ! checkPolyhedronSize( intFlag & IS_CUT_BY_INTERNAL_FACE ); for ( size_t i = 0; i < 8; ++i ) if ( _hexNodes[ i ]._intPoint == &ipTmp ) _hexNodes[ i ]._intPoint = 0; + if ( _hasTooSmall ) + return false; // too small volume + // create a classic cell if possible int nbPolygons = 0; @@ -2292,6 +3174,9 @@ namespace _volumeDefs._nodes.push_back( _polygons[ iF ]._links[ iL ].FirstNode() ); } } + _volumeDefs._solidID = solid->ID(); + + return !_volumeDefs._nodes.empty(); } //================================================================================ /*! @@ -2302,23 +3187,18 @@ namespace { SMESHDS_Mesh* mesh = helper.GetMeshDS(); - size_t nbCells[3] = { _grid->_coords[0].size() - 1, - _grid->_coords[1].size() - 1, - _grid->_coords[2].size() - 1 }; - const size_t nbGridCells = nbCells[0] * nbCells[1] * nbCells[2]; + CellsAroundLink c( _grid, 0 ); + const size_t nbGridCells = c._nbCells[0] * c._nbCells[1] * c._nbCells[2]; vector< Hexahedron* > allHexa( nbGridCells, 0 ); int nbIntHex = 0; // set intersection nodes from GridLine's to links of allHexa - int i,j,k, iDirOther[3][2] = {{ 1,2 },{ 0,2 },{ 0,1 }}; + int i,j,k, cellIndex; for ( int iDir = 0; iDir < 3; ++iDir ) { - int dInd[4][3] = { {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} }; - dInd[1][ iDirOther[iDir][0] ] = -1; - dInd[2][ iDirOther[iDir][1] ] = -1; - dInd[3][ iDirOther[iDir][0] ] = -1; dInd[3][ iDirOther[iDir][1] ] = -1; // loop on GridLine's parallel to iDir LineIndexer lineInd = _grid->GetLineIndexer( iDir ); + CellsAroundLink fourCells( _grid, iDir ); for ( ; lineInd.More(); ++lineInd ) { GridLine& line = _grid->_lines[ iDir ][ lineInd.LineIndex() ]; @@ -2327,23 +3207,15 @@ namespace { // if ( !ip->_node ) continue; // intersection at a grid node lineInd.SetIndexOnLine( ip->_indexOnLine ); + fourCells.Init( lineInd.I(), lineInd.J(), lineInd.K() ); for ( int iL = 0; iL < 4; ++iL ) // loop on 4 cells sharing a link { - i = int(lineInd.I()) + dInd[iL][0]; - j = int(lineInd.J()) + dInd[iL][1]; - k = int(lineInd.K()) + dInd[iL][2]; - if ( i < 0 || i >= (int) nbCells[0] || - j < 0 || j >= (int) nbCells[1] || - k < 0 || k >= (int) nbCells[2] ) continue; - - const size_t hexIndex = _grid->CellIndex( i,j,k ); - Hexahedron *& hex = allHexa[ hexIndex ]; + if ( !fourCells.GetCell( iL, i,j,k, cellIndex )) + continue; + Hexahedron *& hex = allHexa[ cellIndex ]; if ( !hex) { - hex = new Hexahedron( *this ); - hex->_i = i; - hex->_j = j; - hex->_k = k; + hex = new Hexahedron( *this, i, j, k, cellIndex ); ++nbIntHex; } const int iLink = iL + iDir * 4; @@ -2357,22 +3229,24 @@ namespace // implement geom edges into the mesh addEdges( helper, allHexa, edge2faceIDsMap ); - // add not split hexadrons to the mesh + // add not split hexahedra to the mesh int nbAdded = 0; - vector< Hexahedron* > intHexa( nbIntHex, (Hexahedron*) NULL ); + vector< Hexahedron* > intHexa; intHexa.reserve( nbIntHex ); + vector< const SMDS_MeshElement* > boundaryVolumes; boundaryVolumes.reserve( nbIntHex * 1.1 ); for ( size_t i = 0; i < allHexa.size(); ++i ) { + // initialize this by not cut allHexa[ i ] Hexahedron * & hex = allHexa[ i ]; - if ( hex ) + if ( hex ) // split hexahedron { intHexa.push_back( hex ); if ( hex->_nbFaceIntNodes > 0 || hex->_eIntPoints.size() > 0 ) - continue; // treat intersected hex later + continue; // treat intersected hex later in parallel this->init( hex->_i, hex->_j, hex->_k ); } else - { - this->init( i ); + { + this->init( i ); // == init(i,j,k) } if (( _nbCornerNodes == 8 ) && ( _nbBndNodes < _nbCornerNodes || !isInHole() )) @@ -2383,18 +3257,31 @@ namespace _hexNodes[3].Node(), _hexNodes[1].Node(), _hexNodes[4].Node(), _hexNodes[6].Node(), _hexNodes[7].Node(), _hexNodes[5].Node() ); - mesh->SetMeshElementOnShape( el, helper.GetSubShapeID() ); + TGeomID solidID = 0; + if ( _nbBndNodes < _nbCornerNodes ) + { + for ( int iN = 0; iN < 8 && !solidID; ++iN ) + if ( !_hexNodes[iN]._intPoint ) // no intersection + solidID = _hexNodes[iN].Node()->GetShapeID(); + } + else + { + solidID = getSolids()[0]; + } + mesh->SetMeshElementOnShape( el, solidID ); ++nbAdded; if ( hex ) intHexa.pop_back(); + if ( _grid->_toCreateFaces && _nbBndNodes >= 3 ) + { + boundaryVolumes.push_back( el ); + el->setIsMarked( true ); + } } - else if ( _nbCornerNodes > 3 && !hex ) + else if ( _nbCornerNodes > 3 && !hex ) { // all intersection of hex with geometry are at grid nodes - hex = new Hexahedron( *this ); - hex->_i = _i; - hex->_j = _j; - hex->_k = _k; + hex = new Hexahedron( *this, _i, _j, _k, i ); intHexa.push_back( hex ); } } @@ -2406,16 +3293,30 @@ namespace tbb::simple_partitioner()); // ComputeElements() is called here for ( size_t i = 0; i < intHexa.size(); ++i ) if ( Hexahedron * hex = intHexa[ i ] ) - nbAdded += hex->addElements( helper ); + nbAdded += hex->addVolumes( helper ); #else for ( size_t i = 0; i < intHexa.size(); ++i ) if ( Hexahedron * hex = intHexa[ i ] ) { hex->ComputeElements(); - nbAdded += hex->addElements( helper ); + nbAdded += hex->addVolumes( helper ); } #endif + // fill boundaryVolumes with volumes neighboring too small skipped volumes + if ( _grid->_toCreateFaces ) + { + for ( size_t i = 0; i < intHexa.size(); ++i ) + if ( Hexahedron * hex = intHexa[ i ] ) + hex->getBoundaryElems( boundaryVolumes ); + } + + // create boundary mesh faces + addFaces( helper, boundaryVolumes ); + + // create mesh edges + addSegments( helper, edge2faceIDsMap ); + for ( size_t i = 0; i < allHexa.size(); ++i ) if ( allHexa[ i ] ) delete allHexa[ i ]; @@ -2456,25 +3357,36 @@ namespace const double tol = _grid->_tol; E_IntersectPoint ip; + TColStd_MapOfInteger intEdgeIDs; // IDs of not shared INTERNAL EDGES + // Intersect EDGEs with the planes map< TGeomID, vector< TGeomID > >::const_iterator e2fIt = edge2faceIDsMap.begin(); for ( ; e2fIt != edge2faceIDsMap.end(); ++e2fIt ) { const TGeomID edgeID = e2fIt->first; - const TopoDS_Edge & E = TopoDS::Edge( _grid->_shapes( edgeID )); + const TopoDS_Edge & E = TopoDS::Edge( _grid->Shape( edgeID )); BRepAdaptor_Curve curve( E ); - TopoDS_Vertex v1 = helper.IthVertex( 0, E, false ); - TopoDS_Vertex v2 = helper.IthVertex( 1, E, false ); + TopoDS_Vertex v1 = helper.IthVertex( 0, E, false ); + TopoDS_Vertex v2 = helper.IthVertex( 1, E, false ); ip._faceIDs = e2fIt->second; ip._shapeID = edgeID; + bool isInternal = ( ip._faceIDs.size() == 1 && _grid->IsInternal( edgeID )); + if ( isInternal ) + { + intEdgeIDs.Add( edgeID ); + intEdgeIDs.Add( _grid->ShapeID( v1 )); + intEdgeIDs.Add( _grid->ShapeID( v2 )); + } + // discretize the EDGE GCPnts_UniformDeflection discret( curve, deflection, true ); if ( !discret.IsDone() || discret.NbPoints() < 2 ) continue; // perform intersection + E_IntersectPoint* eip, *vip; for ( int iDirZ = 0; iDirZ < 3; ++iDirZ ) { GridPlanes& planes = pln[ iDirZ ]; @@ -2501,7 +3413,7 @@ namespace locateValue( iY1, ip._uvw[iDirY], _grid->_coords[ iDirY ], dIJK[ iDirY ], tol ); locateValue( iZ1, ip._uvw[iDirZ], _grid->_coords[ iDirZ ], dIJK[ iDirZ ], tol ); - int ijk[3]; // grid index where a segment intersect a plane + int ijk[3]; // grid index where a segment intersects a plane ijk[ iDirX ] = iX1; ijk[ iDirY ] = iY1; ijk[ iDirZ ] = iZ1; @@ -2510,10 +3422,12 @@ namespace if ( iDirZ == 0 ) { ip._point = p1; - ip._shapeID = _grid->_shapes.Add( v1 ); - _grid->_edgeIntP.push_back( ip ); - if ( !addIntersection( _grid->_edgeIntP.back(), hexes, ijk, d000 )) - _grid->_edgeIntP.pop_back(); + ip._shapeID = _grid->ShapeID( v1 ); + vip = _grid->Add( ip ); + if ( isInternal ) + vip->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + if ( !addIntersection( vip, hexes, ijk, d000 )) + _grid->Remove( vip ); ip._shapeID = edgeID; } for ( int iP = 2; iP <= discret.NbPoints(); ++iP ) @@ -2541,15 +3455,17 @@ namespace ijk[ iDirZ ] = iZ; // add ip to hex "above" the plane - _grid->_edgeIntP.push_back( ip ); + eip = _grid->Add( ip ); + if ( isInternal ) + eip->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); dIJK[ iDirZ ] = 0; - bool added = addIntersection(_grid->_edgeIntP.back(), hexes, ijk, dIJK); + bool added = addIntersection( eip, hexes, ijk, dIJK); // add ip to hex "below" the plane ijk[ iDirZ ] = iZ-1; - if ( !addIntersection( _grid->_edgeIntP.back(), hexes, ijk, dIJK ) && - !added) - _grid->_edgeIntP.pop_back(); + if ( !addIntersection( eip, hexes, ijk, dIJK ) && + !added ) + _grid->Remove( eip ); } } iZ1 = iZ2; @@ -2560,62 +3476,292 @@ namespace // add the 2nd vertex point to a hexahedron if ( iDirZ == 0 ) { - ip._shapeID = _grid->_shapes.Add( v2 ); - ip._point = p1; + ip._point = p1; + ip._shapeID = _grid->ShapeID( v2 ); _grid->ComputeUVW( p1, ip._uvw ); locateValue( ijk[iDirX], ip._uvw[iDirX], _grid->_coords[iDirX], dIJK[iDirX], tol ); locateValue( ijk[iDirY], ip._uvw[iDirY], _grid->_coords[iDirY], dIJK[iDirY], tol ); ijk[ iDirZ ] = iZ1; - _grid->_edgeIntP.push_back( ip ); - if ( !addIntersection( _grid->_edgeIntP.back(), hexes, ijk, d000 )) - _grid->_edgeIntP.pop_back(); + bool sameV = ( v1.IsSame( v2 )); + if ( !sameV ) + vip = _grid->Add( ip ); + if ( isInternal && !sameV ) + vip->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + if ( !addIntersection( vip, hexes, ijk, d000 ) && !sameV ) + _grid->Remove( vip ); ip._shapeID = edgeID; } } // loop on 3 grid directions } // loop on EDGEs + + if ( intEdgeIDs.Size() > 0 ) + cutByExtendedInternal( hexes, intEdgeIDs ); + + return; } //================================================================================ /*! - * \brief Finds intersection of a curve with a plane - * \param [in] u1 - parameter of one curve point - * \param [in] proj1 - projection of the curve point to the plane normal - * \param [in] u2 - parameter of another curve point - * \param [in] proj2 - projection of the other curve point to the plane normal - * \param [in] proj - projection of a point where the curve intersects the plane - * \param [in] curve - the curve - * \param [in] axis - the plane normal - * \param [in] origin - the plane origin - * \return gp_Pnt - the found intersection point + * \brief Fully cut hexes that are partially cut by INTERNAL FACE. + * Cut them by extended INTERNAL FACE. */ - gp_Pnt Hexahedron::findIntPoint( double u1, double proj1, - double u2, double proj2, - double proj, - BRepAdaptor_Curve& curve, - const gp_XYZ& axis, - const gp_XYZ& origin) + void Hexahedron::cutByExtendedInternal( std::vector< Hexahedron* >& hexes, + const TColStd_MapOfInteger& intEdgeIDs ) { - double r = (( proj - proj1 ) / ( proj2 - proj1 )); - double u = u1 * ( 1 - r ) + u2 * r; - gp_Pnt p = curve.Value( u ); - double newProj = axis * ( p.XYZ() - origin ); - if ( Abs( proj - newProj ) > _grid->_tol / 10. ) + IntAna_IntConicQuad intersection; + SMESHDS_Mesh* meshDS = _grid->_helper->GetMeshDS(); + const double tol2 = _grid->_tol * _grid->_tol; + + for ( size_t iH = 0; iH < hexes.size(); ++iH ) { - if ( r > 0.5 ) - return findIntPoint( u2, proj2, u, newProj, proj, curve, axis, origin ); - else - return findIntPoint( u1, proj2, u, newProj, proj, curve, axis, origin ); - } - return p; + Hexahedron* hex = hexes[ iH ]; + if ( !hex || hex->_eIntPoints.size() < 2 ) + continue; + if ( !intEdgeIDs.Contains( hex->_eIntPoints.back()->_shapeID )) + continue; + + // get 3 points on INTERNAL FACE to construct a cutting plane + gp_Pnt p1 = hex->_eIntPoints[0]->_point; + gp_Pnt p2 = hex->_eIntPoints[1]->_point; + gp_Pnt p3 = hex->mostDistantInternalPnt( iH, p1, p2 ); + + gp_Vec norm = gp_Vec( p1, p2 ) ^ gp_Vec( p1, p3 ); + gp_Pln pln; + try { + pln = gp_Pln( p1, norm ); + } + catch(...) + { + continue; + } + + TGeomID intFaceID = hex->_eIntPoints.back()->_faceIDs.front(); // FACE being "extended" + TGeomID solidID = _grid->GetSolid( intFaceID )->ID(); + + // cut links by the plane + //bool isCut = false; + for ( int iLink = 0; iLink < 12; ++iLink ) + { + _Link& link = hex->_hexLinks[ iLink ]; + if ( !link._fIntPoints.empty() ) + { + // if ( link._fIntPoints[0]->_faceIDs.back() == _grid->PseudoIntExtFaceID() ) + // isCut = true; + continue; // already cut link + } + if ( !link._nodes[0]->Node() || + !link._nodes[1]->Node() ) + continue; // outside link + + if ( link._nodes[0]->IsOnFace( intFaceID )) + { + if ( link._nodes[0]->_intPoint->_faceIDs.back() != _grid->PseudoIntExtFaceID() ) + if ( p1.SquareDistance( link._nodes[0]->Point() ) < tol2 || + p2.SquareDistance( link._nodes[0]->Point() ) < tol2 ) + link._nodes[0]->_intPoint->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + continue; // link is cut by FACE being "extended" + } + if ( link._nodes[1]->IsOnFace( intFaceID )) + { + if ( link._nodes[1]->_intPoint->_faceIDs.back() != _grid->PseudoIntExtFaceID() ) + if ( p1.SquareDistance( link._nodes[1]->Point() ) < tol2 || + p2.SquareDistance( link._nodes[1]->Point() ) < tol2 ) + link._nodes[1]->_intPoint->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + continue; // link is cut by FACE being "extended" + } + gp_Pnt p4 = link._nodes[0]->Point(); + gp_Pnt p5 = link._nodes[1]->Point(); + gp_Lin line( p4, gp_Vec( p4, p5 )); + + intersection.Perform( line, pln ); + if ( !intersection.IsDone() || + intersection.IsInQuadric() || + intersection.IsParallel() || + intersection.NbPoints() < 1 ) + continue; + + double u = intersection.ParamOnConic(1); + if ( u + _grid->_tol < 0 ) + continue; + int iDir = iLink / 4; + int index = (&hex->_i)[iDir]; + double linkLen = _grid->_coords[iDir][index+1] - _grid->_coords[iDir][index]; + if ( u - _grid->_tol > linkLen ) + continue; + + if ( u < _grid->_tol || + u > linkLen - _grid->_tol ) // intersection at grid node + { + int i = ! ( u < _grid->_tol ); // [0,1] + int iN = link._nodes[ i ] - hex->_hexNodes; // [0-7] + + const F_IntersectPoint * & ip = _grid->_gridIntP[ hex->_origNodeInd + _nodeShift[iN] ]; + if ( !ip ) + { + ip = _grid->_extIntPool.getNew(); + ip->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + //ip->_transition = Trans_INTERNAL; + } + else if ( ip->_faceIDs.back() != _grid->PseudoIntExtFaceID() ) + { + ip->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + } + hex->_nbFaceIntNodes++; + //isCut = true; + } + else + { + const gp_Pnt& p = intersection.Point( 1 ); + F_IntersectPoint* ip = _grid->_extIntPool.getNew(); + ip->_node = meshDS->AddNode( p.X(), p.Y(), p.Z() ); + ip->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + ip->_transition = Trans_INTERNAL; + meshDS->SetNodeInVolume( ip->_node, solidID ); + + CellsAroundLink fourCells( _grid, iDir ); + fourCells.Init( hex->_i, hex->_j, hex->_k, iLink ); + int i,j,k, cellIndex; + for ( int iC = 0; iC < 4; ++iC ) // loop on 4 cells sharing the link + { + if ( !fourCells.GetCell( iC, i,j,k, cellIndex )) + continue; + Hexahedron * h = hexes[ cellIndex ]; + if ( !h ) + h = hexes[ cellIndex ] = new Hexahedron( *this, i, j, k, cellIndex ); + const int iL = iC + iDir * 4; + h->_hexLinks[iL]._fIntPoints.push_back( ip ); + h->_nbFaceIntNodes++; + //isCut = true; + } + } + } + + // if ( isCut ) + // for ( size_t i = 0; i < hex->_eIntPoints.size(); ++i ) + // { + // if ( _grid->IsInternal( hex->_eIntPoints[i]->_shapeID ) && + // ! hex->_eIntPoints[i]->IsOnFace( _grid->PseudoIntExtFaceID() )) + // hex->_eIntPoints[i]->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + // } + continue; + + } // loop on all hexes + return; } //================================================================================ /*! - * \brief Returns indices of a hexahedron sub-entities holding a point - * \param [in] ip - intersection point - * \param [out] facets - 0-3 facets holding a point - * \param [out] sub - index of a vertex or an edge holding a point + * \brief Return intersection point on INTERNAL FACE most distant from given ones + */ + gp_Pnt Hexahedron::mostDistantInternalPnt( int hexIndex, const gp_Pnt& p1, const gp_Pnt& p2 ) + { + gp_Pnt resultPnt = p1; + + double maxDist2 = 0; + for ( int iLink = 0; iLink < 12; ++iLink ) // check links + { + _Link& link = _hexLinks[ iLink ]; + for ( size_t i = 0; i < link._fIntPoints.size(); ++i ) + if ( _grid->PseudoIntExtFaceID() != link._fIntPoints[i]->_faceIDs[0] && + _grid->IsInternal( link._fIntPoints[i]->_faceIDs[0] ) && + link._fIntPoints[i]->_node ) + { + gp_Pnt p = SMESH_NodeXYZ( link._fIntPoints[i]->_node ); + double d = p1.SquareDistance( p ); + if ( d > maxDist2 ) + { + resultPnt = p; + maxDist2 = d; + } + else + { + d = p2.SquareDistance( p ); + if ( d > maxDist2 ) + { + resultPnt = p; + maxDist2 = d; + } + } + } + } + setIJK( hexIndex ); + _origNodeInd = _grid->NodeIndex( _i,_j,_k ); + + for ( size_t iN = 0; iN < 8; ++iN ) // check corners + { + _hexNodes[iN]._node = _grid->_nodes [ _origNodeInd + _nodeShift[iN] ]; + _hexNodes[iN]._intPoint = _grid->_gridIntP[ _origNodeInd + _nodeShift[iN] ]; + if ( _hexNodes[iN]._intPoint ) + for ( size_t iF = 0; iF < _hexNodes[iN]._intPoint->_faceIDs.size(); ++iF ) + { + if ( _grid->IsInternal( _hexNodes[iN]._intPoint->_faceIDs[iF])) + { + gp_Pnt p = SMESH_NodeXYZ( _hexNodes[iN]._node ); + double d = p1.SquareDistance( p ); + if ( d > maxDist2 ) + { + resultPnt = p; + maxDist2 = d; + } + else + { + d = p2.SquareDistance( p ); + if ( d > maxDist2 ) + { + resultPnt = p; + maxDist2 = d; + } + } + } + } + } + if ( maxDist2 < _grid->_tol * _grid->_tol ) + return p1; + + return resultPnt; + } + + //================================================================================ + /*! + * \brief Finds intersection of a curve with a plane + * \param [in] u1 - parameter of one curve point + * \param [in] proj1 - projection of the curve point to the plane normal + * \param [in] u2 - parameter of another curve point + * \param [in] proj2 - projection of the other curve point to the plane normal + * \param [in] proj - projection of a point where the curve intersects the plane + * \param [in] curve - the curve + * \param [in] axis - the plane normal + * \param [in] origin - the plane origin + * \return gp_Pnt - the found intersection point + */ + gp_Pnt Hexahedron::findIntPoint( double u1, double proj1, + double u2, double proj2, + double proj, + BRepAdaptor_Curve& curve, + const gp_XYZ& axis, + const gp_XYZ& origin) + { + double r = (( proj - proj1 ) / ( proj2 - proj1 )); + double u = u1 * ( 1 - r ) + u2 * r; + gp_Pnt p = curve.Value( u ); + double newProj = axis * ( p.XYZ() - origin ); + if ( Abs( proj - newProj ) > _grid->_tol / 10. ) + { + if ( r > 0.5 ) + return findIntPoint( u2, proj2, u, newProj, proj, curve, axis, origin ); + else + return findIntPoint( u1, proj2, u, newProj, proj, curve, axis, origin ); + } + return p; + } + + //================================================================================ + /*! + * \brief Returns indices of a hexahedron sub-entities holding a point + * \param [in] ip - intersection point + * \param [out] facets - 0-3 facets holding a point + * \param [out] sub - index of a vertex or an edge holding a point * \return int - number of facets holding a point */ int Hexahedron::getEntity( const E_IntersectPoint* ip, int* facets, int& sub ) @@ -2683,7 +3829,7 @@ namespace /*! * \brief Adds intersection with an EDGE */ - bool Hexahedron::addIntersection( const E_IntersectPoint& ip, + bool Hexahedron::addIntersection( const E_IntersectPoint* ip, vector< Hexahedron* >& hexes, int ijk[], int dIJK[] ) { @@ -2697,16 +3843,17 @@ namespace }; for ( int i = 0; i < 4; ++i ) { - if ( /*0 <= hexIndex[i] &&*/ hexIndex[i] < hexes.size() && hexes[ hexIndex[i] ] ) + if ( hexIndex[i] < hexes.size() && hexes[ hexIndex[i] ] ) { Hexahedron* h = hexes[ hexIndex[i] ]; - // check if ip is really inside the hex + h->_eIntPoints.reserve(2); + h->_eIntPoints.push_back( ip ); + added = true; #ifdef _DEBUG_ - if ( h->isOutParam( ip._uvw )) + // check if ip is really inside the hex + if ( h->isOutParam( ip->_uvw )) throw SALOME_Exception("ip outside a hex"); #endif - h->_eIntPoints.push_back( & ip ); - added = true; } } return added; @@ -2798,8 +3945,10 @@ namespace /*! * \brief Finds nodes on the same EDGE as the first node of avoidSplit. * - * This function is for a case where an EDGE lies on a quad which lies on a FACE - * so that a part of quad in ON and another part in IN + * This function is for + * 1) a case where an EDGE lies on a quad which lies on a FACE + * so that a part of quad in ON and another part is IN + * 2) INTERNAL FACE passes through the 1st node of avoidSplit */ bool Hexahedron::findChainOnEdge( const vector< _OrientedLink >& splits, const _OrientedLink& prevSplit, @@ -2808,19 +3957,16 @@ namespace _Face& quad, vector<_Node*>& chn ) { - if ( !isImplementEdges() ) - return false; - _Node* pn1 = prevSplit.FirstNode(); _Node* pn2 = prevSplit.LastNode(); int avoidFace = pn1->IsLinked( pn2->_intPoint ); // FACE under the quad if ( avoidFace < 1 && pn1->_intPoint ) return false; - _Node* n, *stopNode = avoidSplit.LastNode(); + _Node* n = 0, *stopNode = avoidSplit.LastNode(); chn.clear(); - if ( !quad._eIntNodes.empty() ) + if ( !quad._eIntNodes.empty() ) // connect pn2 with EDGE intersections { chn.push_back( pn2 ); bool found; @@ -2841,7 +3987,7 @@ namespace } int i; - for ( i = splits.size()-1; i >= 0; --i ) + for ( i = splits.size()-1; i >= 0; --i ) // connect new pn2 (at _eIntNodes) with a split { if ( !splits[i] ) continue; @@ -2862,7 +4008,7 @@ namespace break; n = 0; } - if ( n && n != stopNode) + if ( n && n != stopNode ) { if ( chn.empty() ) chn.push_back( pn2 ); @@ -2870,17 +4016,29 @@ namespace iS = i-1; return true; } + else if ( !chn.empty() && chn.back()->_isInternalFlags ) + { + // INTERNAL FACE partially cuts the quad + for ( int i = chn.size() - 2; i >= 0; --i ) + chn.push_back( chn[ i ]); + return true; + } return false; } //================================================================================ /*! * \brief Checks transition at the ginen intersection node of a link */ - bool Hexahedron::isOutPoint( _Link& link, int iP, SMESH_MesherHelper& helper ) const + bool Hexahedron::isOutPoint( _Link& link, int iP, + SMESH_MesherHelper& helper, const Solid* solid ) const { bool isOut = false; - const bool moreIntPoints = ( iP+1 < (int) link._fIntPoints.size() ); + if ( link._fIntNodes[iP]->faces().size() == 1 && + _grid->IsInternal( link._fIntNodes[iP]->face(0) )) + return false; + + const bool moreIntPoints = ( iP+1 < (int) link._fIntNodes.size() ); // get 2 _Node's _Node* n1 = link._fIntNodes[ iP ]; @@ -2894,16 +4052,16 @@ namespace // get all FACEs under n1 and n2 set< TGeomID > faceIDs; - if ( moreIntPoints ) faceIDs.insert( link._fIntPoints[iP+1]->_faceIDs.begin(), - link._fIntPoints[iP+1]->_faceIDs.end() ); + if ( moreIntPoints ) faceIDs.insert( link._fIntNodes[iP+1]->faces().begin(), + link._fIntNodes[iP+1]->faces().end() ); if ( n2->_intPoint ) faceIDs.insert( n2->_intPoint->_faceIDs.begin(), n2->_intPoint->_faceIDs.end() ); if ( faceIDs.empty() ) return false; // n2 is inside if ( n1->_intPoint ) faceIDs.insert( n1->_intPoint->_faceIDs.begin(), n1->_intPoint->_faceIDs.end() ); - faceIDs.insert( link._fIntPoints[iP]->_faceIDs.begin(), - link._fIntPoints[iP]->_faceIDs.end() ); + faceIDs.insert( link._fIntNodes[iP]->faces().begin(), + link._fIntNodes[iP]->faces().end() ); // get a point between 2 nodes gp_Pnt p1 = n1->Point(); @@ -2916,10 +4074,9 @@ namespace for ( ; faceID != faceIDs.end(); ++faceID ) { // project pOnLink on a FACE - if ( *faceID < 1 ) continue; - const TopoDS_Face& face = TopoDS::Face( _grid->_shapes( *faceID )); - GeomAPI_ProjectPointOnSurf& proj = - helper.GetProjector( face, loc, 0.1*_grid->_tol ); + if ( *faceID < 1 || !solid->Contains( *faceID )) continue; + const TopoDS_Face& face = TopoDS::Face( _grid->Shape( *faceID )); + GeomAPI_ProjectPointOnSurf& proj = helper.GetProjector( face, loc, 0.1*_grid->_tol ); gp_Pnt testPnt = pOnLink.Transformed( loc.Transformation().Inverted() ); proj.Perform( testPnt ); if ( proj.IsDone() && proj.NbPoints() > 0 ) @@ -2940,7 +4097,7 @@ namespace 0.1*_grid->_tol, normal ) < 3 ) { - if ( face.Orientation() == TopAbs_REVERSED ) + if ( solid->Orientation( face ) == TopAbs_REVERSED ) normal.Reverse(); gp_Vec v( proj.NearestPoint(), testPnt ); isOut = ( v * normal > 0 ); @@ -2980,7 +4137,7 @@ namespace return; // get shapes of the FACE - const TopoDS_Face& face = TopoDS::Face( _grid->_shapes( faceID )); + const TopoDS_Face& face = TopoDS::Face( _grid->Shape( faceID )); list< TopoDS_Edge > edges; list< int > nbEdges; int nbW = SMESH_Block::GetOrderedEdges (face, edges, nbEdges); @@ -2995,8 +4152,8 @@ namespace for ( int i = 0; i < 2; ++i ) { TGeomID id = i==0 ? - _grid->_shapes.FindIndex( *e ) : - _grid->_shapes.FindIndex( SMESH_MesherHelper::IthVertex( 0, *e )); + _grid->ShapeID( *e ) : + _grid->ShapeID( SMESH_MesherHelper::IthVertex( 0, *e )); if (( id > 0 ) && ( std::find( &nShapeIds[0], nShapeIdsEnd, id ) != nShapeIdsEnd )) { @@ -3015,7 +4172,7 @@ namespace list< TopoDS_Edge >::iterator e = edges.begin(), eMidOut = edges.end(); for ( ; e != edges.end(); ++e ) { - if ( !_grid->_shapes.FindIndex( *e )) + if ( !_grid->ShapeID( *e )) continue; bool isOut = false; gp_Pnt p; @@ -3063,14 +4220,14 @@ namespace TGeomID id, *pID = 0; for ( e = edges.begin(); e != edges.end(); ++e ) { - if (( id = _grid->_shapes.FindIndex( SMESH_MesherHelper::IthVertex( 0, *e ))) && + if (( id = _grid->ShapeID( SMESH_MesherHelper::IthVertex( 0, *e ))) && (( pID = std::find( &nShapeIds[0], nShapeIdsEnd, id )) != nShapeIdsEnd )) { //orderShapeIDs[ nbN ] = id; orderNodes [ nbN++ ] = nodes[ pID - &nShapeIds[0] ]; *pID = -1; } - if (( id = _grid->_shapes.FindIndex( *e )) && + if (( id = _grid->ShapeID( *e )) && (( pID = std::find( &nShapeIds[0], nShapeIdsEnd, id )) != nShapeIdsEnd )) { //orderShapeIDs[ nbN ] = id; @@ -3092,46 +4249,81 @@ namespace /*! * \brief Adds computed elements to the mesh */ - int Hexahedron::addElements(SMESH_MesherHelper& helper) + int Hexahedron::addVolumes( SMESH_MesherHelper& helper ) { + F_IntersectPoint noIntPnt; + const bool toCheckNodePos = _grid->IsToCheckNodePos(); + int nbAdded = 0; // add elements resulted from hexahedron intersection - //for ( size_t i = 0; i < _volumeDefs.size(); ++i ) + for ( _volumeDef* volDef = &_volumeDefs; volDef; volDef = volDef->_next ) { - vector< const SMDS_MeshNode* > nodes( _volumeDefs._nodes.size() ); + vector< const SMDS_MeshNode* > nodes( volDef->_nodes.size() ); for ( size_t iN = 0; iN < nodes.size(); ++iN ) - if ( !( nodes[iN] = _volumeDefs._nodes[iN]->Node() )) + { + if ( !( nodes[iN] = volDef->_nodes[iN].Node() )) { - if ( const E_IntersectPoint* eip = _volumeDefs._nodes[iN]->EdgeIntPnt() ) - nodes[iN] = _volumeDefs._nodes[iN]->_intPoint->_node = + if ( const E_IntersectPoint* eip = volDef->_nodes[iN].EdgeIntPnt() ) + { + nodes[iN] = volDef->_nodes[iN]._intPoint->_node = helper.AddNode( eip->_point.X(), eip->_point.Y(), eip->_point.Z() ); + if ( _grid->ShapeType( eip->_shapeID ) == TopAbs_VERTEX ) + helper.GetMeshDS()->SetNodeOnVertex( nodes[iN], eip->_shapeID ); + else + helper.GetMeshDS()->SetNodeOnEdge( nodes[iN], eip->_shapeID ); + } else throw SALOME_Exception("Bug: no node at intersection point"); } + else if ( volDef->_nodes[iN]._intPoint && + volDef->_nodes[iN]._intPoint->_node == volDef->_nodes[iN]._node ) + { + // Update position of node at EDGE intersection; + // see comment to _Node::Add( E_IntersectPoint ) + SMESHDS_Mesh* mesh = helper.GetMeshDS(); + TGeomID shapeID = volDef->_nodes[iN].EdgeIntPnt()->_shapeID; + mesh->UnSetNodeOnShape( nodes[iN] ); + if ( _grid->ShapeType( shapeID ) == TopAbs_VERTEX ) + mesh->SetNodeOnVertex( nodes[iN], shapeID ); + else + mesh->SetNodeOnEdge( nodes[iN], shapeID ); + } + else if ( toCheckNodePos && + !nodes[iN]->isMarked() && + _grid->ShapeType( nodes[iN]->GetShapeID() ) == TopAbs_FACE ) + { + _grid->SetOnShape( nodes[iN], noIntPnt, /*unset=*/true ); + nodes[iN]->setIsMarked( true ); + } + } - if ( !_volumeDefs._quantities.empty() ) + const SMDS_MeshElement* v = 0; + if ( !volDef->_quantities.empty() ) { - helper.AddPolyhedralVolume( nodes, _volumeDefs._quantities ); + v = helper.AddPolyhedralVolume( nodes, volDef->_quantities ); } else { switch ( nodes.size() ) { - case 8: helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3], - nodes[4],nodes[5],nodes[6],nodes[7] ); + case 8: v = helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3], + nodes[4],nodes[5],nodes[6],nodes[7] ); break; - case 4: helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3] ); + case 4: v = helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3] ); break; - case 6: helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3], nodes[4],nodes[5] ); + case 6: v = helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3],nodes[4],nodes[5] ); break; - case 5: - helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3],nodes[4] ); + case 5: v = helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3],nodes[4] ); break; } } - nbAdded += int ( _volumeDefs._nodes.size() > 0 ); + if (( volDef->_volume = v )) + { + helper.GetMeshDS()->SetMeshElementOnShape( v, volDef->_solidID ); + ++nbAdded; + } } return nbAdded; @@ -3182,7 +4374,8 @@ namespace if ( firstIntPnt ) { hasLinks = true; - allLinksOut = ( firstIntPnt->_transition == Trans_OUT ); + allLinksOut = ( firstIntPnt->_transition == Trans_OUT && + !_grid->IsShared( firstIntPnt->_faceIDs[0] )); } } if ( hasLinks && allLinksOut ) @@ -3191,12 +4384,68 @@ namespace return false; } + //================================================================================ + /*! + * \brief Check if a polyherdon has an edge lying on EDGE shared by strange FACE + * that will be meshed by other algo + */ + bool Hexahedron::hasStrangeEdge() const + { + if ( _eIntPoints.size() < 2 ) + return false; + + TopTools_MapOfShape edges; + for ( size_t i = 0; i < _eIntPoints.size(); ++i ) + { + if ( !_grid->IsStrangeEdge( _eIntPoints[i]->_shapeID )) + continue; + const TopoDS_Shape& s = _grid->Shape( _eIntPoints[i]->_shapeID ); + if ( s.ShapeType() == TopAbs_EDGE ) + { + if ( ! edges.Add( s )) + return true; // an EDGE encounters twice + } + else + { + PShapeIteratorPtr edgeIt = _grid->_helper->GetAncestors( s, + *_grid->_helper->GetMesh(), + TopAbs_EDGE ); + while ( const TopoDS_Shape* edge = edgeIt->next() ) + if ( ! edges.Add( *edge )) + return true; // an EDGE encounters twice + } + } + return false; + } + //================================================================================ /*! * \brief Return true if a polyhedron passes _sizeThreshold criterion */ - bool Hexahedron::checkPolyhedronSize() const + bool Hexahedron::checkPolyhedronSize( bool cutByInternalFace ) const { + if ( cutByInternalFace && !_grid->_toUseThresholdForInternalFaces ) + { + // check if any polygon fully lies on shared/internal FACEs + for ( size_t iP = 0; iP < _polygons.size(); ++iP ) + { + const _Face& polygon = _polygons[iP]; + if ( polygon._links.empty() ) + continue; + bool allNodesInternal = true; + for ( size_t iL = 0; iL < polygon._links.size() && allNodesInternal; ++iL ) + { + _Node* n = polygon._links[ iL ].FirstNode(); + allNodesInternal = (( n->IsCutByInternal() ) || + ( n->_intPoint && _grid->IsAnyShared( n->_intPoint->_faceIDs ))); + } + if ( allNodesInternal ) + return true; + } + } + if ( this->hasStrangeEdge() ) + return true; + double volume = 0; for ( size_t iP = 0; iP < _polygons.size(); ++iP ) { @@ -3217,7 +4466,7 @@ namespace double initVolume = _sideLength[0] * _sideLength[1] * _sideLength[2]; - return volume > initVolume / _sizeThreshold; + return volume > initVolume / _grid->_sizeThreshold; } //================================================================================ /*! @@ -3263,7 +4512,7 @@ namespace } } if ( nbN == 8 ) - _volumeDefs.set( &nodes[0], 8 ); + _volumeDefs.Set( &nodes[0], 8 ); return nbN == 8; } @@ -3295,7 +4544,7 @@ namespace if ( tria->_links[i]._link == link ) { nodes[3] = tria->_links[(i+1)%3].LastNode(); - _volumeDefs.set( &nodes[0], 4 ); + _volumeDefs.Set( &nodes[0], 4 ); return true; } @@ -3340,7 +4589,7 @@ namespace } } if ( nbN == 6 ) - _volumeDefs.set( &nodes[0], 6 ); + _volumeDefs.Set( &nodes[0], 6 ); return ( nbN == 6 ); } @@ -3375,7 +4624,7 @@ namespace if ( tria->_links[i]._link == link ) { nodes[4] = tria->_links[(i+1)%3].LastNode(); - _volumeDefs.set( &nodes[0], 5 ); + _volumeDefs.Set( &nodes[0], 5 ); return true; } @@ -3408,6 +4657,467 @@ namespace ( _grid->_coords[2][ _k ] - _grid->_tol > uvw[2] ) || ( _grid->_coords[2][ _k+1 ] + _grid->_tol < uvw[2] )); } + //================================================================================ + /*! + * \brief Divide a polygon into triangles and modify accordingly an adjacent polyhedron + */ + void splitPolygon( const SMDS_MeshElement* polygon, + SMDS_VolumeTool & volume, + const int facetIndex, + const TGeomID faceID, + const TGeomID solidID, + SMESH_MeshEditor::ElemFeatures& face, + SMESH_MeshEditor& editor, + const bool reinitVolume) + { + SMESH_MeshAlgos::Triangulate divider(/*optimize=*/false); + int nbTrias = divider.GetTriangles( polygon, face.myNodes ); + face.myNodes.resize( nbTrias * 3 ); + + SMESH_MeshEditor::ElemFeatures newVolumeDef; + newVolumeDef.Init( volume.Element() ); + newVolumeDef.SetID( volume.Element()->GetID() ); + + newVolumeDef.myPolyhedQuantities.reserve( volume.NbFaces() + nbTrias ); + newVolumeDef.myNodes.reserve( volume.NbNodes() + nbTrias * 3 ); + + SMESHDS_Mesh* meshDS = editor.GetMeshDS(); + SMDS_MeshElement* newTriangle; + for ( int iF = 0, nF = volume.NbFaces(); iF < nF; iF++ ) + { + if ( iF == facetIndex ) + { + newVolumeDef.myPolyhedQuantities.push_back( 3 ); + newVolumeDef.myNodes.insert( newVolumeDef.myNodes.end(), + face.myNodes.begin(), + face.myNodes.begin() + 3 ); + meshDS->RemoveFreeElement( polygon, 0, false ); + newTriangle = meshDS->AddFace( face.myNodes[0], face.myNodes[1], face.myNodes[2] ); + meshDS->SetMeshElementOnShape( newTriangle, faceID ); + } + else + { + const SMDS_MeshNode** nn = volume.GetFaceNodes( iF ); + const size_t nbFaceNodes = volume.NbFaceNodes ( iF ); + newVolumeDef.myPolyhedQuantities.push_back( nbFaceNodes ); + newVolumeDef.myNodes.insert( newVolumeDef.myNodes.end(), nn, nn + nbFaceNodes ); + } + } + + for ( size_t iN = 3; iN < face.myNodes.size(); iN += 3 ) + { + newVolumeDef.myPolyhedQuantities.push_back( 3 ); + newVolumeDef.myNodes.insert( newVolumeDef.myNodes.end(), + face.myNodes.begin() + iN, + face.myNodes.begin() + iN + 3 ); + newTriangle = meshDS->AddFace( face.myNodes[iN], face.myNodes[iN+1], face.myNodes[iN+2] ); + meshDS->SetMeshElementOnShape( newTriangle, faceID ); + } + + meshDS->RemoveFreeElement( volume.Element(), 0, false ); + SMDS_MeshElement* newVolume = editor.AddElement( newVolumeDef.myNodes, newVolumeDef ); + meshDS->SetMeshElementOnShape( newVolume, solidID ); + + if ( reinitVolume ) + { + volume.Set( 0 ); + volume.Set( newVolume ); + } + return; + } + //================================================================================ + /*! + * \brief Create mesh faces at free facets + */ + void Hexahedron::addFaces( SMESH_MesherHelper& helper, + const vector< const SMDS_MeshElement* > & boundaryVolumes ) + { + if ( !_grid->_toCreateFaces ) + return; + + SMDS_VolumeTool vTool; + vector bndFacets; + SMESH_MeshEditor editor( helper.GetMesh() ); + SMESH_MeshEditor::ElemFeatures face( SMDSAbs_Face ); + SMESHDS_Mesh* meshDS = helper.GetMeshDS(); + + // check if there are internal or shared FACEs + bool hasInternal = ( !_grid->_geometry.IsOneSolid() || + _grid->_geometry._soleSolid.HasInternalFaces() ); + + for ( size_t iV = 0; iV < boundaryVolumes.size(); ++iV ) + { + if ( !vTool.Set( boundaryVolumes[ iV ])) + continue; + + TGeomID solidID = vTool.Element()->GetShapeID(); + Solid * solid = _grid->GetOneOfSolids( solidID ); + + // find boundary facets + + bndFacets.clear(); + for ( int iF = 0, n = vTool.NbFaces(); iF < n; iF++ ) + { + bool isBoundary = vTool.IsFreeFace( iF ); + if ( isBoundary ) + { + bndFacets.push_back( iF ); + } + else if ( hasInternal ) + { + // check if all nodes are on internal/shared FACEs + isBoundary = true; + const SMDS_MeshNode** nn = vTool.GetFaceNodes( iF ); + const size_t nbFaceNodes = vTool.NbFaceNodes ( iF ); + for ( size_t iN = 0; iN < nbFaceNodes && isBoundary; ++iN ) + isBoundary = ( nn[ iN ]->GetShapeID() != solidID ); + if ( isBoundary ) + bndFacets.push_back( -( iF+1 )); // !!! minus ==> to check the FACE + } + } + if ( bndFacets.empty() ) + continue; + + // create faces + + if ( !vTool.IsPoly() ) + vTool.SetExternalNormal(); + for ( size_t i = 0; i < bndFacets.size(); ++i ) // loop on boundary facets + { + const bool isBoundary = ( bndFacets[i] >= 0 ); + const int iFacet = isBoundary ? bndFacets[i] : -bndFacets[i]-1; + const SMDS_MeshNode** nn = vTool.GetFaceNodes( iFacet ); + const size_t nbFaceNodes = vTool.NbFaceNodes ( iFacet ); + face.myNodes.assign( nn, nn + nbFaceNodes ); + + TGeomID faceID = 0; + const SMDS_MeshElement* existFace = 0, *newFace = 0; + + if (( existFace = meshDS->FindElement( face.myNodes, SMDSAbs_Face ))) + { + if ( existFace->isMarked() ) + continue; // created by this method + faceID = existFace->GetShapeID(); + } + else + { + // look for a supporting FACE + for ( size_t iN = 0; iN < nbFaceNodes && !faceID; ++iN ) // look for a node on FACE + { + if ( nn[ iN ]->GetPosition()->GetDim() == 2 ) + faceID = nn[ iN ]->GetShapeID(); + } + for ( size_t iN = 0; iN < nbFaceNodes && !faceID; ++iN ) + { + // look for a father FACE of EDGEs and VERTEXes + const TopoDS_Shape& s1 = _grid->Shape( nn[ iN ]->GetShapeID() ); + const TopoDS_Shape& s2 = _grid->Shape( nn[ iN+1 ]->GetShapeID() ); + if ( s1 != s2 && s1.ShapeType() == TopAbs_EDGE && s2.ShapeType() == TopAbs_EDGE ) + { + TopoDS_Shape f = helper.GetCommonAncestor( s1, s2, *helper.GetMesh(), TopAbs_FACE ); + if ( !f.IsNull() ) + faceID = _grid->ShapeID( f ); + } + } + + bool toCheckFace = faceID && (( !isBoundary ) || + ( hasInternal && _grid->_toUseThresholdForInternalFaces )); + if ( toCheckFace ) // check if all nodes are on the found FACE + { + SMESH_subMesh* faceSM = helper.GetMesh()->GetSubMeshContaining( faceID ); + for ( size_t iN = 0; iN < nbFaceNodes && faceID; ++iN ) + { + TGeomID subID = nn[ iN ]->GetShapeID(); + if ( subID != faceID && !faceSM->DependsOn( subID )) + faceID = 0; + } + if ( !faceID && !isBoundary ) + continue; + } + } + // orient a new face according to supporting FACE orientation in shape_to_mesh + if ( !solid->IsOutsideOriented( faceID )) + { + if ( existFace ) + editor.Reorient( existFace ); + else + std::reverse( face.myNodes.begin(), face.myNodes.end() ); + } + + if ( ! ( newFace = existFace )) + { + face.SetPoly( nbFaceNodes > 4 ); + newFace = editor.AddElement( face.myNodes, face ); + if ( !newFace ) + continue; + newFace->setIsMarked( true ); // to distinguish from face created in getBoundaryElems() + } + + if ( faceID && _grid->IsBoundaryFace( faceID )) // face is not shared + { + // set newFace to the found FACE provided that it fully lies on the FACE + for ( size_t iN = 0; iN < nbFaceNodes && faceID; ++iN ) + if ( nn[iN]->GetShapeID() == solidID ) + { + if ( existFace ) + meshDS->UnSetMeshElementOnShape( existFace, _grid->Shape( faceID )); + faceID = 0; + } + } + + // split a polygon that will be used by other 3D algorithm + if ( faceID && nbFaceNodes > 4 && + !_grid->IsInternal( faceID ) && + !_grid->IsShared( faceID ) && + !_grid->IsBoundaryFace( faceID )) + { + splitPolygon( newFace, vTool, iFacet, faceID, solidID, + face, editor, i+1 < bndFacets.size() ); + } + else + { + if ( faceID ) + meshDS->SetMeshElementOnShape( newFace, faceID ); + else + meshDS->SetMeshElementOnShape( newFace, solidID ); + } + } // loop on bndFacets + } // loop on boundaryVolumes + + + // Orient coherently mesh faces on INTERNAL FACEs + + if ( hasInternal ) + { + TopExp_Explorer faceExp( _grid->_geometry._mainShape, TopAbs_FACE ); + for ( ; faceExp.More(); faceExp.Next() ) + { + if ( faceExp.Current().Orientation() != TopAbs_INTERNAL ) + continue; + + SMESHDS_SubMesh* sm = meshDS->MeshElements( faceExp.Current() ); + if ( !sm ) continue; + + TIDSortedElemSet facesToOrient; + for ( SMDS_ElemIteratorPtr fIt = sm->GetElements(); fIt->more(); ) + facesToOrient.insert( facesToOrient.end(), fIt->next() ); + if ( facesToOrient.size() < 2 ) + continue; + + gp_Dir direction(1,0,0); + const SMDS_MeshElement* anyFace = *facesToOrient.begin(); + editor.Reorient2D( facesToOrient, direction, anyFace ); + } + } + return; + } + + //================================================================================ + /*! + * \brief Create mesh segments. + */ + void Hexahedron::addSegments( SMESH_MesherHelper& helper, + const map< TGeomID, vector< TGeomID > >& edge2faceIDsMap ) + { + SMESHDS_Mesh* mesh = helper.GetMeshDS(); + + std::vector nodes; + std::vector elems; + map< TGeomID, vector< TGeomID > >::const_iterator e2ff = edge2faceIDsMap.begin(); + for ( ; e2ff != edge2faceIDsMap.end(); ++e2ff ) + { + const TopoDS_Edge& edge = TopoDS::Edge( _grid->Shape( e2ff->first )); + const TopoDS_Face& face = TopoDS::Face( _grid->Shape( e2ff->second[0] )); + StdMeshers_FaceSide side( face, edge, helper.GetMesh(), /*isFwd=*/true, /*skipMed=*/true ); + nodes = side.GetOrderedNodes(); + + elems.clear(); + if ( nodes.size() == 2 ) + // check that there is an element connecting two nodes + if ( !mesh->GetElementsByNodes( nodes, elems )) + continue; + + for ( size_t i = 1; i < nodes.size(); i++ ) + { + SMDS_MeshElement* segment = mesh->AddEdge( nodes[i-1], nodes[i] ); + mesh->SetMeshElementOnShape( segment, e2ff->first ); + } + } + return; + } + + //================================================================================ + /*! + * \brief Return created volumes and volumes that can have free facet because of + * skipped small volume. Also create mesh faces on free facets + * of adjacent not-cut volumes id the result volume is too small. + */ + void Hexahedron::getBoundaryElems( vector< const SMDS_MeshElement* > & boundaryElems ) + { + if ( _hasTooSmall /*|| _volumeDefs.IsEmpty()*/ ) + { + // create faces around a missing small volume + TGeomID faceID = 0; + SMESH_MeshEditor editor( _grid->_helper->GetMesh() ); + SMESH_MeshEditor::ElemFeatures polygon( SMDSAbs_Face ); + SMESHDS_Mesh* meshDS = _grid->_helper->GetMeshDS(); + std::vector adjVolumes(2); + for ( size_t iF = 0; iF < _polygons.size(); ++iF ) + { + const size_t nbLinks = _polygons[ iF ]._links.size(); + if ( nbLinks != 4 ) continue; + polygon.myNodes.resize( nbLinks ); + polygon.myNodes.back() = 0; + for ( size_t iL = 0, iN = nbLinks - 1; iL < nbLinks; ++iL, --iN ) + if ( ! ( polygon.myNodes[iN] = _polygons[ iF ]._links[ iL ].FirstNode()->Node() )) + break; + if ( !polygon.myNodes.back() ) + continue; + + meshDS->GetElementsByNodes( polygon.myNodes, adjVolumes, SMDSAbs_Volume ); + if ( adjVolumes.size() != 1 ) + continue; + if ( !adjVolumes[0]->isMarked() ) + { + boundaryElems.push_back( adjVolumes[0] ); + adjVolumes[0]->setIsMarked( true ); + } + + bool sameShape = true; + TGeomID shapeID = polygon.myNodes[0]->GetShapeID(); + for ( size_t i = 1; i < polygon.myNodes.size() && sameShape; ++i ) + sameShape = ( shapeID == polygon.myNodes[i]->GetShapeID() ); + + if ( !sameShape || !_grid->IsSolid( shapeID )) + continue; // some of shapes must be FACE + + if ( !faceID ) + { + faceID = getAnyFace(); + if ( !faceID ) + break; + if ( _grid->IsInternal( faceID ) || + _grid->IsShared( faceID ) || + _grid->IsBoundaryFace( faceID )) + break; // create only if a new face will be used by other 3D algo + } + + Solid * solid = _grid->GetOneOfSolids( adjVolumes[0]->GetShapeID() ); + if ( !solid->IsOutsideOriented( faceID )) + std::reverse( polygon.myNodes.begin(), polygon.myNodes.end() ); + + //polygon.SetPoly( polygon.myNodes.size() > 4 ); + const SMDS_MeshElement* newFace = editor.AddElement( polygon.myNodes, polygon ); + meshDS->SetMeshElementOnShape( newFace, faceID ); + } + } + + // return created volumes + for ( _volumeDef* volDef = &_volumeDefs; volDef; volDef = volDef->_next ) + { + if ( volDef->_volume && !volDef->_volume->isMarked() ) + { + volDef->_volume->setIsMarked( true ); + boundaryElems.push_back( volDef->_volume ); + + if ( _grid->IsToCheckNodePos() ) // un-mark nodes marked in addVolumes() + for ( size_t iN = 0; iN < volDef->_nodes.size(); ++iN ) + volDef->_nodes[iN].Node()->setIsMarked( false ); + } + } + } + + //================================================================================ + /*! + * \brief Set to _hexLinks a next portion of splits located on one side of INTERNAL FACEs + */ + bool Hexahedron::_SplitIterator::Next() + { + if ( _iterationNb > 0 ) + // count used splits + for ( size_t i = 0; i < _splits.size(); ++i ) + { + if ( _splits[i]._iCheckIteration == _iterationNb ) + { + _splits[i]._isUsed = _splits[i]._checkedSplit->_faces[1]; + _nbUsed += _splits[i]._isUsed; + } + if ( !More() ) + return false; + } + + ++_iterationNb; + + bool toTestUsed = ( _nbChecked >= _splits.size() ); + if ( toTestUsed ) + { + // all splits are checked; find all not used splits + for ( size_t i = 0; i < _splits.size(); ++i ) + if ( !_splits[i].IsCheckedOrUsed( toTestUsed )) + _splits[i]._iCheckIteration = _iterationNb; + + _nbUsed = _splits.size(); // to stop iteration + } + else + { + // get any not used/checked split to start from + _freeNodes.clear(); + for ( size_t i = 0; i < _splits.size(); ++i ) + { + if ( !_splits[i].IsCheckedOrUsed( toTestUsed )) + { + _freeNodes.push_back( _splits[i]._nodes[0] ); + _freeNodes.push_back( _splits[i]._nodes[1] ); + _splits[i]._iCheckIteration = _iterationNb; + break; + } + } + // find splits connected to the start one via _freeNodes + for ( size_t iN = 0; iN < _freeNodes.size(); ++iN ) + { + for ( size_t iS = 0; iS < _splits.size(); ++iS ) + { + if ( _splits[iS].IsCheckedOrUsed( toTestUsed )) + continue; + int iN2 = -1; + if ( _freeNodes[iN] == _splits[iS]._nodes[0] ) + iN2 = 1; + else if ( _freeNodes[iN] == _splits[iS]._nodes[1] ) + iN2 = 0; + else + continue; + if ( _freeNodes[iN]->_isInternalFlags > 0 ) + { + if ( _splits[iS]._nodes[ iN2 ]->_isInternalFlags == 0 ) + continue; + if ( !_splits[iS]._nodes[ iN2 ]->IsLinked( _freeNodes[iN]->_intPoint )) + continue; + } + _splits[iS]._iCheckIteration = _iterationNb; + _freeNodes.push_back( _splits[iS]._nodes[ iN2 ]); + } + } + } + // set splits to hex links + + for ( int iL = 0; iL < 12; ++iL ) + _hexLinks[ iL ]._splits.clear(); + + _Link split; + for ( size_t i = 0; i < _splits.size(); ++i ) + { + if ( _splits[i]._iCheckIteration == _iterationNb ) + { + split._nodes[0] = _splits[i]._nodes[0]; + split._nodes[1] = _splits[i]._nodes[1]; + _Link & hexLink = _hexLinks[ _splits[i]._linkID ]; + hexLink._splits.push_back( split ); + _splits[i]._checkedSplit = & hexLink._splits.back(); + ++_nbChecked; + } + } + return More(); + } //================================================================================ /*! @@ -3524,50 +5234,46 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, _computeCanceled = false; SMESH_MesherHelper helper( theMesh ); + SMESHDS_Mesh* meshDS = theMesh.GetMeshDS(); try { Grid grid; - grid._helper = &helper; + grid._helper = &helper; + grid._toAddEdges = _hyp->GetToAddEdges(); + grid._toCreateFaces = _hyp->GetToCreateFaces(); + grid._toConsiderInternalFaces = _hyp->GetToConsiderInternalFaces(); + grid._toUseThresholdForInternalFaces = _hyp->GetToUseThresholdForInternalFaces(); + grid._sizeThreshold = _hyp->GetSizeThreshold(); + grid.InitGeometry( theShape ); vector< TopoDS_Shape > faceVec; { TopTools_MapOfShape faceMap; TopExp_Explorer fExp; for ( fExp.Init( theShape, TopAbs_FACE ); fExp.More(); fExp.Next() ) - if ( !faceMap.Add( fExp.Current() )) - faceMap.Remove( fExp.Current() ); // remove a face shared by two solids - - for ( fExp.ReInit(); fExp.More(); fExp.Next() ) - if ( faceMap.Contains( fExp.Current() )) - faceVec.push_back( fExp.Current() ); + { + bool isNewFace = faceMap.Add( fExp.Current() ); + if ( !grid._toConsiderInternalFaces ) + if ( !isNewFace || fExp.Current().Orientation() == TopAbs_INTERNAL ) + // remove an internal face + faceMap.Remove( fExp.Current() ); + } + faceVec.reserve( faceMap.Extent() ); + faceVec.assign( faceMap.cbegin(), faceMap.cend() ); } vector facesItersectors( faceVec.size() ); - map< TGeomID, vector< TGeomID > > edge2faceIDsMap; - TopExp_Explorer eExp; Bnd_Box shapeBox; for ( size_t i = 0; i < faceVec.size(); ++i ) { - facesItersectors[i]._face = TopoDS::Face ( faceVec[i] ); - facesItersectors[i]._faceID = grid._shapes.Add( faceVec[i] ); + facesItersectors[i]._face = TopoDS::Face( faceVec[i] ); + facesItersectors[i]._faceID = grid.ShapeID( faceVec[i] ); facesItersectors[i]._grid = &grid; shapeBox.Add( facesItersectors[i].GetFaceBndBox() ); - - if ( _hyp->GetToAddEdges() ) - { - helper.SetSubShape( faceVec[i] ); - for ( eExp.Init( faceVec[i], TopAbs_EDGE ); eExp.More(); eExp.Next() ) - { - const TopoDS_Edge& edge = TopoDS::Edge( eExp.Current() ); - if ( !SMESH_Algo::isDegenerated( edge ) && - !helper.IsRealSeam( edge )) - edge2faceIDsMap[ grid._shapes.Add( edge )].push_back( facesItersectors[i]._faceID ); - } - } } - getExactBndBox( faceVec, _hyp->GetAxisDirs(), shapeBox ); + vector xCoords, yCoords, zCoords; _hyp->GetCoordinates( xCoords, yCoords, zCoords, shapeBox ); @@ -3581,7 +5287,7 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, BRepBuilderAPI_Copy copier; for ( size_t i = 0; i < facesItersectors.size(); ++i ) { - if ( !facesItersectors[i].IsThreadSafe(tshapes) ) + if ( !facesItersectors[i].IsThreadSafe( tshapes )) { copier.Perform( facesItersectors[i]._face ); facesItersectors[i]._face = TopoDS::Face( copier ); @@ -3603,33 +5309,36 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, for ( size_t i = 0; i < facesItersectors.size(); ++i ) facesItersectors[i].StoreIntersections(); - TopExp_Explorer solidExp (theShape, TopAbs_SOLID); - helper.SetSubShape( solidExp.Current() ); - helper.SetElementsOnShape( true ); - if ( _computeCanceled ) return false; // create nodes on the geometry - grid.ComputeNodes(helper); + grid.ComputeNodes( helper ); if ( _computeCanceled ) return false; + // get EDGEs to take into account + map< TGeomID, vector< TGeomID > > edge2faceIDsMap; + grid.GetEdgesToImplement( edge2faceIDsMap, theShape, faceVec ); + // create volume elements - Hexahedron hex( _hyp->GetSizeThreshold(), &grid ); + Hexahedron hex( &grid ); int nbAdded = hex.MakeElements( helper, edge2faceIDsMap ); - SMESHDS_Mesh* meshDS = theMesh.GetMeshDS(); if ( nbAdded > 0 ) { - // make all SOLIDs computed - if ( SMESHDS_SubMesh* sm1 = meshDS->MeshElements( solidExp.Current()) ) + if ( !grid._toConsiderInternalFaces ) { - SMDS_ElemIteratorPtr volIt = sm1->GetElements(); - for ( ; solidExp.More() && volIt->more(); solidExp.Next() ) + // make all SOLIDs computed + TopExp_Explorer solidExp( theShape, TopAbs_SOLID ); + if ( SMESHDS_SubMesh* sm1 = meshDS->MeshElements( solidExp.Current()) ) { - const SMDS_MeshElement* vol = volIt->next(); - sm1->RemoveElement( vol ); - meshDS->SetMeshElementOnShape( vol, solidExp.Current() ); + SMDS_ElemIteratorPtr volIt = sm1->GetElements(); + for ( ; solidExp.More() && volIt->more(); solidExp.Next() ) + { + const SMDS_MeshElement* vol = volIt->next(); + sm1->RemoveElement( vol ); + meshDS->SetMeshElementOnShape( vol, solidExp.Current() ); + } } } // make other sub-shapes computed @@ -3637,9 +5346,9 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, } // remove free nodes - if ( SMESHDS_SubMesh * smDS = meshDS->MeshElements( helper.GetSubShapeID() )) + //if ( SMESHDS_SubMesh * smDS = meshDS->MeshElements( helper.GetSubShapeID() )) { - TIDSortedNodeSet nodesToRemove; + std::vector< const SMDS_MeshNode* > nodesToRemove; // get intersection nodes for ( int iDir = 0; iDir < 3; ++iDir ) { @@ -3648,19 +5357,25 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, { multiset< F_IntersectPoint >::iterator ip = lines[i]._intPoints.begin(); for ( ; ip != lines[i]._intPoints.end(); ++ip ) - if ( ip->_node && ip->_node->NbInverseElements() == 0 ) - nodesToRemove.insert( nodesToRemove.end(), ip->_node ); + if ( ip->_node && ip->_node->NbInverseElements() == 0 && !ip->_node->isMarked() ) + { + nodesToRemove.push_back( ip->_node ); + ip->_node->setIsMarked( true ); + } } } // get grid nodes for ( size_t i = 0; i < grid._nodes.size(); ++i ) - if ( grid._nodes[i] && grid._nodes[i]->NbInverseElements() == 0 ) - nodesToRemove.insert( nodesToRemove.end(), grid._nodes[i] ); + if ( grid._nodes[i] && grid._nodes[i]->NbInverseElements() == 0 && + !grid._nodes[i]->isMarked() ) + { + nodesToRemove.push_back( grid._nodes[i] ); + grid._nodes[i]->setIsMarked( true ); + } // do remove - TIDSortedNodeSet::iterator n = nodesToRemove.begin(); - for ( ; n != nodesToRemove.end(); ++n ) - meshDS->RemoveFreeNode( *n, smDS, /*fromGroups=*/false ); + for ( size_t i = 0; i < nodesToRemove.size(); ++i ) + meshDS->RemoveFreeNode( nodesToRemove[i], /*smD=*/0, /*fromGroups=*/false ); } return nbAdded; @@ -3795,4 +5510,3 @@ void StdMeshers_Cartesian_3D::setSubmeshesComputed(SMESH_Mesh& theMesh, for ( TopExp_Explorer soExp( theShape, TopAbs_SOLID ); soExp.More(); soExp.Next() ) _EventListener::setAlwaysComputed( true, theMesh.GetSubMesh( soExp.Current() )); } - diff --git a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx index b70d05091..f1271c105 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx +++ b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx @@ -204,8 +204,8 @@ namespace StdMeshersGUI axisTabLayout->setSpacing( SPACING ); axisTabLayout->addWidget( modeBox , 0, 0, 1, 3 ); - axisTabLayout->addWidget( myInsertBtn , 1, 0, 1, 2 ); - axisTabLayout->addWidget( myDeleteBtn , 2, 0, 1, 2 ); + axisTabLayout->addWidget( myInsertBtn, 1, 0, 1, 2 ); + axisTabLayout->addWidget( myDeleteBtn, 2, 0, 1, 2 ); axisTabLayout->addWidget( myStepLabel, 3, 0 ); axisTabLayout->addWidget( myStepSpin , 3, 1 ); axisTabLayout->addWidget( csFrame , 1, 2, 4, 1 ); @@ -821,6 +821,15 @@ QFrame* StdMeshersGUI_CartesianParamCreator::buildFrame() myAddEdges = new QCheckBox( tr("ADD_EDGES"), GroupC1 ); argGroupLayout->addWidget( myAddEdges, row, 0, 1, 2 ); row++; + myCreateFaces = new QCheckBox( tr("CREATE_FACES"), GroupC1 ); + argGroupLayout->addWidget( myCreateFaces, row, 0, 1, 2 ); + row++; + myConsiderInternalFaces = new QCheckBox( tr("CONSIDER_INTERNAL_FACES"), GroupC1 ); + argGroupLayout->addWidget( myConsiderInternalFaces, row, 0, 1, 2 ); + row++; + myUseThresholdForInternalFaces = new QCheckBox( tr("USE_THRESHOLD_FOR_INTERNAL_FACES"), GroupC1 ); + argGroupLayout->addWidget( myUseThresholdForInternalFaces, row, 0, 1, 2 ); + row++; // 3) Grid definition QTabWidget* tabWdg = new QTabWidget( fr ); @@ -924,6 +933,8 @@ QFrame* StdMeshersGUI_CartesianParamCreator::buildFrame() connect( myOrthogonalChk, SIGNAL( toggled(bool)), SLOT( onOrthogonalAxes(bool))); connect( optimBtn, SIGNAL( clicked(bool)), SLOT( onOptimalAxes(bool))); connect( resetBtn, SIGNAL( clicked(bool)), SLOT( onResetAxes(bool))); + connect( myConsiderInternalFaces, SIGNAL( toggled(bool)), + myUseThresholdForInternalFaces, SLOT( setEnabled(bool))); for ( int i = 0; i < 3; ++i ) { connect( myXDirSpin[i], SIGNAL(valueChanged (const QString&)), @@ -999,6 +1010,9 @@ void StdMeshersGUI_CartesianParamCreator::retrieveParams() const myThreshold->setText( varName ); myAddEdges->setChecked( h->GetToAddEdges() ); + myCreateFaces->setChecked( h->GetToCreateFaces() ); + myConsiderInternalFaces->setChecked( h->GetToConsiderInternalFaces() ); + myUseThresholdForInternalFaces->setChecked( h->GetToUseThresholdForInternalFaces() ); // grid definition for ( int ax = 0; ax < 3; ++ax ) @@ -1086,6 +1100,9 @@ QString StdMeshersGUI_CartesianParamCreator::storeParams() const h->SetVarParameter( myThreshold->text().toLatin1().constData(), "SetSizeThreshold" ); h->SetSizeThreshold( myThreshold->text().toDouble() ); h->SetToAddEdges( myAddEdges->isChecked() ); + h->SetToCreateFaces( myCreateFaces->isChecked() ); + h->SetToConsiderInternalFaces( myConsiderInternalFaces->isChecked() ); + h->SetToUseThresholdForInternalFaces( myUseThresholdForInternalFaces->isChecked() ); // grid for ( int ax = 0; ax < 3; ++ax ) diff --git a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h index 9ae66b9d8..f72b1e531 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h +++ b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h @@ -152,6 +152,9 @@ private: QLineEdit* myName; SMESHGUI_SpinBox* myThreshold; QCheckBox* myAddEdges; + QCheckBox* myCreateFaces; + QCheckBox* myConsiderInternalFaces; + QCheckBox* myUseThresholdForInternalFaces; StdMeshersGUI::GridAxisTab* myAxisTabs[3]; QGroupBox* myFixedPointGrp; diff --git a/src/StdMeshersGUI/StdMeshers_msg_en.ts b/src/StdMeshersGUI/StdMeshers_msg_en.ts index 036b28413..0c0509f19 100644 --- a/src/StdMeshersGUI/StdMeshers_msg_en.ts +++ b/src/StdMeshersGUI/StdMeshers_msg_en.ts @@ -580,6 +580,18 @@ this one for this mesh/sub-mesh. ADD_EDGES Implement Edges + + CREATE_FACES + Create Faces + + + CONSIDER_INTERNAL_FACES + Consider Shared and Internal Faces + + + USE_THRESHOLD_FOR_INTERNAL_FACES + Apply Threshold to Shared / Internal Faces + AXIS_X Axis X diff --git a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx index ec36baf67..fa1cadfcb 100644 --- a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx +++ b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx @@ -240,6 +240,14 @@ void StdMeshers_CartesianParameters3D_i::SetAxesDirs(const SMESH::DirStruct& xDi coords[6] = zDir.PS.x; coords[7] = zDir.PS.y; coords[8] = zDir.PS.z; + + const double* oldCoords = GetImpl()->GetAxisDirs(); + bool isSame = true; + for ( int i = 0; i < 9 && isSame; ++i ) + isSame = ( oldCoords[i] == coords[i] ); + if ( isSame ) + return; + try { this->GetImpl()->SetAxisDirs(coords); @@ -283,6 +291,11 @@ void StdMeshers_CartesianParameters3D_i::GetAxesDirs(SMESH::DirStruct& xDir, void StdMeshers_CartesianParameters3D_i::SetFixedPoint(const SMESH::PointStruct& ps, CORBA::Boolean toUnset) { + SMESH::PointStruct oldPS; + GetFixedPoint( oldPS ); + if ( oldPS.x == ps.x && oldPS.y == ps.y && oldPS.z == ps.z ) + return; + double p[3] = { ps.x, ps.y, ps.z }; GetImpl()->SetFixedPoint( p, toUnset ); @@ -335,6 +348,76 @@ CORBA::Boolean StdMeshers_CartesianParameters3D_i::GetToAddEdges() return GetImpl()->GetToAddEdges(); } +//======================================================================= +//function : SetToConsiderInternalFaces +//purpose : Enables treatment of geom faces, either shared by solids or internal. +//======================================================================= + +void StdMeshers_CartesianParameters3D_i::SetToConsiderInternalFaces(CORBA::Boolean toTreat) +{ + if ( GetToConsiderInternalFaces() == toTreat ) + return; + GetImpl()->SetToConsiderInternalFaces( toTreat ); + SMESH::TPythonDump() << _this() << ".SetToConsiderInternalFaces( " << toTreat << " )"; +} + +//======================================================================= +//function : GetToConsiderInternalFaces +//purpose : Return true if treatment of internal geom faces is enabled +//======================================================================= + +CORBA::Boolean StdMeshers_CartesianParameters3D_i::GetToConsiderInternalFaces() +{ + return GetImpl()->GetToConsiderInternalFaces(); +} + +//======================================================================= +//function : SetToUseThresholdForInternalFaces +//purpose : Enables applying size threshold to grid cells cut by internal geom faces. +//======================================================================= + +void StdMeshers_CartesianParameters3D_i::SetToUseThresholdForInternalFaces(CORBA::Boolean toUse) +{ + if ( GetToUseThresholdForInternalFaces() == toUse ) + return; + GetImpl()->SetToUseThresholdForInternalFaces( toUse ); + SMESH::TPythonDump() << _this() << ".SetToUseThresholdForInternalFaces( " << toUse << " )"; +} + +//======================================================================= +//function : GetToUseThresholdForInternalFaces +//purpose : Return true if applying size threshold to grid cells cut by +// internal geom faces is enabled +//======================================================================= + +CORBA::Boolean StdMeshers_CartesianParameters3D_i::GetToUseThresholdForInternalFaces() +{ + return GetImpl()->GetToUseThresholdForInternalFaces(); +} + +//======================================================================= +//function : SetToCreateFaces +//purpose : Enables creation of mesh faces. +//======================================================================= + +void StdMeshers_CartesianParameters3D_i::SetToCreateFaces(CORBA::Boolean toCreate) +{ + if ( GetToCreateFaces() == toCreate ) + return; + GetImpl()->SetToCreateFaces( toCreate ); + SMESH::TPythonDump() << _this() << ".SetToCreateFaces( " << toCreate << " )"; +} + +//======================================================================= +//function : GetToCreateFaces +//purpose : Check if creation of mesh faces enabled +//======================================================================= + +CORBA::Boolean StdMeshers_CartesianParameters3D_i::GetToCreateFaces() +{ + return GetImpl()->GetToCreateFaces(); +} + //======================================================================= //function : IsGridBySpacing //purpose : Return true if the grid is defined by spacing functions and diff --git a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx index 03dad0cfb..1b273ce77 100644 --- a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx +++ b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx @@ -100,7 +100,7 @@ class STDMESHERS_I_EXPORT StdMeshers_CartesianParameters3D_i: /*! - * \brief Enables implementation of geometrical edges into the mesh. If this feature + * \brief Enable implementation of geometrical edges into the mesh. If this feature * is disabled, sharp edges of the shape are lost ("smoothed") in the mesh if * they don't coincide with the grid lines */ @@ -108,13 +108,32 @@ class STDMESHERS_I_EXPORT StdMeshers_CartesianParameters3D_i: CORBA::Boolean GetToAddEdges(); /*! - * \brief Return true if the grid is defined by spacing functions and + * Enable treatment of geom faces, either shared by solids or internal. + */ + void SetToConsiderInternalFaces(CORBA::Boolean toTreat); + CORBA::Boolean GetToConsiderInternalFaces(); + + /*! + * Enable applying size threshold to grid cells cut by internal geom faces. + */ + void SetToUseThresholdForInternalFaces(CORBA::Boolean toUse); + CORBA::Boolean GetToUseThresholdForInternalFaces(); + + /*! + * Enable creation of mesh faces. + */ + void SetToCreateFaces(CORBA::Boolean toCreate); + CORBA::Boolean GetToCreateFaces(); + + + /*! + * \brief Return true if the grid is defined by spacing functions and * not by node coordinates */ CORBA::Boolean IsGridBySpacing(CORBA::Short axis); /*! - * Returns axes at which number of hexahedra is maximal + * Return axes at which number of hexahedra is maximal */ void ComputeOptimalAxesDirs(GEOM::GEOM_Object_ptr shape, CORBA::Boolean isOrthogonal, @@ -122,7 +141,7 @@ class STDMESHERS_I_EXPORT StdMeshers_CartesianParameters3D_i: SMESH::DirStruct& y, SMESH::DirStruct& z) throw (SALOME::SALOME_Exception); /*! - * \brief Computes node coordinates by spacing functions + * \brief Compute node coordinates by spacing functions * \param x0 - lower coordinate * \param x1 - upper coordinate * \param spaceFuns - space functions -- 2.39.2