From 146533380d6471f13f5c756e04dd59655f1b5a53 Mon Sep 17 00:00:00 2001 From: eap Date: Tue, 10 Feb 2015 14:14:01 +0300 Subject: [PATCH] 22834: [CEA 1347] Viscous layers: be able to choose the extrusion method --- .../SMESH/images/viscous_layers_2d_hyp.png | Bin 0 -> 26908 bytes .../viscous_layers_extrusion_method.png | Bin 0 -> 4929 bytes .../gui/SMESH/images/viscous_layers_hyp.png | Bin 50788 -> 32154 bytes .../gui/SMESH/input/additional_hypo.doc | 23 +- idl/SMESH_BasicHypothesis.idl | 18 + resources/CMakeLists.txt | 3 + resources/StdMeshers.xml.in | 2 +- resources/mesh_extmeth_face_offset.png | Bin 0 -> 257 bytes resources/mesh_extmeth_node_offset.png | Bin 0 -> 284 bytes resources/mesh_extmeth_surf_offset_smooth.png | Bin 0 -> 352 bytes src/SMESH/SMESH_Algo.cxx | 2 +- src/SMESH_I/SMESH_2smeshpy.cxx | 2 +- src/SMESH_SWIG/StdMeshersBuilder.py | 3 +- src/SMESH_SWIG/smesh_algorithm.py | 26 +- src/StdMeshers/StdMeshers_ViscousLayers.cxx | 2404 +++++++++-------- src/StdMeshers/StdMeshers_ViscousLayers.hxx | 32 +- .../StdMeshersGUI_RadioButtonsGrpWdg.cxx | 24 +- .../StdMeshersGUI_RadioButtonsGrpWdg.h | 3 +- .../StdMeshersGUI_StdHypothesisCreator.cxx | 33 +- src/StdMeshersGUI/StdMeshers_images.ts | 15 + src/StdMeshersGUI/StdMeshers_msg_en.ts | 16 + .../StdMeshers_ViscousLayers_i.cxx | 27 + .../StdMeshers_ViscousLayers_i.hxx | 3 + 23 files changed, 1496 insertions(+), 1140 deletions(-) create mode 100644 doc/salome/gui/SMESH/images/viscous_layers_2d_hyp.png create mode 100644 doc/salome/gui/SMESH/images/viscous_layers_extrusion_method.png create mode 100644 resources/mesh_extmeth_face_offset.png create mode 100644 resources/mesh_extmeth_node_offset.png create mode 100644 resources/mesh_extmeth_surf_offset_smooth.png diff --git a/doc/salome/gui/SMESH/images/viscous_layers_2d_hyp.png b/doc/salome/gui/SMESH/images/viscous_layers_2d_hyp.png new file mode 100644 index 0000000000000000000000000000000000000000..4ad6ee0ec4720ea1ee8c55ead9d8bcdb0cb7946f GIT binary patch literal 26908 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%0ABD}=QEghK0ZDG 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 ua@3~J{51rEzzc!gTSGzs&+sRdbbv39 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�!ew#3e~wEJbXx# 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
  • Name - allows to define the name of the hypothesis.
  • Total thickness - gives the total thickness of element layers.
  • Number of layers - defines the number of element layers.
  • Stretch factor - defines the growth factor of element height from the mesh boundary inwards.
  • +
  • Extrusion method (available in 3D only) - defines how + position of nodes are found during prism construction and how + creation of distorted and intersecting prisms is prevented. +
    • Surface offset + smooth method extrudes nodes along normal + to underlying geometrical surface. Smoothing of internal surface of + element layers is possible to avoid creation of invalid prisms.
    • +
    • Face offset method extrudes nodes along average normal of + surrounding mesh faces till intersection with a neighbor mesh face + translated along its own normal by the layers thickness. Thickness + of layers can be limited to avoid creation of invalid prisms.
    • +
    • Node offset method extrudes nodes along average normal of + surrounding mesh faces by the layers thickness. Thickness of + layers can be limited to avoid creation of invalid prisms.
    • +\image html viscous_layers_extrusion_method.png "Prisms created by the tree extrusion methods at the same other parameters" +
  • Specified Faces/Edges are - defines how the shapes specified by the next parameter are used.
  • Faces/Edges with/without layers - @@ -90,11 +107,15 @@ computations. Faces (or edges) can be selected either in the Object Browser or in the VTK Viewer. \note A mesh shown in the 3D Viewer can prevent selection of faces - and edges, just hide the mesh to avoid this. To avoid a long wait when a + and edges, just hide the mesh to avoid this. Sometimes a face to + select is hidden by other faces, in this case consider creating a + group of faces you want to select in the Geometry module.
    + To avoid a long wait when a geometry with many faces (or edges) is displayed, the number of faces (edges) shown at a time is limited by the value of "Sub-shapes preview chunk size" preference (in Preferences/Mesh/General tab). + If faces/edges without layers are specified, the element layers are not constructed on geometrical faces shared by several solids in 3D case and edges shared by several faces in 2D case. In other words, diff --git a/idl/SMESH_BasicHypothesis.idl b/idl/SMESH_BasicHypothesis.idl index 72d1b1a2d..0358ed7f7 100644 --- a/idl/SMESH_BasicHypothesis.idl +++ b/idl/SMESH_BasicHypothesis.idl @@ -857,6 +857,21 @@ module StdMeshers void GetCopySourceMesh(out boolean toCopyMesh,out boolean toCopyGroups); }; + /*! + * Method of computing translation of a node at Viscous Layers construction + */ + enum VLExtrusionMethod { + // node is translated along normal to a surface with possible further smoothing + SURF_OFFSET_SMOOTH, + // node is translated along the average normal of surrounding faces till + // intersection with a neighbor face translated along its own normal + // by the layers thickness + FACE_OFFSET, + // node is translated along the average normal of surrounding faces + // by the layers thickness + NODE_OFFSET + }; + /*! * interface of "Viscous Layers" hypothesis. * This hypothesis specifies parameters of layers of prisms to build @@ -896,6 +911,9 @@ module StdMeshers */ void SetStretchFactor(in double factor) raises (SALOME::SALOME_Exception); double GetStretchFactor(); + + void SetMethod( in VLExtrusionMethod how ); + VLExtrusionMethod GetMethod(); }; /*! diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index 46ca05a36..1e58c3765 100755 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -215,6 +215,9 @@ SET(SMESH_RESOURCES_FILES mesh_measure_length.png mesh_measure_area.png mesh_measure_volume.png + mesh_extmeth_node_offset.png + mesh_extmeth_surf_offset_smooth.png + mesh_extmeth_face_offset.png ) INSTALL(FILES ${SMESH_RESOURCES_FILES} DESTINATION ${SALOME_SMESH_INSTALL_RES_DATA}) diff --git a/resources/StdMeshers.xml.in b/resources/StdMeshers.xml.in index 239ff2cb4..d03fee17d 100644 --- a/resources/StdMeshers.xml.in +++ b/resources/StdMeshers.xml.in @@ -321,7 +321,7 @@ dim ="3"> Hexa_3D=Hexahedron(algo=smeshBuilder.Hexa) - ViscousLayers=ViscousLayers(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetIgnoreFaces()) + ViscousLayers=ViscousLayers(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetFaces(1),SetFaces(2),SetMethod()) diff --git a/resources/mesh_extmeth_face_offset.png b/resources/mesh_extmeth_face_offset.png new file mode 100644 index 0000000000000000000000000000000000000000..918b50743b029eb03b4f7e166eb9818cf78c0da3 GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^N| zgW!U_%O?XxI14-?iy0WWg+Z8+Vb&Z8pdfpRr>`sfeI`zB4f!u6A$&lgMo$;V5Rc<; z!yLH|DDbR4_rKn9cg}@opUsz|4mK@N5SnBX=E8Em%KLqF`t((kmRw@coP1?Ehrw~> zT~Y^UO*dWE;1GMPJ>20JM~_K?QRer%vlx$fNKJ?{JnEo4V?zat&+oR=hDlHIr4LU! xCD>TDme2duYl~!uSCvODEB`)gnYm;q| zgW!U_%O?XxI14-?iy0WWg+Z8+Vb&Z8pdfpRr>`sfeI`zBE$%xr9(@D~&GB?`4DmSr zHq4W+L4hY{>SX&5|M}O7`vx@X=}bG~=^?^%<)x1%1H;_MPJiZ9y>DWjbHG569_M4Th=oAz>mw6{t zM||UnHSB?@ojaB;n;z==o9*!Y%| zgW!U_%O?XxI14-?iy0WWg+Z8+Vb&Z8pdfpRr>`sfeI`yGGpoG1l|VU$2c9mDAs)x? zhQ)F>8}RH2ySbwJkhRtb(tj~A<`x6P)1!*tz2BEBTh)mL zG6o6?7BEFQ9|?aFvf3uDf5KzyXv::min() ) return false; // E seems closed const double tol = Min( 10 * curve.Tolerance(), v1Len * 1e-2 ); - const int nbSamples = 7; + const double nbSamples = 7; for ( int i = 0; i < nbSamples; ++i ) { const double r = ( i + 1 ) / nbSamples; diff --git a/src/SMESH_I/SMESH_2smeshpy.cxx b/src/SMESH_I/SMESH_2smeshpy.cxx index 911375f68..ff88d9c86 100644 --- a/src/SMESH_I/SMESH_2smeshpy.cxx +++ b/src/SMESH_I/SMESH_2smeshpy.cxx @@ -2777,7 +2777,7 @@ void _pyHypothesis::Process( const Handle(_pyCommand)& theCommand) myArgCommands.push_back( theCommand ); usedCommand = true; while ( crMethod.myArgs.size() < i+1 ) - crMethod.myArgs.push_back( "[]" ); + crMethod.myArgs.push_back( "None" ); crMethod.myArgs[ i ] = theCommand->GetArg( crMethod.myArgNb[i] ); } } diff --git a/src/SMESH_SWIG/StdMeshersBuilder.py b/src/SMESH_SWIG/StdMeshersBuilder.py index 1496202ff..6f0a53dc8 100644 --- a/src/SMESH_SWIG/StdMeshersBuilder.py +++ b/src/SMESH_SWIG/StdMeshersBuilder.py @@ -43,8 +43,9 @@ QUADRANGLE = "Quadrangle_2D" ## Algorithm type: Radial Quadrangle 1D-2D algorithm, see StdMeshersBuilder_RadialQuadrangle1D2D RADIAL_QUAD = "RadialQuadrangle_1D2D" -# import items of enum QuadType +# import items of enums for e in StdMeshers.QuadType._items: exec('%s = StdMeshers.%s'%(e,e)) +for e in StdMeshers.VLExtrusionMethod._items: exec('%s = StdMeshers.%s'%(e,e)) #---------------------- # Algorithms diff --git a/src/SMESH_SWIG/smesh_algorithm.py b/src/SMESH_SWIG/smesh_algorithm.py index 8ce382e17..5a33f7475 100644 --- a/src/SMESH_SWIG/smesh_algorithm.py +++ b/src/SMESH_SWIG/smesh_algorithm.py @@ -23,7 +23,7 @@ import salome from salome.geom import geomBuilder -import SMESH +import SMESH, StdMeshers ## The base class to define meshing algorithms # @@ -266,9 +266,22 @@ class Mesh_Algorithm: # the value of \a isFacesToIgnore parameter. # @param isFacesToIgnore if \c True, the Viscous layers are not generated on the # faces specified by the previous parameter (\a faces). + # @param extrMethod extrusion method defines how position of nodes are found during + # prism construction and how creation of distorted and intersecting prisms is + # prevented. Possible values are: + # - StdMeshers.SURF_OFFSET_SMOOTH (default) method extrudes nodes along normal + # to underlying geometrical surface. Smoothing of internal surface of + # element layers can be used to avoid creation of invalid prisms. + # - StdMeshers.FACE_OFFSET method extrudes nodes along average normal of + # surrounding mesh faces till intersection with a neighbor mesh face + # translated along its own normal by the layers thickness. Thickness + # of layers can be limited to avoid creation of invalid prisms. + # - StdMeshers.NODE_OFFSET method extrudes nodes along average normal of + # surrounding mesh faces by the layers thickness. Thickness of + # layers can be limited to avoid creation of invalid prisms. # @ingroup l3_hypos_additi def ViscousLayers(self, thickness, numberOfLayers, stretchFactor, - faces=[], isFacesToIgnore=True ): + faces=[], isFacesToIgnore=True, extrMethod=StdMeshers.SURF_OFFSET_SMOOTH ): if not isinstance(self.algo, SMESH._objref_SMESH_3D_Algo): raise TypeError, "ViscousLayers are supported by 3D algorithms only" if not "ViscousLayers" in self.GetCompatibleHypothesis(): @@ -285,10 +298,11 @@ class Mesh_Algorithm: hyp = self.Hypothesis("ViscousLayers", [thickness, numberOfLayers, stretchFactor, faces, isFacesToIgnore], toAdd=False) - hyp.SetTotalThickness(thickness) - hyp.SetNumberLayers(numberOfLayers) - hyp.SetStretchFactor(stretchFactor) - hyp.SetFaces(faces, isFacesToIgnore) + hyp.SetTotalThickness( thickness ) + hyp.SetNumberLayers( numberOfLayers ) + hyp.SetStretchFactor( stretchFactor ) + hyp.SetFaces( faces, isFacesToIgnore ) + hyp.SetMethod( extrMethod ) self.mesh.AddHypothesis( hyp, self.geom ) return hyp diff --git a/src/StdMeshers/StdMeshers_ViscousLayers.cxx b/src/StdMeshers/StdMeshers_ViscousLayers.cxx index 704144d7a..8f9b39bd2 100644 --- a/src/StdMeshers/StdMeshers_ViscousLayers.cxx +++ b/src/StdMeshers/StdMeshers_ViscousLayers.cxx @@ -324,6 +324,7 @@ namespace VISCOUS_3D struct _2NearEdges; struct _LayerEdge; + struct _EdgesOnShape; typedef map< const SMDS_MeshNode*, _LayerEdge*, TIDCompare > TNode2Edge; //-------------------------------------------------------------------------------- @@ -344,7 +345,7 @@ namespace VISCOUS_3D double _lenFactor; // to compute _len taking _cosin into account // face or edge w/o layer along or near which _LayerEdge is inflated - TopoDS_Shape _sWOL; + //TopoDS_Shape* _sWOL; // simplices connected to the source node (_nodes[0]); // used for smoothing and quality check of _LayerEdge's based on the FACE vector<_Simplex> _simplices; @@ -355,14 +356,16 @@ namespace VISCOUS_3D _Curvature* _curvature; // TODO:: detele _Curvature, _plnNorm - void SetNewLength( double len, SMESH_MesherHelper& helper ); + void SetNewLength( double len, _EdgesOnShape& eos, SMESH_MesherHelper& helper ); bool SetNewLength2d( Handle(Geom_Surface)& surface, const TopoDS_Face& F, + _EdgesOnShape& eos, SMESH_MesherHelper& helper ); void SetDataByNeighbors( const SMDS_MeshNode* n1, const SMDS_MeshNode* n2, + const _EdgesOnShape& eos, SMESH_MesherHelper& helper); - void InvalidateStep( int curStep, bool restoreLength=false ); + void InvalidateStep( int curStep, const _EdgesOnShape& eos, bool restoreLength=false ); void ChooseSmooFunction(const set< TGeomID >& concaveVertices, const TNode2Edge& n2eMap); int Smooth(const int step, const bool isConcaveFace, const bool findBest); @@ -372,6 +375,7 @@ namespace VISCOUS_3D bool FindIntersection( SMESH_ElementSearcher& searcher, double & distance, const double& epsilon, + _EdgesOnShape& eos, const SMDS_MeshElement** face = 0); bool SegTriaInter( const gp_Ax1& lastSegment, const SMDS_MeshNode* n0, @@ -379,10 +383,10 @@ namespace VISCOUS_3D const SMDS_MeshNode* n2, double& dist, const double& epsilon) const; - gp_Ax1 LastSegment(double& segLen) const; - gp_XY LastUV( const TopoDS_Face& F ) const; + gp_Ax1 LastSegment(double& segLen, _EdgesOnShape& eos) const; + gp_XY LastUV( const TopoDS_Face& F, _EdgesOnShape& eos ) const; bool IsOnEdge() const { return _2neibors; } - gp_XYZ Copy( _LayerEdge& other, SMESH_MesherHelper& helper ); + gp_XYZ Copy( _LayerEdge& other, _EdgesOnShape& eos, SMESH_MesherHelper& helper ); void SetCosin( double cosin ); int NbSteps() const { return _pos.size() - 1; } // nb inlation steps @@ -464,30 +468,7 @@ namespace VISCOUS_3D std::swap( _edges[0], _edges[1] ); } }; - //-------------------------------------------------------------------------------- - /*! - * \brief Convex FACE whose radius of curvature is less than the thickness of - * layers. It is used to detect distortion of prisms based on a convex - * FACE and to update normals to enable further increasing the thickness - */ - struct _ConvexFace - { - TopoDS_Face _face; - - // edges whose _simplices are used to detect prism destorsion - vector< _LayerEdge* > _simplexTestEdges; - - // map a sub-shape to it's index in _SolidData::_endEdgeOnShape vector - map< TGeomID, int > _subIdToEdgeEnd; - - bool _normalsFixed; - bool GetCenterOfCurvature( _LayerEdge* ledge, - BRepLProp_SLProps& surfProp, - SMESH_MesherHelper& helper, - gp_Pnt & center ) const; - bool CheckPrisms() const; - }; //-------------------------------------------------------------------------------- /*! @@ -496,7 +477,7 @@ namespace VISCOUS_3D struct AverageHyp { AverageHyp( const StdMeshers_ViscousLayers* hyp = 0 ) - :_nbLayers(0), _nbHyps(0), _thickness(0), _stretchFactor(0) + :_nbLayers(0), _nbHyps(0), _thickness(0), _stretchFactor(0), _method(0) { Add( hyp ); } @@ -509,16 +490,78 @@ namespace VISCOUS_3D //_thickness += hyp->GetTotalThickness(); _thickness = Max( _thickness, hyp->GetTotalThickness() ); _stretchFactor += hyp->GetStretchFactor(); + _method = hyp->GetMethod(); } } double GetTotalThickness() const { return _thickness; /*_nbHyps ? _thickness / _nbHyps : 0;*/ } double GetStretchFactor() const { return _nbHyps ? _stretchFactor / _nbHyps : 0; } int GetNumberLayers() const { return _nbLayers; } + int GetMethod() const { return _method; } + + bool UseSurfaceNormal() const + { return _method == StdMeshers_ViscousLayers::SURF_OFFSET_SMOOTH; } + bool ToSmooth() const + { return _method == StdMeshers_ViscousLayers::SURF_OFFSET_SMOOTH; } + bool IsOffsetMethod() const + { return _method == StdMeshers_ViscousLayers::FACE_OFFSET; } + private: - int _nbLayers, _nbHyps; + int _nbLayers, _nbHyps, _method; double _thickness, _stretchFactor; }; + //-------------------------------------------------------------------------------- + /*! + * \brief _LayerEdge's on a shape and other shape data + */ + struct _EdgesOnShape + { + vector< _LayerEdge* > _edges; + + TopoDS_Shape _shape; + TGeomID _shapeID; + SMESH_subMesh * _subMesh; + // face or edge w/o layer along or near which _edges are inflated + TopoDS_Shape _sWOL; + // averaged StdMeshers_ViscousLayers parameters + AverageHyp _hyp; + bool _toSmooth; + + vector< gp_XYZ > _faceNormals; // if _shape is FACE + vector< _EdgesOnShape* > _faceEOS; // to get _faceNormals of adjacent FACEs + + TopAbs_ShapeEnum ShapeType() const + { return _shape.IsNull() ? TopAbs_SHAPE : _shape.ShapeType(); } + TopAbs_ShapeEnum SWOLType() const + { return _sWOL.IsNull() ? TopAbs_SHAPE : _sWOL.ShapeType(); } + bool GetNormal( const SMDS_MeshElement* face, gp_Vec& norm ); + }; + + //-------------------------------------------------------------------------------- + /*! + * \brief Convex FACE whose radius of curvature is less than the thickness of + * layers. It is used to detect distortion of prisms based on a convex + * FACE and to update normals to enable further increasing the thickness + */ + struct _ConvexFace + { + TopoDS_Face _face; + + // edges whose _simplices are used to detect prism destorsion + vector< _LayerEdge* > _simplexTestEdges; + + // map a sub-shape to _SolidData::_edgesOnShape + map< TGeomID, _EdgesOnShape* > _subIdToEOS; + + bool _normalsFixed; + + bool GetCenterOfCurvature( _LayerEdge* ledge, + BRepLProp_SLProps& surfProp, + SMESH_MesherHelper& helper, + gp_Pnt & center ) const; + bool CheckPrisms() const; + }; + //-------------------------------------------------------------------------------- /*! * \brief Data of a SOLID @@ -542,9 +585,8 @@ namespace VISCOUS_3D // map to find _n2eMap of another _SolidData by a shrink shape shared by two _SolidData's map< TGeomID, TNode2Edge* > _s2neMap; - // edges of _n2eMap. We keep same data in two containers because - // iteration over the map is 5 times longer than over the vector - vector< _LayerEdge* > _edges; + // _LayerEdge's with underlying shapes + vector< _EdgesOnShape > _edgesOnShape; // key: an id of shape (EDGE or VERTEX) shared by a FACE with // layers and a FACE w/o layers @@ -559,16 +601,13 @@ namespace VISCOUS_3D // the adjacent SOLID set< TGeomID > _noShrinkShapes; + int _nbShapesToSmooth; + // to -- for analytic smooth map< TGeomID,Handle(Geom_Curve)> _edge2curve; - // end indices in _edges of _LayerEdge on each shape, first go shapes to smooth - vector< int > _endEdgeOnShape; - int _nbShapesToSmooth; set< TGeomID > _concaveFaces; - // data of averaged StdMeshers_ViscousLayers parameters for each shape with _LayerEdge's - vector< AverageHyp > _hypOnShape; double _maxThickness; // of all _hyps double _minThickness; // of all _hyps @@ -580,38 +619,28 @@ namespace VISCOUS_3D ~_SolidData(); Handle(Geom_Curve) CurveForSmooth( const TopoDS_Edge& E, - const int iFrom, - const int iTo, - const TopoDS_Face& F, - SMESH_MesherHelper& helper, - vector<_LayerEdge* >* edges=0); + _EdgesOnShape& eos, + SMESH_MesherHelper& helper); - void SortOnEdge( const TopoDS_Edge& E, - const int iFrom, - const int iTo, - SMESH_MesherHelper& helper); + void SortOnEdge( const TopoDS_Edge& E, + vector< _LayerEdge* >& edges, + SMESH_MesherHelper& helper); - void Sort2NeiborsOnEdge( const int iFrom, const int iTo); + void Sort2NeiborsOnEdge( vector< _LayerEdge* >& edges ); _ConvexFace* GetConvexFace( const TGeomID faceID ) { map< TGeomID, _ConvexFace >::iterator id2face = _convexFaces.find( faceID ); return id2face == _convexFaces.end() ? 0 : & id2face->second; } - void GetEdgesOnShape( size_t end, int & iBeg, int & iEnd ) - { - iBeg = end > 0 ? _endEdgeOnShape[ end-1 ] : 0; - iEnd = _endEdgeOnShape[ end ]; - } - - bool GetShapeEdges(const TGeomID shapeID, size_t& iEdgeEnd, int* iBeg=0, int* iEnd=0 ) const; + _EdgesOnShape* GetShapeEdges(const TGeomID shapeID ); + _EdgesOnShape* GetShapeEdges(const TopoDS_Shape& shape ); + _EdgesOnShape* GetShapeEdges(const _LayerEdge* edge ) + { return GetShapeEdges( edge->_nodes[0]->getshapeId() ); } - void AddShapesToSmooth( const set< TGeomID >& shapeIDs ); + void AddShapesToSmooth( const set< _EdgesOnShape* >& shape ); - void PrepareEdgesToSmoothOnFace( _LayerEdge** edgeBeg, - _LayerEdge** edgeEnd, - const TopoDS_Face& face, - bool substituteSrcNodes ); + void PrepareEdgesToSmoothOnFace( _EdgesOnShape* eof, bool substituteSrcNodes ); }; //-------------------------------------------------------------------------------- /*! @@ -640,7 +669,7 @@ namespace VISCOUS_3D bool FindNewNormal( const gp_Pnt& center, gp_XYZ& newNormal ); void SetShapes( const TopoDS_Edge& edge, const _ConvexFace& convFace, - const _SolidData& data, + _SolidData& data, SMESH_MesherHelper& helper); }; //-------------------------------------------------------------------------------- @@ -695,7 +724,8 @@ namespace VISCOUS_3D const TopoDS_Shape& hypShape, set& ignoreFaces); bool makeLayer(_SolidData& data); - bool setEdgeData(_LayerEdge& edge, const set& subIds, + void setShapeData( _EdgesOnShape& eos, SMESH_subMesh* sm, _SolidData& data ); + bool setEdgeData(_LayerEdge& edge, _EdgesOnShape& eos, const set& subIds, SMESH_MesherHelper& helper, _SolidData& data); gp_XYZ getFaceNormal(const SMDS_MeshNode* n, const TopoDS_Face& face, @@ -712,12 +742,12 @@ namespace VISCOUS_3D bool findNeiborsOnEdge(const _LayerEdge* edge, const SMDS_MeshNode*& n1, const SMDS_MeshNode*& n2, + _EdgesOnShape& eos, _SolidData& data); void findSimplexTestEdges( _SolidData& data, vector< vector<_LayerEdge*> >& edgesByGeom); void computeGeomSize( _SolidData& data ); - bool sortEdges( _SolidData& data, - vector< vector<_LayerEdge*> >& edgesByGeom); + bool findShapesToSmooth( _SolidData& data); void limitStepSizeByCurvature( _SolidData& data ); void limitStepSize( _SolidData& data, const SMDS_MeshElement* face, @@ -726,8 +756,7 @@ namespace VISCOUS_3D bool inflate(_SolidData& data); bool smoothAndCheck(_SolidData& data, const int nbSteps, double & distToIntersection); bool smoothAnalyticEdge( _SolidData& data, - const int iFrom, - const int iTo, + _EdgesOnShape& eos, Handle(Geom_Surface)& surface, const TopoDS_Face& F, SMESH_MesherHelper& helper); @@ -737,7 +766,7 @@ namespace VISCOUS_3D int stepNb ); bool refine(_SolidData& data); bool shrink(); - bool prepareEdgeToShrink( _LayerEdge& edge, const TopoDS_Face& F, + bool prepareEdgeToShrink( _LayerEdge& edge, _EdgesOnShape& eos, SMESH_MesherHelper& helper, const SMESHDS_SubMesh* faceSubMesh ); void restoreNoShrink( _LayerEdge& edge ) const; @@ -766,13 +795,14 @@ namespace VISCOUS_3D */ class _Shrinker1D { + TopoDS_Edge _geomEdge; vector _initU; vector _normPar; vector _nodes; const _LayerEdge* _edges[2]; bool _done; public: - void AddEdge( const _LayerEdge* e, SMESH_MesherHelper& helper ); + void AddEdge( const _LayerEdge* e, _EdgesOnShape& eos, SMESH_MesherHelper& helper ); void Compute(bool set3D, SMESH_MesherHelper& helper); void RestoreParams(); void SwapSrcTgtNodes(SMESHDS_Mesh* mesh); @@ -815,7 +845,7 @@ namespace VISCOUS_3D }; //-------------------------------------------------------------------------------- /*! - * \brief Retriever of node coordinates either directly of from a surface by node UV. + * \brief Retriever of node coordinates either directly or from a surface by node UV. * \warning Location of a surface is ignored */ struct _NodeCoordHelper @@ -861,7 +891,8 @@ namespace VISCOUS_3D // StdMeshers_ViscousLayers::StdMeshers_ViscousLayers(int hypId, int studyId, SMESH_Gen* gen) :SMESH_Hypothesis(hypId, studyId, gen), - _isToIgnoreShapes(1), _nbLayers(1), _thickness(1), _stretchFactor(1) + _isToIgnoreShapes(1), _nbLayers(1), _thickness(1), _stretchFactor(1), + _method( SURF_OFFSET_SMOOTH ) { _name = StdMeshers_ViscousLayers::GetHypType(); _param_algo_dim = -3; // auxiliary hyp used by 3D algos @@ -888,6 +919,11 @@ void StdMeshers_ViscousLayers::SetStretchFactor(double factor) if ( _stretchFactor != factor ) _stretchFactor = factor, NotifySubMeshesHypothesisModification(); } // -------------------------------------------------------------------------------- +void StdMeshers_ViscousLayers::SetMethod( ExtrusionMethod method ) +{ + if ( _method != method ) + _method = method, NotifySubMeshesHypothesisModification(); +} // -------------------------------------------------------------------------------- SMESH_ProxyMesh::Ptr StdMeshers_ViscousLayers::Compute(SMESH_Mesh& theMesh, const TopoDS_Shape& theShape, @@ -941,18 +977,23 @@ std::ostream & StdMeshers_ViscousLayers::SaveTo(std::ostream & save) for ( size_t i = 0; i < _shapeIds.size(); ++i ) save << " " << _shapeIds[i]; save << " " << !_isToIgnoreShapes; // negate to keep the behavior in old studies. + save << " " << _method; return save; } // -------------------------------------------------------------------------------- std::istream & StdMeshers_ViscousLayers::LoadFrom(std::istream & load) { - int nbFaces, faceID, shapeToTreat; + int nbFaces, faceID, shapeToTreat, method; load >> _nbLayers >> _thickness >> _stretchFactor >> nbFaces; while ( _shapeIds.size() < nbFaces && load >> faceID ) _shapeIds.push_back( faceID ); - if ( load >> shapeToTreat ) + if ( load >> shapeToTreat ) { _isToIgnoreShapes = !shapeToTreat; - else + if ( load >> method ) + _method = (ExtrusionMethod) method; + } + else { _isToIgnoreShapes = true; // old behavior + } return load; } // -------------------------------------------------------------------------------- bool StdMeshers_ViscousLayers::SetParametersByMesh(const SMESH_Mesh* theMesh, @@ -1513,7 +1554,7 @@ SMESH_ComputeErrorPtr _ViscousBuilder::Compute(SMESH_Mesh& theMesh, if ( ! makeLayer(_sdVec[i]) ) return _error; - if ( _sdVec[i]._edges.size() == 0 ) + if ( _sdVec[i]._n2eMap.size() == 0 ) continue; if ( ! inflate(_sdVec[i]) ) @@ -1626,12 +1667,12 @@ bool _ViscousBuilder::findFacesWithLayers(const bool onlyWith) TopExp_Explorer exp; TopTools_IndexedMapOfShape solids; - // collect all faces to ignore defined by hyp + // collect all faces-to-ignore defined by hyp for ( size_t i = 0; i < _sdVec.size(); ++i ) { solids.Add( _sdVec[i]._solid ); - // get faces to ignore defined by each hyp + // get faces-to-ignore defined by each hyp typedef const StdMeshers_ViscousLayers* THyp; typedef std::pair< set, THyp > TFacesOfHyp; list< TFacesOfHyp > ignoreFacesOfHyps; @@ -2052,18 +2093,34 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) TNode2Edge::iterator n2e2; // collect _LayerEdge's of shapes they are based on + vector< _EdgesOnShape >& edgesByGeom = data._edgesOnShape; const int nbShapes = getMeshDS()->MaxShapeIndex(); - vector< vector<_LayerEdge*> > edgesByGeom( nbShapes+1 ); + edgesByGeom.resize( nbShapes+1 ); + // set data of _EdgesOnShape's + if ( SMESH_subMesh* sm = _mesh->GetSubMesh( data._solid )) + { + SMESH_subMeshIteratorPtr smIt = sm->getDependsOnIterator(/*includeSelf=*/false); + while ( smIt->more() ) + { + sm = smIt->next(); + if ( sm->GetSubShape().ShapeType() == TopAbs_FACE && + !faceIds.count( sm->GetId() )) + continue; + setShapeData( edgesByGeom[ sm->GetId() ], sm, data ); + } + } + // make _LayerEdge's for ( set::iterator id = faceIds.begin(); id != faceIds.end(); ++id ) { - SMESHDS_SubMesh* smDS = getMeshDS()->MeshElements( *id ); - if ( !smDS ) return error(SMESH_Comment("Not meshed face ") << *id, data._index ); - const TopoDS_Face& F = TopoDS::Face( getMeshDS()->IndexToShape( *id )); + SMESH_subMesh* sm = _mesh->GetSubMesh( F ); SMESH_ProxyMesh::SubMesh* proxySub = data._proxyMesh->getFaceSubM( F, /*create=*/true); + SMESHDS_SubMesh* smDS = sm->GetSubMeshDS(); + if ( !smDS ) return error(SMESH_Comment("Not meshed face ") << *id, data._index ); + SMDS_ElemIteratorPtr eIt = smDS->GetElements(); while ( eIt->more() ) { @@ -2103,7 +2160,7 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) _LayerEdge* edge = new _LayerEdge(); edge->_nodes.push_back( n ); n2e->second = edge; - edgesByGeom[ shapeID ].push_back( edge ); + edgesByGeom[ shapeID ]._edges.push_back( edge ); const bool noShrink = data._noShrinkShapes.count( shapeID ); SMESH_TNodeXYZ xyz( n ); @@ -2115,7 +2172,7 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) (( n2e2 = (*s2ne).second->find( n )) != s2ne->second->end() )) { _LayerEdge* foundEdge = (*n2e2).second; - gp_XYZ lastPos = edge->Copy( *foundEdge, helper ); + gp_XYZ lastPos = edge->Copy( *foundEdge, edgesByGeom[ shapeID ], helper ); foundEdge->_pos.push_back( lastPos ); // location of the last node is modified and we restore it by foundEdge->_pos.back() const_cast< SMDS_MeshNode* > @@ -2127,7 +2184,7 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) { edge->_nodes.push_back( helper.AddNode( xyz.X(), xyz.Y(), xyz.Z() )); } - if ( !setEdgeData( *edge, subIds, helper, data )) + if ( !setEdgeData( *edge, edgesByGeom[ shapeID ], subIds, helper, data )) return false; } dumpMove(edge->_nodes.back()); @@ -2162,8 +2219,7 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) if ( data._stepSize < 1. ) data._epsilon *= data._stepSize; - // Put _LayerEdge's into the vector data._edges - if ( !sortEdges( data, edgesByGeom )) + if ( !findShapesToSmooth( data )) return false; // limit data._stepSize depending on surface curvature and fill data._convexFaces @@ -2172,57 +2228,61 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) // Set target nodes into _Simplex and _LayerEdge's to _2NearEdges TNode2Edge::iterator n2e; const SMDS_MeshNode* nn[2]; - for ( size_t i = 0; i < data._edges.size(); ++i ) + for ( size_t iS = 0; iS < data._edgesOnShape.size(); ++iS ) { - _LayerEdge* edge = data._edges[i]; - if ( edge->IsOnEdge() ) + _EdgesOnShape& eos = data._edgesOnShape[iS]; + vector< _LayerEdge* >& localEdges = eos._edges; + for ( size_t i = 0; i < localEdges.size(); ++i ) { - // get neighbor nodes - bool hasData = ( edge->_2neibors->_edges[0] ); - if ( hasData ) // _LayerEdge is a copy of another one + _LayerEdge* edge = localEdges[i]; + if ( edge->IsOnEdge() ) { - nn[0] = edge->_2neibors->srcNode(0); - nn[1] = edge->_2neibors->srcNode(1); + // get neighbor nodes + bool hasData = ( edge->_2neibors->_edges[0] ); + if ( hasData ) // _LayerEdge is a copy of another one + { + nn[0] = edge->_2neibors->srcNode(0); + nn[1] = edge->_2neibors->srcNode(1); + } + else if ( !findNeiborsOnEdge( edge, nn[0],nn[1], eos, data )) + { + return false; + } + // set neighbor _LayerEdge's + for ( int j = 0; j < 2; ++j ) + { + if (( n2e = data._n2eMap.find( nn[j] )) == data._n2eMap.end() ) + return error("_LayerEdge not found by src node", data._index); + edge->_2neibors->_edges[j] = n2e->second; + } + if ( !hasData ) + edge->SetDataByNeighbors( nn[0], nn[1], eos, helper ); } - else if ( !findNeiborsOnEdge( edge, nn[0],nn[1], data )) + + for ( size_t j = 0; j < edge->_simplices.size(); ++j ) { - return false; + _Simplex& s = edge->_simplices[j]; + s._nNext = data._n2eMap[ s._nNext ]->_nodes.back(); + s._nPrev = data._n2eMap[ s._nPrev ]->_nodes.back(); } - // set neighbor _LayerEdge's - for ( int j = 0; j < 2; ++j ) + + // For an _LayerEdge on a degenerated EDGE, copy some data from + // a corresponding _LayerEdge on a VERTEX + // (issue 52453, pb on a downloaded SampleCase2-Tet-netgen-mephisto.hdf) + if ( helper.IsDegenShape( edge->_nodes[0]->getshapeId() )) { - if (( n2e = data._n2eMap.find( nn[j] )) == data._n2eMap.end() ) - return error("_LayerEdge not found by src node", data._index); - edge->_2neibors->_edges[j] = n2e->second; + // Generally we should not get here + if ( eos.ShapeType() != TopAbs_EDGE ) + continue; + TopoDS_Vertex V = helper.IthVertex( 0, TopoDS::Edge( eos._shape )); + const SMDS_MeshNode* vN = SMESH_Algo::VertexNode( V, getMeshDS() ); + if (( n2e = data._n2eMap.find( vN )) == data._n2eMap.end() ) + continue; + const _LayerEdge* vEdge = n2e->second; + edge->_normal = vEdge->_normal; + edge->_lenFactor = vEdge->_lenFactor; + edge->_cosin = vEdge->_cosin; } - if ( !hasData ) - edge->SetDataByNeighbors( nn[0], nn[1], helper); - } - - for ( size_t j = 0; j < edge->_simplices.size(); ++j ) - { - _Simplex& s = edge->_simplices[j]; - s._nNext = data._n2eMap[ s._nNext ]->_nodes.back(); - s._nPrev = data._n2eMap[ s._nPrev ]->_nodes.back(); - } - - // For an _LayerEdge on a degenerated EDGE, copy some data from - // a corresponding _LayerEdge on a VERTEX - // (issue 52453, pb on a downloaded SampleCase2-Tet-netgen-mephisto.hdf) - if ( helper.IsDegenShape( edge->_nodes[0]->getshapeId() )) - { - // Generally we should not get here - const TopoDS_Shape& E = getMeshDS()->IndexToShape( edge->_nodes[0]->getshapeId() ); - if ( E.ShapeType() != TopAbs_EDGE ) - continue; - TopoDS_Vertex V = helper.IthVertex( 0, TopoDS::Edge( E )); - const SMDS_MeshNode* vN = SMESH_Algo::VertexNode( V, getMeshDS() ); - if (( n2e = data._n2eMap.find( vN )) == data._n2eMap.end() ) - continue; - const _LayerEdge* vEdge = n2e->second; - edge->_normal = vEdge->_normal; - edge->_lenFactor = vEdge->_lenFactor; - edge->_cosin = vEdge->_cosin; } } @@ -2231,9 +2291,8 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) for ( ; e2c != data._edge2curve.end(); ++e2c ) if ( !e2c->second.IsNull() ) { - size_t iEdgeEnd; int iBeg, iEnd; - if ( data.GetShapeEdges( e2c->first, iEdgeEnd, &iBeg, &iEnd )) - data.Sort2NeiborsOnEdge( iBeg, iEnd ); + if ( _EdgesOnShape* eos = data.GetShapeEdges( e2c->first )) + data.Sort2NeiborsOnEdge( eos->_edges ); } dumpFunctionEnd(); @@ -2310,19 +2369,21 @@ void _ViscousBuilder::limitStepSizeByCurvature( _SolidData& data ) data._convexFaces.clear(); - TopExp_Explorer face( data._solid, TopAbs_FACE ); - for ( ; face.More(); face.Next() ) + for ( size_t iS = 0; iS < data._edgesOnShape.size(); ++iS ) { - const TopoDS_Face& F = TopoDS::Face( face.Current() ); - SMESH_subMesh * sm = _mesh->GetSubMesh( F ); - const TGeomID faceID = sm->GetId(); - if ( data._ignoreFaceIds.count( faceID )) continue; + _EdgesOnShape& eos = data._edgesOnShape[iS]; + if ( eos.ShapeType() != TopAbs_FACE || + data._ignoreFaceIds.count( eos._shapeID )) + continue; + + TopoDS_Face F = TopoDS::Face( eos._shape ); + SMESH_subMesh * sm = eos._subMesh; + const TGeomID faceID = eos._shapeID; BRepAdaptor_Surface surface( F, false ); surfProp.SetSurface( surface ); bool isTooCurved = false; - int iBeg, iEnd; _ConvexFace cnvFace; const double oriFactor = ( F.Orientation() == TopAbs_REVERSED ? +1. : -1. ); @@ -2333,18 +2394,17 @@ void _ViscousBuilder::limitStepSizeByCurvature( _SolidData& data ) const TGeomID subID = sm->GetId(); // find _LayerEdge's of a sub-shape size_t edgesEnd; - if ( data.GetShapeEdges( subID, edgesEnd, &iBeg, &iEnd )) - cnvFace._subIdToEdgeEnd.insert( make_pair( subID, edgesEnd )); + if ( _EdgesOnShape* eos = data.GetShapeEdges( subID )) + cnvFace._subIdToEOS.insert( make_pair( subID, eos )); else continue; // check concavity and curvature and limit data._stepSize const double minCurvature = - 1. / ( data._hypOnShape[ edgesEnd ].GetTotalThickness() * ( 1+theThickToIntersection )); - int nbLEdges = iEnd - iBeg; - int iStep = Max( 1, nbLEdges / nbTestPnt ); - for ( ; iBeg < iEnd; iBeg += iStep ) + 1. / ( eos._hyp.GetTotalThickness() * ( 1+theThickToIntersection )); + size_t iStep = Max( 1, eos._edges.size() / nbTestPnt ); + for ( size_t i = 0; i < eos._edges.size(); i += iStep ) { - gp_XY uv = helper.GetNodeUV( F, data._edges[ iBeg ]->_nodes[0] ); + gp_XY uv = helper.GetNodeUV( F, eos._edges[ i ]->_nodes[0] ); surfProp.SetParameters( uv.X(), uv.Y() ); if ( !surfProp.IsCurvatureDefined() ) continue; @@ -2371,15 +2431,15 @@ void _ViscousBuilder::limitStepSizeByCurvature( _SolidData& data ) // Fill _ConvexFace::_simplexTestEdges. These _LayerEdge's are used to detect // prism distortion. - map< TGeomID, int >::iterator id2end = convFace._subIdToEdgeEnd.find( faceID ); - if ( id2end != convFace._subIdToEdgeEnd.end() ) + map< TGeomID, _EdgesOnShape* >::iterator id2eos = convFace._subIdToEOS.find( faceID ); + if ( id2eos != convFace._subIdToEOS.end() && !id2eos->second->_edges.empty() ) { // there are _LayerEdge's on the FACE it-self; // select _LayerEdge's near EDGEs - data.GetEdgesOnShape( id2end->second, iBeg, iEnd ); - for ( ; iBeg < iEnd; ++iBeg ) + _EdgesOnShape& eos = * id2eos->second; + for ( size_t i = 0; i < eos._edges.size(); ++i ) { - _LayerEdge* ledge = data._edges[ iBeg ]; + _LayerEdge* ledge = eos._edges[ i ]; for ( size_t j = 0; j < ledge->_simplices.size(); ++j ) if ( ledge->_simplices[j]._nNext->GetPosition()->GetDim() < 2 ) { @@ -2398,15 +2458,15 @@ void _ViscousBuilder::limitStepSizeByCurvature( _SolidData& data ) set< const SMDS_MeshNode* > usedNodes; // look for _LayerEdge's with null _sWOL - map< TGeomID, int >::iterator id2end = convFace._subIdToEdgeEnd.begin(); - for ( ; id2end != convFace._subIdToEdgeEnd.end(); ++id2end ) + map< TGeomID, _EdgesOnShape* >::iterator id2oes = convFace._subIdToEOS.begin(); + for ( ; id2oes != convFace._subIdToEOS.end(); ++id2oes ) { - data.GetEdgesOnShape( id2end->second, iBeg, iEnd ); - if ( iBeg >= iEnd || !data._edges[ iBeg ]->_sWOL.IsNull() ) + _EdgesOnShape& eos = * id2eos->second; + if ( !eos._sWOL.IsNull() ) continue; - for ( ; iBeg < iEnd; ++iBeg ) + for ( size_t i = 0; i < eos._edges.size(); ++i ) { - _LayerEdge* ledge = data._edges[ iBeg ]; + _LayerEdge* ledge = eos._edges[ i ]; const SMDS_MeshNode* srcNode = ledge->_nodes[0]; if ( !usedNodes.insert( srcNode ).second ) continue; @@ -2425,12 +2485,11 @@ void _ViscousBuilder::limitStepSizeByCurvature( _SolidData& data ) //================================================================================ /*! - * \brief Separate shapes (and _LayerEdge's on them) to smooth from the rest ones + * \brief Detect shapes (and _LayerEdge's on them) to smooth */ //================================================================================ -bool _ViscousBuilder::sortEdges( _SolidData& data, - vector< vector<_LayerEdge*> >& edgesByGeom) +bool _ViscousBuilder::findShapesToSmooth( _SolidData& data ) { // define allowed thickness computeGeomSize( data ); // compute data._geomSize @@ -2448,203 +2507,286 @@ bool _ViscousBuilder::sortEdges( _SolidData& data, // Find shapes needing smoothing; such a shape has _LayerEdge._normal on it's // boundry inclined to the shape at a sharp angle - list< TGeomID > shapesToSmooth; + //list< TGeomID > shapesToSmooth; TopTools_MapOfShape edgesOfSmooFaces; SMESH_MesherHelper helper( *_mesh ); bool ok = true; - for ( int isEdge = 0; isEdge < 2; ++isEdge ) // loop on [ FACEs, EDGEs ] + vector< _EdgesOnShape >& edgesByGeom = data._edgesOnShape; + data._nbShapesToSmooth = 0; + + for ( size_t iS = 0; iS < edgesByGeom.size(); ++iS ) // check FACEs { - const int dim = isEdge ? 1 : 2; + _EdgesOnShape& eos = edgesByGeom[iS]; + eos._toSmooth = false; + if ( eos._edges.empty() || eos.ShapeType() != TopAbs_FACE ) + continue; - for ( size_t iS = 0; iS < edgesByGeom.size(); ++iS ) + TopExp_Explorer eExp( edgesByGeom[iS]._shape, TopAbs_EDGE ); + for ( ; eExp.More() && !eos._toSmooth; eExp.Next() ) { - vector<_LayerEdge*>& eS = edgesByGeom[iS]; - if ( eS.empty() ) continue; - if ( eS[0]->_nodes[0]->GetPosition()->GetDim() != dim ) continue; - - const TopoDS_Shape& S = getMeshDS()->IndexToShape( iS ); - bool needSmooth = false; - switch ( S.ShapeType() ) + TGeomID iE = getMeshDS()->ShapeToIndex( eExp.Current() ); + vector<_LayerEdge*>& eE = edgesByGeom[ iE ]._edges; + if ( eE.empty() ) continue; + // TopLoc_Location loc; + // Handle(Geom_Surface) surface = BRep_Tool::Surface( TopoDS::Face( S ), loc ); + // bool isPlane = GeomLib_IsPlanarSurface( surface ).IsPlanar(); + //if ( eE[0]->_sWOL.IsNull() ) { - case TopAbs_EDGE: { - - const TopoDS_Edge& E = TopoDS::Edge( S ); - if ( SMESH_Algo::isDegenerated( E ) || !edgesOfSmooFaces.Contains( E )) - break; - - TopoDS_Face F; - if ( !eS[0]->_sWOL.IsNull() && eS[0]->_sWOL.ShapeType() == TopAbs_FACE ) - F = TopoDS::Face( eS[0]->_sWOL ); - - for ( TopoDS_Iterator vIt( S ); vIt.More() && !needSmooth; vIt.Next() ) - { - TGeomID iV = getMeshDS()->ShapeToIndex( vIt.Value() ); - vector<_LayerEdge*>& eV = edgesByGeom[ iV ]; - if ( eV.empty() ) continue; - gp_Vec eDir = getEdgeDir( TopoDS::Edge( S ), TopoDS::Vertex( vIt.Value() )); - double angle = eDir.Angle( eV[0]->_normal ); - double cosin = Cos( angle ); - double cosinAbs = Abs( cosin ); - if ( cosinAbs > theMinSmoothCosin ) + double faceSize; + for ( size_t i = 0; i < eE.size() && !eos._toSmooth; ++i ) + if ( eE[i]->_cosin > theMinSmoothCosin ) { - // always smooth analytic EDGEs - needSmooth = ! data.CurveForSmooth( E, 0, eS.size(), F, helper, &eS ).IsNull(); - - // compare tgtThick with the length of an end segment - SMDS_ElemIteratorPtr eIt = eV[0]->_nodes[0]->GetInverseElementIterator(SMDSAbs_Edge); - while ( eIt->more() && !needSmooth ) + SMDS_ElemIteratorPtr fIt = eE[i]->_nodes[0]->GetInverseElementIterator(SMDSAbs_Face); + while ( fIt->more() && !eos._toSmooth ) { - const SMDS_MeshElement* endSeg = eIt->next(); - if ( endSeg->getshapeId() == iS ) - { - double segLen = - SMESH_TNodeXYZ( endSeg->GetNode(0) ).Distance( endSeg->GetNode(1 )); - needSmooth = needSmoothing( cosinAbs, tgtThick, segLen ); - } + const SMDS_MeshElement* face = fIt->next(); + if ( getDistFromEdge( face, eE[i]->_nodes[0], faceSize )) + eos._toSmooth = needSmoothing( eE[i]->_cosin, tgtThick, faceSize ); } } - } - break; } - case TopAbs_FACE: { + // else + // { + // const TopoDS_Face& F1 = TopoDS::Face( S ); + // const TopoDS_Face& F2 = TopoDS::Face( eE[0]->_sWOL ); + // const TopoDS_Edge& E = TopoDS::Edge( eExp.Current() ); + // for ( size_t i = 0; i < eE.size() && !eos._toSmooth; ++i ) + // { + // gp_Vec dir1 = getFaceDir( F1, E, eE[i]->_nodes[0], helper, ok ); + // gp_Vec dir2 = getFaceDir( F2, E, eE[i]->_nodes[0], helper, ok ); + // double angle = dir1.Angle( ); + // double cosin = cos( angle ); + // eos._toSmooth = ( cosin > theMinSmoothCosin ); + // } + // } + } + if ( eos._toSmooth ) + { + for ( eExp.ReInit(); eExp.More(); eExp.Next() ) + edgesOfSmooFaces.Add( eExp.Current() ); - for ( TopExp_Explorer eExp( S, TopAbs_EDGE ); eExp.More() && !needSmooth; eExp.Next() ) - { - TGeomID iE = getMeshDS()->ShapeToIndex( eExp.Current() ); - vector<_LayerEdge*>& eE = edgesByGeom[ iE ]; - if ( eE.empty() ) continue; - // TopLoc_Location loc; - // Handle(Geom_Surface) surface = BRep_Tool::Surface( TopoDS::Face( S ), loc ); - // bool isPlane = GeomLib_IsPlanarSurface( surface ).IsPlanar(); - //if ( eE[0]->_sWOL.IsNull() ) - { - double faceSize; - for ( size_t i = 0; i < eE.size() && !needSmooth; ++i ) - if ( eE[i]->_cosin > theMinSmoothCosin ) - { - SMDS_ElemIteratorPtr fIt = eE[i]->_nodes[0]->GetInverseElementIterator(SMDSAbs_Face); - while ( fIt->more() && !needSmooth ) - { - const SMDS_MeshElement* face = fIt->next(); - if ( getDistFromEdge( face, eE[i]->_nodes[0], faceSize )) - needSmooth = needSmoothing( eE[i]->_cosin, tgtThick, faceSize ); - } - } - } - // else - // { - // const TopoDS_Face& F1 = TopoDS::Face( S ); - // const TopoDS_Face& F2 = TopoDS::Face( eE[0]->_sWOL ); - // const TopoDS_Edge& E = TopoDS::Edge( eExp.Current() ); - // for ( size_t i = 0; i < eE.size() && !needSmooth; ++i ) - // { - // gp_Vec dir1 = getFaceDir( F1, E, eE[i]->_nodes[0], helper, ok ); - // gp_Vec dir2 = getFaceDir( F2, E, eE[i]->_nodes[0], helper, ok ); - // double angle = dir1.Angle( ); - // double cosin = cos( angle ); - // needSmooth = ( cosin > theMinSmoothCosin ); - // } - // } - } - if ( needSmooth ) - for ( TopExp_Explorer eExp( S, TopAbs_EDGE ); eExp.More(); eExp.Next() ) - edgesOfSmooFaces.Add( eExp.Current() ); + data.PrepareEdgesToSmoothOnFace( &edgesByGeom[iS], /*substituteSrcNodes=*/false ); + } + data._nbShapesToSmooth += eos._toSmooth; - break; - } - case TopAbs_VERTEX: - continue; - default:; - } + } // check FACEs + + for ( size_t iS = 0; iS < edgesByGeom.size(); ++iS ) // check EDGEs + { + _EdgesOnShape& eos = edgesByGeom[iS]; + if ( eos._edges.empty() || eos.ShapeType() != TopAbs_EDGE ) continue; + if ( !eos._hyp.ToSmooth() ) continue; - if ( needSmooth ) + const TopoDS_Edge& E = TopoDS::Edge( edgesByGeom[iS]._shape ); + if ( SMESH_Algo::isDegenerated( E ) || !edgesOfSmooFaces.Contains( E )) + continue; + + for ( TopoDS_Iterator vIt( E ); vIt.More() && !eos._toSmooth; vIt.Next() ) + { + TGeomID iV = getMeshDS()->ShapeToIndex( vIt.Value() ); + vector<_LayerEdge*>& eV = edgesByGeom[ iV ]._edges; + if ( eV.empty() ) continue; + gp_Vec eDir = getEdgeDir( E, TopoDS::Vertex( vIt.Value() )); + double angle = eDir.Angle( eV[0]->_normal ); + double cosin = Cos( angle ); + double cosinAbs = Abs( cosin ); + if ( cosinAbs > theMinSmoothCosin ) { - if ( S.ShapeType() == TopAbs_EDGE ) shapesToSmooth.push_front( iS ); - else shapesToSmooth.push_back ( iS ); + // always smooth analytic EDGEs + eos._toSmooth = ! data.CurveForSmooth( E, eos, helper ).IsNull(); - // preparation for smoothing - if ( S.ShapeType() == TopAbs_FACE ) + // compare tgtThick with the length of an end segment + SMDS_ElemIteratorPtr eIt = eV[0]->_nodes[0]->GetInverseElementIterator(SMDSAbs_Edge); + while ( eIt->more() && !eos._toSmooth ) { - data.PrepareEdgesToSmoothOnFace( & eS[0], - & eS[0] + eS.size(), - TopoDS::Face( S ), - /*substituteSrcNodes=*/false); + const SMDS_MeshElement* endSeg = eIt->next(); + if ( endSeg->getshapeId() == iS ) + { + double segLen = + SMESH_TNodeXYZ( endSeg->GetNode(0) ).Distance( endSeg->GetNode(1 )); + eos._toSmooth = needSmoothing( cosinAbs, tgtThick, segLen ); + } } } + } + data._nbShapesToSmooth += eos._toSmooth; - } // loop on edgesByGeom - } // // loop on [ FACEs, EDGEs ] - - data._edges.reserve( data._n2eMap.size() ); - data._endEdgeOnShape.clear(); - - // first we put _LayerEdge's on shapes to smooth - data._nbShapesToSmooth = 0; - list< TGeomID >::iterator gIt = shapesToSmooth.begin(); - for ( ; gIt != shapesToSmooth.end(); ++gIt ) - { - vector<_LayerEdge*>& eVec = edgesByGeom[ *gIt ]; - if ( eVec.empty() ) continue; - data._edges.insert( data._edges.end(), eVec.begin(), eVec.end() ); - data._endEdgeOnShape.push_back( data._edges.size() ); - data._nbShapesToSmooth++; - eVec.clear(); - } + } // check EDGEs - // then the rest _LayerEdge's + // Reset _cosin if no smooth is allowed by the user for ( size_t iS = 0; iS < edgesByGeom.size(); ++iS ) { - vector<_LayerEdge*>& eVec = edgesByGeom[iS]; - if ( eVec.empty() ) continue; - data._edges.insert( data._edges.end(), eVec.begin(), eVec.end() ); - data._endEdgeOnShape.push_back( data._edges.size() ); - //eVec.clear(); + _EdgesOnShape& eos = edgesByGeom[iS]; + if ( eos._edges.empty() ) continue; + + if ( !eos._hyp.ToSmooth() ) + for ( size_t i = 0; i < eos._edges.size(); ++i ) + eos._edges[i]->SetCosin( 0 ); } - // compute average StdMeshers_ViscousLayers parameters for each shape - data._hypOnShape.clear(); + // int nbShapes = 0; + // for ( size_t iS = 0; iS < edgesByGeom.size(); ++iS ) + // { + // nbShapes += ( edgesByGeom[iS]._edges.size() > 0 ); + // } + // data._edgesOnShape.reserve( nbShapes ); + + // // first we put _LayerEdge's on shapes to smooth (EGDEs go first) + // vector< _LayerEdge* > edges; + // list< TGeomID >::iterator gIt = shapesToSmooth.begin(); + // for ( ; gIt != shapesToSmooth.end(); ++gIt ) + // { + // _EdgesOnShape& eos = edgesByGeom[ *gIt ]; + // if ( eos._edges.empty() ) continue; + // eos._edges.swap( edges ); // avoid copying array + // eos._toSmooth = true; + // data._edgesOnShape.push_back( eos ); + // data._edgesOnShape.back()._edges.swap( edges ); + // } + + // // then the rest _LayerEdge's + // for ( size_t iS = 0; iS < edgesByGeom.size(); ++iS ) + // { + // _EdgesOnShape& eos = edgesByGeom[ *gIt ]; + // if ( eos._edges.empty() ) continue; + // eos._edges.swap( edges ); // avoid copying array + // eos._toSmooth = false; + // data._edgesOnShape.push_back( eos ); + // data._edgesOnShape.back()._edges.swap( edges ); + // } + + return ok; +} + +//================================================================================ +/*! + * \brief initialize data of _EdgesOnShape + */ +//================================================================================ + +void _ViscousBuilder::setShapeData( _EdgesOnShape& eos, + SMESH_subMesh* sm, + _SolidData& data ) +{ + if ( !eos._shape.IsNull() || + sm->GetSubShape().ShapeType() == TopAbs_WIRE ) + return; + + SMESH_MesherHelper helper( *_mesh ); + + eos._subMesh = sm; + eos._shapeID = sm->GetId(); + eos._shape = sm->GetSubShape(); + if ( eos.ShapeType() == TopAbs_FACE ) + eos._shape.Orientation( helper.GetSubShapeOri( data._solid, eos._shape )); + eos._toSmooth = false; + + // set _SWOL + map< TGeomID, TopoDS_Shape >::const_iterator s2s = + data._shrinkShape2Shape.find( eos._shapeID ); + if ( s2s != data._shrinkShape2Shape.end() ) + eos._sWOL = s2s->second; + + // set _hyp if ( data._hyps.size() == 1 ) { - data._hypOnShape.resize( data._endEdgeOnShape.size(), AverageHyp( data._hyps.back() )); + eos._hyp = data._hyps.back(); } else { - data._hypOnShape.resize( data._endEdgeOnShape.size() ); + // compute average StdMeshers_ViscousLayers parameters map< TGeomID, const StdMeshers_ViscousLayers* >::iterator f2hyp; - for ( size_t i = 0; i < data._endEdgeOnShape.size(); ++i ) + if ( eos.ShapeType() == TopAbs_FACE ) + { + if (( f2hyp = data._face2hyp.find( eos._shapeID )) != data._face2hyp.end() ) + eos._hyp = f2hyp->second; + } + else { - int iEnd = data._endEdgeOnShape[i]; - _LayerEdge* LE = data._edges[ iEnd-1 ]; - TGeomID iShape = LE->_nodes[0]->getshapeId(); - const TopoDS_Shape& S = getMeshDS()->IndexToShape( iShape ); - if ( S.ShapeType() == TopAbs_FACE ) + PShapeIteratorPtr fIt = helper.GetAncestors( eos._shape, *_mesh, TopAbs_FACE ); + while ( const TopoDS_Shape* face = fIt->next() ) { - if (( f2hyp = data._face2hyp.find( iShape )) != data._face2hyp.end() ) - { - data._hypOnShape[ i ].Add( f2hyp->second ); - } + TGeomID faceID = getMeshDS()->ShapeToIndex( *face ); + if (( f2hyp = data._face2hyp.find( faceID )) != data._face2hyp.end() ) + eos._hyp.Add( f2hyp->second ); } - else + } + } + + // set _faceNormals + if ( ! eos._hyp.UseSurfaceNormal() ) + { + if ( eos.ShapeType() == TopAbs_FACE ) // get normals to elements on a FACE + { + SMESHDS_SubMesh* smDS = sm->GetSubMeshDS(); + eos._faceNormals.resize( smDS->NbElements() ); + + SMDS_ElemIteratorPtr eIt = smDS->GetElements(); + for ( int iF = 0; eIt->more(); ++iF ) { - PShapeIteratorPtr fIt = SMESH_MesherHelper::GetAncestors( S, *_mesh, TopAbs_FACE ); - while ( const TopoDS_Shape* face = fIt->next() ) - { - TGeomID faceID = getMeshDS()->ShapeToIndex( *face ); - if (( f2hyp = data._face2hyp.find( faceID )) != data._face2hyp.end() ) - { - data._hypOnShape[ i ].Add( f2hyp->second ); - } - } + const SMDS_MeshElement* face = eIt->next(); + if ( !SMESH_MeshAlgos::FaceNormal( face, eos._faceNormals[iF], /*normalized=*/true )) + eos._faceNormals[iF].SetCoord( 0,0,0 ); } + + if ( !helper.IsReversedSubMesh( TopoDS::Face( eos._shape ))) + for ( size_t iF = 0; iF < eos._faceNormals.size(); ++iF ) + eos._faceNormals[iF].Reverse(); } + else // find EOS of adjacent FACEs + { + PShapeIteratorPtr fIt = helper.GetAncestors( eos._shape, *_mesh, TopAbs_FACE ); + while ( const TopoDS_Shape* face = fIt->next() ) + { + TGeomID faceID = getMeshDS()->ShapeToIndex( *face ); + eos._faceEOS.push_back( & data._edgesOnShape[ faceID ]); + if ( eos._faceEOS.back()->_shape.IsNull() ) + // avoid using uninitialised _shapeID in GetNormal() + eos._faceEOS.back()->_shapeID = faceID; + } + } + } +} + +//================================================================================ +/*! + * \brief Returns normal of a face + */ +//================================================================================ + +bool _EdgesOnShape::GetNormal( const SMDS_MeshElement* face, gp_Vec& norm ) +{ + bool ok = false; + const _EdgesOnShape* eos = 0; + + if ( face->getshapeId() == _shapeID ) + { + eos = this; + } + else + { + for ( size_t iF = 0; iF < _faceEOS.size() && !eos; ++iF ) + if ( face->getshapeId() == _faceEOS[ iF ]->_shapeID ) + eos = _faceEOS[ iF ]; } + if (( eos ) && + ( ok = ( face->getIdInShape() < eos->_faceNormals.size() ))) + { + norm = eos->_faceNormals[ face->getIdInShape() ]; + } + else if ( !eos ) + { + debugMsg( "_EdgesOnShape::Normal() failed for face "<GetID() + << " on _shape #" << _shapeID ); + } return ok; } + //================================================================================ /*! * \brief Set data of _LayerEdge needed for smoothing @@ -2653,14 +2795,12 @@ bool _ViscousBuilder::sortEdges( _SolidData& data, //================================================================================ bool _ViscousBuilder::setEdgeData(_LayerEdge& edge, + _EdgesOnShape& eos, const set& subIds, SMESH_MesherHelper& helper, _SolidData& data) { - SMESH_MeshEditor editor(_mesh); - const SMDS_MeshNode* node = edge._nodes[0]; // source node - const SMDS_TypeOfPosition posType = node->GetPosition()->GetTypeOfPosition(); edge._len = 0; edge._2neibors = 0; @@ -2679,18 +2819,23 @@ bool _ViscousBuilder::setEdgeData(_LayerEdge& edge, gp_Vec geomNorm; bool normOK = true; + const bool onShrinkShape = !eos._sWOL.IsNull(); + const bool useGeometry = (( eos._hyp.UseSurfaceNormal() ) || + ( eos.ShapeType() != TopAbs_FACE && !onShrinkShape )); + // get geom FACEs the node lies on + //if ( useGeometry ) { set faceIds; - if ( posType == SMDS_TOP_FACE ) + if ( eos.ShapeType() == TopAbs_FACE ) { - faceIds.insert( node->getshapeId() ); + faceIds.insert( eos._shapeID ); } else { SMDS_ElemIteratorPtr fIt = node->GetInverseElementIterator(SMDSAbs_Face); while ( fIt->more() ) - faceIds.insert( editor.FindShape(fIt->next())); + faceIds.insert( fIt->next()->getshapeId() ); } set::iterator id = faceIds.begin(); for ( ; id != faceIds.end(); ++id ) @@ -2704,111 +2849,130 @@ bool _ViscousBuilder::setEdgeData(_LayerEdge& edge, } } - const TGeomID shapeInd = node->getshapeId(); - map< TGeomID, TopoDS_Shape >::const_iterator s2s = data._shrinkShape2Shape.find( shapeInd ); - const bool onShrinkShape ( s2s != data._shrinkShape2Shape.end() ); - // find _normal - if ( onShrinkShape ) // one of faces the node is on has no layers + if ( useGeometry ) { - TopoDS_Shape vertEdge = getMeshDS()->IndexToShape( s2s->first ); // vertex or edge - if ( s2s->second.ShapeType() == TopAbs_EDGE ) - { - // inflate from VERTEX along EDGE - edge._normal = getEdgeDir( TopoDS::Edge( s2s->second ), TopoDS::Vertex( vertEdge )); - } - else if ( vertEdge.ShapeType() == TopAbs_VERTEX ) - { - // inflate from VERTEX along FACE - edge._normal = getFaceDir( TopoDS::Face( s2s->second ), TopoDS::Vertex( vertEdge ), - node, helper, normOK, &edge._cosin); - } - else + if ( onShrinkShape ) // one of faces the node is on has no layers { - // inflate from EDGE along FACE - edge._normal = getFaceDir( TopoDS::Face( s2s->second ), TopoDS::Edge( vertEdge ), - node, helper, normOK); - } - } - else // layers are on all faces of SOLID the node is on - { - int nbOkNorms = 0; - for ( int iF = 0; iF < totalNbFaces; ++iF ) - { - F = TopoDS::Face( face2Norm[ iF ].first ); - geomNorm = getFaceNormal( node, F, helper, normOK ); - if ( !normOK ) continue; - nbOkNorms++; - - if ( helper.GetSubShapeOri( data._solid, F ) != TopAbs_REVERSED ) - geomNorm.Reverse(); - face2Norm[ iF ].second = geomNorm.XYZ(); - edge._normal += geomNorm.XYZ(); + if ( eos.SWOLType() == TopAbs_EDGE ) + { + // inflate from VERTEX along EDGE + edge._normal = getEdgeDir( TopoDS::Edge( eos._sWOL ), TopoDS::Vertex( eos._shape )); + } + else if ( eos.ShapeType() == TopAbs_VERTEX ) + { + // inflate from VERTEX along FACE + edge._normal = getFaceDir( TopoDS::Face( eos._sWOL ), TopoDS::Vertex( eos._shape ), + node, helper, normOK, &edge._cosin); + } + else + { + // inflate from EDGE along FACE + edge._normal = getFaceDir( TopoDS::Face( eos._sWOL ), TopoDS::Edge( eos._shape ), + node, helper, normOK); + } } - if ( nbOkNorms == 0 ) - return error(SMESH_Comment("Can't get normal to node ") << node->GetID(), data._index); - if ( edge._normal.Modulus() < 1e-3 && nbOkNorms > 1 ) + // layers are on all faces of SOLID the node is on + else { - // opposite normals, re-get normals at shifted positions (IPAL 52426) - edge._normal.SetCoord( 0,0,0 ); + int nbOkNorms = 0; for ( int iF = 0; iF < totalNbFaces; ++iF ) { - const TopoDS_Face& F = face2Norm[iF].first; - geomNorm = getFaceNormal( node, F, helper, normOK, /*shiftInside=*/true ); + F = TopoDS::Face( face2Norm[ iF ].first ); + geomNorm = getFaceNormal( node, F, helper, normOK ); + if ( !normOK ) continue; + nbOkNorms++; + if ( helper.GetSubShapeOri( data._solid, F ) != TopAbs_REVERSED ) geomNorm.Reverse(); - if ( normOK ) - face2Norm[ iF ].second = geomNorm.XYZ(); - edge._normal += face2Norm[ iF ].second; + face2Norm[ iF ].second = geomNorm.XYZ(); + edge._normal += geomNorm.XYZ(); } - } + if ( nbOkNorms == 0 ) + return error(SMESH_Comment("Can't get normal to node ") << node->GetID(), data._index); - if ( totalNbFaces < 3 ) - { - //edge._normal /= totalNbFaces; + if ( edge._normal.Modulus() < 1e-3 && nbOkNorms > 1 ) + { + // opposite normals, re-get normals at shifted positions (IPAL 52426) + edge._normal.SetCoord( 0,0,0 ); + for ( int iF = 0; iF < totalNbFaces; ++iF ) + { + const TopoDS_Face& F = face2Norm[iF].first; + geomNorm = getFaceNormal( node, F, helper, normOK, /*shiftInside=*/true ); + if ( helper.GetSubShapeOri( data._solid, F ) != TopAbs_REVERSED ) + geomNorm.Reverse(); + if ( normOK ) + face2Norm[ iF ].second = geomNorm.XYZ(); + edge._normal += face2Norm[ iF ].second; + } + } + + if ( totalNbFaces < 3 ) + { + //edge._normal /= totalNbFaces; + } + else + { + edge._normal = getWeigthedNormal( node, face2Norm, totalNbFaces ); + } } - else + } + else // !useGeometry - get _normal using surrounding mesh faces + { + + SMDS_ElemIteratorPtr fIt = node->GetInverseElementIterator(SMDSAbs_Face); + while ( fIt->more() ) { - edge._normal = getWeigthedNormal( node, face2Norm, totalNbFaces ); + const SMDS_MeshElement* face = fIt->next(); + if ( eos.GetNormal( face, geomNorm )) + { + edge._normal += geomNorm.XYZ(); + totalNbFaces++; + } } } - // set _cosin - switch ( posType ) + // compute _cosin + //if ( eos._hyp.UseSurfaceNormal() ) { - case SMDS_TOP_FACE: { - edge._cosin = 0; - break; - } - case SMDS_TOP_EDGE: { - TopoDS_Edge E = TopoDS::Edge( helper.GetSubShapeByNode( node, getMeshDS())); - gp_Vec inFaceDir = getFaceDir( F, E, node, helper, normOK ); - double angle = inFaceDir.Angle( edge._normal ); // [0,PI] - edge._cosin = Cos( angle ); - //cout << "Cosin on EDGE " << edge._cosin << " node " << node->GetID() << endl; - break; - } - case SMDS_TOP_VERTEX: { - TopoDS_Vertex V = TopoDS::Vertex( helper.GetSubShapeByNode( node, getMeshDS())); - gp_Vec inFaceDir = getFaceDir( F, V, node, helper, normOK ); - double angle = inFaceDir.Angle( edge._normal ); // [0,PI] - edge._cosin = Cos( angle ); - if ( totalNbFaces > 2 || helper.IsSeamShape( node->getshapeId() )) - for ( int iF = totalNbFaces-2; iF >=0; --iF ) - { - F = face2Norm[ iF ].first; - inFaceDir = getFaceDir( F, V, node, helper, normOK ); - if ( normOK ) { - double angle = inFaceDir.Angle( edge._normal ); - edge._cosin = Max( edge._cosin, Cos( angle )); - } + switch ( eos.ShapeType() ) + { + case TopAbs_FACE: { + edge._cosin = 0; + break; + } + case TopAbs_EDGE: { + TopoDS_Edge E = TopoDS::Edge( eos._shape ); + gp_Vec inFaceDir = getFaceDir( F, E, node, helper, normOK ); + double angle = inFaceDir.Angle( edge._normal ); // [0,PI] + edge._cosin = Cos( angle ); + //cout << "Cosin on EDGE " << edge._cosin << " node " << node->GetID() << endl; + break; + } + case TopAbs_VERTEX: { + if ( eos.SWOLType() != TopAbs_FACE ) { // else _cosin is set by getFaceDir() + TopoDS_Vertex V = TopoDS::Vertex( eos._shape ); + gp_Vec inFaceDir = getFaceDir( F, V, node, helper, normOK ); + double angle = inFaceDir.Angle( edge._normal ); // [0,PI] + edge._cosin = Cos( angle ); + if ( totalNbFaces > 2 || helper.IsSeamShape( node->getshapeId() )) + for ( int iF = totalNbFaces-2; iF >=0; --iF ) + { + F = face2Norm[ iF ].first; + inFaceDir = getFaceDir( F, V, node, helper, normOK=true ); + if ( normOK ) { + double angle = inFaceDir.Angle( edge._normal ); + edge._cosin = Max( edge._cosin, Cos( angle )); + } + } } - //cout << "Cosin on VERTEX " << edge._cosin << " node " << node->GetID() << endl; - break; - } - default: - return error(SMESH_Comment("Invalid shape position of node ")<GetID() << endl; + break; + } + default: + return error(SMESH_Comment("Invalid shape position of node ")<( edge._nodes.back() ); if ( SMESHDS_SubMesh* sm = getMeshDS()->MeshElements( data._solid )) sm->RemoveNode( tgtNode , /*isNodeDeleted=*/false ); // set initial position which is parameters on _sWOL in this case - if ( edge._sWOL.ShapeType() == TopAbs_EDGE ) + if ( eos.SWOLType() == TopAbs_EDGE ) { - double u = helper.GetNodeU( TopoDS::Edge( edge._sWOL ), node, 0, &normOK ); + double u = helper.GetNodeU( TopoDS::Edge( eos._sWOL ), node, 0, &normOK ); edge._pos.push_back( gp_XYZ( u, 0, 0 )); if ( edge._nodes.size() > 1 ) - getMeshDS()->SetNodeOnEdge( tgtNode, TopoDS::Edge( edge._sWOL ), u ); + getMeshDS()->SetNodeOnEdge( tgtNode, TopoDS::Edge( eos._sWOL ), u ); } else // TopAbs_FACE { - gp_XY uv = helper.GetNodeUV( TopoDS::Face( edge._sWOL ), node, 0, &normOK ); + gp_XY uv = helper.GetNodeUV( TopoDS::Face( eos._sWOL ), node, 0, &normOK ); edge._pos.push_back( gp_XYZ( uv.X(), uv.Y(), 0)); if ( edge._nodes.size() > 1 ) - getMeshDS()->SetNodeOnFace( tgtNode, TopoDS::Face( edge._sWOL ), uv.X(), uv.Y() ); + getMeshDS()->SetNodeOnFace( tgtNode, TopoDS::Face( eos._sWOL ), uv.X(), uv.Y() ); } } else { edge._pos.push_back( SMESH_TNodeXYZ( node )); - if ( posType == SMDS_TOP_FACE ) + if ( eos.ShapeType() == TopAbs_FACE ) { _Simplex::GetSimplices( node, edge._simplices, data._ignoreFaceIds, &data ); } @@ -2857,14 +3019,14 @@ bool _ViscousBuilder::setEdgeData(_LayerEdge& edge, // Set neighbour nodes for a _LayerEdge based on EDGE - if ( posType == SMDS_TOP_EDGE /*|| + if ( eos.ShapeType() == TopAbs_EDGE /*|| ( onShrinkShape && posType == SMDS_TOP_VERTEX && fabs( edge._cosin ) < 1e-10 )*/) { edge._2neibors = new _2NearEdges; // target node instead of source ones will be set later // if ( ! findNeiborsOnEdge( &edge, // edge._2neibors->_nodes[0], - // edge._2neibors->_nodes[1], + // edge._2neibors->_nodes[1], eos, // data)) // return false; // edge.SetDataByNeighbors( edge._2neibors->_nodes[0], @@ -3155,14 +3317,15 @@ gp_XYZ _ViscousBuilder::getWeigthedNormal( const SMDS_MeshNode* n, bool _ViscousBuilder::findNeiborsOnEdge(const _LayerEdge* edge, const SMDS_MeshNode*& n1, const SMDS_MeshNode*& n2, + _EdgesOnShape& eos, _SolidData& data) { const SMDS_MeshNode* node = edge->_nodes[0]; - const int shapeInd = node->getshapeId(); + const int shapeInd = eos._shapeID; SMESHDS_SubMesh* edgeSM = 0; - if ( node->GetPosition()->GetTypeOfPosition() == SMDS_TOP_EDGE ) + if ( eos.ShapeType() == TopAbs_EDGE ) { - edgeSM = getMeshDS()->MeshElements( shapeInd ); + edgeSM = eos._subMesh->GetSubMeshDS(); if ( !edgeSM || edgeSM->NbElements() == 0 ) return error(SMESH_Comment("Not meshed EDGE ") << shapeInd, data._index); } @@ -3180,8 +3343,8 @@ bool _ViscousBuilder::findNeiborsOnEdge(const _LayerEdge* edge, } else { - TopoDS_Shape s = SMESH_MesherHelper::GetSubShapeByNode(nNeibor, getMeshDS() ); - if ( !SMESH_MesherHelper::IsSubShape( s, edge->_sWOL )) continue; + TopoDS_Shape s = SMESH_MesherHelper::GetSubShapeByNode( nNeibor, getMeshDS() ); + if ( !SMESH_MesherHelper::IsSubShape( s, eos._sWOL )) continue; } ( iN++ ? n2 : n1 ) = nNeibor; } @@ -3198,9 +3361,10 @@ bool _ViscousBuilder::findNeiborsOnEdge(const _LayerEdge* edge, void _LayerEdge::SetDataByNeighbors( const SMDS_MeshNode* n1, const SMDS_MeshNode* n2, + const _EdgesOnShape& eos, SMESH_MesherHelper& helper) { - if ( _nodes[0]->GetPosition()->GetTypeOfPosition() != SMDS_TOP_EDGE ) + if ( eos.ShapeType() != TopAbs_EDGE ) return; gp_XYZ pos = SMESH_TNodeXYZ( _nodes[0] ); @@ -3224,10 +3388,9 @@ void _LayerEdge::SetDataByNeighbors( const SMDS_MeshNode* n1, // Set _plnNorm - if ( _sWOL.IsNull() ) + if ( eos._sWOL.IsNull() ) { - TopoDS_Shape S = helper.GetSubShapeByNode( _nodes[0], helper.GetMeshDS() ); - TopoDS_Edge E = TopoDS::Edge( S ); + TopoDS_Edge E = TopoDS::Edge( eos._shape ); // if ( SMESH_Algo::isDegenerated( E )) // return; gp_XYZ dirE = getEdgeDir( E, _nodes[0], helper ); @@ -3249,33 +3412,34 @@ void _LayerEdge::SetDataByNeighbors( const SMDS_MeshNode* n1, */ //================================================================================ -gp_XYZ _LayerEdge::Copy( _LayerEdge& other, SMESH_MesherHelper& helper ) +gp_XYZ _LayerEdge::Copy( _LayerEdge& other, + _EdgesOnShape& eos, + SMESH_MesherHelper& helper ) { _nodes = other._nodes; _normal = other._normal; _len = 0; _lenFactor = other._lenFactor; _cosin = other._cosin; - _sWOL = other._sWOL; _2neibors = other._2neibors; _curvature = 0; std::swap( _curvature, other._curvature ); _2neibors = 0; std::swap( _2neibors, other._2neibors ); gp_XYZ lastPos( 0,0,0 ); - if ( _sWOL.ShapeType() == TopAbs_EDGE ) + if ( eos.SWOLType() == TopAbs_EDGE ) { - double u = helper.GetNodeU( TopoDS::Edge( _sWOL ), _nodes[0] ); + double u = helper.GetNodeU( TopoDS::Edge( eos._sWOL ), _nodes[0] ); _pos.push_back( gp_XYZ( u, 0, 0)); - u = helper.GetNodeU( TopoDS::Edge( _sWOL ), _nodes.back() ); + u = helper.GetNodeU( TopoDS::Edge( eos._sWOL ), _nodes.back() ); lastPos.SetX( u ); } else // TopAbs_FACE { - gp_XY uv = helper.GetNodeUV( TopoDS::Face( _sWOL ), _nodes[0]); + gp_XY uv = helper.GetNodeUV( TopoDS::Face( eos._sWOL ), _nodes[0]); _pos.push_back( gp_XYZ( uv.X(), uv.Y(), 0)); - uv = helper.GetNodeUV( TopoDS::Face( _sWOL ), _nodes.back() ); + uv = helper.GetNodeUV( TopoDS::Face( eos._sWOL ), _nodes.back() ); lastPos.SetX( uv.X() ); lastPos.SetY( uv.Y() ); } @@ -3364,12 +3528,13 @@ void _ViscousBuilder::makeGroupOfLE() #ifdef _DEBUG_ for ( size_t i = 0 ; i < _sdVec.size(); ++i ) { - if ( _sdVec[i]._edges.empty() ) continue; + if ( _sdVec[i]._n2eMap.empty() ) continue; dumpFunction( SMESH_Comment("make_LayerEdge_") << i ); - for ( size_t j = 0 ; j < _sdVec[i]._edges.size(); ++j ) + TNode2Edge::iterator n2e; + for ( n2e = _sdVec[i]._n2eMap.begin(); n2e != _sdVec[i]._n2eMap.end(); ++n2e ) { - _LayerEdge* le = _sdVec[i]._edges[j]; + _LayerEdge* le = n2e->second; for ( size_t iN = 1; iN < le->_nodes.size(); ++iN ) dumpCmd(SMESH_Comment("mesh.AddEdge([ ") <_nodes[iN-1]->GetID() << ", " << le->_nodes[iN]->GetID() <<"])"); @@ -3377,12 +3542,12 @@ void _ViscousBuilder::makeGroupOfLE() dumpFunctionEnd(); dumpFunction( SMESH_Comment("makeNormals") << i ); - for ( size_t j = 0 ; j < _sdVec[i]._edges.size(); ++j ) + for ( n2e = _sdVec[i]._n2eMap.begin(); n2e != _sdVec[i]._n2eMap.end(); ++n2e ) { - _LayerEdge& edge = *_sdVec[i]._edges[j]; - SMESH_TNodeXYZ nXYZ( edge._nodes[0] ); - nXYZ += edge._normal * _sdVec[i]._stepSize; - dumpCmd(SMESH_Comment("mesh.AddEdge([ ") <GetID() + _LayerEdge* edge = n2e->second; + SMESH_TNodeXYZ nXYZ( edge->_nodes[0] ); + nXYZ += edge->_normal * _sdVec[i]._stepSize; + dumpCmd(SMESH_Comment("mesh.AddEdge([ ") << edge->_nodes[0]->GetID() << ", mesh.AddNode( " << nXYZ.X()<<","<< nXYZ.Y()<<","<< nXYZ.Z()<<")])"); } dumpFunctionEnd(); @@ -3429,14 +3594,17 @@ void _ViscousBuilder::computeGeomSize( _SolidData& data ) ( SMESH_MeshAlgos::GetElementSearcher( *getMeshDS(), data._proxyMesh->GetFaces( data._solid )) ); - TNode2Edge::iterator n2e = data._n2eMap.begin(), n2eEnd = data._n2eMap.end(); - for ( ; n2e != n2eEnd; ++n2e ) + for ( size_t iS = 0; iS < data._edgesOnShape.size(); ++iS ) { - _LayerEdge* edge = n2e->second; - if ( edge->IsOnEdge() ) continue; - edge->FindIntersection( *searcher, intersecDist, data._epsilon ); - if ( data._geomSize > intersecDist && intersecDist > 0 ) - data._geomSize = intersecDist; + _EdgesOnShape& eos = data._edgesOnShape[ iS ]; + if ( eos._edges.empty() || eos.ShapeType() == TopAbs_EDGE ) + continue; + for ( size_t i = 0; i < eos._edges.size(); ++i ) + { + eos._edges[i]->FindIntersection( *searcher, intersecDist, data._epsilon, eos ); + if ( data._geomSize > intersecDist && intersecDist > 0 ) + data._geomSize = intersecDist; + } } } @@ -3468,7 +3636,6 @@ bool _ViscousBuilder::inflate(_SolidData& data) double avgThick = 0, curThick = 0, distToIntersection = Precision::Infinite(); int nbSteps = 0, nbRepeats = 0; - int iBeg, iEnd, iS; while ( avgThick < 0.99 ) { // new target length @@ -3481,12 +3648,15 @@ bool _ViscousBuilder::inflate(_SolidData& data) // Elongate _LayerEdge's dumpFunction(SMESH_Comment("inflate")<SetNewLength( shapeCurThick, helper ); + eos._edges[i]->SetNewLength( shapeCurThick, eos, helper ); } } dumpFunctionEnd(); @@ -3504,9 +3674,11 @@ bool _ViscousBuilder::inflate(_SolidData& data) return error("Smoothing failed", data._index); #endif dumpFunction(SMESH_Comment("invalidate")<InvalidateStep( nbSteps+1 ); + _EdgesOnShape& eos = data._edgesOnShape[iS]; + for ( size_t i = 0; i < eos._edges.size(); ++i ) + eos._edges[i]->InvalidateStep( nbSteps+1, eos ); } dumpFunctionEnd(); } @@ -3516,15 +3688,18 @@ bool _ViscousBuilder::inflate(_SolidData& data) // Evaluate achieved thickness avgThick = 0; - for ( iBeg = 0, iS = 0; iS < data._endEdgeOnShape.size(); ++iS ) + for ( size_t iS = 0; iS < data._edgesOnShape.size(); ++iS ) { - const double shapeTgtThick = data._hypOnShape[ iS ].GetTotalThickness(); - for ( iEnd = data._endEdgeOnShape[ iS ]; iBeg < iEnd; ++iBeg ) + _EdgesOnShape& eos = data._edgesOnShape[iS]; + if ( eos._edges.empty() ) continue; + + const double shapeTgtThick = eos._hyp.GetTotalThickness(); + for ( size_t i = 0; i < eos._edges.size(); ++i ) { - avgThick += Min( 1., data._edges[iBeg]->_len / shapeTgtThick ); + avgThick += Min( 1., eos._edges[i]->_len / shapeTgtThick ); } } - avgThick /= data._edges.size(); + avgThick /= data._n2eMap.size(); debugMsg( "-- Thickness " << curThick << " ("<< avgThick*100 << "%) reached" ); if ( distToIntersection < tgtThick * avgThick * safeFactor && avgThick < 0.9 ) @@ -3559,14 +3734,13 @@ bool _ViscousBuilder::inflate(_SolidData& data) // Restore position of src nodes moved by infaltion on _noShrinkShapes dumpFunction(SMESH_Comment("restoNoShrink_So")<_nodes.size() == 1 ) - for ( ; iBeg < iEnd; ++iBeg ) + _EdgesOnShape& eos = data._edgesOnShape[iS]; + if ( !eos._edges.empty() && eos._edges[0]->_nodes.size() == 1 ) + for ( size_t i = 0; i < eos._edges.size(); ++i ) { - restoreNoShrink( *data._edges[ iBeg ] ); + restoreNoShrink( *eos._edges[ i ] ); } } dumpFunctionEnd(); @@ -3594,136 +3768,151 @@ bool _ViscousBuilder::smoothAndCheck(_SolidData& data, Handle(Geom_Surface) surface; TopoDS_Face F; - int iBeg, iEnd = 0; - for ( int iS = 0; iS < data._nbShapesToSmooth; ++iS ) + for ( int isFace = 0; isFace < 2; ++isFace ) // smooth on [ EDGEs, FACEs ] { - iBeg = iEnd; - iEnd = data._endEdgeOnShape[ iS ]; - - // need to smooth this shape? - bool toSmooth = ( data._hyps.front() == data._hyps.back() ); - for ( int i = iBeg; i < iEnd && !toSmooth; ++i ) - toSmooth = ( data._edges[ iBeg ]->NbSteps() >= nbSteps+1 ); - if ( !toSmooth ) - { - if ( iS+1 == data._nbShapesToSmooth ) - data._nbShapesToSmooth--; - continue; // target length reached some steps before - } + const TopAbs_ShapeEnum shapeType = isFace ? TopAbs_FACE : TopAbs_EDGE; - // prepare data - if ( !data._edges[ iBeg ]->_sWOL.IsNull() && - data._edges[ iBeg ]->_sWOL.ShapeType() == TopAbs_FACE ) + for ( int iS = 0; iS < data._edgesOnShape.size(); ++iS ) { - if ( !F.IsSame( data._edges[ iBeg ]->_sWOL )) { - F = TopoDS::Face( data._edges[ iBeg ]->_sWOL ); - helper.SetSubShape( F ); - surface = BRep_Tool::Surface( F ); - } - } - else - { - F.Nullify(); surface.Nullify(); - } - const TGeomID sInd = data._edges[ iBeg ]->_nodes[0]->getshapeId(); + _EdgesOnShape& eos = data._edgesOnShape[ iS ]; + if ( !eos._toSmooth || eos.ShapeType() != shapeType ) + continue; - // perform smoothing + // already smoothed? + bool toSmooth = ( eos._edges[ 0 ]->NbSteps() >= nbSteps+1 ); + if ( !toSmooth ) continue; - if ( data._edges[ iBeg ]->IsOnEdge() ) - { - dumpFunction(SMESH_Comment("smooth")<_simplices.size(); ++iF ) + if ( !edge->_simplices[iF].IsForward( edge->_nodes[0], + &edge->_pos.back(), vol )) + return false; + } + continue; // goto to the next EDGE or FACE + } - // try a simple solution on an analytic EDGE - if ( !smoothAnalyticEdge( data, iBeg, iEnd, surface, F, helper )) + // prepare data + if ( eos.SWOLType() == TopAbs_FACE ) { - // smooth on EDGE's - int step = 0; - do { - moved = false; - for ( int i = iBeg; i < iEnd; ++i ) - { - moved |= data._edges[i]->SmoothOnEdge(surface, F, helper); - } - dumpCmd( SMESH_Comment("# end step ")<Smooth( step, isConcaveFace, false )) - badSmooEdges.push_back( data._edges[i] ); - } - else { - for ( int i = iEnd-1; i >= iBeg; --i ) // iterate backward - if ( data._edges[i]->Smooth( step, isConcaveFace, false )) - badSmooEdges.push_back( data._edges[i] ); - } - badNb = badSmooEdges.size(); - improved = ( badNb < oldBadNb ); - - if ( !badSmooEdges.empty() && step >= stepLimit / 2 ) + // try a simple solution on an analytic EDGE + if ( !smoothAnalyticEdge( data, eos, surface, F, helper )) { - // look for the best smooth of _LayerEdge's neighboring badSmooEdges - vector<_Simplex> simplices; - for ( size_t i = 0; i < badSmooEdges.size(); ++i ) - { - _LayerEdge* ledge = badSmooEdges[i]; - _Simplex::GetSimplices( ledge->_nodes[0], simplices, data._ignoreFaceIds ); - for ( size_t iS = 0; iS < simplices.size(); ++iS ) + // smooth on EDGE's + int step = 0; + do { + moved = false; + for ( size_t i = 0; i < eos._edges.size(); ++i ) { - TNode2Edge::iterator n2e = data._n2eMap.find( simplices[iS]._nNext ); - if ( n2e != data._n2eMap.end()) { - _LayerEdge* ledge2 = n2e->second; - if ( ledge2->_nodes[0]->getshapeId() == sInd ) - ledge2->Smooth( step, isConcaveFace, /*findBest=*/true ); - } + moved |= eos._edges[i]->SmoothOnEdge( surface, F, helper ); } + dumpCmd( SMESH_Comment("# end step ")< 0 ) + else { -#ifdef __myDEBUG - double vol = 0; - for ( int i = iBeg; i < iEnd; ++i ) + // smooth on FACE's + + const bool isConcaveFace = data._concaveFaces.count( sInd ); + + int step = 0, stepLimit = 5, badNb = 0; + while (( ++step <= stepLimit ) || improved ) { - _LayerEdge* edge = data._edges[i]; - SMESH_TNodeXYZ tgtXYZ( edge->_nodes.back() ); - for ( size_t j = 0; j < edge->_simplices.size(); ++j ) - if ( !edge->_simplices[j].IsForward( edge->_nodes[0], &tgtXYZ, vol )) + dumpFunction(SMESH_Comment("smooth")<Smooth( step, isConcaveFace, false )) + badSmooEdges.push_back( eos._edges[i] ); + } + + else { + for ( int i = eos._edges.size()-1; i >= 0; --i ) // iterate backward + if ( eos._edges[i]->Smooth( step, isConcaveFace, false )) + badSmooEdges.push_back( eos._edges[i] ); + } + badNb = badSmooEdges.size(); + improved = ( badNb < oldBadNb ); + + if ( !badSmooEdges.empty() && step >= stepLimit / 2 ) + { + // look for the best smooth of _LayerEdge's neighboring badSmooEdges + vector<_Simplex> simplices; + for ( size_t i = 0; i < badSmooEdges.size(); ++i ) { - cout << "Bad simplex ( " << edge->_nodes[0]->GetID()<< " "<< tgtXYZ._node->GetID() - << " "<< edge->_simplices[j]._nPrev->GetID() - << " "<< edge->_simplices[j]._nNext->GetID() << " )" << endl; - return false; + _LayerEdge* ledge = badSmooEdges[i]; + _Simplex::GetSimplices( ledge->_nodes[0], simplices, data._ignoreFaceIds ); + for ( size_t iS = 0; iS < simplices.size(); ++iS ) + { + TNode2Edge::iterator n2e = data._n2eMap.find( simplices[iS]._nNext ); + if ( n2e != data._n2eMap.end()) { + _LayerEdge* ledge2 = n2e->second; + if ( ledge2->_nodes[0]->getshapeId() == sInd ) + ledge2->Smooth( step, isConcaveFace, /*findBest=*/true ); + } + } } + } + // issue 22576 -- no bad faces but still there are intersections to fix + // if ( improved && badNb == 0 ) + // stepLimit = step + 3; + + dumpFunctionEnd(); } + if ( badNb > 0 ) + { +#ifdef __myDEBUG + double vol = 0; + for ( int i = 0; i < eos._edges.size(); ++i ) + { + _LayerEdge* edge = eos._edges[i]; + SMESH_TNodeXYZ tgtXYZ( edge->_nodes.back() ); + for ( size_t j = 0; j < edge->_simplices.size(); ++j ) + if ( !edge->_simplices[j].IsForward( edge->_nodes[0], &tgtXYZ, vol )) + { + cout << "Bad simplex ( " << edge->_nodes[0]->GetID()<< " "<< tgtXYZ._node->GetID() + << " "<< edge->_simplices[j]._nPrev->GetID() + << " "<< edge->_simplices[j]._nNext->GetID() << " )" << endl; + return false; + } + } #endif - return false; - } - } - } // loop on shapes to smooth + return false; + } + } // // smooth on FACE's + } // loop on shapes + } // smooth on [ EDGEs, FACEs ] // Check orientation of simplices of _ConvexFace::_simplexTestEdges map< TGeomID, _ConvexFace >::iterator id2face = data._convexFaces.begin(); @@ -3749,37 +3938,41 @@ bool _ViscousBuilder::smoothAndCheck(_SolidData& data, double dist; const SMDS_MeshElement* intFace = 0; const SMDS_MeshElement* closestFace = 0; - int iLE = 0; - for ( size_t i = 0; i < data._edges.size(); ++i ) + _LayerEdge* le = 0; + for ( int iS = 0; iS < data._edgesOnShape.size(); ++iS ) { - if ( !data._edges[i]->_sWOL.IsNull() ) + _EdgesOnShape& eos = data._edgesOnShape[ iS ]; + if ( eos._edges.empty() || !eos._sWOL.IsNull() ) continue; - if ( data._edges[i]->FindIntersection( *searcher, dist, data._epsilon, &intFace )) - return false; - if ( distToIntersection > dist ) + for ( size_t i = 0; i < eos._edges.size(); ++i ) { - // ignore intersection of a _LayerEdge based on a _ConvexFace with a face - // lying on this _ConvexFace - if ( _ConvexFace* convFace = data.GetConvexFace( intFace->getshapeId() )) - if ( convFace->_subIdToEdgeEnd.count ( data._edges[i]->_nodes[0]->getshapeId() )) - continue; + if ( eos._edges[i]->FindIntersection( *searcher, dist, data._epsilon, eos, &intFace )) + return false; + if ( distToIntersection > dist ) + { + // ignore intersection of a _LayerEdge based on a _ConvexFace with a face + // lying on this _ConvexFace + if ( _ConvexFace* convFace = data.GetConvexFace( intFace->getshapeId() )) + if ( convFace->_subIdToEOS.count ( eos._shapeID )) + continue; - // ignore intersection of a _LayerEdge based on a FACE with an element on this FACE - // ( avoid limiting the thickness on the case of issue 22576) - if ( intFace->getshapeId() == data._edges[i]->_nodes[0]->getshapeId() ) - continue; + // ignore intersection of a _LayerEdge based on a FACE with an element on this FACE + // ( avoid limiting the thickness on the case of issue 22576) + if ( intFace->getshapeId() == eos._shapeID ) + continue; - distToIntersection = dist; - iLE = i; - closestFace = intFace; + distToIntersection = dist; + le = eos._edges[i]; + closestFace = intFace; + } } } #ifdef __myDEBUG if ( closestFace ) { SMDS_MeshElement::iterator nIt = closestFace->begin_nodes(); - cout << "Shortest distance: _LayerEdge nodes: tgt " << data._edges[iLE]->_nodes.back()->GetID() - << " src " << data._edges[iLE]->_nodes[0]->GetID()<< ", intersection with face (" + cout << "Shortest distance: _LayerEdge nodes: tgt " << le->_nodes.back()->GetID() + << " src " << le->_nodes[0]->GetID()<< ", intersection with face (" << (*nIt++)->GetID()<<" "<< (*nIt++)->GetID()<<" "<< (*nIt++)->GetID() << ") distance = " << distToIntersection<< endl; } @@ -3796,32 +3989,26 @@ bool _ViscousBuilder::smoothAndCheck(_SolidData& data, //================================================================================ Handle(Geom_Curve) _SolidData::CurveForSmooth( const TopoDS_Edge& E, - const int iFrom, - const int iTo, - const TopoDS_Face& F, - SMESH_MesherHelper& helper, - vector<_LayerEdge* >* edges) + _EdgesOnShape& eos, + SMESH_MesherHelper& helper) { - TGeomID eIndex = helper.GetMeshDS()->ShapeToIndex( E ); + const TGeomID eIndex = eos._shapeID; map< TGeomID, Handle(Geom_Curve)>::iterator i2curve = _edge2curve.find( eIndex ); if ( i2curve == _edge2curve.end() ) { - if ( edges ) - _edges.swap( *edges ); - // sort _LayerEdge's by position on the EDGE - SortOnEdge( E, iFrom, iTo, helper ); + SortOnEdge( E, eos._edges, helper ); - SMESHDS_SubMesh* smDS = helper.GetMeshDS()->MeshElements( eIndex ); + SMESHDS_SubMesh* smDS = eos._subMesh->GetSubMeshDS(); TopLoc_Location loc; double f,l; Handle(Geom_Line) line; Handle(Geom_Circle) circle; bool isLine, isCirc; - if ( F.IsNull() ) // 3D case + if ( eos._sWOL.IsNull() ) /////////////////////////////////////////// 3D case { // check if the EDGE is a line Handle(Geom_Curve) curve = BRep_Tool::Curve( E, loc, f, l); @@ -3835,35 +4022,39 @@ Handle(Geom_Curve) _SolidData::CurveForSmooth( const TopoDS_Edge& E, if ( !isLine && !isCirc ) // Check if the EDGE is close to a line { - Bnd_B3d bndBox; - SMDS_NodeIteratorPtr nIt = smDS->GetNodes(); - while ( nIt->more() ) - bndBox.Add( SMESH_TNodeXYZ( nIt->next() )); - gp_XYZ size = bndBox.CornerMax() - bndBox.CornerMin(); - - gp_Pnt p0, p1; - if ( iTo-iFrom > 1 ) { - p0 = SMESH_TNodeXYZ( _edges[iFrom]->_nodes[0] ); - p1 = SMESH_TNodeXYZ( _edges[iFrom+1]->_nodes[0] ); - } - else { - p0 = curve->Value( f ); - p1 = curve->Value( l ); - } - const double lineTol = 1e-2 * p0.Distance( p1 ); - for ( int i = 0; i < 3 && !isLine; ++i ) - isLine = ( size.Coord( i+1 ) <= lineTol ); + // Bnd_B3d bndBox; + // SMDS_NodeIteratorPtr nIt = smDS->GetNodes(); + // while ( nIt->more() ) + // bndBox.Add( SMESH_TNodeXYZ( nIt->next() )); + // gp_XYZ size = bndBox.CornerMax() - bndBox.CornerMin(); + + // gp_Pnt p0, p1; + // if ( eos._edges.size() > 1 ) { + // p0 = SMESH_TNodeXYZ( eos._edges[0]->_nodes[0] ); + // p1 = SMESH_TNodeXYZ( eos._edges[1]->_nodes[0] ); + // } + // else { + // p0 = curve->Value( f ); + // p1 = curve->Value( l ); + // } + // const double lineTol = 1e-2 * p0.Distance( p1 ); + // for ( int i = 0; i < 3 && !isLine; ++i ) + // isLine = ( size.Coord( i+1 ) <= lineTol ); ////////// <--- WRONG + + isLine = SMESH_Algo::IsStraight( E ); if ( isLine ) line = new Geom_Line( gp::OX() ); // only type does matter } - if ( !isLine && !isCirc && iTo-iFrom > 2) // Check if the EDGE is close to a circle + if ( !isLine && !isCirc && eos._edges.size() > 2) // Check if the EDGE is close to a circle { // TODO } } - else // 2D case + else //////////////////////////////////////////////////////////////////////// 2D case { + const TopoDS_Face& F = TopoDS::Face( eos._sWOL ); + // check if the EDGE is a line Handle(Geom2d_Curve) curve = BRep_Tool::CurveOnSurface( E, F, f, l); if ( curve->IsKind( STANDARD_TYPE( Geom2d_TrimmedCurve ))) @@ -3886,7 +4077,7 @@ Handle(Geom_Curve) _SolidData::CurveForSmooth( const TopoDS_Edge& E, for ( int i = 0; i < 2 && !isLine; ++i ) isLine = ( size.Coord( i+1 ) <= lineTol ); } - if ( !isLine && !isCirc && iTo-iFrom > 2) // Check if the EDGE is close to a circle + if ( !isLine && !isCirc && eos._edges.size() > 2) // Check if the EDGE is close to a circle { // TODO } @@ -3902,9 +4093,6 @@ Handle(Geom_Curve) _SolidData::CurveForSmooth( const TopoDS_Edge& E, } } - if ( edges ) - _edges.swap( *edges ); - Handle(Geom_Curve)& res = _edge2curve[ eIndex ]; if ( isLine ) res = line; @@ -3922,21 +4110,20 @@ Handle(Geom_Curve) _SolidData::CurveForSmooth( const TopoDS_Edge& E, */ //================================================================================ -void _SolidData::SortOnEdge( const TopoDS_Edge& E, - const int iFrom, - const int iTo, - SMESH_MesherHelper& helper) +void _SolidData::SortOnEdge( const TopoDS_Edge& E, + vector< _LayerEdge* >& edges, + SMESH_MesherHelper& helper) { map< double, _LayerEdge* > u2edge; - for ( int i = iFrom; i < iTo; ++i ) - u2edge.insert( make_pair( helper.GetNodeU( E, _edges[i]->_nodes[0] ), _edges[i] )); + for ( size_t i = 0; i < edges.size(); ++i ) + u2edge.insert( make_pair( helper.GetNodeU( E, edges[i]->_nodes[0] ), edges[i] )); - ASSERT( u2edge.size() == iTo - iFrom ); + ASSERT( u2edge.size() == edges.size() ); map< double, _LayerEdge* >::iterator u2e = u2edge.begin(); - for ( int i = iFrom; i < iTo; ++i, ++u2e ) - _edges[i] = u2e->second; + for ( int i = 0; i < edges.size(); ++i, ++u2e ) + edges[i] = u2e->second; - Sort2NeiborsOnEdge( iFrom, iTo ); + Sort2NeiborsOnEdge( edges ); } //================================================================================ @@ -3945,41 +4132,47 @@ void _SolidData::SortOnEdge( const TopoDS_Edge& E, */ //================================================================================ -void _SolidData::Sort2NeiborsOnEdge( const int iFrom, const int iTo) +void _SolidData::Sort2NeiborsOnEdge( vector< _LayerEdge* >& edges ) { - for ( int i = iFrom; i < iTo-1; ++i ) - if ( _edges[i]->_2neibors->tgtNode(1) != _edges[i+1]->_nodes.back() ) - _edges[i]->_2neibors->reverse(); - if ( iTo - iFrom > 1 && - _edges[iTo-1]->_2neibors->tgtNode(0) != _edges[iTo-2]->_nodes.back() ) - _edges[iTo-1]->_2neibors->reverse(); + for ( size_t i = 0; i < edges.size()-1; ++i ) + if ( edges[i]->_2neibors->tgtNode(1) != edges[i+1]->_nodes.back() ) + edges[i]->_2neibors->reverse(); + + const size_t iLast = edges.size() - 1; + if ( edges.size() > 1 && + edges[iLast]->_2neibors->tgtNode(0) != edges[iLast-1]->_nodes.back() ) + edges[iLast]->_2neibors->reverse(); } //================================================================================ /*! - * \brief Return index corresponding to the shape in _endEdgeOnShape + * \brief Return _EdgesOnShape* corresponding to the shape */ //================================================================================ -bool _SolidData::GetShapeEdges(const TGeomID shapeID, - size_t & iEdgesEnd, - int* iBeg, - int* iEnd ) const +_EdgesOnShape* _SolidData::GetShapeEdges(const TGeomID shapeID ) { - int beg = 0, end = 0; - for ( iEdgesEnd = 0; iEdgesEnd < _endEdgeOnShape.size(); ++iEdgesEnd ) - { - end = _endEdgeOnShape[ iEdgesEnd ]; - TGeomID sID = _edges[ beg ]->_nodes[0]->getshapeId(); - if ( sID == shapeID ) - { - if ( iBeg ) *iBeg = beg; - if ( iEnd ) *iEnd = end; - return true; - } - beg = end; - } - return false; + if ( shapeID < _edgesOnShape.size() && + _edgesOnShape[ shapeID ]._shapeID == shapeID ) + return & _edgesOnShape[ shapeID ]; + + for ( size_t i = 0; i < _edgesOnShape.size(); ++i ) + if ( _edgesOnShape[i]._shapeID == shapeID ) + return & _edgesOnShape[i]; + + return 0; +} + +//================================================================================ +/*! + * \brief Return _EdgesOnShape* corresponding to the shape + */ +//================================================================================ + +_EdgesOnShape* _SolidData::GetShapeEdges(const TopoDS_Shape& shape ) +{ + SMESHDS_Mesh* meshDS = _proxyMesh->GetMesh()->GetMeshDS(); + return GetShapeEdges( meshDS->ShapeToIndex( shape )); } //================================================================================ @@ -3988,22 +4181,19 @@ bool _SolidData::GetShapeEdges(const TGeomID shapeID, */ //================================================================================ -void _SolidData::PrepareEdgesToSmoothOnFace( _LayerEdge** edgeBeg, - _LayerEdge** edgeEnd, - const TopoDS_Face& face, - bool substituteSrcNodes ) +void _SolidData::PrepareEdgesToSmoothOnFace( _EdgesOnShape* eof, bool substituteSrcNodes ) { set< TGeomID > vertices; SMESH_MesherHelper helper( *_proxyMesh->GetMesh() ); - if ( isConcave( face, helper, &vertices )) - _concaveFaces.insert( (*edgeBeg)->_nodes[0]->getshapeId() ); + if ( isConcave( TopoDS::Face( eof->_shape ), helper, &vertices )) + _concaveFaces.insert( eof->_shapeID ); - for ( _LayerEdge** edge = edgeBeg; edge != edgeEnd; ++edge ) - (*edge)->_smooFunction = 0; + for ( size_t i = 0; i < eof->_edges.size(); ++i ) + eof->_edges[i]->_smooFunction = 0; - for ( ; edgeBeg != edgeEnd; ++edgeBeg ) + for ( size_t i = 0; i < eof->_edges.size(); ++i ) { - _LayerEdge* edge = *edgeBeg; + _LayerEdge* edge = eof->_edges[i]; _Simplex::GetSimplices ( edge->_nodes[0], edge->_simplices, _ignoreFaceIds, this, /*sort=*/true ); @@ -4035,72 +4225,20 @@ void _SolidData::PrepareEdgesToSmoothOnFace( _LayerEdge** edgeBeg, */ //================================================================================ -void _SolidData::AddShapesToSmooth( const set< TGeomID >& faceIDs ) +void _SolidData::AddShapesToSmooth( const set< _EdgesOnShape* >& eosSet ) { - // convert faceIDs to indices in _endEdgeOnShape - set< size_t > iEnds; - size_t end; - set< TGeomID >::const_iterator fId = faceIDs.begin(); - for ( ; fId != faceIDs.end(); ++fId ) - if ( GetShapeEdges( *fId, end ) && end >= _nbShapesToSmooth ) - iEnds.insert( end ); - - set< size_t >::iterator endsIt = iEnds.begin(); - - // "add" by move of _nbShapesToSmooth - int nbFacesToAdd = iEnds.size(); - while ( endsIt != iEnds.end() && *endsIt == _nbShapesToSmooth ) + set< _EdgesOnShape * >::const_iterator eos = eosSet.begin(); + for ( ; eos != eosSet.end(); ++eos ) { - ++endsIt; - ++_nbShapesToSmooth; - --nbFacesToAdd; - } - if ( endsIt == iEnds.end() ) - return; - - // Move _LayerEdge's on FACEs just after _nbShapesToSmooth + if ( !*eos || (*eos)->_toSmooth ) continue; - vector< _LayerEdge* > nonSmoothLE, smoothLE; - size_t lastSmooth = *iEnds.rbegin(); - int iBeg, iEnd; - for ( size_t i = _nbShapesToSmooth; i <= lastSmooth; ++i ) - { - bool toSmooth = iEnds.count(i); - vector< _LayerEdge* > & edgesVec = toSmooth ? smoothLE : nonSmoothLE; - iBeg = i ? _endEdgeOnShape[ i-1 ] : 0; - iEnd = _endEdgeOnShape[ i ]; - edgesVec.insert( edgesVec.end(), _edges.begin() + iBeg, _edges.begin() + iEnd ); + (*eos)->_toSmooth = true; - // preparation for smoothing on FACE - if ( toSmooth && _edges[iBeg]->_nodes[0]->GetPosition()->GetDim() == 2 ) + if ( (*eos)->ShapeType() == TopAbs_FACE ) { - TopoDS_Shape S = SMESH_MesherHelper::GetSubShapeByNode( _edges[iBeg]->_nodes[0], - _proxyMesh->GetMeshDS() ); - if ( !S.IsNull() && S.ShapeType() == TopAbs_FACE ) - { - PrepareEdgesToSmoothOnFace( &_edges[ iBeg ], - &_edges[ iEnd ], - TopoDS::Face( S ), - /*substituteSrcNodes=*/true ); - } + PrepareEdgesToSmoothOnFace( *eos, /*substituteSrcNodes=*/true ); } } - - iBeg = _nbShapesToSmooth ? _endEdgeOnShape[ _nbShapesToSmooth-1 ] : 0; - std::copy( smoothLE.begin(), smoothLE.end(), &_edges[ iBeg ] ); - std::copy( nonSmoothLE.begin(), nonSmoothLE.end(), &_edges[ iBeg + smoothLE.size()]); - - // update _endEdgeOnShape - for ( size_t i = _nbShapesToSmooth; i < _endEdgeOnShape.size(); ++i ) - { - TGeomID curShape = _edges[ iBeg ]->_nodes[0]->getshapeId(); - while ( ++iBeg < _edges.size() && - curShape == _edges[ iBeg ]->_nodes[0]->getshapeId() ); - - _endEdgeOnShape[ i ] = iBeg; - } - - _nbShapesToSmooth += nbFacesToAdd; } //================================================================================ @@ -4110,26 +4248,25 @@ void _SolidData::AddShapesToSmooth( const set< TGeomID >& faceIDs ) //================================================================================ bool _ViscousBuilder::smoothAnalyticEdge( _SolidData& data, - const int iFrom, - const int iTo, + _EdgesOnShape& eos, Handle(Geom_Surface)& surface, const TopoDS_Face& F, SMESH_MesherHelper& helper) { - TopoDS_Shape S = helper.GetSubShapeByNode( data._edges[ iFrom ]->_nodes[0], - helper.GetMeshDS()); - TopoDS_Edge E = TopoDS::Edge( S ); + const TopoDS_Edge& E = TopoDS::Edge( eos._shape ); - Handle(Geom_Curve) curve = data.CurveForSmooth( E, iFrom, iTo, F, helper ); + Handle(Geom_Curve) curve = data.CurveForSmooth( E, eos, helper ); if ( curve.IsNull() ) return false; + const size_t iFrom = 0, iTo = eos._edges.size(); + // compute a relative length of segments vector< double > len( iTo-iFrom+1 ); { double curLen, prevLen = len[0] = 1.0; for ( int i = iFrom; i < iTo; ++i ) { - curLen = prevLen * data._edges[i]->_2neibors->_wgt[0] / data._edges[i]->_2neibors->_wgt[1]; + curLen = prevLen * eos._edges[i]->_2neibors->_wgt[0] / eos._edges[i]->_2neibors->_wgt[1]; len[i-iFrom+1] = len[i-iFrom] + curLen; prevLen = curLen; } @@ -4139,26 +4276,28 @@ bool _ViscousBuilder::smoothAnalyticEdge( _SolidData& data, { if ( F.IsNull() ) // 3D { - SMESH_TNodeXYZ p0( data._edges[iFrom]->_2neibors->tgtNode(0)); - SMESH_TNodeXYZ p1( data._edges[iTo-1]->_2neibors->tgtNode(1)); + SMESH_TNodeXYZ p0( eos._edges[iFrom]->_2neibors->tgtNode(0)); + SMESH_TNodeXYZ p1( eos._edges[iTo-1]->_2neibors->tgtNode(1)); for ( int i = iFrom; i < iTo; ++i ) { double r = len[i-iFrom] / len.back(); gp_XYZ newPos = p0 * ( 1. - r ) + p1 * r; - data._edges[i]->_pos.back() = newPos; - SMDS_MeshNode* tgtNode = const_cast( data._edges[i]->_nodes.back() ); + eos._edges[i]->_pos.back() = newPos; + SMDS_MeshNode* tgtNode = const_cast( eos._edges[i]->_nodes.back() ); tgtNode->setXYZ( newPos.X(), newPos.Y(), newPos.Z() ); dumpMove( tgtNode ); } } else { - // gp_XY uv0 = helper.GetNodeUV( F, data._edges[iFrom]->_2neibors->tgtNode(0)); - // gp_XY uv1 = helper.GetNodeUV( F, data._edges[iTo-1]->_2neibors->tgtNode(1)); - gp_XY uv0 = data._edges[iFrom]->_2neibors->_edges[0]->LastUV( F ); - gp_XY uv1 = data._edges[iTo-1]->_2neibors->_edges[1]->LastUV( F ); - if ( data._edges[iFrom]->_2neibors->tgtNode(0) == - data._edges[iTo-1]->_2neibors->tgtNode(1) ) // closed edge + // gp_XY uv0 = helper.GetNodeUV( F, eos._edges[iFrom]->_2neibors->tgtNode(0)); + // gp_XY uv1 = helper.GetNodeUV( F, eos._edges[iTo-1]->_2neibors->tgtNode(1)); + _LayerEdge* e0 = eos._edges[iFrom]->_2neibors->_edges[0]; + _LayerEdge* e1 = eos._edges[iTo-1]->_2neibors->_edges[1]; + gp_XY uv0 = e0->LastUV( F, *data.GetShapeEdges( e0 )); + gp_XY uv1 = e1->LastUV( F, *data.GetShapeEdges( e1 )); + if ( eos._edges[iFrom]->_2neibors->tgtNode(0) == + eos._edges[iTo-1]->_2neibors->tgtNode(1) ) // closed edge { int iPeriodic = helper.GetPeriodicIndex(); if ( iPeriodic == 1 || iPeriodic == 2 ) @@ -4173,10 +4312,10 @@ bool _ViscousBuilder::smoothAnalyticEdge( _SolidData& data, { double r = len[i-iFrom] / len.back(); gp_XY newUV = uv0 + r * rangeUV; - data._edges[i]->_pos.back().SetCoord( newUV.X(), newUV.Y(), 0 ); + eos._edges[i]->_pos.back().SetCoord( newUV.X(), newUV.Y(), 0 ); gp_Pnt newPos = surface->Value( newUV.X(), newUV.Y() ); - SMDS_MeshNode* tgtNode = const_cast( data._edges[i]->_nodes.back() ); + SMDS_MeshNode* tgtNode = const_cast( eos._edges[i]->_nodes.back() ); tgtNode->setXYZ( newPos.X(), newPos.Y(), newPos.Z() ); dumpMove( tgtNode ); @@ -4195,8 +4334,8 @@ bool _ViscousBuilder::smoothAnalyticEdge( _SolidData& data, if ( F.IsNull() ) // 3D { - if ( data._edges[iFrom]->_2neibors->tgtNode(0) == - data._edges[iTo-1]->_2neibors->tgtNode(1) ) + if ( eos._edges[iFrom]->_2neibors->tgtNode(0) == + eos._edges[iTo-1]->_2neibors->tgtNode(1) ) return true; // closed EDGE - nothing to do return false; // TODO ??? @@ -4205,12 +4344,12 @@ bool _ViscousBuilder::smoothAnalyticEdge( _SolidData& data, { const gp_XY center( center3D.X(), center3D.Y() ); - gp_XY uv0 = data._edges[iFrom]->_2neibors->_edges[0]->LastUV( F ); - gp_XY uvM = data._edges[iFrom]->LastUV( F ); - gp_XY uv1 = data._edges[iTo-1]->_2neibors->_edges[1]->LastUV( F ); - // gp_XY uv0 = helper.GetNodeUV( F, data._edges[iFrom]->_2neibors->tgtNode(0)); - // gp_XY uvM = helper.GetNodeUV( F, data._edges[iFrom]->_nodes.back()); - // gp_XY uv1 = helper.GetNodeUV( F, data._edges[iTo-1]->_2neibors->tgtNode(1)); + _LayerEdge* e0 = eos._edges[iFrom]->_2neibors->_edges[0]; + _LayerEdge* eM = eos._edges[iFrom]; + _LayerEdge* e1 = eos._edges[iTo-1]->_2neibors->_edges[1]; + gp_XY uv0 = e0->LastUV( F, *data.GetShapeEdges( e0 ) ); + gp_XY uvM = eM->LastUV( F, *data.GetShapeEdges( eM ) ); + gp_XY uv1 = e1->LastUV( F, *data.GetShapeEdges( e1 ) ); gp_Vec2d vec0( center, uv0 ); gp_Vec2d vecM( center, uvM ); gp_Vec2d vec1( center, uv1 ); @@ -4226,10 +4365,10 @@ bool _ViscousBuilder::smoothAnalyticEdge( _SolidData& data, { double newU = uLast * len[i-iFrom] / len.back(); gp_Pnt2d newUV = ElCLib::Value( newU, circ ); - data._edges[i]->_pos.back().SetCoord( newUV.X(), newUV.Y(), 0 ); + eos._edges[i]->_pos.back().SetCoord( newUV.X(), newUV.Y(), 0 ); gp_Pnt newPos = surface->Value( newUV.X(), newUV.Y() ); - SMDS_MeshNode* tgtNode = const_cast( data._edges[i]->_nodes.back() ); + SMDS_MeshNode* tgtNode = const_cast( eos._edges[i]->_nodes.back() ); tgtNode->setXYZ( newPos.X(), newPos.Y(), newPos.Z() ); dumpMove( tgtNode ); @@ -4267,30 +4406,35 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, vector< const SMDS_MeshNode*> nodes(4); // of a tmp mesh face dumpFunction(SMESH_Comment("makeTmpFacesOnEdges")<IsOnEdge() || !edge->_sWOL.IsNull() ) continue; - const SMDS_MeshNode* tgt1 = edge->_nodes.back(); - for ( int j = 0; j < 2; ++j ) // loop on _2NearEdges + _EdgesOnShape& eos = data._edgesOnShape[ iS ]; + if ( eos.ShapeType() != TopAbs_EDGE || !eos._sWOL.IsNull() ) + continue; + for ( size_t i = 0; i < eos._edges.size(); ++i ) { - const SMDS_MeshNode* tgt2 = edge->_2neibors->tgtNode(j); - pair< set< SMESH_TLink >::iterator, bool > link_isnew = - extrudedLinks.insert( SMESH_TLink( tgt1, tgt2 )); - if ( !link_isnew.second ) + _LayerEdge* edge = eos._edges[i]; + const SMDS_MeshNode* tgt1 = edge->_nodes.back(); + for ( int j = 0; j < 2; ++j ) // loop on _2NearEdges { - extrudedLinks.erase( link_isnew.first ); - continue; // already extruded and will no more encounter - } - // a _LayerEdge containg tgt2 - _LayerEdge* neiborEdge = edge->_2neibors->_edges[j]; + const SMDS_MeshNode* tgt2 = edge->_2neibors->tgtNode(j); + pair< set< SMESH_TLink >::iterator, bool > link_isnew = + extrudedLinks.insert( SMESH_TLink( tgt1, tgt2 )); + if ( !link_isnew.second ) + { + extrudedLinks.erase( link_isnew.first ); + continue; // already extruded and will no more encounter + } + // a _LayerEdge containg tgt2 + _LayerEdge* neiborEdge = edge->_2neibors->_edges[j]; - _TmpMeshFaceOnEdge* f = new _TmpMeshFaceOnEdge( edge, neiborEdge, --_tmpFaceID ); - tmpFaces.push_back( f ); + _TmpMeshFaceOnEdge* f = new _TmpMeshFaceOnEdge( edge, neiborEdge, --_tmpFaceID ); + tmpFaces.push_back( f ); - dumpCmd(SMESH_Comment("mesh.AddFace([ ") - <_nn[0]->GetID()<<", "<_nn[1]->GetID()<<", " - <_nn[2]->GetID()<<", "<_nn[3]->GetID()<<" ])"); + dumpCmd(SMESH_Comment("mesh.AddFace([ ") + <_nn[0]->GetID()<<", "<_nn[1]->GetID()<<", " + <_nn[2]->GetID()<<", "<_nn[3]->GetID()<<" ])"); + } } } dumpFunctionEnd(); @@ -4312,22 +4456,26 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, TLEdge2LEdgeSet edge2CloseEdge; const double eps = data._epsilon * data._epsilon; - for ( size_t i = 0; i < data._edges.size(); ++i ) + for ( size_t iS = 0; iS < data._edgesOnShape.size(); ++iS ) { - _LayerEdge* edge = data._edges[i]; - if (( !edge->IsOnEdge() ) && - ( edge->_sWOL.IsNull() || edge->_sWOL.ShapeType() != TopAbs_FACE )) + _EdgesOnShape& eos = data._edgesOnShape[ iS ]; + if (( eos.ShapeType() != TopAbs_EDGE ) && + ( eos._sWOL.IsNull() || eos.SWOLType() != TopAbs_FACE )) continue; - if ( edge->FindIntersection( *searcher, dist, eps, &face )) + for ( size_t i = 0; i < eos._edges.size(); ++i ) { - const _TmpMeshFaceOnEdge* f = (const _TmpMeshFaceOnEdge*) face; - set< _LayerEdge*, _LayerEdgeCmp > & ee = edge2CloseEdge[ edge ]; - ee.insert( f->_le1 ); - ee.insert( f->_le2 ); - if ( f->_le1->IsOnEdge() && f->_le1->_sWOL.IsNull() ) - edge2CloseEdge[ f->_le1 ].insert( edge ); - if ( f->_le2->IsOnEdge() && f->_le2->_sWOL.IsNull() ) - edge2CloseEdge[ f->_le2 ].insert( edge ); + _LayerEdge* edge = eos._edges[i]; + if ( edge->FindIntersection( *searcher, dist, eps, eos, &face )) + { + const _TmpMeshFaceOnEdge* f = (const _TmpMeshFaceOnEdge*) face; + set< _LayerEdge*, _LayerEdgeCmp > & ee = edge2CloseEdge[ edge ]; + ee.insert( f->_le1 ); + ee.insert( f->_le2 ); + if ( f->_le1->IsOnEdge() && data.GetShapeEdges( f->_le1 )->_sWOL.IsNull() ) + edge2CloseEdge[ f->_le1 ].insert( edge ); + if ( f->_le2->IsOnEdge() && data.GetShapeEdges( f->_le2 )->_sWOL.IsNull() ) + edge2CloseEdge[ f->_le2 ].insert( edge ); + } } } @@ -4337,7 +4485,7 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, { dumpFunction(SMESH_Comment("updateNormals")< shapesToSmooth; + set< _EdgesOnShape* > shapesToSmooth; // vector to store new _normal and _cosin for each edge in edge2CloseEdge vector< pair< _LayerEdge*, _LayerEdge > > edge2newEdge( edge2CloseEdge.size() ); @@ -4351,6 +4499,9 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, edge2newEdge[ iE ].first = NULL; + _EdgesOnShape* eos1 = data.GetShapeEdges( edge1 ); + if ( !eos1 ) continue; + // find EDGEs the edges reside // TopoDS_Edge E1, E2; // TopoDS_Shape S = helper.GetSubShapeByNode( edge1->_nodes[0], getMeshDS() ); @@ -4360,7 +4511,7 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, set< _LayerEdge*, _LayerEdgeCmp >::iterator eIt = ee.begin(); for ( ; !edge2 && eIt != ee.end(); ++eIt ) { - if ( edge1->_sWOL == (*eIt)->_sWOL ) + if ( eos1->_sWOL == data.GetShapeEdges( *eIt )->_sWOL ) edge2 = *eIt; } if ( !edge2 ) continue; @@ -4445,11 +4596,11 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, if ( edge1->_cosin < theMinSmoothCosin && newEdge._cosin > theMinSmoothCosin ) { - if ( edge1->_sWOL.IsNull() ) + if ( eos1->_sWOL.IsNull() ) { SMDS_ElemIteratorPtr fIt = edge1->_nodes[0]->GetInverseElementIterator(SMDSAbs_Face); while ( fIt->more() ) - shapesToSmooth.insert( fIt->next()->getshapeId() ); + shapesToSmooth.insert( data.GetShapeEdges( fIt->next()->getshapeId() )); //limitStepSize( data, fIt->next(), edge1->_cosin ); // too late } else // edge1 inflates along a FACE @@ -4458,12 +4609,12 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, PShapeIteratorPtr eIt = helper.GetAncestors( V, *_mesh, TopAbs_EDGE ); while ( const TopoDS_Shape* E = eIt->next() ) { - if ( !helper.IsSubShape( *E, /*FACE=*/edge1->_sWOL )) + if ( !helper.IsSubShape( *E, /*FACE=*/eos1->_sWOL )) continue; gp_Vec edgeDir = getEdgeDir( TopoDS::Edge( *E ), TopoDS::Vertex( V )); double angle = edgeDir.Angle( newEdge._normal ); // [0,PI] if ( angle < M_PI / 2 ) - shapesToSmooth.insert( getMeshDS()->ShapeToIndex( *E )); + shapesToSmooth.insert( data.GetShapeEdges( *E )); } } } @@ -4478,17 +4629,19 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, _LayerEdge* edge1 = edge2newEdge[ iE ].first; _LayerEdge& newEdge = edge2newEdge[ iE ].second; if ( !edge1 ) continue; + _EdgesOnShape* eos1 = data.GetShapeEdges( edge1 ); + if ( !eos1 ) continue; edge1->_normal = newEdge._normal; edge1->SetCosin( newEdge._cosin ); - edge1->InvalidateStep( 1 ); + edge1->InvalidateStep( 1, *eos1 ); edge1->_len = 0; - edge1->SetNewLength( data._stepSize, helper ); + edge1->SetNewLength( data._stepSize, *eos1, helper ); if ( edge1->IsOnEdge() ) { const SMDS_MeshNode * n1 = edge1->_2neibors->srcNode(0); const SMDS_MeshNode * n2 = edge1->_2neibors->srcNode(1); - edge1->SetDataByNeighbors( n1, n2, helper ); + edge1->SetDataByNeighbors( n1, n2, *eos1, helper ); } // Update normals and other dependent data of not intersecting _LayerEdge's @@ -4501,6 +4654,8 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, _LayerEdge* neighbor = edge1->_2neibors->_edges[j]; if ( edge2CloseEdge.count ( neighbor )) continue; // j-th neighbor is also intersected + _EdgesOnShape* eos = data.GetShapeEdges( neighbor ); + if ( !eos ) continue; _LayerEdge* prevEdge = edge1; const int nbSteps = 10; for ( int step = nbSteps; step; --step ) // step from edge1 in j-th direction @@ -4520,11 +4675,11 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, neighbor->_normal = newNorm; neighbor->SetCosin( prevEdge->_cosin * r + nextEdge->_cosin * (1-r) ); - neighbor->SetDataByNeighbors( prevEdge->_nodes[0], nextEdge->_nodes[0], helper ); + neighbor->SetDataByNeighbors( prevEdge->_nodes[0], nextEdge->_nodes[0], *eos, helper ); - neighbor->InvalidateStep( 1 ); + neighbor->InvalidateStep( 1, *eos ); neighbor->_len = 0; - neighbor->SetNewLength( data._stepSize, helper ); + neighbor->SetNewLength( data._stepSize, *eos, helper ); // goto the next neighbor prevEdge = neighbor; @@ -4575,21 +4730,19 @@ bool _ViscousBuilder::updateNormalsOfConvexFaces( _SolidData& data, Bnd_B3d centersBox; // bbox of centers of curvature of _LayerEdge's on VERTEXes Bnd_B3d nodesBox; gp_Pnt center; - int iBeg, iEnd; - map< TGeomID, int >::iterator id2end = convFace._subIdToEdgeEnd.begin(); - for ( ; id2end != convFace._subIdToEdgeEnd.end(); ++id2end ) + map< TGeomID, _EdgesOnShape* >::iterator id2oes = convFace._subIdToEOS.begin(); + for ( ; id2oes != convFace._subIdToEOS.end(); ++id2oes ) { - data.GetEdgesOnShape( id2end->second, iBeg, iEnd ); - - if ( meshDS->IndexToShape( id2end->first ).ShapeType() == TopAbs_VERTEX ) + _EdgesOnShape& eos = *(id2oes->second); + if ( eos.ShapeType() == TopAbs_VERTEX ) { - _LayerEdge* ledge = data._edges[ iBeg ]; + _LayerEdge* ledge = eos._edges[ 0 ]; if ( convFace.GetCenterOfCurvature( ledge, surfProp, helper, center )) centersBox.Add( center ); } - for ( ; iBeg < iEnd; ++iBeg ) - nodesBox.Add( SMESH_TNodeXYZ( data._edges[ iBeg ]->_nodes[0] )); + for ( size_t i = 0; i < eos._edges.size(); ++i ) + nodesBox.Add( SMESH_TNodeXYZ( eos._edges[ i ]->_nodes[0] )); } if ( centersBox.IsVoid() ) { @@ -4611,25 +4764,24 @@ bool _ViscousBuilder::updateNormalsOfConvexFaces( _SolidData& data, gp_XYZ avgNormal( 0,0,0 ); nbEdges = 0; - id2end = convFace._subIdToEdgeEnd.begin(); - for ( ; id2end != convFace._subIdToEdgeEnd.end(); ++id2end ) + id2oes = convFace._subIdToEOS.begin(); + for ( ; id2oes != convFace._subIdToEOS.end(); ++id2oes ) { - data.GetEdgesOnShape( id2end->second, iBeg, iEnd ); + _EdgesOnShape& eos = *(id2oes->second); // set data of _CentralCurveOnEdge - const TopoDS_Shape& S = meshDS->IndexToShape( id2end->first ); - if ( S.ShapeType() == TopAbs_EDGE ) + if ( eos.ShapeType() == TopAbs_EDGE ) { _CentralCurveOnEdge& ceCurve = centerCurves[ nbEdges++ ]; - ceCurve.SetShapes( TopoDS::Edge(S), convFace, data, helper ); - if ( !data._edges[ iBeg ]->_sWOL.IsNull() ) + ceCurve.SetShapes( TopoDS::Edge( eos._shape ), convFace, data, helper ); + if ( !eos._sWOL.IsNull() ) ceCurve._adjFace.Nullify(); else ceCurve._ledges.insert( ceCurve._ledges.end(), - &data._edges[ iBeg ], &data._edges[ iEnd ]); + eos._edges.begin(), eos._edges.end()); } // summarize normals - for ( ; iBeg < iEnd; ++iBeg ) - avgNormal += data._edges[ iBeg ]->_normal; + for ( size_t i = 0; i < eos._edges.size(); ++i ) + avgNormal += eos._edges[ i ]->_normal; } double normSize = avgNormal.SquareModulus(); if ( normSize < 1e-200 ) @@ -4665,17 +4817,16 @@ bool _ViscousBuilder::updateNormalsOfConvexFaces( _SolidData& data, avgCosin /= nbCosin; // set _LayerEdge::_normal = avgNormal - id2end = convFace._subIdToEdgeEnd.begin(); - for ( ; id2end != convFace._subIdToEdgeEnd.end(); ++id2end ) + id2oes = convFace._subIdToEOS.begin(); + for ( ; id2oes != convFace._subIdToEOS.end(); ++id2oes ) { - data.GetEdgesOnShape( id2end->second, iBeg, iEnd ); - const TopoDS_Shape& S = meshDS->IndexToShape( id2end->first ); - if ( S.ShapeType() != TopAbs_EDGE ) - for ( int i = iBeg; i < iEnd; ++i ) - data._edges[ i ]->_cosin = avgCosin; + _EdgesOnShape& eos = *(id2oes->second); + if ( eos.ShapeType() != TopAbs_EDGE ) + for ( size_t i = 0; i < eos._edges.size(); ++i ) + eos._edges[ i ]->_cosin = avgCosin; - for ( ; iBeg < iEnd; ++iBeg ) - data._edges[ iBeg ]->_normal = avgNormal; + for ( size_t i = 0; i < eos._edges.size(); ++i ) + eos._edges[ i ]->_normal = avgNormal; } } else // if ( isSpherical ) @@ -4700,17 +4851,16 @@ bool _ViscousBuilder::updateNormalsOfConvexFaces( _SolidData& data, // get _LayerEdge's of the EDGE TGeomID edgeID = meshDS->ShapeToIndex( edge ); - id2end = convFace._subIdToEdgeEnd.find( edgeID ); - if ( id2end == convFace._subIdToEdgeEnd.end() ) + _EdgesOnShape* eos = data.GetShapeEdges( edgeID ); + if ( !eos || eos->_edges.empty() ) { // no _LayerEdge's on EDGE, use _LayerEdge's on VERTEXes for ( int iV = 0; iV < 2; ++iV ) { TopoDS_Vertex v = helper.IthVertex( iV, edge ); TGeomID vID = meshDS->ShapeToIndex( v ); - int end = convFace._subIdToEdgeEnd[ vID ]; - int iBeg = end > 0 ? data._endEdgeOnShape[ end-1 ] : 0; - vertexLEdges[ iV ] = data._edges[ iBeg ]; + eos = data.GetShapeEdges( vID ); + vertexLEdges[ iV ] = eos->_edges[ 0 ]; } edgeLEdge = &vertexLEdges[0]; edgeLEdgeEnd = edgeLEdge + 2; @@ -4719,15 +4869,14 @@ bool _ViscousBuilder::updateNormalsOfConvexFaces( _SolidData& data, } else { - data.GetEdgesOnShape( id2end->second, iBeg, iEnd ); - if ( id2end->second >= data._nbShapesToSmooth ) - data.SortOnEdge( edge, iBeg, iEnd, helper ); - edgeLEdge = &data._edges[ iBeg ]; - edgeLEdgeEnd = edgeLEdge + iEnd - iBeg; - vertexLEdges[0] = data._edges[ iBeg ]->_2neibors->_edges[0]; - vertexLEdges[1] = data._edges[ iEnd-1 ]->_2neibors->_edges[1]; - - if ( ! data._edges[ iBeg ]->_sWOL.IsNull() ) + if ( ! eos->_toSmooth ) + data.SortOnEdge( edge, eos->_edges, helper ); + edgeLEdge = &eos->_edges[ 0 ]; + edgeLEdgeEnd = edgeLEdge + eos->_edges.size(); + vertexLEdges[0] = eos->_edges.front()->_2neibors->_edges[0]; + vertexLEdges[1] = eos->_edges.back() ->_2neibors->_edges[1]; + + if ( ! eos->_sWOL.IsNull() ) centerCurves[ iE ]._adjFace.Nullify(); } @@ -4829,15 +4978,15 @@ bool _ViscousBuilder::updateNormalsOfConvexFaces( _SolidData& data, if ( nbCosin > 0 ) avgCosin /= nbCosin; const TGeomID faceID = meshDS->ShapeToIndex( convFace._face ); - map< TGeomID, int >::iterator id2end = convFace._subIdToEdgeEnd.find( faceID ); - if ( id2end != convFace._subIdToEdgeEnd.end() ) + map< TGeomID, _EdgesOnShape* >::iterator id2oes = convFace._subIdToEOS.find( faceID ); + if ( id2oes != convFace._subIdToEOS.end() ) { int iE = 0; gp_XYZ newNorm; - data.GetEdgesOnShape( id2end->second, iBeg, iEnd ); - for ( ; iBeg < iEnd; ++iBeg ) + _EdgesOnShape& eos = * ( id2oes->second ); + for ( size_t i = 0; i < eos._edges.size(); ++i ) { - _LayerEdge* ledge = data._edges[ iBeg ]; + _LayerEdge* ledge = eos._edges[ i ]; if ( !convFace.GetCenterOfCurvature( ledge, surfProp, helper, center )) continue; for ( size_t i = 0; i < centerCurves.size(); ++i, ++iE ) @@ -4863,17 +5012,17 @@ bool _ViscousBuilder::updateNormalsOfConvexFaces( _SolidData& data, dumpFunction(SMESH_Comment("updateNormalsOfConvexFaces")<ShapeToIndex( convFace._face )); - id2end = convFace._subIdToEdgeEnd.begin(); - for ( ; id2end != convFace._subIdToEdgeEnd.end(); ++id2end ) + id2oes = convFace._subIdToEOS.begin(); + for ( ; id2oes != convFace._subIdToEOS.end(); ++id2oes ) { - data.GetEdgesOnShape( id2end->second, iBeg, iEnd ); - for ( ; iBeg < iEnd; ++iBeg ) + _EdgesOnShape& eos = * ( id2oes->second ); + for ( size_t i = 0; i < eos._edges.size(); ++i ) { - _LayerEdge* & ledge = data._edges[ iBeg ]; + _LayerEdge* & ledge = eos._edges[ i ]; double len = ledge->_len; - ledge->InvalidateStep( stepNb + 1, /*restoreLength=*/true ); + ledge->InvalidateStep( stepNb + 1, eos, /*restoreLength=*/true ); ledge->SetCosin( ledge->_cosin ); - ledge->SetNewLength( len, helper ); + ledge->SetNewLength( len, eos, helper ); } } // loop on sub-shapes of convFace._face @@ -4881,7 +5030,7 @@ bool _ViscousBuilder::updateNormalsOfConvexFaces( _SolidData& data, // Find FACEs adjacent to convFace._face that got necessity to smooth // as a result of normals modification - set< TGeomID > adjFacesToSmooth; + set< _EdgesOnShape* > adjFacesToSmooth; for ( size_t iE = 0; iE < centerCurves.size(); ++iE ) { if ( centerCurves[ iE ]._adjFace.IsNull() || @@ -4891,7 +5040,7 @@ bool _ViscousBuilder::updateNormalsOfConvexFaces( _SolidData& data, { if ( centerCurves[ iE ]._ledges[ iLE ]->_cosin > theMinSmoothCosin ) { - adjFacesToSmooth.insert( meshDS->ShapeToIndex( centerCurves[ iE ]._adjFace )); + adjFacesToSmooth.insert( data.GetShapeEdges( centerCurves[ iE ]._adjFace )); break; } } @@ -5013,7 +5162,7 @@ bool _CentralCurveOnEdge::FindNewNormal( const gp_Pnt& center, gp_XYZ& newNormal void _CentralCurveOnEdge::SetShapes( const TopoDS_Edge& edge, const _ConvexFace& convFace, - const _SolidData& data, + _SolidData& data, SMESH_MesherHelper& helper) { _edge = edge; @@ -5025,10 +5174,8 @@ void _CentralCurveOnEdge::SetShapes( const TopoDS_Edge& edge, _adjFace = TopoDS::Face( *F ); _adjFaceToSmooth = false; // _adjFace already in a smoothing queue ? - size_t end; - TGeomID adjFaceID = helper.GetMeshDS()->ShapeToIndex( *F ); - if ( data.GetShapeEdges( adjFaceID, end )) - _adjFaceToSmooth = ( end < data._nbShapesToSmooth ); + if ( _EdgesOnShape* eos = data.GetShapeEdges( _adjFace )) + _adjFaceToSmooth = eos->_toSmooth; break; } } @@ -5043,11 +5190,12 @@ void _CentralCurveOnEdge::SetShapes( const TopoDS_Edge& edge, bool _LayerEdge::FindIntersection( SMESH_ElementSearcher& searcher, double & distance, const double& epsilon, + _EdgesOnShape& eos, const SMDS_MeshElement** face) { vector< const SMDS_MeshElement* > suspectFaces; double segLen; - gp_Ax1 lastSegment = LastSegment(segLen); + gp_Ax1 lastSegment = LastSegment( segLen, eos ); searcher.GetElementsNearLine( lastSegment, SMDSAbs_Face, suspectFaces ); bool segmentIntersected = false; @@ -5113,7 +5261,7 @@ bool _LayerEdge::FindIntersection( SMESH_ElementSearcher& searcher, */ //================================================================================ -gp_Ax1 _LayerEdge::LastSegment(double& segLen) const +gp_Ax1 _LayerEdge::LastSegment(double& segLen, _EdgesOnShape& eos) const { // find two non-coincident positions gp_XYZ orig = _pos.back(); @@ -5140,18 +5288,18 @@ gp_Ax1 _LayerEdge::LastSegment(double& segLen) const else { gp_Pnt pPrev = _pos[ iPrev ]; - if ( !_sWOL.IsNull() ) + if ( !eos._sWOL.IsNull() ) { TopLoc_Location loc; - if ( _sWOL.ShapeType() == TopAbs_EDGE ) + if ( eos.SWOLType() == TopAbs_EDGE ) { double f,l; - Handle(Geom_Curve) curve = BRep_Tool::Curve( TopoDS::Edge( _sWOL ), loc, f,l); + Handle(Geom_Curve) curve = BRep_Tool::Curve( TopoDS::Edge( eos._sWOL ), loc, f,l); pPrev = curve->Value( pPrev.X() ).Transformed( loc ); } else { - Handle(Geom_Surface) surface = BRep_Tool::Surface( TopoDS::Face(_sWOL), loc ); + Handle(Geom_Surface) surface = BRep_Tool::Surface( TopoDS::Face( eos._sWOL ), loc ); pPrev = surface->Value( pPrev.X(), pPrev.Y() ).Transformed( loc ); } dir = SMESH_TNodeXYZ( _nodes.back() ) - pPrev.XYZ(); @@ -5172,17 +5320,17 @@ gp_Ax1 _LayerEdge::LastSegment(double& segLen) const */ //================================================================================ -gp_XY _LayerEdge::LastUV( const TopoDS_Face& F ) const +gp_XY _LayerEdge::LastUV( const TopoDS_Face& F, _EdgesOnShape& eos ) const { - if ( F.IsSame( _sWOL )) // F is my FACE + if ( F.IsSame( eos._sWOL )) // F is my FACE return gp_XY( _pos.back().X(), _pos.back().Y() ); - if ( _sWOL.IsNull() || _sWOL.ShapeType() != TopAbs_EDGE ) // wrong call + if ( eos.SWOLType() != TopAbs_EDGE ) // wrong call return gp_XY( 1e100, 1e100 ); // _sWOL is EDGE of F; _pos.back().X() is the last U on the EDGE double f, l, u = _pos.back().X(); - Handle(Geom2d_Curve) C2d = BRep_Tool::CurveOnSurface( TopoDS::Edge(_sWOL), F, f,l); + Handle(Geom2d_Curve) C2d = BRep_Tool::CurveOnSurface( TopoDS::Edge(eos._sWOL), F, f,l); if ( !C2d.IsNull() && f <= u && u <= l ) return C2d->Value( u ).XY(); @@ -5884,7 +6032,7 @@ gp_XYZ _LayerEdge::smoothNefPolygon() */ //================================================================================ -void _LayerEdge::SetNewLength( double len, SMESH_MesherHelper& helper ) +void _LayerEdge::SetNewLength( double len, _EdgesOnShape& eos, SMESH_MesherHelper& helper ) { if ( _len - len > -1e-6 ) { @@ -5893,19 +6041,48 @@ void _LayerEdge::SetNewLength( double len, SMESH_MesherHelper& helper ) } SMDS_MeshNode* n = const_cast< SMDS_MeshNode*>( _nodes.back() ); - SMESH_TNodeXYZ oldXYZ( n ); - gp_XYZ nXYZ = oldXYZ + _normal * ( len - _len ) * _lenFactor; - n->setXYZ( nXYZ.X(), nXYZ.Y(), nXYZ.Z() ); + gp_XYZ oldXYZ = SMESH_TNodeXYZ( n ); + gp_XYZ newXYZ; + if ( eos._hyp.IsOffsetMethod() ) + { + newXYZ = oldXYZ; + gp_Vec faceNorm; + SMDS_ElemIteratorPtr faceIt = _nodes[0]->GetInverseElementIterator( SMDSAbs_Face ); + while ( faceIt->more() ) + { + const SMDS_MeshElement* face = faceIt->next(); + if ( !eos.GetNormal( face, faceNorm )) + continue; - _pos.push_back( nXYZ ); + // translate plane of a face + gp_XYZ baryCenter = oldXYZ + faceNorm.XYZ() * ( len - _len ); + + // find point of intersection of the face plane located at baryCenter + // and _normal located at newXYZ + double d = -( faceNorm.XYZ() * baryCenter ); // d of plane equation ax+by+cz+d=0 + double dot = ( faceNorm.XYZ() * _normal ); + if ( dot < std::numeric_limits::min() ) + dot = ( len - _len ) * 1e-3; + double step = -( faceNorm.XYZ() * newXYZ + d ) / dot; + newXYZ += step * _normal; + } + } + else + { + newXYZ = oldXYZ + _normal * ( len - _len ) * _lenFactor; + } + n->setXYZ( newXYZ.X(), newXYZ.Y(), newXYZ.Z() ); + + _pos.push_back( newXYZ ); _len = len; - if ( !_sWOL.IsNull() ) + + if ( !eos._sWOL.IsNull() ) { double distXYZ[4]; - if ( _sWOL.ShapeType() == TopAbs_EDGE ) + if ( eos.SWOLType() == TopAbs_EDGE ) { double u = Precision::Infinite(); // to force projection w/o distance check - helper.CheckNodeU( TopoDS::Edge( _sWOL ), n, u, 1e-10, /*force=*/true, distXYZ ); + helper.CheckNodeU( TopoDS::Edge( eos._sWOL ), n, u, 1e-10, /*force=*/true, distXYZ ); _pos.back().SetCoord( u, 0, 0 ); if ( _nodes.size() > 1 ) { @@ -5916,7 +6093,7 @@ void _LayerEdge::SetNewLength( double len, SMESH_MesherHelper& helper ) else // TopAbs_FACE { gp_XY uv( Precision::Infinite(), 0 ); - helper.CheckNodeUV( TopoDS::Face( _sWOL ), n, uv, 1e-10, /*force=*/true, distXYZ ); + helper.CheckNodeUV( TopoDS::Face( eos._sWOL ), n, uv, 1e-10, /*force=*/true, distXYZ ); _pos.back().SetCoord( uv.X(), uv.Y(), 0 ); if ( _nodes.size() > 1 ) { @@ -5936,7 +6113,7 @@ void _LayerEdge::SetNewLength( double len, SMESH_MesherHelper& helper ) */ //================================================================================ -void _LayerEdge::InvalidateStep( int curStep, bool restoreLength ) +void _LayerEdge::InvalidateStep( int curStep, const _EdgesOnShape& eos, bool restoreLength ) { if ( _pos.size() > curStep ) { @@ -5946,15 +6123,15 @@ void _LayerEdge::InvalidateStep( int curStep, bool restoreLength ) _pos.resize( curStep ); gp_Pnt nXYZ = _pos.back(); SMDS_MeshNode* n = const_cast< SMDS_MeshNode*>( _nodes.back() ); - if ( !_sWOL.IsNull() ) + if ( !eos._sWOL.IsNull() ) { TopLoc_Location loc; - if ( _sWOL.ShapeType() == TopAbs_EDGE ) + if ( eos.SWOLType() == TopAbs_EDGE ) { SMDS_EdgePosition* pos = static_cast( n->GetPosition() ); pos->SetUParameter( nXYZ.X() ); double f,l; - Handle(Geom_Curve) curve = BRep_Tool::Curve( TopoDS::Edge( _sWOL ), loc, f,l); + Handle(Geom_Curve) curve = BRep_Tool::Curve( TopoDS::Edge( eos._sWOL ), loc, f,l); nXYZ = curve->Value( nXYZ.X() ).Transformed( loc ); } else @@ -5962,7 +6139,7 @@ void _LayerEdge::InvalidateStep( int curStep, bool restoreLength ) SMDS_FacePosition* pos = static_cast( n->GetPosition() ); pos->SetUParameter( nXYZ.X() ); pos->SetVParameter( nXYZ.Y() ); - Handle(Geom_Surface) surface = BRep_Tool::Surface( TopoDS::Face(_sWOL), loc ); + Handle(Geom_Surface) surface = BRep_Tool::Surface( TopoDS::Face(eos._sWOL), loc ); nXYZ = surface->Value( nXYZ.X(), nXYZ.Y() ).Transformed( loc ); } } @@ -5998,186 +6175,186 @@ bool _ViscousBuilder::refine(_SolidData& data) // Create intermediate nodes on each _LayerEdge - int iS = 0, iEnd = data._endEdgeOnShape[ iS ]; - - for ( size_t i = 0; i < data._edges.size(); ++i ) + for ( size_t iS = 0; iS < data._edgesOnShape.size(); ++iS ) { - _LayerEdge& edge = *data._edges[i]; + _EdgesOnShape& eos = data._edgesOnShape[iS]; + if ( eos._edges.empty() ) continue; - if ( edge._nodes.size() < 2 ) + if ( eos._edges[0]->_nodes.size() < 2 ) continue; // on _noShrinkShapes - // get parameters of layers for the edge - if ( i == iEnd ) - iEnd = data._endEdgeOnShape[ ++iS ]; - const AverageHyp& hyp = data._hypOnShape[ iS ]; + for ( size_t i = 0; i < eos._edges.size(); ++i ) + { + _LayerEdge& edge = *eos._edges[i]; - // get accumulated length of segments - vector< double > segLen( edge._pos.size() ); - segLen[0] = 0.0; - for ( size_t j = 1; j < edge._pos.size(); ++j ) - segLen[j] = segLen[j-1] + (edge._pos[j-1] - edge._pos[j] ).Modulus(); + // get accumulated length of segments + vector< double > segLen( edge._pos.size() ); + segLen[0] = 0.0; + for ( size_t j = 1; j < edge._pos.size(); ++j ) + segLen[j] = segLen[j-1] + (edge._pos[j-1] - edge._pos[j] ).Modulus(); - // allocate memory for new nodes if it is not yet refined - const SMDS_MeshNode* tgtNode = edge._nodes.back(); - if ( edge._nodes.size() == 2 ) - { - edge._nodes.resize( hyp.GetNumberLayers() + 1, 0 ); - edge._nodes[1] = 0; - edge._nodes.back() = tgtNode; - } - // get data of a shrink shape - if ( !edge._sWOL.IsNull() && edge._sWOL != prevSWOL ) - { - isOnEdge = ( edge._sWOL.ShapeType() == TopAbs_EDGE ); - if ( isOnEdge ) - { - geomEdge = TopoDS::Edge( edge._sWOL ); - curve = BRep_Tool::Curve( geomEdge, loc, f,l); - } - else - { - geomFace = TopoDS::Face( edge._sWOL ); - surface = BRep_Tool::Surface( geomFace, loc ); - } - prevSWOL = edge._sWOL; - } - // restore shapePos of the last node by already treated _LayerEdge of another _SolidData - const TGeomID baseShapeId = edge._nodes[0]->getshapeId(); - if ( baseShapeId != prevBaseId ) - { - map< TGeomID, TNode2Edge* >::iterator s2ne = data._s2neMap.find( baseShapeId ); - n2eMap = ( s2ne == data._s2neMap.end() ) ? 0 : n2eMap = s2ne->second; - prevBaseId = baseShapeId; - } - _LayerEdge* edgeOnSameNode = 0; - if ( n2eMap && (( n2e = n2eMap->find( edge._nodes[0] )) != n2eMap->end() )) - { - edgeOnSameNode = n2e->second; - const gp_XYZ& otherTgtPos = edgeOnSameNode->_pos.back(); - SMDS_PositionPtr lastPos = tgtNode->GetPosition(); - if ( isOnEdge ) + // allocate memory for new nodes if it is not yet refined + const SMDS_MeshNode* tgtNode = edge._nodes.back(); + if ( edge._nodes.size() == 2 ) { - SMDS_EdgePosition* epos = static_cast( lastPos ); - epos->SetUParameter( otherTgtPos.X() ); + edge._nodes.resize( eos._hyp.GetNumberLayers() + 1, 0 ); + edge._nodes[1] = 0; + edge._nodes.back() = tgtNode; } - else + // get data of a shrink shape + if ( !eos._sWOL.IsNull() && eos._sWOL != prevSWOL ) { - SMDS_FacePosition* fpos = static_cast( lastPos ); - fpos->SetUParameter( otherTgtPos.X() ); - fpos->SetVParameter( otherTgtPos.Y() ); - } - } - // calculate height of the first layer - double h0; - const double T = segLen.back(); //data._hyp.GetTotalThickness(); - const double f = hyp.GetStretchFactor(); - const int N = hyp.GetNumberLayers(); - const double fPowN = pow( f, N ); - if ( fPowN - 1 <= numeric_limits::min() ) - h0 = T / N; - else - h0 = T * ( f - 1 )/( fPowN - 1 ); - - const double zeroLen = std::numeric_limits::min(); - - // create intermediate nodes - double hSum = 0, hi = h0/f; - size_t iSeg = 1; - for ( size_t iStep = 1; iStep < edge._nodes.size(); ++iStep ) - { - // compute an intermediate position - hi *= f; - hSum += hi; - while ( hSum > segLen[iSeg] && iSeg < segLen.size()-1) - ++iSeg; - int iPrevSeg = iSeg-1; - while ( fabs( segLen[iPrevSeg] - segLen[iSeg]) <= zeroLen && iPrevSeg > 0 ) - --iPrevSeg; - double r = ( segLen[iSeg] - hSum ) / ( segLen[iSeg] - segLen[iPrevSeg] ); - gp_Pnt pos = r * edge._pos[iPrevSeg] + (1-r) * edge._pos[iSeg]; - - SMDS_MeshNode*& node = const_cast< SMDS_MeshNode*& >( edge._nodes[ iStep ]); - if ( !edge._sWOL.IsNull() ) - { - // compute XYZ by parameters + isOnEdge = ( eos.SWOLType() == TopAbs_EDGE ); if ( isOnEdge ) { - u = pos.X(); - if ( !node ) - pos = curve->Value( u ).Transformed(loc); + geomEdge = TopoDS::Edge( eos._sWOL ); + curve = BRep_Tool::Curve( geomEdge, loc, f,l); } else { - uv.SetCoord( pos.X(), pos.Y() ); - if ( !node ) - pos = surface->Value( pos.X(), pos.Y() ).Transformed(loc); + geomFace = TopoDS::Face( eos._sWOL ); + surface = BRep_Tool::Surface( geomFace, loc ); } + prevSWOL = eos._sWOL; } - // create or update the node - if ( !node ) + // restore shapePos of the last node by already treated _LayerEdge of another _SolidData + const TGeomID baseShapeId = edge._nodes[0]->getshapeId(); + if ( baseShapeId != prevBaseId ) { - node = helper.AddNode( pos.X(), pos.Y(), pos.Z()); - if ( !edge._sWOL.IsNull() ) + map< TGeomID, TNode2Edge* >::iterator s2ne = data._s2neMap.find( baseShapeId ); + n2eMap = ( s2ne == data._s2neMap.end() ) ? 0 : n2eMap = s2ne->second; + prevBaseId = baseShapeId; + } + _LayerEdge* edgeOnSameNode = 0; + if ( n2eMap && (( n2e = n2eMap->find( edge._nodes[0] )) != n2eMap->end() )) + { + edgeOnSameNode = n2e->second; + const gp_XYZ& otherTgtPos = edgeOnSameNode->_pos.back(); + SMDS_PositionPtr lastPos = tgtNode->GetPosition(); + if ( isOnEdge ) { - if ( isOnEdge ) - getMeshDS()->SetNodeOnEdge( node, geomEdge, u ); - else - getMeshDS()->SetNodeOnFace( node, geomFace, uv.X(), uv.Y() ); + SMDS_EdgePosition* epos = static_cast( lastPos ); + epos->SetUParameter( otherTgtPos.X() ); } else { - getMeshDS()->SetNodeInVolume( node, helper.GetSubShapeID() ); + SMDS_FacePosition* fpos = static_cast( lastPos ); + fpos->SetUParameter( otherTgtPos.X() ); + fpos->SetVParameter( otherTgtPos.Y() ); } } + // calculate height of the first layer + double h0; + const double T = segLen.back(); //data._hyp.GetTotalThickness(); + const double f = eos._hyp.GetStretchFactor(); + const int N = eos._hyp.GetNumberLayers(); + const double fPowN = pow( f, N ); + if ( fPowN - 1 <= numeric_limits::min() ) + h0 = T / N; else - { - if ( !edge._sWOL.IsNull() ) + h0 = T * ( f - 1 )/( fPowN - 1 ); + + const double zeroLen = std::numeric_limits::min(); + + // create intermediate nodes + double hSum = 0, hi = h0/f; + size_t iSeg = 1; + for ( size_t iStep = 1; iStep < edge._nodes.size(); ++iStep ) + { + // compute an intermediate position + hi *= f; + hSum += hi; + while ( hSum > segLen[iSeg] && iSeg < segLen.size()-1) + ++iSeg; + int iPrevSeg = iSeg-1; + while ( fabs( segLen[iPrevSeg] - segLen[iSeg]) <= zeroLen && iPrevSeg > 0 ) + --iPrevSeg; + double r = ( segLen[iSeg] - hSum ) / ( segLen[iSeg] - segLen[iPrevSeg] ); + gp_Pnt pos = r * edge._pos[iPrevSeg] + (1-r) * edge._pos[iSeg]; + + SMDS_MeshNode*& node = const_cast< SMDS_MeshNode*& >( edge._nodes[ iStep ]); + if ( !eos._sWOL.IsNull() ) { - // make average pos from new and current parameters + // compute XYZ by parameters if ( isOnEdge ) { - u = 0.5 * ( u + helper.GetNodeU( geomEdge, node )); - pos = curve->Value( u ).Transformed(loc); - - SMDS_EdgePosition* epos = static_cast( node->GetPosition() ); - epos->SetUParameter( u ); + u = pos.X(); + if ( !node ) + pos = curve->Value( u ).Transformed(loc); } else { - uv = 0.5 * ( uv + helper.GetNodeUV( geomFace, node )); - pos = surface->Value( uv.X(), uv.Y()).Transformed(loc); + uv.SetCoord( pos.X(), pos.Y() ); + if ( !node ) + pos = surface->Value( pos.X(), pos.Y() ).Transformed(loc); + } + } + // create or update the node + if ( !node ) + { + node = helper.AddNode( pos.X(), pos.Y(), pos.Z()); + if ( !eos._sWOL.IsNull() ) + { + if ( isOnEdge ) + getMeshDS()->SetNodeOnEdge( node, geomEdge, u ); + else + getMeshDS()->SetNodeOnFace( node, geomFace, uv.X(), uv.Y() ); + } + else + { + getMeshDS()->SetNodeInVolume( node, helper.GetSubShapeID() ); + } + } + else + { + if ( !eos._sWOL.IsNull() ) + { + // make average pos from new and current parameters + if ( isOnEdge ) + { + u = 0.5 * ( u + helper.GetNodeU( geomEdge, node )); + pos = curve->Value( u ).Transformed(loc); - SMDS_FacePosition* fpos = static_cast( node->GetPosition() ); - fpos->SetUParameter( uv.X() ); - fpos->SetVParameter( uv.Y() ); + SMDS_EdgePosition* epos = static_cast( node->GetPosition() ); + epos->SetUParameter( u ); + } + else + { + uv = 0.5 * ( uv + helper.GetNodeUV( geomFace, node )); + pos = surface->Value( uv.X(), uv.Y()).Transformed(loc); + + SMDS_FacePosition* fpos = static_cast( node->GetPosition() ); + fpos->SetUParameter( uv.X() ); + fpos->SetVParameter( uv.Y() ); + } } + node->setXYZ( pos.X(), pos.Y(), pos.Z() ); } - node->setXYZ( pos.X(), pos.Y(), pos.Z() ); + } // loop on edge._nodes + + if ( !eos._sWOL.IsNull() ) // prepare for shrink() + { + if ( isOnEdge ) + edge._pos.back().SetCoord( u, 0,0); + else + edge._pos.back().SetCoord( uv.X(), uv.Y() ,0); + + if ( edgeOnSameNode ) + edgeOnSameNode->_pos.back() = edge._pos.back(); } - } // loop on edge._nodes - if ( !edge._sWOL.IsNull() ) // prepare for shrink() - { - if ( isOnEdge ) - edge._pos.back().SetCoord( u, 0,0); - else - edge._pos.back().SetCoord( uv.X(), uv.Y() ,0); + } // loop on eos._edges to create nodes - if ( edgeOnSameNode ) - edgeOnSameNode->_pos.back() = edge._pos.back(); - } - } // loop on data._edges to create nodes + if ( !getMeshDS()->IsEmbeddedMode() ) + // Log node movement + for ( size_t i = 0; i < eos._edges.size(); ++i ) + { + SMESH_TNodeXYZ p ( eos._edges[i]->_nodes.back() ); + getMeshDS()->MoveNode( p._node, p.X(), p.Y(), p.Z() ); + } + } - if ( !getMeshDS()->IsEmbeddedMode() ) - // Log node movement - for ( size_t i = 0; i < data._edges.size(); ++i ) - { - _LayerEdge& edge = *data._edges[i]; - SMESH_TNodeXYZ p ( edge._nodes.back() ); - getMeshDS()->MoveNode( p._node, p.X(), p.Y(), p.Z() ); - } // Create volumes @@ -6372,6 +6549,7 @@ bool _ViscousBuilder::shrink() // EDGE's to shrink map< TGeomID, _Shrinker1D > e2shrMap; + vector< _EdgesOnShape* > subEOS; vector< _LayerEdge* > lEdges; // loop on FACES to srink mesh on @@ -6419,30 +6597,25 @@ bool _ViscousBuilder::shrink() } // Find _LayerEdge's inflated along F + subEOS.clear(); lEdges.clear(); { - set< TGeomID > subIDs; SMESH_subMeshIteratorPtr subIt = sm->getDependsOnIterator(/*includeSelf=*/false); while ( subIt->more() ) - subIDs.insert( subIt->next()->GetId() ); - - int iBeg, iEnd = 0; - for ( int iS = 0; iS < data._endEdgeOnShape.size() && !subIDs.empty(); ++iS ) { - iBeg = iEnd; - iEnd = data._endEdgeOnShape[ iS ]; - TGeomID shapeID = data._edges[ iBeg ]->_nodes[0]->getshapeId(); - set< TGeomID >::iterator idIt = subIDs.find( shapeID ); - if ( idIt == subIDs.end() || - data._edges[ iBeg ]->_sWOL.IsNull() ) continue; - subIDs.erase( idIt ); + const TGeomID subID = subIt->next()->GetId(); + if ( data._noShrinkShapes.count( subID )) + continue; + _EdgesOnShape* eos = data.GetShapeEdges( subID ); + if ( !eos || eos->_sWOL.IsNull() ) continue; - if ( !data._noShrinkShapes.count( shapeID )) - for ( ; iBeg < iEnd; ++iBeg ) - { - lEdges.push_back( data._edges[ iBeg ] ); - prepareEdgeToShrink( *data._edges[ iBeg ], F, helper, smDS ); - } + subEOS.push_back( eos ); + + for ( size_t i = 0; i < eos->_edges.size(); ++i ) + { + lEdges.push_back( eos->_edges[ i ] ); + prepareEdgeToShrink( *eos->_edges[ i ], *eos, helper, smDS ); + } } } @@ -6456,25 +6629,29 @@ bool _ViscousBuilder::shrink() // Replace source nodes by target nodes in mesh faces to shrink dumpFunction(SMESH_Comment("replNodesOnFace")<first); // debug const SMDS_MeshNode* nodes[20]; - for ( size_t i = 0; i < lEdges.size(); ++i ) + for ( size_t iS = 0; iS < subEOS.size(); ++iS ) { - _LayerEdge& edge = *lEdges[i]; - const SMDS_MeshNode* srcNode = edge._nodes[0]; - const SMDS_MeshNode* tgtNode = edge._nodes.back(); - SMDS_ElemIteratorPtr fIt = srcNode->GetInverseElementIterator(SMDSAbs_Face); - while ( fIt->more() ) + _EdgesOnShape& eos = * subEOS[ iS ]; + for ( size_t i = 0; i < eos._edges.size(); ++i ) { - const SMDS_MeshElement* f = fIt->next(); - if ( !smDS->Contains( f )) - continue; - SMDS_NodeIteratorPtr nIt = f->nodeIterator(); - for ( int iN = 0; nIt->more(); ++iN ) + _LayerEdge& edge = *eos._edges[i]; + const SMDS_MeshNode* srcNode = edge._nodes[0]; + const SMDS_MeshNode* tgtNode = edge._nodes.back(); + SMDS_ElemIteratorPtr fIt = srcNode->GetInverseElementIterator(SMDSAbs_Face); + while ( fIt->more() ) { - const SMDS_MeshNode* n = nIt->next(); - nodes[iN] = ( n == srcNode ? tgtNode : n ); + const SMDS_MeshElement* f = fIt->next(); + if ( !smDS->Contains( f )) + continue; + SMDS_NodeIteratorPtr nIt = f->nodeIterator(); + for ( int iN = 0; nIt->more(); ++iN ) + { + const SMDS_MeshNode* n = nIt->next(); + nodes[iN] = ( n == srcNode ? tgtNode : n ); + } + helper.GetMeshDS()->ChangeElementNodes( f, nodes, f->NbNodes() ); + dumpChangeNodes( f ); } - helper.GetMeshDS()->ChangeElementNodes( f, nodes, f->NbNodes() ); - dumpChangeNodes( f ); } } dumpFunctionEnd(); @@ -6504,21 +6681,25 @@ bool _ViscousBuilder::shrink() // Find EDGE's to shrink and set simpices to LayerEdge's set< _Shrinker1D* > eShri1D; { - for ( size_t i = 0; i < lEdges.size(); ++i ) + for ( size_t iS = 0; iS < subEOS.size(); ++iS ) { - _LayerEdge* edge = lEdges[i]; - if ( edge->_sWOL.ShapeType() == TopAbs_EDGE ) + _EdgesOnShape& eos = * subEOS[ iS ]; + if ( eos.SWOLType() == TopAbs_EDGE ) { - TGeomID edgeIndex = getMeshDS()->ShapeToIndex( edge->_sWOL ); - _Shrinker1D& srinker = e2shrMap[ edgeIndex ]; + SMESH_subMesh* edgeSM = _mesh->GetSubMesh( eos._sWOL ); + _Shrinker1D& srinker = e2shrMap[ edgeSM->GetId() ]; eShri1D.insert( & srinker ); - srinker.AddEdge( edge, helper ); - VISCOUS_3D::ToClearSubWithMain( _mesh->GetSubMesh( edge->_sWOL ), data._solid ); + srinker.AddEdge( eos._edges[0], eos, helper ); + VISCOUS_3D::ToClearSubWithMain( edgeSM, data._solid ); // restore params of nodes on EGDE if the EDGE has been already - // srinked while srinking another FACE + // srinked while srinking other FACE srinker.RestoreParams(); } - _Simplex::GetSimplices( /*tgtNode=*/edge->_nodes.back(), edge->_simplices, ignoreShapes ); + for ( size_t i = 0; i < eos._edges.size(); ++i ) + { + _LayerEdge& edge = * eos._edges[i]; + _Simplex::GetSimplices( /*tgtNode=*/edge._nodes.back(), edge._simplices, ignoreShapes ); + } } } @@ -6553,9 +6734,13 @@ bool _ViscousBuilder::shrink() // ----------------------------------------------- dumpFunction(SMESH_Comment("moveBoundaryOnF")<first<<"_st"<SetNewLength2d( surface,F,helper ); + _EdgesOnShape& eos = * subEOS[ iS ]; + for ( size_t i = 0; i < eos._edges.size(); ++i ) + { + shrinked |= eos._edges[i]->SetNewLength2d( surface, F, eos, helper ); + } } dumpFunctionEnd(); @@ -6579,7 +6764,7 @@ bool _ViscousBuilder::shrink() moved = false; for ( size_t i = 0; i < nodesToSmooth.size(); ++i ) { - moved |= nodesToSmooth[i].Smooth( badNb,surface,helper,refSign, + moved |= nodesToSmooth[i].Smooth( badNb, surface, helper, refSign, smoothType, /*set3D=*/isConcaveFace); } if ( badNb < oldBadNb ) @@ -6695,17 +6880,17 @@ bool _ViscousBuilder::shrink() //================================================================================ bool _ViscousBuilder::prepareEdgeToShrink( _LayerEdge& edge, - const TopoDS_Face& F, + _EdgesOnShape& eos, SMESH_MesherHelper& helper, const SMESHDS_SubMesh* faceSubMesh) { const SMDS_MeshNode* srcNode = edge._nodes[0]; const SMDS_MeshNode* tgtNode = edge._nodes.back(); - if ( edge._sWOL.ShapeType() == TopAbs_FACE ) + if ( eos.SWOLType() == TopAbs_FACE ) { - gp_XY srcUV( edge._pos[0].X(), edge._pos[0].Y() );//helper.GetNodeUV( F, srcNode ); - gp_XY tgtUV = edge.LastUV( F ); //helper.GetNodeUV( F, tgtNode ); + gp_XY srcUV ( edge._pos[0].X(), edge._pos[0].Y() ); //helper.GetNodeUV( F, srcNode ); + gp_XY tgtUV = edge.LastUV( TopoDS::Face( eos._sWOL ), eos ); //helper.GetNodeUV( F, tgtNode ); gp_Vec2d uvDir( srcUV, tgtUV ); double uvLen = uvDir.Magnitude(); uvDir /= uvLen; @@ -6722,7 +6907,7 @@ bool _ViscousBuilder::prepareEdgeToShrink( _LayerEdge& edge, } else // _sWOL is TopAbs_EDGE { - const TopoDS_Edge& E = TopoDS::Edge( edge._sWOL ); + const TopoDS_Edge& E = TopoDS::Edge( eos._sWOL ); SMESHDS_SubMesh* edgeSM = getMeshDS()->MeshElements( E ); if ( !edgeSM || edgeSM->NbElements() == 0 ) return error(SMESH_Comment("Not meshed EDGE ") << getMeshDS()->ShapeToIndex( E )); @@ -6968,6 +7153,7 @@ void _ViscousBuilder::fixBadFaces(const TopoDS_Face& F, bool _LayerEdge::SetNewLength2d( Handle(Geom_Surface)& surface, const TopoDS_Face& F, + _EdgesOnShape& eos, SMESH_MesherHelper& helper ) { if ( _pos.empty() ) @@ -6975,7 +7161,7 @@ bool _LayerEdge::SetNewLength2d( Handle(Geom_Surface)& surface, SMDS_MeshNode* tgtNode = const_cast< SMDS_MeshNode*& >( _nodes.back() ); - if ( _sWOL.ShapeType() == TopAbs_FACE ) + if ( eos.SWOLType() == TopAbs_FACE ) { gp_XY curUV = helper.GetNodeUV( F, tgtNode ); gp_Pnt2d tgtUV( _pos[0].X(), _pos[0].Y() ); @@ -7024,7 +7210,7 @@ bool _LayerEdge::SetNewLength2d( Handle(Geom_Surface)& surface, } else // _sWOL is TopAbs_EDGE { - const TopoDS_Edge& E = TopoDS::Edge( _sWOL ); + const TopoDS_Edge& E = TopoDS::Edge( eos._sWOL ); const SMDS_MeshNode* n2 = _simplices[0]._nPrev; SMDS_EdgePosition* tgtPos = static_cast( tgtNode->GetPosition() ); @@ -7230,21 +7416,26 @@ gp_XY _SmoothNode::computeAngularPos(vector& uv, _SolidData::~_SolidData() { - for ( size_t i = 0; i < _edges.size(); ++i ) + TNode2Edge::iterator n2e = _n2eMap.begin(); + for ( ; n2e != _n2eMap.end(); ++n2e ) { - if ( _edges[i] && _edges[i]->_2neibors ) - delete _edges[i]->_2neibors; - delete _edges[i]; + _LayerEdge* & e = n2e->second; + if ( e && e->_2neibors ) + delete e->_2neibors; + delete e; + e = NULL; } - _edges.clear(); + _n2eMap.clear(); } //================================================================================ /*! - * \brief Add a _LayerEdge inflated along the EDGE + * \brief Keep a _LayerEdge inflated along the EDGE */ //================================================================================ -void _Shrinker1D::AddEdge( const _LayerEdge* e, SMESH_MesherHelper& helper ) +void _Shrinker1D::AddEdge( const _LayerEdge* e, + _EdgesOnShape& eos, + SMESH_MesherHelper& helper ) { // init if ( _nodes.empty() ) @@ -7255,16 +7446,16 @@ void _Shrinker1D::AddEdge( const _LayerEdge* e, SMESH_MesherHelper& helper ) // check _LayerEdge if ( e == _edges[0] || e == _edges[1] ) return; - if ( e->_sWOL.IsNull() || e->_sWOL.ShapeType() != TopAbs_EDGE ) + if ( eos.SWOLType() != TopAbs_EDGE ) throw SALOME_Exception(LOCALIZED("Wrong _LayerEdge is added")); - if ( _edges[0] && _edges[0]->_sWOL != e->_sWOL ) + if ( _edges[0] && !_geomEdge.IsSame( eos._sWOL )) throw SALOME_Exception(LOCALIZED("Wrong _LayerEdge is added")); // store _LayerEdge - const TopoDS_Edge& E = TopoDS::Edge( e->_sWOL ); + _geomEdge = TopoDS::Edge( eos._sWOL ); double f,l; - BRep_Tool::Range( E, f,l ); - double u = helper.GetNodeU( E, e->_nodes[0], e->_nodes.back()); + BRep_Tool::Range( _geomEdge, f,l ); + double u = helper.GetNodeU( _geomEdge, e->_nodes[0], e->_nodes.back()); _edges[ u < 0.5*(f+l) ? 0 : 1 ] = e; // Update _nodes @@ -7274,11 +7465,11 @@ void _Shrinker1D::AddEdge( const _LayerEdge* e, SMESH_MesherHelper& helper ) if ( _nodes.empty() ) { - SMESHDS_SubMesh * eSubMesh = helper.GetMeshDS()->MeshElements( E ); + SMESHDS_SubMesh * eSubMesh = helper.GetMeshDS()->MeshElements( _geomEdge ); if ( !eSubMesh || eSubMesh->NbNodes() < 1 ) return; TopLoc_Location loc; - Handle(Geom_Curve) C = BRep_Tool::Curve(E, loc, f,l); + Handle(Geom_Curve) C = BRep_Tool::Curve( _geomEdge, loc, f,l ); GeomAdaptor_Curve aCurve(C, f,l); const double totLen = GCPnts_AbscissaPoint::Length(aCurve, f, l); @@ -7294,7 +7485,7 @@ void _Shrinker1D::AddEdge( const _LayerEdge* e, SMESH_MesherHelper& helper ) node == tgtNode0 || node == tgtNode1 ) continue; // refinement nodes _nodes.push_back( node ); - _initU.push_back( helper.GetNodeU( E, node )); + _initU.push_back( helper.GetNodeU( _geomEdge, node )); double len = GCPnts_AbscissaPoint::Length(aCurve, f, _initU.back()); _normPar.push_back( len / totLen ); } @@ -7328,17 +7519,16 @@ void _Shrinker1D::Compute(bool set3D, SMESH_MesherHelper& helper) _done = (( !_edges[0] || _edges[0]->_pos.empty() ) && ( !_edges[1] || _edges[1]->_pos.empty() )); - const TopoDS_Edge& E = TopoDS::Edge( e->_sWOL ); double f,l; if ( set3D || _done ) { - Handle(Geom_Curve) C = BRep_Tool::Curve(E, f,l); + Handle(Geom_Curve) C = BRep_Tool::Curve(_geomEdge, f,l); GeomAdaptor_Curve aCurve(C, f,l); if ( _edges[0] ) - f = helper.GetNodeU( E, _edges[0]->_nodes.back(), _nodes[0] ); + f = helper.GetNodeU( _geomEdge, _edges[0]->_nodes.back(), _nodes[0] ); if ( _edges[1] ) - l = helper.GetNodeU( E, _edges[1]->_nodes.back(), _nodes.back() ); + l = helper.GetNodeU( _geomEdge, _edges[1]->_nodes.back(), _nodes.back() ); double totLen = GCPnts_AbscissaPoint::Length( aCurve, f, l ); for ( size_t i = 0; i < _nodes.size(); ++i ) @@ -7357,11 +7547,11 @@ void _Shrinker1D::Compute(bool set3D, SMESH_MesherHelper& helper) } else { - BRep_Tool::Range( E, f,l ); + BRep_Tool::Range( _geomEdge, f,l ); if ( _edges[0] ) - f = helper.GetNodeU( E, _edges[0]->_nodes.back(), _nodes[0] ); + f = helper.GetNodeU( _geomEdge, _edges[0]->_nodes.back(), _nodes[0] ); if ( _edges[1] ) - l = helper.GetNodeU( E, _edges[1]->_nodes.back(), _nodes.back() ); + l = helper.GetNodeU( _geomEdge, _edges[1]->_nodes.back(), _nodes.back() ); for ( size_t i = 0; i < _nodes.size(); ++i ) { @@ -7404,7 +7594,7 @@ void _Shrinker1D::SwapSrcTgtNodes( SMESHDS_Mesh* mesh ) { if ( !_edges[i] ) continue; - SMESHDS_SubMesh * eSubMesh = mesh->MeshElements( _edges[i]->_sWOL ); + SMESHDS_SubMesh * eSubMesh = mesh->MeshElements( _geomEdge ); if ( !eSubMesh ) return; const SMDS_MeshNode* srcNode = _edges[i]->_nodes[0]; const SMDS_MeshNode* tgtNode = _edges[i]->_nodes.back(); @@ -7568,19 +7758,21 @@ bool _ViscousBuilder::addBoundaryElements() for ( int isFirst = 0; isFirst < 2; ++isFirst ) { _LayerEdge* edge = isFirst ? ledges.front() : ledges.back(); - if ( !edge->_sWOL.IsNull() && edge->_sWOL.ShapeType() == TopAbs_EDGE ) + _EdgesOnShape* eos = data.GetShapeEdges( edge ); + if ( eos && eos->SWOLType() == TopAbs_EDGE ) { vector< const SMDS_MeshNode*>& nn = edge->_nodes; if ( nn.size() < 2 || nn[1]->GetInverseElementIterator( SMDSAbs_Edge )->more() ) continue; - helper.SetSubShape( edge->_sWOL ); + helper.SetSubShape( eos->_sWOL ); helper.SetElementsOnShape( true ); for ( size_t z = 1; z < nn.size(); ++z ) helper.AddEdge( nn[z-1], nn[z] ); } } - } - } + + } // loop on EDGE's + } // loop on _SolidData's return true; } diff --git a/src/StdMeshers/StdMeshers_ViscousLayers.hxx b/src/StdMeshers/StdMeshers_ViscousLayers.hxx index 9c26048b6..d76fa680f 100644 --- a/src/StdMeshers/StdMeshers_ViscousLayers.hxx +++ b/src/StdMeshers/StdMeshers_ViscousLayers.hxx @@ -42,29 +42,44 @@ public: // Set boundary shapes (faces in 3D, edges in 2D) either to exclude from // treatment or to make the Viscous Layers on - void SetBndShapes(const std::vector& shapeIds, bool toIgnore); + void SetBndShapes(const std::vector& shapeIds, bool toIgnore); std::vector GetBndShapes() const { return _shapeIds; } - bool IsToIgnoreShapes() const { return _isToIgnoreShapes; } + bool IsToIgnoreShapes() const { return _isToIgnoreShapes; } // Set total thickness of layers of prisms - void SetTotalThickness(double thickness); + void SetTotalThickness(double thickness); double GetTotalThickness() const { return _thickness; } // Set number of layers of prisms - void SetNumberLayers(int nb); - int GetNumberLayers() const { return _nbLayers; } + void SetNumberLayers(int nb); + int GetNumberLayers() const { return _nbLayers; } // Set factor (>1.0) of growth of layer thickness towards inside of mesh - void SetStretchFactor(double factor); + void SetStretchFactor(double factor); double GetStretchFactor() const { return _stretchFactor; } + // Method of computing node translation + enum ExtrusionMethod { + // node is translated along normal to a surface with possible further smoothing + SURF_OFFSET_SMOOTH, + // node is translated along the average normal of surrounding faces till + // intersection with a neighbor face translated along its own normal + // by the layers thickness + FACE_OFFSET, + // node is translated along the average normal of surrounding faces + // by the layers thickness + NODE_OFFSET + }; + void SetMethod( ExtrusionMethod how ); + ExtrusionMethod GetMethod() const { return _method; } + // Computes temporary 2D mesh to be used by 3D algorithm. // Return SMESH_ProxyMesh for each SOLID in theShape SMESH_ProxyMesh::Ptr Compute(SMESH_Mesh& theMesh, const TopoDS_Shape& theShape, const bool toMakeN2NMap=false) const; - // Checks compatibility of assigned StdMeshers_ViscousLayers hypotheses + // Checks compatibility of assigned StdMeshers_ViscousLayers hypotheses static SMESH_ComputeErrorPtr CheckHypothesis(SMESH_Mesh& aMesh, const TopoDS_Shape& aShape, @@ -81,8 +96,6 @@ public: * \param theMesh - the built mesh * \param theShape - the geometry of interest * \retval bool - true if parameter values have been successfully defined - * - * Just return false as this hypothesis does not have parameters values */ virtual bool SetParametersByMesh(const SMESH_Mesh* theMesh, const TopoDS_Shape& theShape); @@ -102,6 +115,7 @@ public: int _nbLayers; double _thickness; double _stretchFactor; + ExtrusionMethod _method; }; class SMESH_subMesh; diff --git a/src/StdMeshersGUI/StdMeshersGUI_RadioButtonsGrpWdg.cxx b/src/StdMeshersGUI/StdMeshersGUI_RadioButtonsGrpWdg.cxx index 1d939f28b..1f6ee6cd6 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_RadioButtonsGrpWdg.cxx +++ b/src/StdMeshersGUI/StdMeshersGUI_RadioButtonsGrpWdg.cxx @@ -18,7 +18,12 @@ // #include "StdMeshersGUI_RadioButtonsGrpWdg.h" -#include +#include "SMESHGUI.h" + +#include + +#include +#include #include #include #include @@ -44,17 +49,28 @@ StdMeshersGUI_RadioButtonsGrpWdg::StdMeshersGUI_RadioButtonsGrpWdg( const QStrin */ //================================================================================ -void StdMeshersGUI_RadioButtonsGrpWdg::setButtonLabels( const QStringList& buttonLabels ) +void StdMeshersGUI_RadioButtonsGrpWdg::setButtonLabels( const QStringList& buttonLabels, + const QStringList& buttonIcons ) { - QVBoxLayout* layout = new QVBoxLayout( this ); + QGridLayout* layout = new QGridLayout( this ); layout->setSpacing(SPACING); layout->setMargin(MARGIN); for ( int id = 0; id < buttonLabels.size(); ++id ) { QRadioButton* button = new QRadioButton( buttonLabels.at(id), this ); - layout->addWidget( button ); + layout->addWidget( button, id, 0 ); myButtonGrp->addButton( button, id ); + + if ( id < buttonIcons.count() ) + { + QPixmap pmi (SMESHGUI::resourceMgr()->loadPixmap("SMESH", buttonIcons.at(id))); + if ( !pmi.isNull() ) { + QLabel* pixLabel = new QLabel( this ); + pixLabel->setPixmap( pmi ); + layout->addWidget( pixLabel, id, 1 ); + } + } } } diff --git a/src/StdMeshersGUI/StdMeshersGUI_RadioButtonsGrpWdg.h b/src/StdMeshersGUI/StdMeshersGUI_RadioButtonsGrpWdg.h index a0133136b..fcb67209f 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_RadioButtonsGrpWdg.h +++ b/src/StdMeshersGUI/StdMeshersGUI_RadioButtonsGrpWdg.h @@ -38,7 +38,8 @@ class STDMESHERSGUI_EXPORT StdMeshersGUI_RadioButtonsGrpWdg : public QGroupBox public: StdMeshersGUI_RadioButtonsGrpWdg (const QString& title); - void setButtonLabels( const QStringList& buttonLabels ); + void setButtonLabels( const QStringList& buttonLabels, + const QStringList& buttonIcons=QStringList()); void setChecked(int id); diff --git a/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx b/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx index e912e113d..dc83e7f51 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx +++ b/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx @@ -714,17 +714,18 @@ QString StdMeshersGUI_StdHypothesisCreator::storeParams() const StdMeshers::StdMeshers_ViscousLayers_var h = StdMeshers::StdMeshers_ViscousLayers::_narrow( hypothesis() ); - h->SetVarParameter( params[0].text(), "SetTotalThickness" ); + h->SetVarParameter ( params[0].text(), "SetTotalThickness" ); h->SetTotalThickness( params[0].myValue.toDouble() ); - h->SetVarParameter( params[1].text(), "SetNumberLayers" ); + h->SetVarParameter ( params[1].text(), "SetNumberLayers" ); h->SetNumberLayers ( params[1].myValue.toInt() ); - h->SetVarParameter( params[2].text(), "SetStretchFactor" ); + h->SetVarParameter ( params[2].text(), "SetStretchFactor" ); h->SetStretchFactor ( params[2].myValue.toDouble() ); + h->SetMethod (( StdMeshers::VLExtrusionMethod ) params[3].myValue.toInt() ); - if ( StdMeshersGUI_SubShapeSelectorWdg* idsWg = - widget< StdMeshersGUI_SubShapeSelectorWdg >( 4 )) + if ( StdMeshersGUI_SubShapeSelectorWdg* idsWg = + widget< StdMeshersGUI_SubShapeSelectorWdg >( 5 )) { - h->SetFaces( idsWg->GetListOfIDs(), params[3].myValue.toInt() ); + h->SetFaces( idsWg->GetListOfIDs(), params[4].myValue.toInt() ); } } else if( hypType()=="ViscousLayers2D" ) @@ -732,11 +733,11 @@ QString StdMeshersGUI_StdHypothesisCreator::storeParams() const StdMeshers::StdMeshers_ViscousLayers2D_var h = StdMeshers::StdMeshers_ViscousLayers2D::_narrow( hypothesis() ); - h->SetVarParameter( params[0].text(), "SetTotalThickness" ); + h->SetVarParameter ( params[0].text(), "SetTotalThickness" ); h->SetTotalThickness( params[0].myValue.toDouble() ); - h->SetVarParameter( params[1].text(), "SetNumberLayers" ); + h->SetVarParameter ( params[1].text(), "SetNumberLayers" ); h->SetNumberLayers ( params[1].myValue.toInt() ); - h->SetVarParameter( params[2].text(), "SetStretchFactor" ); + h->SetVarParameter ( params[2].text(), "SetStretchFactor" ); h->SetStretchFactor ( params[2].myValue.toDouble() ); if ( StdMeshersGUI_SubShapeSelectorWdg* idsWg = @@ -1242,6 +1243,20 @@ bool StdMeshersGUI_StdHypothesisCreator::stdParams( ListOfStdParams& p ) const p.append( item ); customWidgets()->append (0); + item.myName = tr( "EXTRUSION_METHOD" ); + p.append( item ); + StdMeshersGUI_RadioButtonsGrpWdg* methodWdg = new StdMeshersGUI_RadioButtonsGrpWdg(""); + methodWdg->setButtonLabels ( QStringList() + << tr("EXTMETH_SURF_OFFSET_SMOOTH") + << tr("EXTMETH_FACE_OFFSET") + << tr("EXTMETH_NODE_OFFSET"), + QStringList() + << tr("ICON_EXTMETH_SURF_OFFSET_SMOOTH") + << tr("ICON_EXTMETH_FACE_OFFSET") + << tr("ICON_EXTMETH_NODE_OFFSET")); + methodWdg->setChecked( (int) h->GetMethod() ); + customWidgets()->append( methodWdg ); + QString aMainEntry = SMESHGUI_GenericHypothesisCreator::getMainShapeEntry(); QString aSubEntry = SMESHGUI_GenericHypothesisCreator::getShapeEntry(); if ( !aMainEntry.isEmpty() ) diff --git a/src/StdMeshersGUI/StdMeshers_images.ts b/src/StdMeshersGUI/StdMeshers_images.ts index 537118cf5..b94521ab3 100644 --- a/src/StdMeshersGUI/StdMeshers_images.ts +++ b/src/StdMeshersGUI/StdMeshers_images.ts @@ -283,4 +283,19 @@ mesh_quadrangle_reduced.png + + StdMeshersGUI_StdHypothesisCreator + + ICON_EXTMETH_SURF_OFFSET_SMOOTH + mesh_extmeth_surf_offset_smooth.png + + + ICON_EXTMETH_NODE_OFFSET + mesh_extmeth_node_offset.png + + + ICON_EXTMETH_FACE_OFFSET + mesh_extmeth_face_offset.png + + diff --git a/src/StdMeshersGUI/StdMeshers_msg_en.ts b/src/StdMeshersGUI/StdMeshers_msg_en.ts index 5e38ea864..faabbbb75 100644 --- a/src/StdMeshersGUI/StdMeshers_msg_en.ts +++ b/src/StdMeshersGUI/StdMeshers_msg_en.ts @@ -43,6 +43,22 @@ mesh/sub-mesh. Consider creating another hypothesis instead of using this one for this mesh/sub-mesh. + + EXTMETH_SURF_OFFSET_SMOOTH + Surface offset + smooth + + + EXTRUSION_METHOD + Extrusion method + + + EXTMETH_NODE_OFFSET + Node offset + + + EXTMETH_FACE_OFFSET + Face offset + @default diff --git a/src/StdMeshers_I/StdMeshers_ViscousLayers_i.cxx b/src/StdMeshers_I/StdMeshers_ViscousLayers_i.cxx index 6a1f41daa..1d67ca542 100644 --- a/src/StdMeshers_I/StdMeshers_ViscousLayers_i.cxx +++ b/src/StdMeshers_I/StdMeshers_ViscousLayers_i.cxx @@ -230,6 +230,33 @@ throw ( SALOME::SALOME_Exception ) return GetImpl()->GetStretchFactor(); } +//================================================================================ +/*! + * \brief Set Method of computing translation of a node + */ +//================================================================================ + +void StdMeshers_ViscousLayers_i::SetMethod( ::StdMeshers::VLExtrusionMethod how ) +{ + GetImpl()->SetMethod( ::StdMeshers_ViscousLayers::ExtrusionMethod( how )); + const char* methNames[3] = { "SURF_OFFSET_SMOOTH", + "FACE_OFFSET", + "NODE_OFFSET" }; + if ( how >= 0 && how < 3 ) + SMESH::TPythonDump() << _this() << ".SetMethod( StdMeshers." << methNames[ how ]<< " )"; +} + +//================================================================================ +/*! + * \brief Return Method of computing translation of a node + */ +//================================================================================ + +::StdMeshers::VLExtrusionMethod StdMeshers_ViscousLayers_i::GetMethod() +{ + return (::StdMeshers::VLExtrusionMethod) GetImpl()->GetMethod(); +} + //============================================================================= /*! * Get implementation diff --git a/src/StdMeshers_I/StdMeshers_ViscousLayers_i.hxx b/src/StdMeshers_I/StdMeshers_ViscousLayers_i.hxx index e2e5b4b9d..e320f0c53 100644 --- a/src/StdMeshers_I/StdMeshers_ViscousLayers_i.hxx +++ b/src/StdMeshers_I/StdMeshers_ViscousLayers_i.hxx @@ -65,6 +65,9 @@ class STDMESHERS_I_EXPORT StdMeshers_ViscousLayers_i: void SetStretchFactor(::CORBA::Double factor) throw ( SALOME::SALOME_Exception ); ::CORBA::Double GetStretchFactor(); + void SetMethod( ::StdMeshers::VLExtrusionMethod how ); + ::StdMeshers::VLExtrusionMethod GetMethod(); + // Get implementation ::StdMeshers_ViscousLayers* GetImpl(); -- 2.30.2