From f7aba4830d53719b963fdb7fccc98b760fdef2d1 Mon Sep 17 00:00:00 2001 From: vsr Date: Wed, 15 Jan 2014 09:41:17 +0000 Subject: [PATCH] Merge from BR_imps_2013 14/01/2014 --- .../examples/defining_hypotheses_ex01.py | 15 +- .../examples/defining_hypotheses_ex08.py | 35 +- doc/salome/gui/SMESH/images/a-geometric1d.png | Bin 0 -> 16439 bytes .../gui/SMESH/images/viscous_layers_hyp.png | Bin 46476 -> 50788 bytes .../gui/SMESH/input/1d_meshing_hypo.doc | 26 +- doc/salome/gui/SMESH/input/about_hypo.doc | 2 + .../gui/SMESH/input/additional_hypo.doc | 46 +- .../SMESH/input/tui_defining_hypotheses.doc | 3 +- idl/SMESH_BasicHypothesis.idl | 181 +- idl/SMESH_Mesh.idl | 5 + resources/StdMeshers.xml.in | 29 +- src/SMESH/SMESH_Algo.cxx | 2 +- src/SMESH/SMESH_MesherHelper.cxx | 2 +- src/SMESH/SMESH_Pattern.cxx | 154 +- src/SMESH/SMESH_Pattern.hxx | 2 +- src/SMESHGUI/SMESHGUI_Hypotheses.cxx | 2 + src/SMESHGUI/SMESHGUI_HypothesesUtils.cxx | 35 +- src/SMESHGUI/SMESHGUI_HypothesesUtils.h | 2 +- src/SMESHGUI/SMESHGUI_MeshDlg.cxx | 46 +- src/SMESHGUI/SMESHGUI_MeshDlg.h | 5 + src/SMESHGUI/SMESHGUI_MeshOp.cxx | 220 +- src/SMESHGUI/SMESHGUI_MeshOp.h | 8 +- src/SMESHGUI/SMESH_msg_en.ts | 24 + src/SMESHGUI/SMESH_msg_fr.ts | 24 + src/SMESHUtils/SMESH_Block.cxx | 27 +- src/SMESHUtils/SMESH_Block.hxx | 19 +- src/SMESHUtils/SMESH_MeshAlgos.cxx | 33 + src/SMESHUtils/SMESH_MeshAlgos.hxx | 42 +- src/SMESHUtils/SMESH_TypeDefs.hxx | 13 +- src/SMESH_I/SMESH_2smeshpy.cxx | 1 + src/SMESH_I/SMESH_DumpPython.cxx | 31 +- src/SMESH_I/SMESH_Mesh_i.cxx | 27 + src/SMESH_I/SMESH_Mesh_i.hxx | 5 + src/SMESH_I/SMESH_PythonDump.hxx | 7 + src/SMESH_SWIG/StdMeshersBuilder.py | 119 +- src/SMESH_SWIG/smeshBuilder.py | 18 +- src/SMESH_SWIG/smesh_algorithm.py | 23 +- src/StdMeshers/CMakeLists.txt | 4 + .../StdMeshers_CartesianParameters3D.cxx | 93 +- .../StdMeshers_CartesianParameters3D.hxx | 15 + src/StdMeshers/StdMeshers_Cartesian_3D.cxx | 1316 +++++++++-- src/StdMeshers/StdMeshers_FaceSide.cxx | 55 +- src/StdMeshers/StdMeshers_FaceSide.hxx | 40 +- src/StdMeshers/StdMeshers_Geometric1D.cxx | 204 ++ src/StdMeshers/StdMeshers_Geometric1D.hxx | 67 + src/StdMeshers/StdMeshers_Hexa_3D.cxx | 10 +- src/StdMeshers/StdMeshers_Prism_3D.cxx | 38 +- src/StdMeshers/StdMeshers_Projection_2D.cxx | 4 +- src/StdMeshers/StdMeshers_Propagation.cxx | 58 +- src/StdMeshers/StdMeshers_Propagation.hxx | 21 +- .../StdMeshers_QuadrangleParams.cxx | 80 +- .../StdMeshers_QuadrangleParams.hxx | 27 +- src/StdMeshers/StdMeshers_Quadrangle_2D.cxx | 1943 ++++++++++++++--- src/StdMeshers/StdMeshers_Quadrangle_2D.hxx | 130 +- src/StdMeshers/StdMeshers_Regular_1D.cxx | 126 +- src/StdMeshers/StdMeshers_Regular_1D.hxx | 3 +- src/StdMeshers/StdMeshers_Reversible1D.cxx | 99 + src/StdMeshers/StdMeshers_Reversible1D.hxx | 59 + .../StdMeshers_UseExisting_1D2D.cxx | 9 +- src/StdMeshers/StdMeshers_ViscousLayers.cxx | 303 +-- .../StdMeshersGUI_CartesianParamCreator.cxx | 20 +- .../StdMeshersGUI_CartesianParamCreator.h | 20 +- .../StdMeshersGUI_StdHypothesisCreator.cxx | 87 +- src/StdMeshersGUI/StdMeshers_images.ts | 16 +- src/StdMeshersGUI/StdMeshers_msg_en.ts | 30 +- src/StdMeshersGUI/StdMeshers_msg_fr.ts | 16 + src/StdMeshers_I/CMakeLists.txt | 2 + .../StdMeshers_CartesianParameters3D_i.cxx | 22 + .../StdMeshers_CartesianParameters3D_i.hxx | 8 + src/StdMeshers_I/StdMeshers_Geometric1D_i.cxx | 143 ++ src/StdMeshers_I/StdMeshers_Geometric1D_i.hxx | 65 + src/StdMeshers_I/StdMeshers_ObjRefUlils.cxx | 19 +- src/StdMeshers_I/StdMeshers_ObjRefUlils.hxx | 4 +- src/StdMeshers_I/StdMeshers_Propagation_i.cxx | 47 +- src/StdMeshers_I/StdMeshers_Propagation_i.hxx | 23 +- .../StdMeshers_QuadrangleParams_i.cxx | 176 +- .../StdMeshers_QuadrangleParams_i.hxx | 28 +- .../StdMeshers_Reversible1D_i.cxx | 143 ++ .../StdMeshers_Reversible1D_i.hxx | 66 + .../StdMeshers_ViscousLayers_i.cxx | 65 +- .../StdMeshers_ViscousLayers_i.hxx | 5 + src/StdMeshers_I/StdMeshers_i.cxx | 5 + 82 files changed, 5895 insertions(+), 1052 deletions(-) create mode 100644 doc/salome/gui/SMESH/images/a-geometric1d.png create mode 100644 src/StdMeshers/StdMeshers_Geometric1D.cxx create mode 100644 src/StdMeshers/StdMeshers_Geometric1D.hxx create mode 100644 src/StdMeshers/StdMeshers_Reversible1D.cxx create mode 100644 src/StdMeshers/StdMeshers_Reversible1D.hxx create mode 100644 src/StdMeshers_I/StdMeshers_Geometric1D_i.cxx create mode 100644 src/StdMeshers_I/StdMeshers_Geometric1D_i.hxx create mode 100644 src/StdMeshers_I/StdMeshers_Reversible1D_i.cxx create mode 100644 src/StdMeshers_I/StdMeshers_Reversible1D_i.hxx diff --git a/doc/salome/examples/defining_hypotheses_ex01.py b/doc/salome/examples/defining_hypotheses_ex01.py index 4bb75c56b..1d5d281c8 100644 --- a/doc/salome/examples/defining_hypotheses_ex01.py +++ b/doc/salome/examples/defining_hypotheses_ex01.py @@ -1,12 +1,11 @@ -# Arithmetic 1D +# Arithmetic 1D and Geometric Progression import salome salome.salome_init() -import GEOM + from salome.geom import geomBuilder geompy = geomBuilder.New(salome.myStudy) -import SMESH, SALOMEDS from salome.smesh import smeshBuilder smesh = smeshBuilder.New(salome.myStudy) @@ -21,12 +20,20 @@ hexa = smesh.Mesh(box, "Box : hexahedrical mesh") algo1D = hexa.Segment() # optionally reverse node distribution on certain edges -allEdges = geompy.SubShapeAllSortedIDs( box, geompy.ShapeType["EDGE"]) +allEdges = geompy.SubShapeAllSorted( box, geompy.ShapeType["EDGE"]) reversedEdges = [ allEdges[0], allEdges[4] ] # define "Arithmetic1D" hypothesis to cut all edges in several segments with increasing arithmetic length algo1D.Arithmetic1D(1, 4, reversedEdges) +# define "Geometric Progression" hypothesis on one edge to cut this edge in segments with length increasing by 20% starting from 1 +gpAlgo = hexa.Segment( allEdges[1] ) +gpAlgo.GeometricProgression( 1, 1.2 ) + +# propagate distribution of nodes computed using "Geometric Progression" to parallel edges +gpAlgo.PropagationOfDistribution() + + # create a quadrangle 2D algorithm for faces hexa.Quadrangle() diff --git a/doc/salome/examples/defining_hypotheses_ex08.py b/doc/salome/examples/defining_hypotheses_ex08.py index 34fcbc377..3c877f3da 100644 --- a/doc/salome/examples/defining_hypotheses_ex08.py +++ b/doc/salome/examples/defining_hypotheses_ex08.py @@ -11,7 +11,8 @@ from salome.smesh import smeshBuilder smesh = smeshBuilder.New(salome.myStudy) # create a box -box = geompy.MakeBoxDXDYDZ(10., 10., 10.) +base = geompy.MakeSketcher("Sketcher:F 0 0:TT 10 0:TT 20 10:TT 0 10:WF", theName="F") +box = geompy.MakePrismDXDYDZ( base, 0,0,10 ) geompy.addToStudy(box, "Box") # get one edge of the box to put local hypothesis on @@ -20,7 +21,7 @@ EdgeX = geompy.GetEdgeNearPoint(box, p5) geompy.addToStudyInFather(box, EdgeX, "Edge [0,0,0 - 10,0,0]") # create a hexahedral mesh on the box -hexa = smesh.Mesh(box, "Box : hexahedrical mesh") +hexa = smesh.Mesh(box, "Propagation of hypothesis") # set global algorithms and hypotheses algo1D = hexa.Segment() @@ -28,15 +29,37 @@ hexa.Quadrangle() hexa.Hexahedron() algo1D.NumberOfSegments(4) -# create a sub-mesh with local 1D hypothesis and propagation +# create a sub-mesh with local 1D hypothesis and "Propagation of 1D Hypothesis" algo_local = hexa.Segment(EdgeX) # define "Arithmetic1D" hypothesis to cut an edge in several segments with increasing length algo_local.Arithmetic1D(1, 4) -# define "Propagation" hypothesis that propagates all other 1D hypotheses -# from all edges on the opposite side of a face in case of quadrangular faces +# define "Propagation" hypothesis that propagates "Arithmetic1D" hypothesis +# from 'EdgeX' on opposite sides of all quadilateral faces algo_local.Propagation() -# compute the mesh +# compute the mesh which contains prisms hexa.Compute() + + +# create another mesh on the box +mesh = smesh.Mesh(box, "Propagation of distribution of nodes") + +# set global algorithms and hypotheses +algo1D = mesh.Segment() +mesh.Quadrangle() +mesh.Hexahedron() +algo1D.NumberOfSegments(4) + +# create a sub-mesh with local 1D hypothesis and "Propagation of Node Distribution" +algo_local = mesh.Segment(EdgeX) +algo_local.Arithmetic1D(1, 4) + +# define "Propagation Of Distribution" hypothesis that propagates +# distribution of nodes generated by "Arithmetic1D" hypothesis +# from 'EdgeX' on opposite sides of all quadilateral faces +algo_local.PropagationOfDistribution() + +# compute the mesh which contains hexahedra only +mesh.Compute() diff --git a/doc/salome/gui/SMESH/images/a-geometric1d.png b/doc/salome/gui/SMESH/images/a-geometric1d.png new file mode 100644 index 0000000000000000000000000000000000000000..a60be94a4a8ff4f2220b53154b6285878001469b GIT binary patch literal 16439 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�!ew#3e~wEJbXx# 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 Pji9BjtCmYTdF_7y-vm63 diff --git a/doc/salome/gui/SMESH/input/1d_meshing_hypo.doc b/doc/salome/gui/SMESH/input/1d_meshing_hypo.doc index 470fe0c4d..b992bc8e2 100644 --- a/doc/salome/gui/SMESH/input/1d_meshing_hypo.doc +++ b/doc/salome/gui/SMESH/input/1d_meshing_hypo.doc @@ -6,6 +6,7 @@
  • \ref adaptive_1d_anchor "Adaptive"
  • \ref arithmetic_1d_anchor "Arithmetic 1D"
  • +
  • \ref geometric_1d_anchor "Geometric Progression"
  • \ref average_length_anchor "Local Length"
  • \ref max_length_anchor "Max Size"
  • \ref deflection_1d_anchor "Deflection 1D"
  • @@ -55,7 +56,30 @@ picking them in the 3D viewer or by selecting the edges or groups of edges in th \image html b-ithmetic1d.png "Arithmetic 1D hypothesis - the size of mesh elements gradually increases" See Also a sample TUI Script of a -\ref tui_1d_arithmetic "Defining Arithmetic 1D hypothesis" operation. +\ref tui_1d_arithmetic "Defining Arithmetic 1D and Geometric Progression hypothesis" operation. + +
    +\anchor geometric_1d_anchor +

    Geometric Progression hypothesis

    + +Geometric Progression hypothesis allows to split edges into +segments with a length that changes in geometric progression (Lk = +Lk-1 * d) beginning from a given starting length and with a given +common ratio. + +The direction of the splitting is defined by the orientation of the +underlying geometrical edge. "Reverse Edges" list box allows to +specify the edges for which the splitting should be made in the +direction opposing to their orientation. This list box is enabled only +if the geometry object is selected for the meshing. In this case the +user can select edges to be reversed either directly picking them in +the 3D viewer or by selecting the edges or groups of edges in the +Object Browser. + +\image html a-geometric1d.png + +See Also a sample TUI Script of a +\ref tui_1d_arithmetic "Defining Arithmetic 1D and Geometric Progression hypothesis" operation.
    \anchor deflection_1d_anchor diff --git a/doc/salome/gui/SMESH/input/about_hypo.doc b/doc/salome/gui/SMESH/input/about_hypo.doc index 09768cc7c..65d5c7ad0 100644 --- a/doc/salome/gui/SMESH/input/about_hypo.doc +++ b/doc/salome/gui/SMESH/input/about_hypo.doc @@ -20,6 +20,7 @@ In \b MESH there are the following Basic Hypotheses:
  • \ref max_length_anchor "Max Size"
  • \ref adaptive_1d_anchor "Adaptive"
  • \ref arithmetic_1d_anchor "Arithmetic 1D"
  • +
  • \ref geometric_1d_anchor "Geometric 1D"
  • \ref start_and_end_length_anchor "Start and end length"
  • \ref deflection_1d_anchor "Deflection 1D"
  • \ref automatic_length_anchor "Automatic Length"
  • @@ -41,6 +42,7 @@ There also exist with other hypotheses:
    • \ref propagation_anchor "Propagation of 1D Hypothesis on opposite edges"
    • +
    • \ref propagofdistribution_anchor "Propagation of Node Distribution on Opposite Edges"
    • \ref viscous_layers_anchor "Viscous layers"
    • \ref quadratic_mesh_anchor "Quadratic mesh"
    • \ref non_conform_allowed_anchor "Non conform mesh allowed"
    • diff --git a/doc/salome/gui/SMESH/input/additional_hypo.doc b/doc/salome/gui/SMESH/input/additional_hypo.doc index 938c7870e..adccc49aa 100644 --- a/doc/salome/gui/SMESH/input/additional_hypo.doc +++ b/doc/salome/gui/SMESH/input/additional_hypo.doc @@ -38,6 +38,19 @@ has been locally defined on the opposite edge.
      See Also a sample TUI Script of a \ref tui_propagation "Propagation hypothesis" operation +\anchor propagofdistribution_anchor +

      Propagation of Node Distribution on Opposite Edges

      + +Propagation of Node Distribution on Opposite Edges allows to propagate +distribution of nodes onto an opposite edge. If a local hypothesis and +propagation are defined on an edge of a quadrangular face, the +opposite edge will have the same number of nodes and the same +relations between segment lengths, unless another hypothesis +has been locally defined on the opposite edge. + +
      See Also a sample TUI Script of a +\ref tui_propagation "Propagation hypothesis" operation + \anchor quadrangle_preference_anchor

      Quadrangle Preference

      @@ -68,29 +81,29 @@ computations.
    • Number of layers - defines the number of element layers.
    • Stretch factor - defines the growth factor of element height from the mesh boundary inwards.
    • -
    • Specified Edges are - defines how the shapes specified by +
    • Specified Faces/Edges are - defines how the shapes specified by the next parameter are used. -
    • Faces without layers and Edges with/without layers - - in the 3D case it defines geometrical faces on which element layers - should not be constructed; in the 2D case it defines geometrical edges - on which element layers either should be or should not be - constructed, depending on the value of the previous parameter - (Specified Edges are). +
    • Faces/Edges with/without layers - + defines geometrical faces or edges on which element layers + either should be or should not be constructed, depending on the + value of the previous parameter (Specified Faces/Edges are). + 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 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).
      - Whatever shapes are specified by this - parameter, 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 the element layers can be - constructed on boundary faces and edges, and are not constructed on - internal faces and edges. There is an exception to this rule in 2D - case: if "Viscous Layers 2D" hypothesis is assigned to a sub-mesh, - the element layers can be constructed on boundary edges of the shape - of this sub-mesh. + 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, + in this mode the element layers can be constructed on boundary faces + and edges only, and are not constructed on internal faces and + edges. There is an exception to this rule: if a hypothesis is + assigned to a sub-mesh, the element layers can be constructed on + boundary faces/edges of the shape of this sub-mesh, at same time + possibly being internal faces/edges within the whole model. \image html viscous_layers_on_submesh.png 2D viscous layers constructed on boundary edges of a sub-mesh on a disk face.
    • @@ -101,5 +114,4 @@ computations.
      See also a sample TUI script of a \ref tui_viscous_layers "Viscous layers construction". - */ diff --git a/doc/salome/gui/SMESH/input/tui_defining_hypotheses.doc b/doc/salome/gui/SMESH/input/tui_defining_hypotheses.doc index 333017d77..863bf76e7 100644 --- a/doc/salome/gui/SMESH/input/tui_defining_hypotheses.doc +++ b/doc/salome/gui/SMESH/input/tui_defining_hypotheses.doc @@ -9,6 +9,7 @@ This page provides example codes of \ref tui_defining_meshing_algos
      • \ref tui_1d_adaptive "Adaptive 1D" hypothesis
      • \ref tui_1d_arithmetic "Arithmetic 1D" hypothesis
      • +
      • \ref tui_1d_arithmetic "Geometric Progression" hypothesis
      • \ref tui_deflection_1d "Deflection 1D and Number of Segments" hypotheses
      • \ref tui_start_and_end_length "Start and End Length" hypotheses
      • \ref tui_average_length "Local Length"
      • @@ -44,7 +45,7 @@ This page provides example codes of \ref tui_defining_meshing_algos
        \anchor tui_1d_arithmetic -

        Arithmetic 1D

        +

        Arithmetic 1D and Geometric Progression

        \tui_script{defining_hypotheses_ex01.py}
        diff --git a/idl/SMESH_BasicHypothesis.idl b/idl/SMESH_BasicHypothesis.idl index 798475b30..186b06681 100644 --- a/idl/SMESH_BasicHypothesis.idl +++ b/idl/SMESH_BasicHypothesis.idl @@ -127,10 +127,36 @@ module StdMeshers double GetFineness(); }; + /*! + * Common inteface of 1D hypotheses that can be reversed + */ + interface Reversible1D + { + /*! + * Set list of edges to reverse + */ + void SetReversedEdges( in SMESH::long_array list ); + + /*! + * Returns list of edges to reverse + */ + SMESH::long_array GetReversedEdges(); + + /*! + * Set entry of the main object + */ + void SetObjectEntry( in string entry ); + + /*! + * Get the entry of the main object + */ + string GetObjectEntry(); + }; + /*! * StdMeshers_NumberOfSegments: interface of "Nb. Segments" hypothesis */ - interface StdMeshers_NumberOfSegments : SMESH::SMESH_Hypothesis + interface StdMeshers_NumberOfSegments : SMESH::SMESH_Hypothesis, Reversible1D { /*! * Builds and returns point distribution according to passed density function @@ -209,32 +235,12 @@ module StdMeshers */ long ConversionMode() raises (SALOME::SALOME_Exception); - - /*! - * Set list of edges to reverse - */ - void SetReversedEdges( in SMESH::long_array list ); - - /*! - * Returns list of edges to reverse - */ - SMESH::long_array GetReversedEdges(); - - /*! - * Set entry of the main object - */ - void SetObjectEntry( in string entry ); - - /*! - * Get the entry of the main object - */ - string GetObjectEntry(); }; /*! * StdMeshers_Arithmetic1D: interface of "Arithmetic 1D" hypothesis */ - interface StdMeshers_Arithmetic1D : SMESH::SMESH_Hypothesis + interface StdMeshers_Arithmetic1D : SMESH::SMESH_Hypothesis, Reversible1D { /*! * Sets or parameter value @@ -260,26 +266,36 @@ module StdMeshers * Returns or parameter value */ double GetLength(in boolean isStartLength); - + + }; + + /*! + * StdMeshers_Arithmetic1D: interface of "Geometric 1D" hypothesis + */ + interface StdMeshers_Geometric1D : SMESH::SMESH_Hypothesis, Reversible1D + { /*! - * Set list of edges to reverse + * Sets length of the first segment */ - void SetReversedEdges( in SMESH::long_array list ); - + void SetStartLength(in double length) + raises (SALOME::SALOME_Exception); + /*! - * Returns list of edges to reverse + * Sets value of Common Ratio */ - SMESH::long_array GetReversedEdges(); - + void SetCommonRatio(in double factor) + raises (SALOME::SALOME_Exception); + /*! - * Set entry of the main object + * Returns length of the first segment */ - void SetObjectEntry( in string entry ); - + double GetStartLength(); + /*! - * Get the entry of the main object + * Returns value of Common Ratio */ - string GetObjectEntry(); + double GetCommonRatio(); + }; /*! @@ -319,7 +335,7 @@ module StdMeshers /*! * StdMeshers_StartEndLength: interface of "Start and End Length" hypothesis */ - interface StdMeshers_StartEndLength : SMESH::SMESH_Hypothesis + interface StdMeshers_StartEndLength : SMESH::SMESH_Hypothesis, Reversible1D { /*! * Sets or parameter value @@ -346,25 +362,6 @@ module StdMeshers */ double GetLength(in boolean isStartLength); - /*! - * Set list of edges to reverse - */ - void SetReversedEdges( in SMESH::long_array list ); - - /*! - * Returns list of edges to reverse - */ - SMESH::long_array GetReversedEdges(); - - /*! - * Set entry of the main object - */ - void SetObjectEntry( in string entry ); - - /*! - * Get the entry of the main object - */ - string GetObjectEntry(); }; @@ -388,7 +385,7 @@ module StdMeshers /*! * StdMeshers_FixedPoints1D: interface of "Fixed points 1D" hypothesis */ - interface StdMeshers_FixedPoints1D : SMESH::SMESH_Hypothesis + interface StdMeshers_FixedPoints1D : SMESH::SMESH_Hypothesis, Reversible1D { /*! * Sets some points on edge using parameter on curve from 0 to 1 @@ -410,26 +407,7 @@ module StdMeshers * Returns list of numbers of segments */ SMESH::long_array GetNbSegments(); - - /*! - * Set list of edges to reverse - */ - void SetReversedEdges( in SMESH::long_array list ); - - /*! - * Returns list of edges to reverse - */ - SMESH::long_array GetReversedEdges(); - - /*! - * Set entry of the main object - */ - void SetObjectEntry( in string entry ); - - /*! - * Get the entry of the main object - */ - string GetObjectEntry(); + }; /*! @@ -483,7 +461,8 @@ module StdMeshers }; /*! - * StdMeshers_Propagation: interface of "Propagation" hypothesis. + * StdMeshers_Propagation: interface of "Propagation of 1D Hyp. on + * Opposite Edges" hypothesis. * Presence of this hypothesis on any edge propagates any other 1D * hypothesis from this edge on all edges, opposite to it. * It concerns only edges of quadrangle faces. @@ -492,6 +471,17 @@ module StdMeshers { }; + /*! + * StdMeshers_Propagation: interface of "Propagation of Node + * Distribution on Opposite Edges" hypothesis. + * Presence of this hypothesis on any edge propagates distribution of nodes + * from this edge on all edges, opposite to it. + * It concerns only edges of quadrangle faces. + */ + interface StdMeshers_PropagOfDistribution : SMESH::SMESH_Hypothesis + { + }; + /*! * StdMeshers_QuadranglePreference: interface of "QuadranglePreference" hypothesis. * This hypothesis is used by StdMeshers_Quadrangle_2D algorithm. @@ -807,6 +797,22 @@ module StdMeshers * Get the type of quadrangulation */ QuadType GetQuadType(); + + /*! + * Set positions of enforced nodes + */ + void SetEnforcedNodes(in GEOM::ListOfGO vertices, in SMESH::nodes_array points) + raises (SALOME::SALOME_Exception); + + /*! + * Returns positions of enforced nodes + */ + void GetEnforcedNodes(out GEOM::ListOfGO vertices, out SMESH::nodes_array points); + + /*! + * Returns entries of shapes defining enforced nodes + */ + SMESH::string_array GetEnfVertices(); }; /*! @@ -865,6 +871,14 @@ module StdMeshers void SetIgnoreFaces(in SMESH::long_array faceIDs) raises (SALOME::SALOME_Exception); SMESH::long_array GetIgnoreFaces(); + /*! + * Set faces either to exclude from treatment or to make the Viscous Layers on. + */ + void SetFaces(in SMESH::long_array faceIDs, + in boolean toIgnore) raises (SALOME::SALOME_Exception); + SMESH::long_array GetFaces(); + boolean GetIsToIgnoreFaces(); + /*! * Set total thickness of layers of prisms */ @@ -936,7 +950,7 @@ module StdMeshers /*! * Set size threshold. A polyhedral cell got by cutting an initial * hexahedron by geometry boundary is considered small and is removed if - * it's size is \athreshold times less than the size of the initial hexahedron. + * it's size is \a threshold times less than the size of the initial hexahedron. * threshold must be > 1.0 */ void SetSizeThreshold(in double threshold) raises (SALOME::SALOME_Exception); @@ -971,6 +985,13 @@ module StdMeshers void GetGridSpacing(out SMESH::string_array spaceFunctions, out SMESH::double_array internalPoints, in short axis) raises (SALOME::SALOME_Exception); + /*! + * Enables implementation of geometrical edges into the mesh. If this feature + * is disabled, sharp edges of the shape are lost ("smoothed") in the mesh if + * they don't coincide with the grid lines + */ + void SetToAddEdges(in boolean toAdd); + boolean GetToAddEdges(); /*! * \brief Computes node coordinates by spacing functions @@ -978,13 +999,15 @@ module StdMeshers * \param x1 - upper coordinate * \param spaceFuns - space functions * \param points - internal points - * \param coords - the computed coordinates + * \param axisName - e.g. "X" + * \return the computed coordinates */ SMESH::double_array ComputeCoordinates(in double x0, in double x1, in SMESH::string_array spaceFuns, in SMESH::double_array points, - in string axisName ) raises (SALOME::SALOME_Exception); + in string axisName ) + raises (SALOME::SALOME_Exception); }; /*! diff --git a/idl/SMESH_Mesh.idl b/idl/SMESH_Mesh.idl index fb85b15af..ff60e68ad 100644 --- a/idl/SMESH_Mesh.idl +++ b/idl/SMESH_Mesh.idl @@ -925,6 +925,11 @@ module SMESH */ long_array GetElemFaceNodes(in long elemId, in short faceIndex); + /*! + * Returns three components of normal of given mesh face (or an empty array in KO case) + */ + double_array GetFaceNormal(in long faceId); + /*! * Returns an element based on all given nodes. */ diff --git a/resources/StdMeshers.xml.in b/resources/StdMeshers.xml.in index d80b4e7d9..d6f6527b6 100644 --- a/resources/StdMeshers.xml.in +++ b/resources/StdMeshers.xml.in @@ -55,6 +55,11 @@ icon-id ="mesh_hypo_length.png" dim ="1"/> + + + + @@ -215,12 +226,14 @@ LocalLength=LocalLength(SetLength(1),,SetPrecision(1)) MaxLength=MaxSize(SetLength(1)) Arithmetic1D=Arithmetic1D(SetStartLength(),SetEndLength(),SetReversedEdges()) + GeometricProgression=GeometricProgression(SetStartLength(),SetCommonRatio(),SetReversedEdges()) StartEndLength=StartEndLength(SetStartLength(),SetEndLength(),SetReversedEdges()) Deflection1D=Deflection1D(SetDeflection()) Adaptive1D=Adaptive(SetMinSize(),SetMaxSize(),SetDeflection()) AutomaticLength=AutomaticLength(SetFineness()) FixedPoints1D=FixedPoints1D(SetPoints(),SetNbSegments(),SetReversedEdges()) Propagation=Propagation() + PropagOfDistribution=PropagationOfDistribution() QuadraticMesh=QuadraticMesh() @@ -228,8 +241,8 @@ @@ -238,12 +251,14 @@ LocalLength=LocalLength(SetLength(), ,SetPrecision()) MaxLength=MaxSize(SetLength()) Arithmetic1D=Arithmetic1D(SetStartLength(),SetEndLength(),SetReversedEdges()) + GeometricProgression=GeometricProgression(SetStartLength(),SetCommonRatio(),SetReversedEdges()) StartEndLength=StartEndLength(SetStartLength(),SetEndLength(),SetReversedEdges()) Deflection1D=Deflection1D(SetDeflection()) Adaptive1D=Adaptive(SetMinSize(),SetMaxSize(),SetDeflection()) AutomaticLength=AutomaticLength(SetFineness()) FixedPoints1D=FixedPoints1D(SetPoints(),SetNbSegments(),SetReversedEdges()) Propagation=Propagation() + PropagOfDistribution=PropagationOfDistribution() QuadraticMesh=QuadraticMesh() @@ -292,6 +307,7 @@ label-id ="Hexahedron (i,j,k)" icon-id ="mesh_algo_hexa.png" input ="QUAD" + output ="HEXA,PENTA" need-geom="false" opt-hypos="ViscousLayers" dim ="3"> @@ -379,6 +395,7 @@ label-id="3D Extrusion" icon-id ="mesh_algo_hexa.png" input ="QUAD,TRIA" + output ="HEXA,PENTA,OCTA,POLYHEDRON" dim ="3"> Prism_3D=Prism() @@ -390,6 +407,7 @@ icon-id ="mesh_algo_hexa.png" hypos ="NumberOfLayers, LayerDistribution" input ="QUAD,TRIA" + output ="HEXA,PENTA,OCTA,POLYHEDRON" dim ="3"> RadialPrism_3D=Prism('RadialPrism_3D') @@ -424,7 +442,7 @@ icon-id ="mesh_algo_quad.png" hypos ="NumberOfLayers2D, LayerDistribution2D" input ="EDGE" - output ="QUAD,TRIA" + output ="QUAD" dim ="2"> RadialQuadrangle_1D2D=Quadrangle(algo=smeshBuilder.RADIAL_QUAD) @@ -437,6 +455,7 @@ icon-id ="mesh_algo_hexa.png" hypos ="CartesianParameters3D" support-submeshes="false" + output ="HEXA" dim ="3"> Cartesian_3D=BodyFitted() diff --git a/src/SMESH/SMESH_Algo.cxx b/src/SMESH/SMESH_Algo.cxx index 19a1e4b09..8beda034e 100644 --- a/src/SMESH/SMESH_Algo.cxx +++ b/src/SMESH/SMESH_Algo.cxx @@ -410,7 +410,7 @@ bool SMESH_Algo::GetSortedNodesOnEdge(const SMESHDS_Mesh* theM return false; SMESHDS_SubMesh * eSubMesh = theMesh->MeshElements( theEdge ); - if ( !eSubMesh || !eSubMesh->GetElements()->more() ) + if ( !eSubMesh || ( eSubMesh->NbElements()==0 && eSubMesh->NbNodes() == 0)) return false; // edge is not meshed int nbNodes = 0; diff --git a/src/SMESH/SMESH_MesherHelper.cxx b/src/SMESH/SMESH_MesherHelper.cxx index b7d8759e9..06f247009 100644 --- a/src/SMESH/SMESH_MesherHelper.cxx +++ b/src/SMESH/SMESH_MesherHelper.cxx @@ -896,7 +896,7 @@ bool SMESH_MesherHelper::CheckNodeU(const TopoDS_Edge& E, const bool force, double distXYZ[4]) const { - int shapeID = n->getshapeId(); + int shapeID = n->getshapeId(); bool infinit = Precision::IsInfinite( u ); if ( force || toCheckPosOnShape( shapeID ) || infinit ) { diff --git a/src/SMESH/SMESH_Pattern.cxx b/src/SMESH/SMESH_Pattern.cxx index 63e6f365f..5d0f5e536 100644 --- a/src/SMESH/SMESH_Pattern.cxx +++ b/src/SMESH/SMESH_Pattern.cxx @@ -2641,10 +2641,10 @@ bool SMESH_Pattern::Apply (const SMDS_MeshFace* theFace, list< const SMDS_MeshNode* > nodes; list< const SMDS_MeshNode* >::iterator n = nodes.end(); - SMDS_ElemIteratorPtr noIt = theFace->nodesIterator(); + SMDS_NodeIteratorPtr noIt = theFace->nodeIterator(); int iSub = 0; while ( noIt->more() && iSub < nbFaceNodes ) { - const SMDS_MeshNode* node = smdsNode( noIt->next() ); + const SMDS_MeshNode* node = noIt->next(); nodes.push_back( node ); if ( iSub++ == theNodeIndexOnKeyPoint1 ) n = --nodes.end(); @@ -2661,7 +2661,7 @@ bool SMESH_Pattern::Apply (const SMDS_MeshFace* theFace, list< gp_XYZ > xyzList; myOrderedNodes.resize( nbFaceNodes ); for ( iSub = 0, n = nodes.begin(); n != nodes.end(); ++n ) { - xyzList.push_back( gp_XYZ( (*n)->X(), (*n)->Y(), (*n)->Z() )); + xyzList.push_back( SMESH_TNodeXYZ( *n )); myOrderedNodes[ iSub++] = *n; } @@ -2963,11 +2963,6 @@ bool SMESH_Pattern::Apply (SMESH_Mesh* theMesh, myXYZ.resize( myPoints.size() * theFaces.size(), undefinedXYZ() ); myElements.reserve( theFaces.size() ); - // to find point index - map< TPoint*, int > pointIndex; - for ( int i = 0; i < myPoints.size(); i++ ) - pointIndex.insert( make_pair( & myPoints[ i ], i )); - int ind1 = 0; // lowest point index for a face // meshed geometry @@ -3019,7 +3014,7 @@ bool SMESH_Pattern::Apply (SMESH_Mesh* theMesh, { list< TPoint* > & linkPoints = getShapePoints( eID++ ); const SMDS_MeshNode* n1 = myOrderedNodes[ i ]; - const SMDS_MeshNode* n2 = myOrderedNodes[ i + 1 == nbNodes ? 0 : i + 1 ]; + const SMDS_MeshNode* n2 = myOrderedNodes[( i+1 ) % nbNodes ]; // make a link and a node set TNodeSet linkSet, node1Set; linkSet.insert( n1 ); @@ -3028,7 +3023,7 @@ bool SMESH_Pattern::Apply (SMESH_Mesh* theMesh, list< TPoint* >::iterator p = linkPoints.begin(); { // map the first link point to n1 - int nId = pointIndex[ *p ] + ind1; + int nId = ( *p - &myPoints[0] ) + ind1; myXYZIdToNodeMap[ nId ] = n1; list< list< int > >& groups = myIdsOnBoundary[ node1Set ]; groups.push_back(list< int > ()); @@ -3040,7 +3035,7 @@ bool SMESH_Pattern::Apply (SMESH_Mesh* theMesh, list< int >& indList = groups.back(); // add points to the map excluding the end points for ( p++; *p != linkPoints.back(); p++ ) - indList.push_back( pointIndex[ *p ] + ind1 ); + indList.push_back( ( *p - &myPoints[0] ) + ind1 ); } ind1 += myPoints.size(); } @@ -3443,7 +3438,7 @@ void SMESH_Pattern::mergePoints (const bool uniteGroups) Bnd_Box box; TNodeSet::const_iterator n = nodes.begin(); for ( ; n != nodes.end(); ++n ) - box.Add( gp_Pnt( (*n)->X(), (*n)->Y(), (*n)->Z() )); + box.Add( gp_Pnt( SMESH_TNodeXYZ( *n ))); double x, y, z, X, Y, Z; box.Get( x, y, z, X, Y, Z ); gp_Pnt p( x, y, z ), P( X, Y, Z ); @@ -3454,7 +3449,7 @@ void SMESH_Pattern::mergePoints (const bool uniteGroups) bool unite = ( uniteGroups && nodes.size() == 2 ); map< double, int > distIndMap; const SMDS_MeshNode* node = *nodes.begin(); - gp_Pnt P( node->X(), node->Y(), node->Z() ); + gp_Pnt P = SMESH_TNodeXYZ( node ); // compare points, replace indices @@ -3928,32 +3923,142 @@ bool SMESH_Pattern::MakeMesh(SMESH_Mesh* theMesh, myXYZ[ i ].Y(), myXYZ[ i ].Z()); } - } + if ( theMesh->HasShapeToMesh() ) + { + // set nodes on EDGEs (IMP 22368) + SMESH_MesherHelper helper( *theMesh ); + helper.ToFixNodeParameters( true ); + map< TNodeSet, list< list< int > > >::iterator idListIt = myIdsOnBoundary.begin(); + for ( ; idListIt != myIdsOnBoundary.end(); idListIt++ ) + { + list >& groups = idListIt->second; + const TNodeSet& nodes = idListIt->first; + if ( nodes.size() != 2 ) + continue; // not a link + const SMDS_MeshNode* n1 = *nodes.begin(); + const SMDS_MeshNode* n2 = *nodes.rbegin(); + TopoDS_Shape S1 = helper.GetSubShapeByNode( n1, aMeshDS ); + TopoDS_Shape S2 = helper.GetSubShapeByNode( n2, aMeshDS ); + if ( S1.IsNull() || S1.ShapeType() < TopAbs_EDGE || + S2.IsNull() || S2.ShapeType() < TopAbs_EDGE ) + continue; + TopoDS_Shape S; + if ( S1.ShapeType() == TopAbs_EDGE ) + { + if ( S1 == S2 || helper.IsSubShape( S2, S1 )) + S = S1; + } + else if ( S2.ShapeType() == TopAbs_EDGE ) + { + if ( helper.IsSubShape( S1, S2 )) + S = S2; + } + else + { + S = helper.GetCommonAncestor( S1, S2, *theMesh, TopAbs_EDGE ); + } + if ( S.IsNull() ) + continue; + const TopoDS_Edge & E = TopoDS::Edge( S ); + helper.SetSubShape( E ); + list >::iterator g = groups.begin(); + for ( ; g != groups.end(); ++g ) + { + list< int >& ids = *g; + list< int >::iterator id = ids.begin(); + for ( ; id != ids.end(); ++id ) + if ( nodesVector[ *id ] && nodesVector[ *id ]->getshapeId() < 1 ) + { + double u = 1e100; + aMeshDS->SetNodeOnEdge( nodesVector[ *id ], E, u ); + helper.CheckNodeU( E, nodesVector[ *id ], u, 1e-7, true ); + } + } + } + } + } // if ( onMeshElements ) + else { nodesVector.resize( myPoints.size(), 0 ); - // to find point index - map< TPoint*, int > pointIndex; - for ( int i = 0; i < myPoints.size(); i++ ) - pointIndex.insert( make_pair( & myPoints[ i ], i )); + // find existing nodes on EDGEs and VERTEXes (IMP 22368) + map< int, list< TPoint* > >::iterator idPointIt = myShapeIDToPointsMap.begin(); + if ( !myShapeIDMap.IsEmpty() && aMeshDS->NbNodes() > 0 ) + + for ( ; idPointIt != myShapeIDToPointsMap.end(); idPointIt++ ) + { + const TopoDS_Shape& S = myShapeIDMap( idPointIt->first ); + list< TPoint* > & points = idPointIt->second; + if ( points.empty() ) + continue; + + switch ( S.ShapeType() ) + { + case TopAbs_VERTEX: + { + int pIndex = points.back() - &myPoints[0]; + if ( !nodesVector[ pIndex ] ) + nodesVector[ pIndex ] = SMESH_Algo::VertexNode( TopoDS::Vertex( S ), aMeshDS ); + break; + } + case TopAbs_EDGE: + { + const TopoDS_Edge& edge = TopoDS::Edge( S ); + map< double, const SMDS_MeshNode* > paramsOfNodes; + if ( !SMESH_Algo::GetSortedNodesOnEdge( aMeshDS, edge, + /*ignoreMediumNodes=*/false, + paramsOfNodes ) + || paramsOfNodes.size() < 3 ) + break; + // points on VERTEXes are included with wrong myU + list< TPoint* >::reverse_iterator pItR = ++points.rbegin(); + list< TPoint* >::iterator pItF = ++points.begin(); + const bool isForward = ( (*pItF)->myU < (*pItR)->myU ); + map< double, const SMDS_MeshNode* >::iterator u2n = ++paramsOfNodes.begin(); + map< double, const SMDS_MeshNode* >::iterator u2nEnd = --paramsOfNodes.end(); + TPoint* p; + while ( u2n != u2nEnd && pItF != points.end() ) + { + const double u = u2n->first; + const SMDS_MeshNode* n = u2n->second; + const double tol = ( (++u2n)->first - u ) / 20; + do + { + p = ( isForward ? *pItF : *pItR ); + if ( Abs( u - p->myU ) < tol ) + { + int pIndex = p - &myPoints[0]; + if ( !nodesVector [ pIndex ] ) + nodesVector [ pIndex ] = n; + ++pItF; + ++pItR; + break; + } + } + while ( p->myU < u && ( ++pItF, ++pItR != points.rend() )); + } + break; + } + default:; + } + } // end of "find existing nodes on EDGEs and VERTEXes" // loop on sub-shapes of myShape: create nodes - map< int, list< TPoint* > >::iterator idPointIt = myShapeIDToPointsMap.begin(); + idPointIt = myShapeIDToPointsMap.begin(); for ( ; idPointIt != myShapeIDToPointsMap.end(); idPointIt++ ) { TopoDS_Shape S; - //SMESHDS_SubMesh * subMeshDS = 0; if ( !myShapeIDMap.IsEmpty() ) { S = myShapeIDMap( idPointIt->first ); - //subMeshDS = aMeshDS->MeshElements( S ); } list< TPoint* > & points = idPointIt->second; list< TPoint* >::iterator pIt = points.begin(); for ( ; pIt != points.end(); pIt++ ) { TPoint* point = *pIt; - int pIndex = pointIndex[ point ]; + //int pIndex = pointIndex[ point ]; + int pIndex = point - &myPoints[0]; if ( nodesVector [ pIndex ] ) continue; SMDS_MeshNode* node = aMeshDS->AddNode (point->myXYZ.X(), @@ -4148,8 +4253,9 @@ void SMESH_Pattern::createElements(SMESH_Mesh* theMes SMDS_ElemIteratorPtr noIt = elem->nodesIterator(); while ( noIt->more() ) { SMDS_MeshNode* node = const_cast(smdsNode( noIt->next() )); - if (!node->getshapeId() && - shellNodes.find( node ) == shellNodes.end() ) { + if ( node->getshapeId() < 1 && + shellNodes.find( node ) == shellNodes.end() ) + { if ( S.ShapeType() == TopAbs_FACE ) aMeshDS->SetNodeOnFace( node, shapeID, Precision::Infinite(),// <- it's a sign that UV is not set diff --git a/src/SMESH/SMESH_Pattern.hxx b/src/SMESH/SMESH_Pattern.hxx index b2606e463..b57624c2d 100644 --- a/src/SMESH/SMESH_Pattern.hxx +++ b/src/SMESH/SMESH_Pattern.hxx @@ -356,7 +356,7 @@ private: // all functions assure that shapes are indexed so that first go // ordered vertices, then ordered edge, then faces and maybe a shell TopTools_IndexedMapOfOrientedShape myShapeIDMap; - std::map< int, std::list< TPoint* > > myShapeIDToPointsMap; + std::map< int, std::list< TPoint*> > myShapeIDToPointsMap; // for the 2d case: // nb of key-points in each of pattern boundaries diff --git a/src/SMESHGUI/SMESHGUI_Hypotheses.cxx b/src/SMESHGUI/SMESHGUI_Hypotheses.cxx index fa9add8e0..e8ebeea25 100644 --- a/src/SMESHGUI/SMESHGUI_Hypotheses.cxx +++ b/src/SMESHGUI/SMESHGUI_Hypotheses.cxx @@ -551,6 +551,8 @@ QString SMESHGUI_GenericHypothesisCreator::helpPage() const aHelpFileName = "a1d_meshing_hypo_page.html#max_length_anchor"; else if ( aHypType == "Arithmetic1D") aHelpFileName = "a1d_meshing_hypo_page.html#arithmetic_1d_anchor"; + else if ( aHypType == "GeometricProgression") + aHelpFileName = "a1d_meshing_hypo_page.html#geometric_1d_anchor"; else if ( aHypType == "FixedPoints1D") aHelpFileName = "a1d_meshing_hypo_page.html#fixed_points_1d_anchor"; else if ( aHypType == "MaxElementArea") diff --git a/src/SMESHGUI/SMESHGUI_HypothesesUtils.cxx b/src/SMESHGUI/SMESHGUI_HypothesesUtils.cxx index 2d45966ed..58003954a 100644 --- a/src/SMESHGUI/SMESHGUI_HypothesesUtils.cxx +++ b/src/SMESHGUI/SMESHGUI_HypothesesUtils.cxx @@ -296,24 +296,45 @@ namespace SMESH } - QStringList GetHypothesesSets(int maxDim) + QStringList GetHypothesesSets(int maxDim, const QString& MeshType) { QStringList aSetNameList; // Init list of available hypotheses, if needed InitAvailableHypotheses(); - QList::iterator hypoSet; - for ( hypoSet = myListOfHypothesesSets.begin(); + for ( hypoSet = myListOfHypothesesSets.begin(); hypoSet != myListOfHypothesesSets.end(); ++hypoSet ) { HypothesesSet* aSet = *hypoSet; - if ( aSet && - ( aSet->count( true ) || aSet->count( false )) && - aSet->maxDim() <= maxDim) + bool isAvailable = false; + if ( !MeshType.isEmpty() ) + { + if ( aSet->maxDim() != maxDim) + continue; + aSet->init( true ); + while ( aSet->next(), aSet->more() ) + { + if ( HypothesisData* hypData = SMESH::GetHypothesisData( aSet->current() ) ) + { + QStringList::const_iterator inElemType = hypData->OutputTypes.begin(); + for ( ; inElemType != hypData->OutputTypes.end(); inElemType++ ) + { + if ( *inElemType == MeshType ){ + isAvailable = true; + break; + } + } + } + if ( isAvailable ) break; + } + } + else if ( aSet && ( aSet->count( true ) || aSet->count( false )) && + aSet->maxDim() <= maxDim) { - aSetNameList.append( mangledHypoSetName( aSet )); + isAvailable = true; } + if ( isAvailable ) aSetNameList.append( mangledHypoSetName( aSet )); } aSetNameList.removeDuplicates(); aSetNameList.sort(); diff --git a/src/SMESHGUI/SMESHGUI_HypothesesUtils.h b/src/SMESHGUI/SMESHGUI_HypothesesUtils.h index a2c12f595..333418b31 100644 --- a/src/SMESHGUI/SMESHGUI_HypothesesUtils.h +++ b/src/SMESHGUI/SMESHGUI_HypothesesUtils.h @@ -71,7 +71,7 @@ namespace SMESH const bool = false, const bool = true); SMESHGUI_EXPORT - QStringList GetHypothesesSets( int maxDim ); + QStringList GetHypothesesSets( int, const QString& ); SMESHGUI_EXPORT HypothesesSet* GetHypothesesSet( const QString& ); diff --git a/src/SMESHGUI/SMESHGUI_MeshDlg.cxx b/src/SMESHGUI/SMESHGUI_MeshDlg.cxx index 4b030bbc0..94d9ce681 100644 --- a/src/SMESHGUI/SMESHGUI_MeshDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_MeshDlg.cxx @@ -364,6 +364,9 @@ SMESHGUI_MeshDlg::SMESHGUI_MeshDlg( const bool theToCreate, const bool theIsMesh // geometry createObject( tr( "GEOMETRY" ), mainFrame(), Geom ); myGeomPopup = 0; + // mesh type + QLabel* anMeshTypeLbl = new QLabel( tr( "MESH_TYPE" ), this ); + myMeshType = new QComboBox( this ); // Create tab widget @@ -398,10 +401,16 @@ SMESHGUI_MeshDlg::SMESHGUI_MeshDlg( const bool theToCreate, const bool theIsMesh aLay->addWidget( objectWg( Geom, Label ), 2, 0 ); aLay->addWidget( objectWg( Geom, Btn ), 2, 1 ); aLay->addWidget( objectWg( Geom, Control ), 2, 2 ); - aLay->addWidget( myTabWg, 4, 0, 1, 3 ); - aLay->addWidget( myHypoSetButton, 5, 0, 1, 3 ); + aLay->addWidget( anMeshTypeLbl, 3, 0 ); + aLay->addWidget( myMeshType, 3, 2 ); + aLay->addWidget( myTabWg, 5, 0, 1, 3 ); + aLay->addWidget( myHypoSetButton, 6, 0, 1, 3 ); aLay->setRowMinimumHeight( 3, 20 ); + myMeshType->clear(); + + // Connect signals and slots + connect( myMeshType, SIGNAL( activated( int ) ), SLOT( onChangedMeshType( int ) ) ); // Disable controls if necessary setObjectShown( Mesh, false ); if ( theToCreate ) @@ -615,3 +624,36 @@ int SMESHGUI_MeshDlg::getActiveObject() return i; return -1; } +//================================================================================ +/*! + * \brief Sets available types of mesh + * \param theTypeMesh - list of available types of mesh + */ +//================================================================================ +void SMESHGUI_MeshDlg::setAvailableMeshType( const QStringList& theTypeMesh ) +{ + myMeshType->clear(); + myMeshType->addItems(theTypeMesh); +} +//================================================================================ +/*! + * \brief Emits selectMeshType( const int, const int ) signal + * + * SLOT is called when a combo box "mesh type" is selected. + */ +//================================================================================ +void SMESHGUI_MeshDlg::onChangedMeshType( const int isIndex ) +{ + emit selectMeshType( Dim3D - myTabWg->currentIndex(), isIndex ); +} +//================================================================================ +/*! + * \brief Get current index types of mesh + */ +//================================================================================ +int SMESHGUI_MeshDlg::currentMeshType( ) +{ + return myMeshType->currentIndex( ); +} + + diff --git a/src/SMESHGUI/SMESHGUI_MeshDlg.h b/src/SMESHGUI/SMESHGUI_MeshDlg.h index 6ba8e6cb9..08f11ff6f 100644 --- a/src/SMESHGUI/SMESHGUI_MeshDlg.h +++ b/src/SMESHGUI/SMESHGUI_MeshDlg.h @@ -74,21 +74,26 @@ public: void enableTab(const int); bool isTabEnabled(const int) const; int getActiveObject(); + void setAvailableMeshType(const QStringList& ); + int currentMeshType(); signals: void hypoSet( const QString& ); void geomSelectionByMesh( bool ); + void selectMeshType( const int, const int ); private slots: void onHypoSetPopup( QAction* ); void onGeomPopup( QAction* ); void onGeomSelectionButton( bool ); + void onChangedMeshType( const int ); private: QMap myTabs; QTabWidget* myTabWg; QToolButton* myHypoSetButton; QMenu* myGeomPopup; + QComboBox* myMeshType; }; /*! diff --git a/src/SMESHGUI/SMESHGUI_MeshOp.cxx b/src/SMESHGUI/SMESHGUI_MeshOp.cxx index 62a5c4b74..eb0a30ed7 100644 --- a/src/SMESHGUI/SMESHGUI_MeshOp.cxx +++ b/src/SMESHGUI/SMESHGUI_MeshOp.cxx @@ -95,6 +95,7 @@ SMESHGUI_MeshOp::SMESHGUI_MeshOp( const bool theToCreate, const bool theIsMesh ) if ( GeometryGUI::GetGeomGen()->_is_nil() )// check that GEOM_Gen exists GeometryGUI::InitGeomGen(); myIsOnGeometry = true; + myMaxShapeDim = -1; } //================================================================================ @@ -211,14 +212,13 @@ void SMESHGUI_MeshOp::startOperation() } connect( myDlg, SIGNAL( hypoSet( const QString& )), SLOT( onHypoSet( const QString& ))); connect( myDlg, SIGNAL( geomSelectionByMesh( bool )), SLOT( onGeomSelectionByMesh( bool ))); - + connect( myDlg, SIGNAL( selectMeshType( const int, const int ) ), SLOT( onAlgoSetByMeshType( const int, const int))); if ( myToCreate ) if ( myIsMesh ) myHelpFileName = "constructing_meshes_page.html"; else myHelpFileName = "constructing_submeshes_page.html"; else myHelpFileName = "editing_meshes_page.html"; } SMESHGUI_SelectionOp::startOperation(); - // iterate through dimensions and get available algoritms, set them to the dialog _PTR(SComponent) aFather = SMESH::GetActiveStudyDocument()->FindComponent( "SMESH" ); for ( int i = SMESH::DIM_0D; i <= SMESH::DIM_3D; i++ ) @@ -245,6 +245,11 @@ void SMESHGUI_MeshOp::startOperation() myDlg->activateObject( SMESHGUI_MeshDlg::Obj ); myDlg->setCurrentTab( SMESH::DIM_3D ); + + QStringList TypeMeshList; + createMeshTypeList( TypeMeshList ); + setAvailableMeshType( TypeMeshList ); + myDlg->show(); myDlg->setGeomPopupEnabled(false); selectionDone(); @@ -582,7 +587,8 @@ void SMESHGUI_MeshOp::selectionDone() onAlgoSelected(-1, i); } myDlg->setMaxHypoDim( shapeDim ); - myDlg->setHypoSets( SMESH::GetHypothesesSets( shapeDim )); + myMaxShapeDim = shapeDim; + myDlg->setHypoSets( SMESH::GetHypothesesSets( shapeDim, "" )); if (!myToCreate) // edition: read hypotheses { @@ -669,12 +675,16 @@ void SMESHGUI_MeshOp::selectionDone() for (int i = SMESH::DIM_0D;i < SMESH::DIM_3D; ++i) { myDlg->disableTab(i); } + myMaxShapeDim = -1; //Hide labels and fields (Mesh ang Geometry) myDlg->setObjectShown( SMESHGUI_MeshDlg::Mesh, false ); myDlg->setObjectShown( SMESHGUI_MeshDlg::Geom, false ); myDlg->adjustSize(); readMesh(); } + QStringList TypeMeshList; + createMeshTypeList( TypeMeshList ); + setAvailableMeshType( TypeMeshList ); } catch ( const SALOME::SALOME_Exception& S_ex ) { @@ -1380,7 +1390,19 @@ void SMESHGUI_MeshOp::onAlgoSelected( const int theIndex, availableHyps( aDim, Algo, anAvailable, myAvailableHypData[ aDim ][ Algo ]); myDlg->tab( aDim )->setAvailableHyps( Algo, anAvailable ); } - + // check that tab enable, if algorithm building needed algo is one less than dimension + if ( algoData && myIsOnGeometry && !algoData->InputTypes.isEmpty() && + ( aDim > SMESH::DIM_0D ) && !isAccessibleDim( aDim - 1 ) ){ + myDlg->enableTab( aDim - 1 ); + } + if ( (myDlg->currentMeshType() != MT_ANY) && + (( !algoData && ( aDim > SMESH::DIM_0D ) && isAccessibleDim( aDim - 1 )) || + ( algoData && myIsOnGeometry && algoData->InputTypes.isEmpty() && + ( aDim > SMESH::DIM_0D ) && isAccessibleDim( aDim - 1 ) ) ) ){ + for (int i = aDim - 1; i >= SMESH::DIM_0D; i--){ + if ( isAccessibleDim( i ) ) myDlg->disableTab( i ); + } + } // check that algorithms of other dimentions are compatible with // the selected one @@ -2343,3 +2365,193 @@ void SMESHGUI_MeshOp::selectObject( _PTR(SObject) theSObj ) const sm->setSelectedObjects( anIOList, false ); } } +//================================================================================ +/*! + * \brief Create available list types of mesh + * \param theTypeMesh - Output list of available types of mesh + */ +//================================================================================ +void SMESHGUI_MeshOp::createMeshTypeList( QStringList& theTypeMesh) +{ + theTypeMesh.clear(); + theTypeMesh.append( tr( "MT_ANY" ) ); + if ( myIsOnGeometry && ( myMaxShapeDim >= 2 || myMaxShapeDim == -1 ) ) + { + theTypeMesh.append( tr( "MT_TRIANGULAR" ) ); + theTypeMesh.append( tr( "MT_QUADRILATERAL" ) ); + } + if ( myIsOnGeometry && ( myMaxShapeDim == 3 || myMaxShapeDim == -1 ) ) + { + theTypeMesh.append( tr( "MT_TETRAHEDRAL" ) ); + theTypeMesh.append( tr( "MT_HEXAHEDRAL" ) ); + } + +} +//================================================================================ +/*! + * \brief Set available types of mesh + * \param theTypeMesh - List of available types of mesh + */ +//================================================================================ +void SMESHGUI_MeshOp::setAvailableMeshType( const QStringList& theTypeMesh ) +{ + myDlg->setAvailableMeshType( theTypeMesh ); +} + +//================================================================================ +/*! + * \brief SLOT. Is called when the user select type of mesh + * \param theTabIndex - Index of current active tab + * \param theIndex - Index of current type of mesh + */ +//================================================================================ +void SMESHGUI_MeshOp::onAlgoSetByMeshType( const int theTabIndex, const int theIndex) +{ + int aDim; + if ( !myIsOnGeometry ) return; + THypDataList anAvailableAlgsData; + QStringList anAvailableAlgs; + QString anCompareType = "ANY"; + bool isAvailableChoiceAlgo = false; + int anCurrentAvailableAlgo = 0; + bool isNone = true; + switch ( theIndex ) { + case MT_ANY: + { + for ( int dim = SMESH::DIM_2D; dim <= SMESH::DIM_3D; dim++ ) + { + isNone = currentHyp( dim, Algo ) < 0; + isAvailableChoiceAlgo = false; + // retrieves a list of available algorithms from resources + availableHyps( dim, Algo, anAvailableAlgs, anAvailableAlgsData ); + //return current algo in current tab + if ( !isNone && !myAvailableHypData[dim][Algo].empty() ){ + for (int i = 0 ; i < anAvailableAlgsData.count(); i++) + { + HypothesisData* algoAny = anAvailableAlgsData.at(i); + HypothesisData* algoCur = myAvailableHypData[dim][Algo].at( currentHyp( dim, Algo ) ); + QString tem = algoAny->Label; + if ( algoAny->Label == algoCur->Label ){ + isAvailableChoiceAlgo = true; + anCurrentAvailableAlgo = i; + break; + } + } + } + else if ( !isNone ){ + isAvailableChoiceAlgo = true; + anCurrentAvailableAlgo = currentHyp( dim, Algo ); + } + //set new algorithm list and select the current algorithm + myAvailableHypData[dim][Algo] = anAvailableAlgsData; + myDlg->tab( dim )->setAvailableHyps( Algo, anAvailableAlgs ); + if ( isAvailableChoiceAlgo ) + setCurrentHyp( dim, Algo, anCurrentAvailableAlgo ); + } + int aMaxShapeDim = ( myMaxShapeDim == -1 ) ? SMESH::DIM_3D : myMaxShapeDim; + for ( int i = SMESH::DIM_0D; i <= aMaxShapeDim; i++ ) { + myDlg->enableTab( i ); + } + myDlg->setCurrentTab( theTabIndex ); + myDlg->setHypoSets( SMESH::GetHypothesesSets( aMaxShapeDim, "" ) ); + } + break; + case MT_TRIANGULAR:{ + aDim = SMESH::DIM_2D; + anCompareType = "TRIA"; + } + break; + case MT_QUADRILATERAL:{ + aDim = SMESH::DIM_2D; + anCompareType = "QUAD"; + } + break; + case MT_TETRAHEDRAL:{ + aDim = SMESH::DIM_3D; + anCompareType = "TETRA"; + } + break; + case MT_HEXAHEDRAL:{ + aDim = SMESH::DIM_3D; + anCompareType = "HEXA"; + } + break; + default:; + } + if ( anCompareType != "ANY" ) + { + QString anCurrentAlgo; + bool isReqDisBound = true; + isNone = currentHyp( aDim, Algo ) < 0; + // retrieves a list of available algorithms from resources + availableHyps( aDim, Algo, anAvailableAlgs, anAvailableAlgsData ); + // finding algorithm which is selected + if ( !isNone && !myAvailableHypData[aDim][Algo].empty() && + myAvailableHypData[aDim][Algo].count() != anAvailableAlgsData.count() ){ + anCurrentAlgo = myAvailableHypData[aDim][Algo].at( currentHyp( aDim, Algo ) )->Label; + isReqDisBound = myAvailableHypData[aDim][Algo].at( currentHyp( aDim, Algo ) )->InputTypes.isEmpty(); + } + else if ( !isNone ){ + anCurrentAlgo = anAvailableAlgsData.at( currentHyp( aDim, Algo ) )->Label; + isReqDisBound = anAvailableAlgsData.at( currentHyp( aDim, Algo ) )->InputTypes.isEmpty(); + } + anAvailableAlgs.clear(); + myAvailableHypData[aDim][Algo].clear(); + // finding and adding algorithm depending on the type mesh + for ( int i = 0 ; i < anAvailableAlgsData.count(); i++ ) + { + HypothesisData* algoIn = anAvailableAlgsData.at( i ); + bool isAvailableAlgo = ( algoIn->OutputTypes.count() == 0 ); + QStringList::const_iterator inElemType = algoIn->OutputTypes.begin(); + for ( ; inElemType != algoIn->OutputTypes.end(); inElemType++ ) + { + if ( *inElemType == anCompareType ){ + isAvailableAlgo = true; + break; + } + } + if ( isAvailableAlgo || algoIn->OutputTypes.count()==0 ){ + anAvailableAlgs.append( algoIn->Label ); + myAvailableHypData[aDim][Algo].append( algoIn ); + } + //algorithm will be active, if the chosen algorithm available in the current mesh type + if ( !isNone && isAvailableAlgo && algoIn->Label == anCurrentAlgo ){ + isAvailableChoiceAlgo = true; + anCurrentAvailableAlgo = anAvailableAlgs.count() - 1 ; + } + } + //set new algorithm list and select the current algorithm + myDlg->tab( aDim )->setAvailableHyps( Algo, anAvailableAlgs ); + if ( isAvailableChoiceAlgo ) + setCurrentHyp( aDim, Algo, anCurrentAvailableAlgo ); + int aMaxShapeDim = ( myMaxShapeDim == -1 ) ? SMESH::DIM_3D : myMaxShapeDim; + if ( isNone || isReqDisBound || !isAvailableChoiceAlgo ) { + for ( int i = SMESH::DIM_0D; i <= aMaxShapeDim; i++ ) { + if ( aDim != i ) { + myDlg->disableTab( i ); + setCurrentHyp(i, Algo, -1); + } + } + } + else if ( !isNone ){ + if ( aDim == SMESH::DIM_2D){ + myDlg->disableTab( SMESH::DIM_3D ); + setCurrentHyp( SMESH::DIM_3D, Algo, -1); + } + for ( int i = aMaxShapeDim; i > SMESH::DIM_0D; i-- ) + { + isReqDisBound = ( currentHyp( i, Algo ) < 0 ) ? true : myAvailableHypData[i][Algo].at( currentHyp( i, Algo ) )->InputTypes.isEmpty(); + if ( aMaxShapeDim != i && isReqDisBound) { + for (int j = i - 1; j >= SMESH::DIM_0D; j--){ + myDlg->disableTab( j ); + setCurrentHyp( j , Algo, -1 ); + } + break; + } + } + } + myDlg->setHypoSets( SMESH::GetHypothesesSets( aDim, anCompareType ) ); + myDlg->enableTab( aDim ); + myDlg->setCurrentTab( aDim ); + } +} diff --git a/src/SMESHGUI/SMESHGUI_MeshOp.h b/src/SMESHGUI/SMESHGUI_MeshOp.h index 91c39ed72..5f5688267 100644 --- a/src/SMESHGUI/SMESHGUI_MeshOp.h +++ b/src/SMESHGUI/SMESHGUI_MeshOp.h @@ -48,6 +48,7 @@ class SMESHGUI_EXPORT SMESHGUI_MeshOp : public SMESHGUI_SelectionOp public: enum HypType{ Algo = 0, MainHyp, AddHyp, NbHypTypes }; + enum MeshType{ MT_ANY = 0, MT_TRIANGULAR, MT_QUADRILATERAL, MT_TETRAHEDRAL, MT_HEXAHEDRAL }; typedef std::pair THypItem; typedef QList< THypItem > THypList; @@ -83,6 +84,7 @@ protected slots: void processSet(); void onHypoCreated( int ); void onHypoEdited( int ); + void onAlgoSetByMeshType( const int, const int ); private: typedef QList THypDataList; // typedef: list of hypothesis data @@ -125,7 +127,8 @@ private: char* isSubmeshIgnored() const; _PTR(SObject) getSubmeshByGeom() const; void selectObject( _PTR(SObject) ) const; - + void createMeshTypeList( QStringList& ); + void setAvailableMeshType( const QStringList& ); private: SMESHGUI_MeshDlg* myDlg; SMESHGUI_ShapeByMeshOp* myShapeByMeshOp; @@ -136,13 +139,12 @@ private: TDim2Type2HypList myExistingHyps; //!< all hypothesis of SMESH module TDim2Type2HypList myObjHyps; //!< hypothesis assigned to the current // edited mesh/sub-mesh - // hypdata corresponding to hypotheses present in myDlg THypDataList myAvailableHypData[4][NbHypTypes]; bool myIgnoreAlgoSelection; HypothesesSet* myHypoSet; - int myDim, myType; + int myDim, myType, myMaxShapeDim; QString myObjectToSelect; }; diff --git a/src/SMESHGUI/SMESH_msg_en.ts b/src/SMESHGUI/SMESH_msg_en.ts index 174bcf165..df41ace14 100644 --- a/src/SMESHGUI/SMESH_msg_en.ts +++ b/src/SMESHGUI/SMESH_msg_en.ts @@ -5977,6 +5977,10 @@ Please specify them and try again MESH Mesh + + MESH_TYPE + Mesh type + NAME Name @@ -6032,6 +6036,26 @@ Please specify it and try again MESH_IS_NULL Mesh is null + + MT_ANY + Any + + + MT_HEXAHEDRAL + Hexahedral + + + MT_TETRAHEDRAL + Tetrahedral + + + MT_TRIANGULAR + Triangular + + + MT_QUADRILATERAL + Quadrilaterial + NAME_OF_MESH_IS_EMPTY Name of mesh is empty diff --git a/src/SMESHGUI/SMESH_msg_fr.ts b/src/SMESHGUI/SMESH_msg_fr.ts index 749b17e7e..b8ddd3e1b 100755 --- a/src/SMESHGUI/SMESH_msg_fr.ts +++ b/src/SMESHGUI/SMESH_msg_fr.ts @@ -5971,6 +5971,10 @@ Indiquez-les et essayez de nouveau MESH Maillage + + MESH_TYPE + Mesh type + NAME Nom @@ -6026,6 +6030,26 @@ Spécifiez-le et essayez de nouveau MESH_IS_NULL Le maillage est nul + + MT_ANY + Any + + + MT_HEXAHEDRAL + Hexahedral + + + MT_TETRAHEDRAL + Tetrahedral + + + MT_TRIANGULAR + Triangular + + + MT_QUADRILATERAL + Quadrilaterial + NAME_OF_MESH_IS_EMPTY Le nom du maillage est vide diff --git a/src/SMESHUtils/SMESH_Block.cxx b/src/SMESHUtils/SMESH_Block.cxx index 7d2ed2a71..fd40175d8 100644 --- a/src/SMESHUtils/SMESH_Block.cxx +++ b/src/SMESHUtils/SMESH_Block.cxx @@ -26,6 +26,11 @@ // #include "SMESH_Block.hxx" +#include "SMDS_MeshNode.hxx" +#include "SMDS_MeshVolume.hxx" +#include "SMDS_VolumeTool.hxx" +#include "SMESH_MeshAlgos.hxx" + #include #include #include @@ -56,10 +61,7 @@ #include #include -#include "SMDS_MeshNode.hxx" -#include "SMDS_MeshVolume.hxx" -#include "SMDS_VolumeTool.hxx" -#include "utilities.h" +#include #include #include @@ -309,24 +311,15 @@ gp_XYZ SMESH_Block::TFace::Point( const gp_XYZ& theParams ) const namespace { + inline bool isPntInTria( const gp_XY& p, const gp_XY& t0, const gp_XY& t1, const gp_XY& t2 ) { - const double // matrix 2x2 - T11 = t0.X()-t2.X(), T12 = t1.X()-t2.X(), - T21 = t0.Y()-t2.Y(), T22 = t1.Y()-t2.Y(); - const double Tdet = T11*T22 - T12*T21; // matrix determinant - if ( Abs( Tdet ) < std::numeric_limits::min() ) - return false; - // matrix inverse - const double t11 = T22, t12 = -T12, t21 = -T21, t22 = T11; - // vector - const double r11 = p.X()-t2.X(), r12 = p.Y()-t2.Y(); - // barycentric coordinates: mutiply matrix by vector - const double bc0 = (t11 * r11 + t12 * r12)/Tdet; - const double bc1 = (t21 * r11 + t22 * r12)/Tdet; + double bc0, bc1; + SMESH_MeshAlgos::GetBarycentricCoords( p, t0, t1, t2, bc0, bc1 ); return ( bc0 >= 0. && bc1 >= 0. && bc0 + bc1 <= 1. ); } + inline bool isPntInQuad( const gp_XY& p, const gp_XY& q0, const gp_XY& q1, const gp_XY& q2, const gp_XY& q3 ) { diff --git a/src/SMESHUtils/SMESH_Block.hxx b/src/SMESHUtils/SMESH_Block.hxx index a2fcd0a1a..4fc690137 100644 --- a/src/SMESHUtils/SMESH_Block.hxx +++ b/src/SMESHUtils/SMESH_Block.hxx @@ -68,20 +68,19 @@ class SMESHUtils_EXPORT SMESH_Block: public math_FunctionSetWithDerivatives // ---------------------------- ID_NONE = 0, - ID_V000 = 1, ID_V100, ID_V010, ID_V110, ID_V001, ID_V101, ID_V011, ID_V111, + ID_V000 = 1, ID_V100, ID_V010, ID_V110, ID_V001, ID_V101, ID_V011, ID_V111, // 1-8 - ID_Ex00, ID_Ex10, ID_Ex01, ID_Ex11, - ID_E0y0, ID_E1y0, ID_E0y1, ID_E1y1, - ID_E00z, ID_E10z, ID_E01z, ID_E11z, + ID_Ex00, ID_Ex10, ID_Ex01, ID_Ex11, // 9-12 + ID_E0y0, ID_E1y0, ID_E0y1, ID_E1y1, // 13-16 + ID_E00z, ID_E10z, ID_E01z, ID_E11z, // 17-20 - ID_Fxy0, ID_Fxy1, ID_Fx0z, ID_Fx1z, ID_F0yz, ID_F1yz, + ID_Fxy0, ID_Fxy1, ID_Fx0z, ID_Fx1z, ID_F0yz, ID_F1yz, // 21-26 - ID_Shell - }; - enum { // to use TShapeID for indexing certain type subshapes + ID_Shell, // 27 - ID_FirstV = ID_V000, ID_FirstE = ID_Ex00, ID_FirstF = ID_Fxy0 + // to use TShapeID for indexing certain type subshapes + ID_FirstV = ID_V000, ID_FirstE = ID_Ex00, ID_FirstF = ID_Fxy0 }; @@ -285,7 +284,7 @@ public: std::list< int > & theNbEdgesInWires, TopoDS_Vertex theFirstVertex=TopoDS_Vertex(), const bool theShapeAnalysisAlgo=false); - // Return nb wires and a list of oredered edges. + // Return nb wires and a list of ordered edges. // It is used to assign indices to subshapes. // theFirstVertex may be NULL. // Always try to set a seam edge first diff --git a/src/SMESHUtils/SMESH_MeshAlgos.cxx b/src/SMESHUtils/SMESH_MeshAlgos.cxx index 45acf33b1..2ecd29a90 100644 --- a/src/SMESHUtils/SMESH_MeshAlgos.cxx +++ b/src/SMESHUtils/SMESH_MeshAlgos.cxx @@ -1386,6 +1386,39 @@ double SMESH_MeshAlgos::GetDistance( const SMDS_MeshFace* face, return badDistance; } +//================================================================================ +/*! + * \brief Returns barycentric coordinates of a point within a triangle. + * A not returned bc2 = 1. - bc0 - bc1. + * The point lies within the triangle if ( bc0 >= 0 && bc1 >= 0 && bc0+bc1 <= 1 ) + */ +//================================================================================ + +void SMESH_MeshAlgos::GetBarycentricCoords( const gp_XY& p, + const gp_XY& t0, + const gp_XY& t1, + const gp_XY& t2, + double & bc0, + double & bc1) +{ + const double // matrix 2x2 + T11 = t0.X()-t2.X(), T12 = t1.X()-t2.X(), + T21 = t0.Y()-t2.Y(), T22 = t1.Y()-t2.Y(); + const double Tdet = T11*T22 - T12*T21; // matrix determinant + if ( Abs( Tdet ) < std::numeric_limits::min() ) + { + bc0 = bc1 = 2.; + return; + } + // matrix inverse + const double t11 = T22, t12 = -T12, t21 = -T21, t22 = T11; + // vector + const double r11 = p.X()-t2.X(), r12 = p.Y()-t2.Y(); + // barycentric coordinates: mutiply matrix by vector + bc0 = (t11 * r11 + t12 * r12)/Tdet; + bc1 = (t21 * r11 + t22 * r12)/Tdet; +} + //======================================================================= //function : FindFaceInSet //purpose : Return a face having linked nodes n1 and n2 and which is diff --git a/src/SMESHUtils/SMESH_MeshAlgos.hxx b/src/SMESHUtils/SMESH_MeshAlgos.hxx index a02ba10dd..dc4d8fcb4 100644 --- a/src/SMESHUtils/SMESH_MeshAlgos.hxx +++ b/src/SMESHUtils/SMESH_MeshAlgos.hxx @@ -97,9 +97,16 @@ namespace SMESH_MeshAlgos /*! * \brief Return true if the point is IN or ON of the element */ - SMESHUtils_EXPORT bool IsOut( const SMDS_MeshElement* element, const gp_Pnt& point, double tol ); + SMESHUtils_EXPORT + bool IsOut( const SMDS_MeshElement* element, const gp_Pnt& point, double tol ); - SMESHUtils_EXPORT double GetDistance( const SMDS_MeshFace* face, const gp_Pnt& point ); + SMESHUtils_EXPORT + double GetDistance( const SMDS_MeshFace* face, const gp_Pnt& point ); + + SMESHUtils_EXPORT + void GetBarycentricCoords( const gp_XY& point, + const gp_XY& t0, const gp_XY& t1, const gp_XY& t2, + double & bc0, double & bc1); /*! * Return a face having linked nodes n1 and n2 and which is @@ -107,35 +114,40 @@ namespace SMESH_MeshAlgos * - in elemSet provided that !elemSet.empty() * i1 and i2 optionally returns indices of n1 and n2 */ - SMESHUtils_EXPORT const SMDS_MeshElement* - FindFaceInSet(const SMDS_MeshNode* n1, - const SMDS_MeshNode* n2, - const TIDSortedElemSet& elemSet, - const TIDSortedElemSet& avoidSet, - int* i1=0, - int* i2=0); + SMESHUtils_EXPORT + const SMDS_MeshElement* FindFaceInSet(const SMDS_MeshNode* n1, + const SMDS_MeshNode* n2, + const TIDSortedElemSet& elemSet, + const TIDSortedElemSet& avoidSet, + int* i1=0, + int* i2=0); /*! * \brief Calculate normal of a mesh face */ - SMESHUtils_EXPORT bool FaceNormal(const SMDS_MeshElement* F, gp_XYZ& normal, bool normalized=true); + SMESHUtils_EXPORT + bool FaceNormal(const SMDS_MeshElement* F, gp_XYZ& normal, bool normalized=true); /*! * \brief Return nodes common to two elements */ - SMESHUtils_EXPORT std::vector< const SMDS_MeshNode*> GetCommonNodes(const SMDS_MeshElement* e1, + SMESHUtils_EXPORT + std::vector< const SMDS_MeshNode*> GetCommonNodes(const SMDS_MeshElement* e1, const SMDS_MeshElement* e2); /*! * \brief Return SMESH_NodeSearcher. The caller is responsible for deleteing it */ - SMESHUtils_EXPORT SMESH_NodeSearcher* GetNodeSearcher( SMDS_Mesh& mesh ); + SMESHUtils_EXPORT + SMESH_NodeSearcher* GetNodeSearcher( SMDS_Mesh& mesh ); /*! * \brief Return SMESH_ElementSearcher. The caller is responsible for deleting it */ - SMESHUtils_EXPORT SMESH_ElementSearcher* GetElementSearcher( SMDS_Mesh& mesh ); - SMESHUtils_EXPORT SMESH_ElementSearcher* GetElementSearcher( SMDS_Mesh& mesh, - SMDS_ElemIteratorPtr elemIt ); + SMESHUtils_EXPORT + SMESH_ElementSearcher* GetElementSearcher( SMDS_Mesh& mesh ); + SMESHUtils_EXPORT + SMESH_ElementSearcher* GetElementSearcher( SMDS_Mesh& mesh, + SMDS_ElemIteratorPtr elemIt ); } #endif diff --git a/src/SMESHUtils/SMESH_TypeDefs.hxx b/src/SMESHUtils/SMESH_TypeDefs.hxx index 2555cc371..6e5bda2a5 100644 --- a/src/SMESHUtils/SMESH_TypeDefs.hxx +++ b/src/SMESHUtils/SMESH_TypeDefs.hxx @@ -29,9 +29,10 @@ #include "SMESH_Utils.hxx" -#include +#include "SMDS_MeshNode.hxx" #include +#include #include #include @@ -42,7 +43,7 @@ typedef std::map, TIDCompare > TElemOfElemListMap; typedef std::map, TIDCompare > TElemOfNodeListMap; -typedef std::map TNodeNodeMap; +typedef std::map TNodeNodeMap; //!< Set of elements sorted by ID, to be used to assure predictability of edition typedef std::set< const SMDS_MeshElement*, TIDCompare > TIDSortedElemSet; @@ -50,8 +51,8 @@ typedef std::set< const SMDS_MeshNode*, TIDCompare > TIDSortedNodeSet; typedef std::pair< const SMDS_MeshNode*, const SMDS_MeshNode* > NLink; -struct faceQuadStruct; // defined in StdMeshers_Quadrangle_2D.hxx -typedef boost::shared_ptr TFaceQuadStructPtr; +struct FaceQuadStruct; // defined in StdMeshers_Quadrangle_2D.hxx +typedef boost::shared_ptr TFaceQuadStructPtr; namespace SMESHUtils @@ -137,6 +138,10 @@ typedef struct uvPtStruct double x, y; // 2d parameter, normalized [0,1] const SMDS_MeshNode * node; + uvPtStruct(): node(NULL) {} + + inline gp_XY UV() const { return gp_XY( u, v ); } + struct NodeAccessor // accessor to iterate on nodes in UVPtStructVec { static const SMDS_MeshNode* value(std::vector< uvPtStruct >::const_iterator it) diff --git a/src/SMESH_I/SMESH_2smeshpy.cxx b/src/SMESH_I/SMESH_2smeshpy.cxx index 929da0dcf..7a59ad87f 100644 --- a/src/SMESH_I/SMESH_2smeshpy.cxx +++ b/src/SMESH_I/SMESH_2smeshpy.cxx @@ -2075,6 +2075,7 @@ bool _pyMesh::NeedMeshAccess( const Handle(_pyCommand)& theCommand ) "GetSubMeshElementsId","GetSubMeshNodesId","GetSubMeshElementType","Dump","GetNodeXYZ", "GetNodeInverseElements","GetShapeID","GetShapeIDForElem","GetElemNbNodes", "GetElemNode","IsMediumNode","IsMediumNodeOfAnyElem","ElemNbEdges","ElemNbFaces", + "GetElemFaceNodes", "GetFaceNormal", "FindElementByNodes", "IsPoly","IsQuadratic","BaryCenter","GetHypothesisList", "SetAutoColor", "GetAutoColor", "Clear", "ConvertToStandalone", "GetMeshOrder", "SetMeshOrder" ,"" }; // <- mark of end diff --git a/src/SMESH_I/SMESH_DumpPython.cxx b/src/SMESH_I/SMESH_DumpPython.cxx index 43ffd029b..78d9bf959 100644 --- a/src/SMESH_I/SMESH_DumpPython.cxx +++ b/src/SMESH_I/SMESH_DumpPython.cxx @@ -240,13 +240,20 @@ namespace SMESH template void DumpArray(const TArray& theArray, TPythonDump & theStream) { - theStream << "[ "; - for (int i = 1; i <= theArray.length(); i++) { - theStream << theArray[i-1]; - if ( i < theArray.length() ) - theStream << ", "; + if ( theArray.length() == 0 ) + { + theStream << "[]"; + } + else + { + theStream << "[ "; + for (int i = 1; i <= theArray.length(); i++) { + theStream << theArray[i-1]; + if ( i < theArray.length() ) + theStream << ", "; + } + theStream << " ]"; } - theStream << " ]"; } TPythonDump& @@ -263,6 +270,13 @@ namespace SMESH return *this; } + TPythonDump& + TPythonDump::operator<<(const SMESH::nodes_array& theArg) + { + DumpArray( theArg, *this ); + return *this; + } + TPythonDump& TPythonDump::operator<<(const SMESH::string_array& theArray) { @@ -517,6 +531,11 @@ namespace SMESH DumpArray( *theList, *this ); return *this; } + TPythonDump& TPythonDump::operator<<(const GEOM::ListOfGO& theList) + { + DumpArray( theList, *this ); + return *this; + } TPythonDump& TPythonDump::operator<<(const SMESH::ListOfIDSources& theList) { DumpArray( theList, *this ); diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 812d3c0cd..f6beb92d5 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -44,6 +44,7 @@ #include "SMESH_Gen_i.hxx" #include "SMESH_Group.hxx" #include "SMESH_Group_i.hxx" +#include "SMESH_MeshAlgos.hxx" #include "SMESH_MeshEditor.hxx" #include "SMESH_MeshEditor_i.hxx" #include "SMESH_MeshPartDS.hxx" @@ -4023,6 +4024,32 @@ SMESH::long_array* SMESH_Mesh_i::GetElemFaceNodes(CORBA::Long elemId, return aResult._retn(); } +//======================================================================= +//function : GetElemFaceNodes +//purpose : Returns three components of normal of given mesh face. +//======================================================================= + +SMESH::double_array* SMESH_Mesh_i::GetFaceNormal(CORBA::Long elemId) +{ + if ( _preMeshInfo ) + _preMeshInfo->FullLoadFromFile(); + + SMESH::double_array_var aResult = new SMESH::double_array(); + + if ( SMESHDS_Mesh* mesh = _impl->GetMeshDS() ) + { + gp_XYZ normal; + if ( SMESH_MeshAlgos::FaceNormal( mesh->FindElement(elemId), normal, /*normalized=*/true )) + { + aResult->length( 3 ); + aResult[ 0 ] = normal.X(); + aResult[ 1 ] = normal.Y(); + aResult[ 2 ] = normal.Z(); + } + } + return aResult._retn(); +} + //======================================================================= //function : FindElementByNodes //purpose : Returns an element based on all given nodes. diff --git a/src/SMESH_I/SMESH_Mesh_i.hxx b/src/SMESH_I/SMESH_Mesh_i.hxx index c52699bb2..2615bef48 100644 --- a/src/SMESH_I/SMESH_Mesh_i.hxx +++ b/src/SMESH_I/SMESH_Mesh_i.hxx @@ -532,6 +532,11 @@ public: */ SMESH::long_array* GetElemFaceNodes(CORBA::Long elemId, CORBA::Short faceIndex); + /*! + * Returns three components of normal of given mesh face (or an empty array in KO case) + */ + SMESH::double_array* GetFaceNormal(CORBA::Long faceId); + /*! * Returns an element based on all given nodes. */ diff --git a/src/SMESH_I/SMESH_PythonDump.hxx b/src/SMESH_I/SMESH_PythonDump.hxx index 595e732fa..1f9d5a623 100644 --- a/src/SMESH_I/SMESH_PythonDump.hxx +++ b/src/SMESH_I/SMESH_PythonDump.hxx @@ -27,6 +27,7 @@ #include #include CORBA_SERVER_HEADER(SMESH_Mesh) +#include CORBA_SERVER_HEADER(GEOM_Gen) #include CORBA_SERVER_HEADER(SALOMEDS) #include @@ -162,6 +163,9 @@ namespace SMESH TPythonDump& operator<<(const SMESH::string_array& theArg); + TPythonDump& + operator<<(const SMESH::nodes_array& theArg); + TPythonDump& operator<<(SMESH::SMESH_Hypothesis_ptr theArg); @@ -216,6 +220,9 @@ namespace SMESH TPythonDump& operator<<(const SMESH::ListOfGroups * theList); + TPythonDump& + operator<<(const GEOM::ListOfGO& theList); + TPythonDump& operator<<(const SMESH::ListOfIDSources& theList); diff --git a/src/SMESH_SWIG/StdMeshersBuilder.py b/src/SMESH_SWIG/StdMeshersBuilder.py index e28659b55..4e5b2d217 100644 --- a/src/SMESH_SWIG/StdMeshersBuilder.py +++ b/src/SMESH_SWIG/StdMeshersBuilder.py @@ -199,7 +199,8 @@ class StdMeshersBuilder_Segment(Mesh_Algorithm): hyp.SetDeflection(deflection) return hyp - ## Defines "Arithmetic1D" hypothesis to cut an edge in several segments with increasing arithmetic length + ## Defines "Arithmetic1D" hypothesis to cut an edge in several segments with a length + # that changes in arithmetic progression # @param start defines the length of the first segment # @param end defines the length of the last segment # @param reversedEdges is a list of edges to mesh using reversed orientation. @@ -226,6 +227,32 @@ class StdMeshersBuilder_Segment(Mesh_Algorithm): hyp.SetObjectEntry( entry ) return hyp + ## Defines "GeometricProgression" hypothesis to cut an edge in several + # segments with a length that changes in Geometric progression + # @param start defines the length of the first segment + # @param ratio defines the common ratio of the geometric progression + # @param reversedEdges is a list of edges to mesh using reversed orientation. + # A list item can also be a tuple (edge, 1st_vertex_of_edge) + # @param UseExisting if ==true - searches for an existing hypothesis created with + # the same parameters, else (default) - creates a new one + # @return an instance of StdMeshers_Geometric1D hypothesis + # @ingroup l3_hypos_1dhyps + def GeometricProgression(self, start, ratio, reversedEdges=[], UseExisting=0): + reversedEdgeInd = self.ReversedEdgeIndices(reversedEdges) + entry = self.MainShapeEntry() + from salome.smesh.smeshBuilder import IsEqual + compFun = lambda hyp, args: ( IsEqual(hyp.GetLength(1), args[0]) and \ + IsEqual(hyp.GetLength(0), args[1]) and \ + hyp.GetReversedEdges() == args[2] and \ + (not args[2] or hyp.GetObjectEntry() == args[3])) + hyp = self.Hypothesis("GeometricProgression", [start, ratio, reversedEdgeInd, entry], + UseExisting=UseExisting, CompareMethod=compFun) + hyp.SetStartLength( start ) + hyp.SetCommonRatio( ratio ) + hyp.SetReversedEdges( reversedEdgeInd ) + hyp.SetObjectEntry( entry ) + return hyp + ## Defines "FixedPoints1D" hypothesis to cut an edge using parameter # on curve from 0 to 1 (additionally it is neecessary to check # orientation of edges and create list of reversed edges if it is @@ -237,7 +264,7 @@ class StdMeshersBuilder_Segment(Mesh_Algorithm): # A list item can also be a tuple (edge, 1st_vertex_of_edge) # @param UseExisting if ==true - searches for an existing hypothesis created with # the same parameters, else (default) - creates a new one - # @return an instance of StdMeshers_Arithmetic1D hypothesis + # @return an instance of StdMeshers_FixedPoints1D hypothesis # @ingroup l3_hypos_1dhyps def FixedPoints1D(self, points, nbSegs=[1], reversedEdges=[], UseExisting=0): if not isinstance(reversedEdges,list): #old version script, before adding reversedEdges @@ -295,12 +322,23 @@ class StdMeshersBuilder_Segment(Mesh_Algorithm): hyp.SetDeflection(d) return hyp - ## Defines "Propagation" hypothesis that propagates all other hypotheses on all other edges that are at - # the opposite side in case of quadrangular faces + ## Defines "Propagation" hypothesis that propagates 1D hypotheses + # from an edge where this hypothesis is assigned to + # on all other edges that are at the opposite side in case of quadrangular faces + # This hypothesis should be assigned to an edge to propagate a hypothesis from. # @ingroup l3_hypos_additi def Propagation(self): return self.Hypothesis("Propagation", UseExisting=1, CompareMethod=self.CompareEqualHyp) + ## Defines "Propagation of Node Distribution" hypothesis that propagates + # distribution of nodes from an edge where this hypothesis is assigned to, + # to opposite edges of quadrangular faces, so that number of segments on all these + # edges will be the same, as well as relations between segment lengths. + # @ingroup l3_hypos_additi + def PropagationOfDistribution(self): + return self.Hypothesis("PropagOfDistribution", UseExisting=1, + CompareMethod=self.CompareEqualHyp) + ## Defines "AutomaticLength" hypothesis # @param fineness for the fineness [0-1] # @param UseExisting if ==true - searches for an existing hypothesis created with the @@ -552,28 +590,56 @@ class StdMeshersBuilder_Quadrangle(Mesh_Algorithm): # same number of segments, the other pair must have an even difference # between the numbers of segments on the sides. # @param triangleVertex: vertex of a trilateral geometrical face, around which triangles - # will be created while other elements will be quadrangles. - # Vertex can be either a GEOM_Object or a vertex ID within the - # shape to mesh - # @param UseExisting: if ==true - searches for the existing hypothesis created with - # the same parameters, else (default) - creates a new one + # will be created while other elements will be quadrangles. + # Vertex can be either a GEOM_Object or a vertex ID within the + # shape to mesh + # @param enfVertices: list of shapes defining positions where nodes (enforced nodes) + # must be created by the mesher. Shapes can be of any type, + # vertices of given shapes define positions of enforced nodes. + # Only vertices successfully projected to the face are used. + # @param enfPoint: list of points giving positions of enforced nodes. + # Point can be defined either as SMESH.PointStruct's + # ([SMESH.PointStruct(x1,y1,z1), SMESH.PointStruct(x2,y2,z2),...]) + # or triples of values ([[x1,y1,z1], [x2,y2,z2], ...]). + # In the case if the defined QuadrangleParameters() refer to a sole face, + # all given points must lie on this face, else the mesher fails. + # @param UseExisting: if \c True - searches for the existing hypothesis created with + # the same parameters, else (default) - creates a new one # @ingroup l3_hypos_quad - def QuadrangleParameters(self, quadType=StdMeshers.QUAD_STANDARD, triangleVertex=0, UseExisting=0): - import GEOM + def QuadrangleParameters(self, quadType=StdMeshers.QUAD_STANDARD, triangleVertex=0, + enfVertices=[],enfPoints=[],UseExisting=0): + import GEOM, SMESH vertexID = triangleVertex if isinstance( triangleVertex, GEOM._objref_GEOM_Object ): vertexID = self.mesh.geompyD.GetSubShapeID( self.mesh.geom, triangleVertex ) + if isinstance( enfVertices, int ) and not enfPoints and not UseExisting: + # a call of old syntax, before inserting enfVertices and enfPoints before UseExisting + UseExisting, enfVertices = enfVertices, [] + pStructs, xyz = [], [] + for p in enfPoints: + if isinstance( p, SMESH.PointStruct ): + xyz.append(( p.x, p.y, p.z )) + pStructs.append( p ) + else: + xyz.append(( p[0], p[1], p[2] )) + pStructs.append( SMESH.PointStruct( p[0], p[1], p[2] )) if not self.params: compFun = lambda hyp,args: \ hyp.GetQuadType() == args[0] and \ - ( hyp.GetTriaVertex()==args[1] or ( hyp.GetTriaVertex()<1 and args[1]<1)) - self.params = self.Hypothesis("QuadrangleParams", [quadType,vertexID], + (hyp.GetTriaVertex()==args[1] or ( hyp.GetTriaVertex()<1 and args[1]<1)) and \ + ((hyp.GetEnforcedNodes()) == (args[2],args[3])) # True w/o enfVertices only + entries = [ shape.GetStudyEntry() for shape in enfVertices ] + self.params = self.Hypothesis("QuadrangleParams", [quadType,vertexID,entries,xyz], UseExisting = UseExisting, CompareMethod=compFun) pass if self.params.GetQuadType() != quadType: self.params.SetQuadType(quadType) if vertexID > 0: self.params.SetTriaVertex( vertexID ) + from salome.smesh.smeshBuilder import AssureGeomPublished + for v in enfVertices: + AssureGeomPublished( self.mesh, v ) + self.params.SetEnforcedNodes( enfVertices, pStructs ) return self.params ## Defines "QuadrangleParams" hypothesis with a type of quadrangulation that only @@ -980,7 +1046,8 @@ class StdMeshersBuilder_Prism3D(Mesh_Algorithm): return hyp ## Defines "Arithmetic1D" hypothesis, specifying the distribution of segments - # to build between the inner and the outer shells with a length that changes in arithmetic progression + # to build between the inner and the outer shells with a length that changes + # in arithmetic progression # @param start the length of the first segment # @param end the length of the last segment def Arithmetic1D(self, start, end ): @@ -992,6 +1059,20 @@ class StdMeshersBuilder_Prism3D(Mesh_Algorithm): hyp.SetLength(end , 0) return hyp + ## Defines "GeometricProgression" hypothesis, specifying the distribution of segments + # to build between the inner and the outer shells with a length that changes + # in Geometric progression + # @param start the length of the first segment + # @param ratio the common ratio of the geometric progression + def GeometricProgression(self, start, ratio ): + if self.algoType != "RadialPrism_3D": + print "Prism_3D algorith doesn't support any hyposesis" + return None + hyp = self.OwnHypothesis("GeometricProgression", [start, ratio]) + hyp.SetStartLength( start ) + hyp.SetCommonRatio( ratio ) + return hyp + ## Defines "StartEndLength" hypothesis, specifying distribution of segments # to build between the inner and the outer shells as geometric length increasing # @param start for the length of the first segment @@ -1148,6 +1229,16 @@ class StdMeshersBuilder_RadialQuadrangle1D2D(Mesh_Algorithm): hyp.SetLength(end , 0) return hyp + ## Defines "GeometricProgression" hypothesis, specifying the distribution of segments + # with a length that changes in Geometric progression + # @param start the length of the first segment + # @param ratio the common ratio of the geometric progression + def GeometricProgression(self, start, ratio ): + hyp = self.OwnHypothesis("GeometricProgression", [start, ratio]) + hyp.SetStartLength( start ) + hyp.SetCommonRatio( ratio ) + return hyp + ## Defines "StartEndLength" hypothesis, specifying distribution of segments # as geometric length increasing # @param start for the length of the first segment diff --git a/src/SMESH_SWIG/smeshBuilder.py b/src/SMESH_SWIG/smeshBuilder.py index 5e68dc230..a5aae93d2 100644 --- a/src/SMESH_SWIG/smeshBuilder.py +++ b/src/SMESH_SWIG/smeshBuilder.py @@ -2418,6 +2418,12 @@ class Mesh: def GetElemFaceNodes(self,elemId, faceIndex): return self.mesh.GetElemFaceNodes(elemId, faceIndex) + ## Returns three components of normal of given mesh face + # (or an empty array in KO case) + # @ingroup l1_meshinfo + def GetFaceNormal(self, faceId): + return self.mesh.GetFaceNormal(faceId) + ## Returns an element based on all given nodes. # @ingroup l1_meshinfo def FindElementByNodes(self,nodes): @@ -3181,7 +3187,8 @@ class Mesh: # Note that nodes built on edges and boundary nodes are always fixed. # @param MaxNbOfIterations the maximum number of iterations # @param MaxAspectRatio varies in range [1.0, inf] - # @param Method is Laplacian(LAPLACIAN_SMOOTH) or Centroidal(CENTROIDAL_SMOOTH) + # @param Method is either Laplacian (SMESH.SMESH_MeshEditor.LAPLACIAN_SMOOTH) + # or Centroidal (SMESH.SMESH_MeshEditor.CENTROIDAL_SMOOTH) # @return TRUE in case of success, FALSE otherwise. # @ingroup l2_modif_smooth def Smooth(self, IDsOfElements, IDsOfFixedNodes, @@ -3199,7 +3206,8 @@ class Mesh: # Note that nodes built on edges and boundary nodes are always fixed. # @param MaxNbOfIterations the maximum number of iterations # @param MaxAspectRatio varies in range [1.0, inf] - # @param Method is Laplacian(LAPLACIAN_SMOOTH) or Centroidal(CENTROIDAL_SMOOTH) + # @param Method is either Laplacian (SMESH.SMESH_MeshEditor.LAPLACIAN_SMOOTH) + # or Centroidal (SMESH.SMESH_MeshEditor.CENTROIDAL_SMOOTH) # @return TRUE in case of success, FALSE otherwise. # @ingroup l2_modif_smooth def SmoothObject(self, theObject, IDsOfFixedNodes, @@ -3215,7 +3223,8 @@ class Mesh: # Note that nodes built on edges and boundary nodes are always fixed. # @param MaxNbOfIterations the maximum number of iterations # @param MaxAspectRatio varies in range [1.0, inf] - # @param Method is Laplacian(LAPLACIAN_SMOOTH) or Centroidal(CENTROIDAL_SMOOTH) + # @param Method is either Laplacian (SMESH.SMESH_MeshEditor.LAPLACIAN_SMOOTH) + # or Centroidal (SMESH.SMESH_MeshEditor.CENTROIDAL_SMOOTH) # @return TRUE in case of success, FALSE otherwise. # @ingroup l2_modif_smooth def SmoothParametric(self, IDsOfElements, IDsOfFixedNodes, @@ -3233,7 +3242,8 @@ class Mesh: # Note that nodes built on edges and boundary nodes are always fixed. # @param MaxNbOfIterations the maximum number of iterations # @param MaxAspectRatio varies in range [1.0, inf] - # @param Method Laplacian(LAPLACIAN_SMOOTH) or Centroidal(CENTROIDAL_SMOOTH) + # @param Method is either Laplacian (SMESH.SMESH_MeshEditor.LAPLACIAN_SMOOTH) + # or Centroidal (SMESH.SMESH_MeshEditor.CENTROIDAL_SMOOTH) # @return TRUE in case of success, FALSE otherwise. # @ingroup l2_modif_smooth def SmoothParametricObject(self, theObject, IDsOfFixedNodes, diff --git a/src/SMESH_SWIG/smesh_algorithm.py b/src/SMESH_SWIG/smesh_algorithm.py index a81f825a6..0d018d52e 100644 --- a/src/SMESH_SWIG/smesh_algorithm.py +++ b/src/SMESH_SWIG/smesh_algorithm.py @@ -267,21 +267,26 @@ class Mesh_Algorithm: # @param thickness total thickness of layers of prisms # @param numberOfLayers number of layers of prisms # @param stretchFactor factor (>1.0) of growth of layer thickness towards inside of mesh - # @param ignoreFaces list of geometrical faces (or their ids) not to generate layers on + # @param faces list of geometrical faces (or their ids). + # Viscous layers are either generated on these faces or not, depending on + # 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). # @ingroup l3_hypos_additi - def ViscousLayers(self, thickness, numberOfLayers, stretchFactor, ignoreFaces=[]): + def ViscousLayers(self, thickness, numberOfLayers, stretchFactor, + faces=[], isFacesToIgnore=True ): 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(): raise TypeError, "ViscousLayers are not supported by %s"%self.algo.GetName() - if ignoreFaces and isinstance( ignoreFaces[0], geomBuilder.GEOM._objref_GEOM_Object ): - ignoreFaces = [ self.mesh.geompyD.GetSubShapeID(self.mesh.geom, f) for f in ignoreFaces ] + if faces and isinstance( faces[0], geomBuilder.GEOM._objref_GEOM_Object ): + faces = [ self.mesh.geompyD.GetSubShapeID(self.mesh.geom, f) for f in faces ] hyp = self.Hypothesis("ViscousLayers", - [thickness, numberOfLayers, stretchFactor, ignoreFaces]) + [thickness, numberOfLayers, stretchFactor, faces]) hyp.SetTotalThickness(thickness) hyp.SetNumberLayers(numberOfLayers) hyp.SetStretchFactor(stretchFactor) - hyp.SetIgnoreFaces(ignoreFaces) + hyp.SetFaces(faces, isFacesToIgnore) return hyp ## Defines "ViscousLayers2D" hypothesis to give parameters of layers of quadrilateral @@ -290,9 +295,9 @@ class Mesh_Algorithm: # @param thickness total thickness of layers of quadrilaterals # @param numberOfLayers number of layers # @param stretchFactor factor (>1.0) of growth of layer thickness towards inside of mesh - # @param edges list of geometrical edge (or their ids). + # @param edges list of geometrical edges (or their ids). # Viscous layers are either generated on these edges or not, depending on - # the values of \a isEdgesToIgnore parameter. + # the value of \a isEdgesToIgnore parameter. # @param isEdgesToIgnore if \c True, the Viscous layers are not generated on the # edges specified by the previous parameter (\a edges). # @ingroup l3_hypos_additi @@ -313,7 +318,7 @@ class Mesh_Algorithm: hyp.SetEdges(edges, isEdgesToIgnore) return hyp - ## Transform a list of ether edges or tuples (edge, 1st_vertex_of_edge) + ## Transform a list of either edges or tuples (edge, 1st_vertex_of_edge) # into a list acceptable to SetReversedEdges() of some 1D hypotheses # @ingroup l3_hypos_1dhyps def ReversedEdgeIndices(self, reverseList): diff --git a/src/StdMeshers/CMakeLists.txt b/src/StdMeshers/CMakeLists.txt index de84b5026..2dde1b291 100644 --- a/src/StdMeshers/CMakeLists.txt +++ b/src/StdMeshers/CMakeLists.txt @@ -75,8 +75,10 @@ ENDIF(SALOME_SMESH_ENABLE_MEFISTO) # header files / no moc processing SET(StdMeshers_HEADERS StdMeshers_LocalLength.hxx + StdMeshers_Reversible1D.hxx StdMeshers_StartEndLength.hxx StdMeshers_Arithmetic1D.hxx + StdMeshers_Geometric1D.hxx StdMeshers_FixedPoints1D.hxx StdMeshers_NumberOfSegments.hxx StdMeshers_Deflection1D.hxx @@ -136,8 +138,10 @@ ENDIF(SALOME_SMESH_ENABLE_MEFISTO) # sources / static SET(StdMeshers_SOURCES StdMeshers_LocalLength.cxx + StdMeshers_Reversible1D.cxx StdMeshers_StartEndLength.cxx StdMeshers_Arithmetic1D.cxx + StdMeshers_Geometric1D.cxx StdMeshers_FixedPoints1D.cxx StdMeshers_NumberOfSegments.cxx StdMeshers_Deflection1D.cxx diff --git a/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx b/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx index fa4aa909f..369d4e424 100644 --- a/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx +++ b/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx @@ -32,11 +32,12 @@ #include "utilities.h" -#include -#include - #include +#include +#include +#include + using namespace std; //======================================================================= @@ -48,10 +49,23 @@ StdMeshers_CartesianParameters3D::StdMeshers_CartesianParameters3D(int h int studyId, SMESH_Gen * gen) : SMESH_Hypothesis(hypId, studyId, gen), - _sizeThreshold( 4.0 ) // default according to the customer specification + _sizeThreshold( 4.0 ), // default according to the customer specification + _toAddEdges( false ) { _name = "CartesianParameters3D"; // used by "Cartesian_3D" _param_algo_dim = 3; // 3D + + _axisDirs[0] = 1.; + _axisDirs[1] = 0.; + _axisDirs[2] = 0.; + + _axisDirs[3] = 0.; + _axisDirs[4] = 1.; + _axisDirs[5] = 0.; + + _axisDirs[6] = 0.; + _axisDirs[7] = 0.; + _axisDirs[8] = 1.; } @@ -308,6 +322,44 @@ void StdMeshers_CartesianParameters3D::GetCoordinates(std::vector& xNode zNodes = _coords[2]; } +//======================================================================= +//function : SetAxisDirs +//purpose : Sets directions of axes +//======================================================================= + +void StdMeshers_CartesianParameters3D::SetAxisDirs(const double* the9DirComps) + throw ( SALOME_Exception ) +{ + gp_Vec x( the9DirComps[0], + the9DirComps[1], + the9DirComps[2] ); + gp_Vec y( the9DirComps[3], + the9DirComps[4], + the9DirComps[5] ); + gp_Vec z( the9DirComps[6], + the9DirComps[7], + the9DirComps[8] ); + if ( x.Magnitude() < RealSmall() || + y.Magnitude() < RealSmall() || + z.Magnitude() < RealSmall() ) + throw SALOME_Exception("Zero magnitude of axis direction"); + + if ( x.IsParallel( y, M_PI / 180. ) || + x.IsParallel( z, M_PI / 180. ) || + y.IsParallel( z, M_PI / 180. )) + throw SALOME_Exception("Parallel axis directions"); + + bool isChanged = false; + for ( int i = 0; i < 9; ++i ) + { + if ( Abs( _axisDirs[i] - the9DirComps[i] ) > 1e-7 ) + isChanged = true; + _axisDirs[i] = the9DirComps[i]; + } + if ( isChanged ) + NotifySubMeshesHypothesisModification(); +} + //======================================================================= //function : GetGrid //purpose : Return coordinates of node positions along the three axes @@ -332,6 +384,33 @@ double StdMeshers_CartesianParameters3D::GetSizeThreshold() const return _sizeThreshold; } +//======================================================================= +//function : SetToAddEdges +//purpose : Enables implementation of geometrical edges into the mesh. If this feature +// is disabled, sharp edges of the shape are lost ("smoothed") in the mesh if +// they don't coincide with the grid lines +//======================================================================= + +void StdMeshers_CartesianParameters3D::SetToAddEdges(bool toAdd) +{ + if ( _toAddEdges != toAdd ) + { + _toAddEdges = toAdd; + NotifySubMeshesHypothesisModification(); + } +} + +//======================================================================= +//function : GetToAddEdges +//purpose : Returns true if implementation of geometrical edges into the +// mesh is enabled +//======================================================================= + +bool StdMeshers_CartesianParameters3D::GetToAddEdges() const +{ + return _toAddEdges; +} + //======================================================================= //function : IsDefined //purpose : Return true if parameters are well defined @@ -369,6 +448,7 @@ std::ostream & StdMeshers_CartesianParameters3D::SaveTo(std::ostream & save) for ( size_t j = 0; j < _spaceFunctions[i].size(); ++j ) save << _spaceFunctions[i][j] << " "; } + save << _toAddEdges << " "; return save; } @@ -382,7 +462,7 @@ std::istream & StdMeshers_CartesianParameters3D::LoadFrom(std::istream & load) { bool ok; - ok = (load >> _sizeThreshold ); + ok = ( load >> _sizeThreshold ); for ( int ax = 0; ax < 3; ++ax ) { if (ok) @@ -419,6 +499,9 @@ std::istream & StdMeshers_CartesianParameters3D::LoadFrom(std::istream & load) } } } + + load >> _toAddEdges; + return load; } diff --git a/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx b/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx index adb22f00c..98970956b 100644 --- a/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx +++ b/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx @@ -100,6 +100,10 @@ public: std::vector& yNodes, std::vector& zNodes, const Bnd_Box& bndBox) const throw ( SALOME_Exception ); + + void SetAxisDirs(const double* the9DirComps) throw ( SALOME_Exception ); + const double* GetAxisDirs() const { return _axisDirs; } + /*! * Set size threshold. A polyhedral cell got by cutting an initial * hexahedron by geometry boundary is considered small and is removed if @@ -111,6 +115,14 @@ public: */ double GetSizeThreshold() const; + /*! + * \brief Enables implementation of geometrical edges into the mesh. If this feature + * is disabled, sharp edges of the shape are lost ("smoothed") in the mesh if + * they don't coincide with the grid lines + */ + void SetToAddEdges(bool toAdd); + bool GetToAddEdges() const; + /*! * \brief Return true if parameters are well defined */ @@ -138,7 +150,10 @@ public: std::vector _spaceFunctions[3]; std::vector _internalPoints[3]; + double _axisDirs[9]; + double _sizeThreshold; + bool _toAddEdges; }; #endif diff --git a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx index 83447b16a..36b2e2d48 100644 --- a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx +++ b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx @@ -33,10 +33,11 @@ #include "SMESH_subMeshEventListener.hxx" #include "StdMeshers_CartesianParameters3D.hxx" -#include "utilities.h" -#include "Utils_ExceptHandlers.hxx" +#include +#include #include +#include #include #include #include @@ -44,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -63,7 +65,6 @@ #include #include #include -#include #include #include #include @@ -76,7 +77,7 @@ #include #include -//#undef WITH_TBB +#undef WITH_TBB #ifdef WITH_TBB #include //#include @@ -150,6 +151,8 @@ bool StdMeshers_Cartesian_3D::CheckHypothesis (SMESH_Mesh& aMesh, namespace { + typedef int TGeomID; + //============================================================================= // Definitions of internal utils // -------------------------------------------------------------------------- @@ -161,17 +164,40 @@ namespace }; // -------------------------------------------------------------------------- /*! - * \brief Data of intersection between a GridLine and a TopoDS_Face + * \brief Common data of any intersection between a Grid and a shape */ - struct IntersectionPoint + struct B_IntersectPoint { - double _paramOnLine; - mutable Transition _transition; mutable const SMDS_MeshNode* _node; - mutable size_t _indexOnLine; + mutable vector< TGeomID > _faceIDs; + + B_IntersectPoint(): _node(NULL) {} + void Add( const vector< TGeomID >& fIDs, const SMDS_MeshNode* n=0 ) const; + bool HasCommonFace( const B_IntersectPoint * other ) const; + bool IsOnFace( int faceID ) const; + virtual ~B_IntersectPoint() {} + }; + // -------------------------------------------------------------------------- + /*! + * \brief Data of intersection between a GridLine and a TopoDS_Face + */ + struct F_IntersectPoint : public B_IntersectPoint + { + double _paramOnLine; + mutable Transition _transition; + mutable size_t _indexOnLine; - IntersectionPoint(): _node(0) {} - bool operator< ( const IntersectionPoint& o ) const { return _paramOnLine < o._paramOnLine; } + bool operator< ( const F_IntersectPoint& o ) const { return _paramOnLine < o._paramOnLine; } + }; + // -------------------------------------------------------------------------- + /*! + * \brief Data of intersection between GridPlanes and a TopoDS_EDGE + */ + struct E_IntersectPoint : public B_IntersectPoint + { + gp_Pnt _point; + double _uvw[3]; + TGeomID _shapeID; }; // -------------------------------------------------------------------------- /*! @@ -181,10 +207,23 @@ namespace { gp_Lin _line; double _length; // line length - multiset< IntersectionPoint > _intPoints; + multiset< F_IntersectPoint > _intPoints; void RemoveExcessIntPoints( const double tol ); - bool GetIsOutBefore( multiset< IntersectionPoint >::iterator ip, bool prevIsOut ); + bool GetIsOutBefore( multiset< F_IntersectPoint >::iterator ip, bool prevIsOut ); + }; + // -------------------------------------------------------------------------- + /*! + * \brief Planes of the grid used to find intersections of an EDGE with a hexahedron + */ + struct GridPlanes + { + double _factor; + gp_XYZ _uNorm, _vNorm, _zNorm; + vector< gp_XYZ > _origins; // origin points of all planes in one direction + vector< double > _zProjs; // projections of origins to _zNorm + + gp_XY GetUV( const gp_Pnt& p, const gp_Pnt& origin ); }; // -------------------------------------------------------------------------- /*! @@ -234,11 +273,15 @@ namespace struct Grid { vector< double > _coords[3]; // coordinates of grid nodes + gp_XYZ _axes [3]; // axis directions vector< GridLine > _lines [3]; // in 3 directions double _tol, _minCellSize; - vector< const SMDS_MeshNode* > _nodes; // mesh nodes at grid nodes - vector< bool > _isBndNode; // is mesh node at intersection with geometry + vector< const SMDS_MeshNode* > _nodes; // mesh nodes at grid nodes + vector< const F_IntersectPoint* > _gridIntP; // grid node intersection with geometry + + list< E_IntersectPoint > _edgeIntP; // intersections with EDGEs + TopTools_IndexedMapOfShape _shapes; size_t CellIndex( size_t i, size_t j, size_t k ) const { @@ -257,6 +300,7 @@ namespace void SetCoordinates(const vector& xCoords, const vector& yCoords, const vector& zCoords, + const double* axesDirs, const TopoDS_Shape& shape ); void ComputeNodes(SMESH_MesherHelper& helper); }; @@ -302,10 +346,11 @@ namespace struct FaceGridIntersector { TopoDS_Face _face; + TGeomID _faceID; Grid* _grid; Bnd_Box _bndBox; __IntCurvesFace_Intersector* _surfaceInt; - vector< std::pair< GridLine*, IntersectionPoint > > _intersections; + vector< std::pair< GridLine*, F_IntersectPoint > > _intersections; FaceGridIntersector(): _grid(0), _surfaceInt(0) {} void Intersect(); @@ -314,7 +359,12 @@ namespace void StoreIntersections() { for ( size_t i = 0; i < _intersections.size(); ++i ) - _intersections[i].first->_intPoints.insert( _intersections[i].second ); + { + multiset< F_IntersectPoint >::iterator ip = + _intersections[i].first->_intPoints.insert( _intersections[i].second ); + ip->_faceIDs.reserve( 1 ); + ip->_faceIDs.push_back( _faceID ); + } } const Bnd_Box& GetFaceBndBox() { @@ -352,7 +402,7 @@ namespace gp_Torus _torus; __IntCurvesFace_Intersector* _surfaceInt; - vector< IntersectionPoint > _intPoints; + vector< F_IntersectPoint > _intPoints; void IntersectWithPlane (const GridLine& gridLine); void IntersectWithCylinder(const GridLine& gridLine); @@ -381,20 +431,54 @@ namespace struct _Face; struct _Link; // -------------------------------------------------------------------------------- - struct _Node //!< node either at a hexahedron corner or at GridLine intersection + struct _Node //!< node either at a hexahedron corner or at intersection { - const SMDS_MeshNode* _node; // mesh node at hexahedron corner - const IntersectionPoint* _intPoint; - - _Node(const SMDS_MeshNode* n=0, const IntersectionPoint* ip=0):_node(n), _intPoint(ip) {} - const SMDS_MeshNode* Node() const { return _intPoint ? _intPoint->_node : _node; } - //bool IsCorner() const { return _node; } + const SMDS_MeshNode* _node; // mesh node at hexahedron corner + const B_IntersectPoint* _intPoint; + + _Node(const SMDS_MeshNode* n=0, const B_IntersectPoint* ip=0):_node(n), _intPoint(ip) {} + const SMDS_MeshNode* Node() const + { return ( _intPoint && _intPoint->_node ) ? _intPoint->_node : _node; } + const F_IntersectPoint* FaceIntPnt() const + { return static_cast< const F_IntersectPoint* >( _intPoint ); } + const E_IntersectPoint* EdgeIntPnt() const + { return static_cast< const E_IntersectPoint* >( _intPoint ); } + void Add( const E_IntersectPoint* ip ) + { + if ( !_intPoint ) { + _intPoint = ip; + } + else if ( !_intPoint->_node ) { + ip->Add( _intPoint->_faceIDs ); + _intPoint = ip; + } + else { + _intPoint->Add( ip->_faceIDs ); + } + } + bool IsLinked( const B_IntersectPoint* other ) const + { + return _intPoint && _intPoint->HasCommonFace( other ); + } + bool IsOnFace( int faceID ) const // returns true if faceID is found + { + return _intPoint ? _intPoint->IsOnFace( faceID ) : false; + } + gp_Pnt Point() const + { + if ( const SMDS_MeshNode* n = Node() ) + return SMESH_TNodeXYZ( n ); + if ( const E_IntersectPoint* eip = + dynamic_cast< const E_IntersectPoint* >( _intPoint )) + return eip->_point; + return gp_Pnt( 1e100, 0, 0 ); + } }; // -------------------------------------------------------------------------------- struct _Link // link connecting two _Node's { _Node* _nodes[2]; - vector< _Node> _intNodes; // _Node's at GridLine intersections + vector< _Node > _intNodes; // _Node's at GridLine intersections vector< _Link > _splits; vector< _Face*> _faces; }; @@ -412,21 +496,38 @@ namespace } _Node* FirstNode() const { return _link->_nodes[ _reverse ]; } _Node* LastNode() const { return _link->_nodes[ !_reverse ]; } + operator bool() const { return _link; } + vector< TGeomID > GetNotUsedFace(const set& usedIDs ) const // returns a supporting FACEs + { + vector< TGeomID > faces; + const B_IntersectPoint *ip0, *ip1; + if (( ip0 = _link->_nodes[0]->_intPoint ) && + ( ip1 = _link->_nodes[1]->_intPoint )) + { + for ( size_t i = 0; i < ip0->_faceIDs.size(); ++i ) + if ( ip1->IsOnFace ( ip0->_faceIDs[i] ) && + !usedIDs.count( ip0->_faceIDs[i] ) ) + faces.push_back( ip0->_faceIDs[i] ); + } + return faces; + } }; // -------------------------------------------------------------------------------- struct _Face { - vector< _OrientedLink > _links; - vector< _Link > _polyLinks; // links added to close a polygonal face + vector< _OrientedLink > _links; // links on GridLine's + vector< _Link > _polyLinks; // links added to close a polygonal face + vector< _Node > _edgeNodes; // nodes at intersection with EDGEs }; // -------------------------------------------------------------------------------- struct _volumeDef // holder of nodes of a volume mesh element { - vector< const SMDS_MeshNode* > _nodes; - vector< int > _quantities; + //vector< const SMDS_MeshNode* > _nodes; + vector< _Node* > _nodes; + vector< int > _quantities; typedef boost::shared_ptr<_volumeDef> Ptr; - void set( const vector< const SMDS_MeshNode* >& nodes, - const vector< int > quant = vector< int >() ) + void set( const vector< _Node* >& nodes, + const vector< int >& quant = vector< int >() ) { _nodes = nodes; _quantities = quant; } // static Ptr New( const vector< const SMDS_MeshNode* >& nodes, // const vector< int > quant = vector< int >() ) @@ -440,13 +541,19 @@ namespace // topology of a hexahedron int _nodeShift[8]; - _Node _hexNodes[8]; - _Link _hexLinks[12]; - _Face _hexQuads[6]; + _Node _hexNodes [8]; + _Link _hexLinks [12]; + _Face _hexQuads [6]; // faces resulted from hexahedron intersection vector< _Face > _polygons; + // intresections with EDGEs + vector< const E_IntersectPoint* > _edgeIntPnts; + + // nodes inside the hexahedron (at VERTEXes) + vector< _Node > _vertexNodes; + // computed volume elements //vector< _volumeDef::Ptr > _volumeDefs; _volumeDef _volumeDefs; @@ -459,7 +566,8 @@ namespace public: Hexahedron(const double sizeThreshold, Grid* grid); - int MakeElements(SMESH_MesherHelper& helper); + int MakeElements(SMESH_MesherHelper& helper, + const map< TGeomID, vector< TGeomID > >& edge2faceIDsMap); void ComputeElements(); void Init() { init( _i, _j, _k ); } @@ -467,6 +575,17 @@ namespace Hexahedron(const Hexahedron& other ); void init( size_t i, size_t j, size_t k ); void init( size_t i ); + void addEdges(SMESH_MesherHelper& helper, + vector< Hexahedron* >& intersectedHex, + const map< TGeomID, vector< TGeomID > >& edge2faceIDsMap); + gp_Pnt findIntPoint( double u1, double proj1, double u2, double proj2, + double proj, BRepAdaptor_Curve& curve, + const gp_XYZ& axis, const gp_XYZ& origin ); + int getEntity( const E_IntersectPoint* ip, int* facets, int& sub ); + bool addIntersection( const E_IntersectPoint& ip, + vector< Hexahedron* >& hexes, + int ijk[], int dIJK[] ); + bool findChain( _Node* n1, _Node* n2, _Face& quad, vector<_Node*>& chainNodes ); int addElements(SMESH_MesherHelper& helper); bool isInHole() const; bool checkPolyhedronSize() const; @@ -474,8 +593,17 @@ namespace bool addTetra(); bool addPenta(); bool addPyra (); + _Node* FindEqualNode( vector< _Node >& nodes, + const E_IntersectPoint* ip, + const double tol2 ) + { + for ( size_t i = 0; i < nodes.size(); ++i ) + if ( nodes[i].Point().SquareDistance( ip->_point ) <= tol2 ) + return & nodes[i]; + return 0; + } }; - + #ifdef WITH_TBB // -------------------------------------------------------------------------- /*! @@ -507,11 +635,34 @@ namespace _faceVec[i].Intersect(); } }; - #endif + //============================================================================= // Implementation of internal utils //============================================================================= + /*! + * \brief adjust \a i to have \a val between values[i] and values[i+1] + */ + inline void locateValue( int & i, double val, const vector& values, + int& di, double tol ) + { + val += values[0]; // input \a val is measured from 0. + if ( i > values.size()-2 ) + i = values.size()-2; + else + while ( i+2 < values.size() && val > values[ i+1 ]) + ++i; + while ( i > 0 && val < values[ i ]) + --i; + + if ( i > 0 && val - values[ i ] < tol ) + di = -1; + else if ( i+2 < values.size() && values[ i+1 ] - val < tol ) + di = 1; + else + di = 0; + } + //============================================================================= /* * Remove coincident intersection points */ @@ -520,15 +671,16 @@ namespace if ( _intPoints.size() < 2 ) return; set< Transition > tranSet; - multiset< IntersectionPoint >::iterator ip1, ip2 = _intPoints.begin(); + multiset< F_IntersectPoint >::iterator ip1, ip2 = _intPoints.begin(); while ( ip2 != _intPoints.end() ) { tranSet.clear(); ip1 = ip2++; - while ( ip2->_paramOnLine - ip1->_paramOnLine <= tol && ip2 != _intPoints.end()) + while ( ip2 != _intPoints.end() && ip2->_paramOnLine - ip1->_paramOnLine <= tol ) { tranSet.insert( ip1->_transition ); tranSet.insert( ip2->_transition ); + ip2->Add( ip1->_faceIDs ); _intPoints.erase( ip1 ); ip1 = ip2++; } @@ -547,7 +699,7 @@ namespace /* * Return "is OUT" state for nodes before the given intersection point */ - bool GridLine::GetIsOutBefore( multiset< IntersectionPoint >::iterator ip, bool prevIsOut ) + bool GridLine::GetIsOutBefore( multiset< F_IntersectPoint >::iterator ip, bool prevIsOut ) { if ( ip->_transition == Trans_IN ) return true; @@ -558,7 +710,7 @@ namespace // singularity point (apex of a cone) if ( _intPoints.size() == 1 || ip == _intPoints.begin() ) return true; - multiset< IntersectionPoint >::iterator ipBef = ip, ipAft = ++ip; + multiset< F_IntersectPoint >::iterator ipBef = ip, ipAft = ++ip; if ( ipAft == _intPoints.end() ) return false; --ipBef; @@ -569,13 +721,65 @@ namespace return prevIsOut; // _transition == Trans_TANGENT } //================================================================================ + /* + * Returns parameters of a point in i-th plane + */ + gp_XY GridPlanes::GetUV( const gp_Pnt& p, const gp_Pnt& origin ) + { + gp_Vec v( origin, p ); + return gp_XY( v.Dot( _uNorm ) * _factor, + v.Dot( _vNorm ) * _factor ); + } + //================================================================================ + /* + * Adds face IDs + */ + void B_IntersectPoint::Add( const vector< TGeomID >& fIDs, + const SMDS_MeshNode* n) const + { + if ( _faceIDs.empty() ) + _faceIDs = fIDs; + else + for ( size_t i = 0; i < fIDs.size(); ++i ) + { + vector< TGeomID >::iterator it = + std::find( _faceIDs.begin(), _faceIDs.end(), fIDs[i] ); + if ( it == _faceIDs.end() ) + _faceIDs.push_back( fIDs[i] ); + } + if ( !_node ) + _node = n; + } + //================================================================================ + /* + * Returns \c true if \a other B_IntersectPoint holds the same face ID + */ + bool B_IntersectPoint::HasCommonFace( const B_IntersectPoint * other ) const + { + if ( other ) + for ( size_t i = 0; i < other->_faceIDs.size(); ++i ) + if ( IsOnFace( other->_faceIDs[i] ) ) + return true; + return false; + } + //================================================================================ + /* + * Returns \c true if \a faceID in in this->_faceIDs + */ + bool B_IntersectPoint::IsOnFace( int faceID ) const // returns true if faceID is found + { + vector< TGeomID >::const_iterator it = + std::find( _faceIDs.begin(), _faceIDs.end(), faceID ); + return ( it != _faceIDs.end() ); + } + //================================================================================ /* * Return an iterator on GridLine's in a given direction */ LineIndexer Grid::GetLineIndexer(size_t iDir) const { const size_t indices[] = { 1,2,0, 0,2,1, 0,1,2 }; - const string s[] = { "X", "Y", "Z" }; + const string s [] = { "X", "Y", "Z" }; LineIndexer li( _coords[0].size(), _coords[1].size(), _coords[2].size(), indices[iDir*3], indices[iDir*3+1], indices[iDir*3+2], s[indices[iDir*3]], s[indices[iDir*3+1]], s[indices[iDir*3+2]]); @@ -588,11 +792,21 @@ namespace void Grid::SetCoordinates(const vector& xCoords, const vector& yCoords, const vector& zCoords, + const double* axesDirs, const TopoDS_Shape& shape) { _coords[0] = xCoords; _coords[1] = yCoords; _coords[2] = zCoords; + _axes[0].SetCoord( axesDirs[0], + axesDirs[1], + axesDirs[2]); + _axes[1].SetCoord( axesDirs[3], + axesDirs[4], + axesDirs[5]); + _axes[2].SetCoord( axesDirs[6], + axesDirs[7], + axesDirs[8]); // compute tolerance _minCellSize = Precision::Infinite(); @@ -649,7 +863,7 @@ namespace const size_t nbGridNodes = _coords[0].size() * _coords[1].size() * _coords[2].size(); vector< bool > isNodeOut( nbGridNodes, false ); _nodes.resize( nbGridNodes, 0 ); - _isBndNode.resize( nbGridNodes, false ); + _gridIntP.resize( nbGridNodes, NULL ); for ( int iDir = 0; iDir < 3; ++iDir ) // loop on 3 line directions { @@ -669,8 +883,8 @@ namespace GridLine& line = _lines[ iDir ][ li.LineIndex() ]; line.RemoveExcessIntPoints( _tol ); - multiset< IntersectionPoint >& intPnts = _lines[ iDir ][ li.LineIndex() ]._intPoints; - multiset< IntersectionPoint >::iterator ip = intPnts.begin(); + multiset< F_IntersectPoint >& intPnts = _lines[ iDir ][ li.LineIndex() ]._intPoints; + multiset< F_IntersectPoint >::iterator ip = intPnts.begin(); bool isOut = true; const double* nodeCoord = & coords[0], *coord0 = nodeCoord, *coordEnd = coord0 + coords.size(); @@ -706,14 +920,18 @@ namespace else { int nodeIndex = nIndex0 + nShift * ( nodeCoord-coord0 ); - if ( ! _nodes[ nodeIndex ] ) + if ( !_nodes[ nodeIndex ] ) { li.SetIndexOnLine( nodeCoord-coord0 ); double xyz[3] = { _coords[0][ li.I() ], _coords[1][ li.J() ], _coords[2][ li.K() ]}; - _nodes[ nodeIndex ] = helper.AddNode( xyz[0], xyz[1], xyz[2] ); - _isBndNode[ nodeIndex ] = true; + _nodes [ nodeIndex ] = helper.AddNode( xyz[0], xyz[1], xyz[2] ); + _gridIntP[ nodeIndex ] = & * ip; } - //ip->_node = _nodes[ nodeIndex ]; + if ( _gridIntP[ nodeIndex ] ) + _gridIntP[ nodeIndex ]->Add( ip->_faceIDs ); + else + _gridIntP[ nodeIndex ] = & * ip; + // ip->_node = _nodes[ nodeIndex ]; -- to differ from ip on links ip->_indexOnLine = nodeCoord-coord0; if ( ++nodeCoord < coordEnd ) nodeParam = *nodeCoord - *coord0; @@ -744,7 +962,7 @@ namespace LineIndexer li = GetLineIndexer( iDir ); for ( ; li.More(); ++li ) { - multiset< IntersectionPoint >& intPnts = _lines[ iDir ][ li.LineIndex() ]._intPoints; + multiset< F_IntersectPoint >& intPnts = _lines[ iDir ][ li.LineIndex() ]._intPoints; if ( intPnts.empty() ) continue; if ( intPnts.size() == 1 ) { @@ -944,7 +1162,7 @@ namespace { if ( !toClassify || UVIsOnFace() ) { - IntersectionPoint p; + F_IntersectPoint p; p._paramOnLine = _w; p._transition = _transition; _intPoints.push_back( p ); @@ -1291,16 +1509,18 @@ namespace _origNodeInd = _grid->NodeIndex( i,j,k ); for ( int iN = 0; iN < 8; ++iN ) { - _hexNodes[iN]._node = _grid->_nodes[ _origNodeInd + _nodeShift[iN] ]; + _hexNodes[iN]._node = _grid->_nodes [ _origNodeInd + _nodeShift[iN] ]; + _hexNodes[iN]._intPoint = _grid->_gridIntP[ _origNodeInd + _nodeShift[iN] ]; _nbCornerNodes += bool( _hexNodes[iN]._node ); - _nbBndNodes += _grid->_isBndNode[ _origNodeInd + _nodeShift[iN] ]; + _nbBndNodes += bool( _hexNodes[iN]._intPoint ); } _sideLength[0] = _grid->_coords[0][i+1] - _grid->_coords[0][i]; _sideLength[1] = _grid->_coords[1][j+1] - _grid->_coords[1][j]; _sideLength[2] = _grid->_coords[2][k+1] - _grid->_coords[2][k]; - if ( _nbCornerNodes < 8 && _nbIntNodes + _nbCornerNodes > 3) + if ( _nbIntNodes + _edgeIntPnts.size() > 0 && + _nbIntNodes + _nbCornerNodes + _edgeIntPnts.size() > 3) { _Link split; // create sub-links (_splits) by splitting links with _intNodes @@ -1309,21 +1529,120 @@ namespace _Link& link = _hexLinks[ iLink ]; link._splits.clear(); split._nodes[ 0 ] = link._nodes[0]; - for ( size_t i = 0; i < link._intNodes.size(); ++ i ) + bool isOut = ( ! link._nodes[0]->Node() ); + //int iEnd = link._intNodes.size() - bool( link._nodes[1]->_intPoint ); + for ( size_t i = 0; i < link._intNodes.size(); ++i ) { - if ( split._nodes[ 0 ]->Node() ) + if ( link._intNodes[i].Node() ) { - split._nodes[ 1 ] = &link._intNodes[i]; - link._splits.push_back( split ); + if ( split._nodes[ 0 ]->Node() && !isOut ) + { + split._nodes[ 1 ] = &link._intNodes[i]; + link._splits.push_back( split ); + } + split._nodes[ 0 ] = &link._intNodes[i]; + } + switch ( link._intNodes[i].FaceIntPnt()->_transition ) { + case Trans_OUT: isOut = true; break; + case Trans_IN : isOut = false; break; + default:; // isOut remains the same } - split._nodes[ 0 ] = &link._intNodes[i]; } - if ( link._nodes[ 1 ]->Node() && split._nodes[ 0 ]->Node() ) + if ( link._nodes[ 1 ]->Node() && split._nodes[ 0 ]->Node() && !isOut ) { split._nodes[ 1 ] = link._nodes[1]; link._splits.push_back( split ); } } + + // Create _Node's at intersections with EDGEs. + + const double tol2 = _grid->_tol * _grid->_tol; + int facets[3], nbFacets, subEntity; + + for ( size_t iP = 0; iP < _edgeIntPnts.size(); ++iP ) + { + nbFacets = getEntity( _edgeIntPnts[iP], facets, subEntity ); + _Node* equalNode = 0; + switch( nbFacets ) { + case 1: // in a _Face + { + _Face& quad = _hexQuads[ facets[0] - SMESH_Block::ID_FirstF ]; + equalNode = FindEqualNode( quad._edgeNodes, _edgeIntPnts[ iP ], tol2 ); + if ( equalNode ) { + equalNode->Add( _edgeIntPnts[ iP ] ); + } + else { + quad._edgeNodes.push_back( _Node( 0, _edgeIntPnts[ iP ])); + ++_nbIntNodes; + } + break; + } + case 2: // on a _Link + { + _Link& link = _hexLinks[ subEntity - SMESH_Block::ID_FirstE ]; + if ( link._splits.size() > 0 ) + { + equalNode = FindEqualNode( link._intNodes, _edgeIntPnts[ iP ], tol2 ); + if ( equalNode ) + equalNode->Add( _edgeIntPnts[ iP ] ); + } + else + { + for ( int iF = 0; iF < 2; ++iF ) + { + _Face& quad = _hexQuads[ facets[iF] - SMESH_Block::ID_FirstF ]; + equalNode = FindEqualNode( quad._edgeNodes, _edgeIntPnts[ iP ], tol2 ); + if ( equalNode ) { + equalNode->Add( _edgeIntPnts[ iP ] ); + } + else { + quad._edgeNodes.push_back( _Node( 0, _edgeIntPnts[ iP ])); + ++_nbIntNodes; + } + } + } + break; + } + case 3: // at a corner + { + _Node& node = _hexNodes[ subEntity - SMESH_Block::ID_FirstV ]; + if ( node.Node() > 0 ) + { + if ( node._intPoint ) + node._intPoint->Add( _edgeIntPnts[ iP ]->_faceIDs, _edgeIntPnts[ iP ]->_node ); + } + else + { + for ( int iF = 0; iF < 3; ++iF ) + { + _Face& quad = _hexQuads[ facets[iF] - SMESH_Block::ID_FirstF ]; + equalNode = FindEqualNode( quad._edgeNodes, _edgeIntPnts[ iP ], tol2 ); + if ( equalNode ) { + equalNode->Add( _edgeIntPnts[ iP ] ); + } + else { + quad._edgeNodes.push_back( _Node( 0, _edgeIntPnts[ iP ])); + ++_nbIntNodes; + } + } + } + break; + } + default: // inside a hex + { + equalNode = FindEqualNode( _vertexNodes, _edgeIntPnts[ iP ], tol2 ); + if ( equalNode ) { + equalNode->Add( _edgeIntPnts[ iP ] ); + } + else { + _vertexNodes.push_back( _Node( 0, _edgeIntPnts[iP] )); + ++_nbIntNodes; + } + } + } // switch( nbFacets ) + + } // loop on _edgeIntPnts } } //================================================================================ @@ -1351,87 +1670,173 @@ namespace if ( _nbCornerNodes + _nbIntNodes < 4 ) return; - if ( _nbBndNodes == _nbCornerNodes && isInHole() ) + if ( _nbBndNodes == _nbCornerNodes && _nbIntNodes == 0 && isInHole() ) return; _polygons.clear(); - - vector polyhedraNodes; - vector quantities; + _polygons.reserve( 10 ); // create polygons from quadrangles and get their nodes - vector<_Node*> nodes; - nodes.reserve( _nbCornerNodes + _nbIntNodes ); - _Link polyLink; - polyLink._faces.reserve( 1 ); + vector< _OrientedLink > splits; + vector<_Node*> chainNodes; + + bool hasEdgeIntersections = !_edgeIntPnts.empty(); for ( int iF = 0; iF < 6; ++iF ) // loop on 6 sides of a hexahedron { - const _Face& quad = _hexQuads[ iF ] ; + _Face& quad = _hexQuads[ iF ] ; _polygons.resize( _polygons.size() + 1 ); - _Face& polygon = _polygons.back(); - polygon._links.clear(); - polygon._polyLinks.clear(); polygon._polyLinks.reserve( 10 ); + _Face* polygon = &_polygons.back(); + polygon->_polyLinks.reserve( 20 ); - // add splits of a link to a polygon and collect info on nodes - //int nbIn = 0, nbOut = 0, nbCorners = 0; - nodes.clear(); + splits.clear(); for ( int iE = 0; iE < 4; ++iE ) // loop on 4 sides of a quadrangle + for ( int iS = 0; iS < quad._links[ iE ].NbResultLinks(); ++iS ) + splits.push_back( quad._links[ iE ].ResultLink( iS )); + + // add splits of links to a polygon and add _polyLinks to make + // polygon's boundary closed + + int nbSplits = splits.size(); + if ( nbSplits < 2 && quad._edgeNodes.empty() ) + nbSplits = 0; + + if ( nbSplits == 0 && !quad._edgeNodes.empty() ) + { + // make _vertexNodes from _edgeNodes of an empty quad + const double tol2 = _grid->_tol * _grid->_tol; + for ( size_t iP = 0; iP < quad._edgeNodes.size(); ++iP ) + { + _Node* equalNode = + FindEqualNode( _vertexNodes, quad._edgeNodes[ iP ].EdgeIntPnt(), tol2 ); + if ( equalNode ) + equalNode->Add( quad._edgeNodes[ iP ].EdgeIntPnt() ); + else + _vertexNodes.push_back( quad._edgeNodes[ iP ]); + } + } + + while ( nbSplits > 0 ) { - int nbSpits = quad._links[ iE ].NbResultLinks(); - for ( int iS = 0; iS < nbSpits; ++iS ) + size_t iS = 0; + while ( !splits[ iS ] ) + ++iS; + + if ( !polygon->_links.empty() ) + { + _polygons.resize( _polygons.size() + 1 ); + polygon = &_polygons.back(); + polygon->_polyLinks.reserve( 20 ); + } + polygon->_links.push_back( splits[ iS ] ); + splits[ iS++ ]._link = 0; + --nbSplits; + + _Node* nFirst = polygon->_links.back().FirstNode(); + _Node *n1,*n2 = polygon->_links.back().LastNode(); + for ( ; nFirst != n2 && iS < splits.size(); ++iS ) { - _OrientedLink split = quad._links[ iE ].ResultLink( iS ); - _Node* n = split.FirstNode(); - if ( !polygon._links.empty() ) + _OrientedLink& split = splits[ iS ]; + if ( !split ) continue; + + n1 = split.FirstNode(); + if ( n1 != n2 ) { - _Node* nPrev = polygon._links.back().LastNode(); - if ( nPrev != n ) + // try to connect to intersections with EDGES + if ( quad._edgeNodes.size() > 0 && + findChain( n2, n1, quad, chainNodes )) { - polyLink._nodes[0] = nPrev; - polyLink._nodes[1] = n; - polygon._polyLinks.push_back( polyLink ); - polygon._links.push_back( _OrientedLink( &polygon._polyLinks.back() )); - nodes.push_back( nPrev ); + for ( size_t i = 1; i < chainNodes.size(); ++i ) + { + polyLink._nodes[0] = chainNodes[i-1]; + polyLink._nodes[1] = chainNodes[i]; + polygon->_polyLinks.push_back( polyLink ); + polygon->_links.push_back( _OrientedLink( &polygon->_polyLinks.back() )); + } } + // try to connect to a split ending on the same FACE + else + { + _OrientedLink foundSplit; + for ( int i = iS; i < splits.size() && !foundSplit; ++i ) + if (( foundSplit = splits[ i ]) && + ( n2->IsLinked( foundSplit.FirstNode()->_intPoint ))) + { + polyLink._nodes[0] = n2; + polyLink._nodes[1] = foundSplit.FirstNode(); + polygon->_polyLinks.push_back( polyLink ); + polygon->_links.push_back( _OrientedLink( &polygon->_polyLinks.back() )); + iS = i - 1; + } + else + { + foundSplit._link = 0; + } + if ( foundSplit ) + { + n2 = foundSplit.FirstNode(); + continue; + } + else + { + if ( n2->IsLinked( nFirst->_intPoint )) + break; + polyLink._nodes[0] = n2; + polyLink._nodes[1] = n1; + polygon->_polyLinks.push_back( polyLink ); + polygon->_links.push_back( _OrientedLink( &polygon->_polyLinks.back() )); + } + } + } + polygon->_links.push_back( split ); + split._link = 0; + --nbSplits; + n2 = polygon->_links.back().LastNode(); + + } // loop on splits + + if ( nFirst != n2 ) // close a polygon + { + findChain( n2, nFirst, quad, chainNodes ); + for ( size_t i = 1; i < chainNodes.size(); ++i ) + { + polyLink._nodes[0] = chainNodes[i-1]; + polyLink._nodes[1] = chainNodes[i]; + polygon->_polyLinks.push_back( polyLink ); + polygon->_links.push_back( _OrientedLink( &polygon->_polyLinks.back() )); } - polygon._links.push_back( split ); - nodes.push_back( n ); } - } - if ( polygon._links.size() > 1 ) - { - _Node* n1 = polygon._links.back().LastNode(); - _Node* n2 = polygon._links.front().FirstNode(); - if ( n1 != n2 ) + + if ( polygon->_links.size() < 3 && nbSplits > 0 ) { - polyLink._nodes[0] = n1; - polyLink._nodes[1] = n2; - polygon._polyLinks.push_back( polyLink ); - polygon._links.push_back( _OrientedLink( &polygon._polyLinks.back() )); - nodes.push_back( n1 ); + polygon->_polyLinks.clear(); + polygon->_links.clear(); } - // add polygon to its links - for ( size_t iL = 0; iL < polygon._links.size(); ++iL ) - polygon._links[ iL ]._link->_faces.push_back( &polygon ); - // store polygon nodes - quantities.push_back( nodes.size() ); - for ( size_t i = 0; i < nodes.size(); ++i ) - polyhedraNodes.push_back( nodes[i]->Node() ); - } - else - { - _polygons.resize( _polygons.size() - 1 ); - } - } + } // while ( nbSplits > 0 ) + + if ( polygon->_links.size() < 3 ) + _polygons.pop_back(); + + } // loop on 6 sides of a hexahedron // create polygons closing holes in a polyhedron + // add polygons to their links + for ( size_t iP = 0; iP < _polygons.size(); ++iP ) + { + _Face& polygon = _polygons[ iP ]; + for ( size_t iL = 0; iL < polygon._links.size(); ++iL ) + { + polygon._links[ iL ]._link->_faces.reserve( 2 ); + polygon._links[ iL ]._link->_faces.push_back( &polygon ); + } + } // find free links vector< _OrientedLink* > freeLinks; + freeLinks.reserve(20); for ( size_t iP = 0; iP < _polygons.size(); ++iP ) { _Face& polygon = _polygons[ iP ]; @@ -1439,58 +1844,157 @@ namespace if ( polygon._links[ iL ]._link->_faces.size() < 2 ) freeLinks.push_back( & polygon._links[ iL ]); } - // make closed chains of free links int nbFreeLinks = freeLinks.size(); if ( 0 < nbFreeLinks && nbFreeLinks < 3 ) return; + + set usedFaceIDs; + + // make closed chains of free links while ( nbFreeLinks > 0 ) { - nodes.clear(); _polygons.resize( _polygons.size() + 1 ); _Face& polygon = _polygons.back(); - polygon._links.clear(); + polygon._polyLinks.reserve( 20 ); + polygon._links.reserve( 20 ); - // get a remaining link to start from _OrientedLink* curLink = 0; - for ( size_t iL = 0; iL < freeLinks.size() && !curLink; ++iL ) - if (( curLink = freeLinks[ iL ] )) - freeLinks[ iL ] = 0; - nodes.push_back( curLink->LastNode() ); - polygon._links.push_back( *curLink ); - - // find all links connected to curLink - _Node* curNode = 0; - do + _Node* curNode; + if ( !hasEdgeIntersections ) { - curNode = curLink->FirstNode(); - curLink = 0; + // get a remaining link to start from for ( size_t iL = 0; iL < freeLinks.size() && !curLink; ++iL ) - if ( freeLinks[ iL ] && freeLinks[ iL ]->LastNode() == curNode ) - { - curLink = freeLinks[ iL ]; + if (( curLink = freeLinks[ iL ] )) freeLinks[ iL ] = 0; - nodes.push_back( curNode ); - polygon._links.push_back( *curLink ); + polygon._links.push_back( *curLink ); + --nbFreeLinks; + do + { + // find all links connected to curLink + curNode = curLink->FirstNode(); + curLink = 0; + for ( size_t iL = 0; iL < freeLinks.size() && !curLink; ++iL ) + if ( freeLinks[ iL ] && freeLinks[ iL ]->LastNode() == curNode ) + { + curLink = freeLinks[ iL ]; + freeLinks[ iL ] = 0; + polygon._links.push_back( *curLink ); + --nbFreeLinks; + } + } while ( curLink ); + } + else // there are intersections with EDGEs + { + TGeomID curFace; + // get a remaining link to start from, one lying on minimal + // nb of FACEs + { + map< vector< TGeomID >, int > facesOfLink; + map< vector< TGeomID >, int >::iterator f2l; + for ( size_t iL = 0; iL < freeLinks.size(); ++iL ) + if ( freeLinks[ iL ] ) + { + f2l = facesOfLink.insert + ( make_pair( freeLinks[ iL ]->GetNotUsedFace( usedFaceIDs ), iL )).first; + if ( f2l->first.size() == 1 ) + break; + } + f2l = facesOfLink.begin(); + if ( f2l->first.empty() ) + return; + curFace = f2l->first[0]; + curLink = freeLinks[ f2l->second ]; + freeLinks[ f2l->second ] = 0; + } + usedFaceIDs.insert( curFace ); + polygon._links.push_back( *curLink ); + --nbFreeLinks; + + // find all links bounding a FACE of curLink + do + { + // go forward from curLink + curNode = curLink->LastNode(); + curLink = 0; + for ( size_t iL = 0; iL < freeLinks.size() && !curLink; ++iL ) + if ( freeLinks[ iL ] && + freeLinks[ iL ]->FirstNode() == curNode && + freeLinks[ iL ]->LastNode()->IsOnFace( curFace )) + { + curLink = freeLinks[ iL ]; + freeLinks[ iL ] = 0; + polygon._links.push_back( *curLink ); + --nbFreeLinks; + } + } while ( curLink ); + + std::reverse( polygon._links.begin(), polygon._links.end() ); + + curLink = & polygon._links.back(); + do + { + // go backward from curLink + curNode = curLink->FirstNode(); + curLink = 0; + for ( size_t iL = 0; iL < freeLinks.size() && !curLink; ++iL ) + if ( freeLinks[ iL ] && + freeLinks[ iL ]->LastNode() == curNode && + freeLinks[ iL ]->FirstNode()->IsOnFace( curFace )) + { + curLink = freeLinks[ iL ]; + freeLinks[ iL ] = 0; + polygon._links.push_back( *curLink ); + --nbFreeLinks; + } + } while ( curLink ); + + curNode = polygon._links.back().FirstNode(); + + if ( polygon._links[0].LastNode() != curNode ) + { + if ( !_vertexNodes.empty() ) + { + // add links with _vertexNodes if not already used + for ( size_t iN = 0; iN < _vertexNodes.size(); ++iN ) + if ( _vertexNodes[ iN ].IsOnFace( curFace )) + { + bool used = ( curNode == &_vertexNodes[ iN ] ); + for ( size_t iL = 0; iL < polygon._links.size() && !used; ++iL ) + used = ( &_vertexNodes[ iN ] == polygon._links[ iL ].LastNode() ); + if ( !used ) + { + polyLink._nodes[0] = &_vertexNodes[ iN ]; + polyLink._nodes[1] = curNode; + polygon._polyLinks.push_back( polyLink ); + polygon._links.push_back( _OrientedLink( &polygon._polyLinks.back() )); + freeLinks.push_back( &polygon._links.back() ); + ++nbFreeLinks; + curNode = &_vertexNodes[ iN ]; + } + // TODO: to reorder _vertexNodes within polygon, if there are several ones + } } - } while ( curLink ); + polyLink._nodes[0] = polygon._links[0].LastNode(); + polyLink._nodes[1] = curNode; + polygon._polyLinks.push_back( polyLink ); + polygon._links.push_back( _OrientedLink( &polygon._polyLinks.back() )); + freeLinks.push_back( &polygon._links.back() ); + ++nbFreeLinks; + } - nbFreeLinks -= polygon._links.size(); + } // if there are intersections with EDGEs - if ( curNode != nodes.front() || polygon._links.size() < 3 ) + if ( polygon._links.size() < 3 || + polygon._links[0].LastNode() != polygon._links.back().FirstNode() ) return; // closed polygon not found -> invalid polyhedron - quantities.push_back( nodes.size() ); - for ( size_t i = 0; i < nodes.size(); ++i ) - polyhedraNodes.push_back( nodes[i]->Node() ); - - // add polygon to its links and reverse links - for ( size_t i = 0; i < polygon._links.size(); ++i ) + // add polygon to its links + for ( size_t iL = 0; iL < polygon._links.size(); ++iL ) { - polygon._links[i].Reverse(); - polygon._links[i]._link->_faces.push_back( &polygon ); + polygon._links[ iL ]._link->_faces.reserve( 2 ); + polygon._links[ iL ]._link->_faces.push_back( &polygon ); + polygon._links[ iL ].Reverse(); } - - //const size_t firstPoly = _polygons.size(); - } + } // while ( nbFreeLinks > 0 ) if ( ! checkPolyhedronSize() ) { @@ -1505,20 +2009,32 @@ namespace else if ( nbNodes == 6 && _polygons.size() == 5 ) isClassicElem = addPenta(); else if ( nbNodes == 5 && _polygons.size() == 5 ) isClassicElem = addPyra (); if ( !isClassicElem ) - _volumeDefs.set( polyhedraNodes, quantities ); + { + _volumeDefs._nodes.clear(); + _volumeDefs._quantities.clear(); + + for ( size_t iF = 0; iF < _polygons.size(); ++iF ) + { + const size_t nbLinks = _polygons[ iF ]._links.size(); + _volumeDefs._quantities.push_back( nbLinks ); + for ( size_t iL = 0; iL < nbLinks; ++iL ) + _volumeDefs._nodes.push_back( _polygons[ iF ]._links[ iL ].FirstNode() ); + } + } } //================================================================================ /*! * \brief Create elements in the mesh */ - int Hexahedron::MakeElements(SMESH_MesherHelper& helper) + int Hexahedron::MakeElements(SMESH_MesherHelper& helper, + const map< TGeomID, vector< TGeomID > >& edge2faceIDsMap) { SMESHDS_Mesh* mesh = helper.GetMeshDS(); size_t nbCells[3] = { _grid->_coords[0].size() - 1, _grid->_coords[1].size() - 1, _grid->_coords[2].size() - 1 }; - const size_t nbGridCells = nbCells[0] *nbCells [1] * nbCells[2]; + const size_t nbGridCells = nbCells[0] * nbCells[1] * nbCells[2]; vector< Hexahedron* > intersectedHex( nbGridCells, 0 ); int nbIntHex = 0; @@ -1535,10 +2051,10 @@ namespace for ( ; lineInd.More(); ++lineInd ) { GridLine& line = _grid->_lines[ iDir ][ lineInd.LineIndex() ]; - multiset< IntersectionPoint >::const_iterator ip = line._intPoints.begin(); + multiset< F_IntersectPoint >::const_iterator ip = line._intPoints.begin(); for ( ; ip != line._intPoints.end(); ++ip ) { - if ( !ip->_node ) continue; + //if ( !ip->_node ) continue; lineInd.SetIndexOnLine( ip->_indexOnLine ); for ( int iL = 0; iL < 4; ++iL ) // loop on 4 cells sharing a link { @@ -1561,12 +2077,15 @@ namespace } const int iLink = iL + iDir * 4; hex->_hexLinks[iLink]._intNodes.push_back( _Node( 0, &(*ip) )); - hex->_nbIntNodes++; + hex->_nbIntNodes += bool( ip->_node ); } } } } + // implement geom edges into the mesh + addEdges( helper, intersectedHex, edge2faceIDsMap ); + // add not split hexadrons to the mesh int nbAdded = 0; vector intHexInd( nbIntHex ); @@ -1636,6 +2155,366 @@ namespace return nbAdded; } + //================================================================================ + /*! + * \brief Implements geom edges into the mesh + */ + void Hexahedron::addEdges(SMESH_MesherHelper& helper, + vector< Hexahedron* >& hexes, + const map< TGeomID, vector< TGeomID > >& edge2faceIDsMap) + { + if ( edge2faceIDsMap.empty() ) return; + + // Prepare planes for intersecting with EDGEs + GridPlanes pln[3]; + { + gp_XYZ origPnt = ( _grid->_coords[0][0] * _grid->_axes[0] + + _grid->_coords[1][0] * _grid->_axes[1] + + _grid->_coords[2][0] * _grid->_axes[2] ); + for ( int iDirZ = 0; iDirZ < 3; ++iDirZ ) // iDirZ gives normal direction to planes + { + GridPlanes& planes = pln[ iDirZ ]; + int iDirX = ( iDirZ + 1 ) % 3; + int iDirY = ( iDirZ + 2 ) % 3; + planes._uNorm = ( _grid->_axes[ iDirY ] ^ _grid->_axes[ iDirZ ] ).Normalized(); + planes._vNorm = ( _grid->_axes[ iDirZ ] ^ _grid->_axes[ iDirX ] ).Normalized(); + planes._zNorm = ( _grid->_axes[ iDirX ] ^ _grid->_axes[ iDirY ] ).Normalized(); + double uvDot = planes._uNorm * planes._vNorm; + planes._factor = sqrt( 1. - uvDot * uvDot ); + planes._origins.resize( _grid->_coords[ iDirZ ].size() ); + planes._zProjs.resize ( _grid->_coords[ iDirZ ].size() ); + planes._origins[0] = origPnt; + planes._zProjs [0] = 0; + const double zFactor = _grid->_axes[ iDirZ ] * planes._zNorm; + const vector< double > & u = _grid->_coords[ iDirZ ]; + for ( int i = 1; i < planes._origins.size(); ++i ) + { + planes._origins[i] = origPnt + _grid->_axes[ iDirZ ] * ( u[i] - u[0] ); + planes._zProjs [i] = zFactor * ( u[i] - u[0] ); + } + } + } + const double deflection = _grid->_minCellSize / 20.; + const double tol = _grid->_tol; + // int facets[6] = { SMESH_Block::ID_F0yz, SMESH_Block::ID_F1yz, + // SMESH_Block::ID_Fx0z, SMESH_Block::ID_Fx1z, + // SMESH_Block::ID_Fxy0, SMESH_Block::ID_Fxy1 }; + E_IntersectPoint ip; + //ip._faceIDs.reserve(2); + + // Intersect EDGEs with the planes + map< TGeomID, vector< TGeomID > >::const_iterator e2fIt = edge2faceIDsMap.begin(); + for ( ; e2fIt != edge2faceIDsMap.end(); ++e2fIt ) + { + const TGeomID edgeID = e2fIt->first; + const TopoDS_Edge & E = TopoDS::Edge( _grid->_shapes( edgeID )); + BRepAdaptor_Curve curve( E ); + + ip._faceIDs = e2fIt->second; + ip._shapeID = edgeID; + + // discretize the EGDE + GCPnts_UniformDeflection discret( curve, deflection, true ); + if ( !discret.IsDone() || discret.NbPoints() < 2 ) + continue; + + // perform intersection + for ( int iDirZ = 0; iDirZ < 3; ++iDirZ ) + { + GridPlanes& planes = pln[ iDirZ ]; + int iDirX = ( iDirZ + 1 ) % 3; + int iDirY = ( iDirZ + 2 ) % 3; + double xLen = _grid->_coords[ iDirX ].back() - _grid->_coords[ iDirX ][0]; + double yLen = _grid->_coords[ iDirY ].back() - _grid->_coords[ iDirY ][0]; + double zFactor = _grid->_axes[ iDirZ ] * planes._zNorm; + int dIJK[3], d000[3] = { 0,0,0 }; + + // locate the 1st point of a segment within the grid + gp_XYZ p1 = discret.Value( 1 ).XYZ(); + double u1 = discret.Parameter( 1 ); + double zProj1 = planes._zNorm * ( p1 - planes._origins[0] ); + gp_Pnt orig = planes._origins[0] + planes._zNorm * zProj1; + gp_XY uv = planes.GetUV( p1, orig ); + int iX1 = int( uv.X() / xLen * ( _grid->_coords[ iDirX ].size() - 1. )); + int iY1 = int( uv.Y() / yLen * ( _grid->_coords[ iDirY ].size() - 1. )); + int iZ1 = int( zProj1 / planes._zProjs.back() * ( planes._zProjs.size() - 1. )); + locateValue( iX1, uv.X(), _grid->_coords[ iDirX ], dIJK[ iDirX ], tol ); + locateValue( iY1, uv.Y(), _grid->_coords[ iDirY ], dIJK[ iDirY ], tol ); + locateValue( iZ1, zProj1, planes._zProjs , dIJK[ iDirZ ], tol ); + + int ijk[3]; // grid index where a segment intersect a plane + ijk[ iDirX ] = iX1; + ijk[ iDirY ] = iY1; + ijk[ iDirZ ] = iZ1; + ip._uvw[ iDirX ] = uv.X() + _grid->_coords[ iDirX ][0]; + ip._uvw[ iDirY ] = uv.Y() + _grid->_coords[ iDirY ][0]; + ip._uvw[ iDirZ ] = zProj1 / zFactor + _grid->_coords[ iDirZ ][0]; + + // add the 1st vertex point to a hexahedron + if ( iDirZ == 0 ) + { + //ip._shapeID = _grid->_shapes.Add( helper.IthVertex( 0, curve.Edge(),/*CumOri=*/false)); + ip._point = p1; + _grid->_edgeIntP.push_back( ip ); + if ( !addIntersection( _grid->_edgeIntP.back(), hexes, ijk, d000 )) + _grid->_edgeIntP.pop_back(); + } + for ( int iP = 2; iP <= discret.NbPoints(); ++iP ) + { + // locate the 2nd point of a segment within the grid + gp_XYZ p2 = discret.Value( iP ).XYZ(); + double u2 = discret.Parameter( iP ); + double zProj2 = planes._zNorm * ( p2 - planes._origins[0] ); + int iZ2 = iZ1; + locateValue( iZ2, zProj2, planes._zProjs, dIJK[ iDirZ ], tol ); + + // treat intersections with planes between 2 end points of a segment + int dZ = ( iZ1 <= iZ2 ) ? +1 : -1; + int iZ = iZ1 + ( iZ1 < iZ2 ); + for ( int i = 0, nb = Abs( iZ1 - iZ2 ); i < nb; ++i, iZ += dZ ) + { + ip._point = findIntPoint( u1, zProj1, u2, zProj2, + planes._zProjs[ iZ ], + curve, planes._zNorm, planes._origins[0] ); + gp_XY uv = planes.GetUV( ip._point, planes._origins[ iZ ]); + locateValue( ijk[ iDirX ], uv.X(), _grid->_coords[ iDirX ], dIJK[ iDirX ], tol ); + locateValue( ijk[ iDirY ], uv.Y(), _grid->_coords[ iDirY ], dIJK[ iDirY ], tol ); + ijk[ iDirZ ] = iZ; + ip._uvw[ iDirX ] = uv.X() + _grid->_coords[ iDirX ][0]; + ip._uvw[ iDirY ] = uv.Y() + _grid->_coords[ iDirY ][0]; + ip._uvw[ iDirZ ] = planes._zProjs[ iZ ] / zFactor + _grid->_coords[ iDirZ ][0]; + + // add ip to hex "above" the plane + _grid->_edgeIntP.push_back( ip ); + dIJK[ iDirZ ] = 0; + bool added = addIntersection(_grid->_edgeIntP.back(), hexes, ijk, dIJK); + + // add ip to hex "below" the plane + ijk[ iDirZ ] = iZ-1; + if ( !addIntersection( _grid->_edgeIntP.back(), hexes, ijk, dIJK ) && + !added) + _grid->_edgeIntP.pop_back(); + } + iZ1 = iZ2; + p1 = p2; + u1 = u2; + zProj1 = zProj2; + } + // add the 2nd vertex point to a hexahedron + if ( iDirZ == 0 ) + { + orig = planes._origins[0] + planes._zNorm * zProj1; + uv = planes.GetUV( p1, orig ); + locateValue( ijk[ iDirX ], uv.X(), _grid->_coords[ iDirX ], dIJK[ iDirX ], tol ); + locateValue( ijk[ iDirY ], uv.Y(), _grid->_coords[ iDirY ], dIJK[ iDirY ], tol ); + ijk[ iDirZ ] = iZ1; + ip._uvw[ iDirX ] = uv.X() + _grid->_coords[ iDirX ][0]; + ip._uvw[ iDirY ] = uv.Y() + _grid->_coords[ iDirY ][0]; + ip._uvw[ iDirZ ] = zProj1 / zFactor + _grid->_coords[ iDirZ ][0]; + ip._point = p1; + _grid->_edgeIntP.push_back( ip ); + if ( !addIntersection( _grid->_edgeIntP.back(), hexes, ijk, d000 )) + _grid->_edgeIntP.pop_back(); + } + } // loop on 3 grid directions + } // loop on EDGEs + + // Create nodes at found intersections + // const E_IntersectPoint* eip; + // for ( size_t i = 0; i < hexes.size(); ++i ) + // { + // Hexahedron* h = hexes[i]; + // if ( !h ) continue; + // for ( int iF = 0; iF < 6; ++iF ) + // { + // _Face& quad = h->_hexQuads[ iF ]; + // for ( size_t iP = 0; iP < quad._edgeNodes.size(); ++iP ) + // if ( !quad._edgeNodes[ iP ]._node ) + // if (( eip = quad._edgeNodes[ iP ].EdgeIntPnt() )) + // quad._edgeNodes[ iP ]._intPoint->_node = helper.AddNode( eip->_point.X(), + // eip->_point.Y(), + // eip->_point.Z() ); + // } + // for ( size_t iP = 0; iP < hexes[i]->_vertexNodes.size(); ++iP ) + // if (( eip = h->_vertexNodes[ iP ].EdgeIntPnt() )) + // h->_vertexNodes[ iP ]._intPoint->_node = helper.AddNode( eip->_point.X(), + // eip->_point.Y(), + // eip->_point.Z() ); + // } + } + + //================================================================================ + /*! + * \brief Finds intersection of a curve with a plane + * \param [in] u1 - parameter of one curve point + * \param [in] proj1 - projection of the curve point to the plane normal + * \param [in] u2 - parameter of another curve point + * \param [in] proj2 - projection of the other curve point to the plane normal + * \param [in] proj - projection of a point where the curve intersects the plane + * \param [in] curve - the curve + * \param [in] axis - the plane normal + * \param [in] origin - the plane origin + * \return gp_Pnt - the found intersection point + */ + //================================================================================ + + gp_Pnt Hexahedron::findIntPoint( double u1, double proj1, + double u2, double proj2, + double proj, + BRepAdaptor_Curve& curve, + const gp_XYZ& axis, + const gp_XYZ& origin) + { + double r = (( proj - proj1 ) / ( proj2 - proj1 )); + double u = u1 * ( 1 - r ) + u2 * r; + gp_Pnt p = curve.Value( u ); + double newProj = axis * ( p.XYZ() - origin ); + if ( Abs( proj - newProj ) > _grid->_tol / 10. ) + { + if ( r > 0.5 ) + return findIntPoint( u2, proj2, u, newProj, proj, curve, axis, origin ); + else + return findIntPoint( u1, proj2, u, newProj, proj, curve, axis, origin ); + } + return p; + } + + //================================================================================ + /*! + * \brief Returns index of a hexahedron sub-entities holding a point + * \param [in] ip - intersection point + * \param [out] facets - 0-3 facets holding a point + * \param [out] sub - index of a vertex or an edge holding a point + * \return int - number of facets holding a point + */ + int Hexahedron::getEntity( const E_IntersectPoint* ip, int* facets, int& sub ) + { + enum { X = 1, Y = 2, Z = 4 }; // == 001, 010, 100 + int nbFacets = 0; + int vertex = 0, egdeMask = 0; + + if ( Abs( _grid->_coords[0][ _i ] - ip->_uvw[0] ) < _grid->_tol ) { + facets[ nbFacets++ ] = SMESH_Block::ID_F0yz; + egdeMask |= X; + } + else if ( Abs( _grid->_coords[0][ _i+1 ] - ip->_uvw[0] ) < _grid->_tol ) { + facets[ nbFacets++ ] = SMESH_Block::ID_F1yz; + vertex |= X; + egdeMask |= X; + } + if ( Abs( _grid->_coords[1][ _j ] - ip->_uvw[1] ) < _grid->_tol ) { + facets[ nbFacets++ ] = SMESH_Block::ID_Fx0z; + egdeMask |= Y; + } + else if ( Abs( _grid->_coords[1][ _j+1 ] - ip->_uvw[1] ) < _grid->_tol ) { + facets[ nbFacets++ ] = SMESH_Block::ID_Fx1z; + vertex |= Y; + egdeMask |= Y; + } + if ( Abs( _grid->_coords[2][ _k ] - ip->_uvw[2] ) < _grid->_tol ) { + facets[ nbFacets++ ] = SMESH_Block::ID_Fxy0; + egdeMask |= Z; + } + else if ( Abs( _grid->_coords[2][ _k+1 ] - ip->_uvw[2] ) < _grid->_tol ) { + facets[ nbFacets++ ] = SMESH_Block::ID_Fxy1; + vertex |= Z; + egdeMask |= Z; + } + + switch ( nbFacets ) + { + case 0: sub = 0; break; + case 1: sub = facets[0]; break; + case 2: { + const int edge [3][8] = { + { SMESH_Block::ID_E00z, SMESH_Block::ID_E10z, + SMESH_Block::ID_E01z, SMESH_Block::ID_E11z }, + { SMESH_Block::ID_E0y0, SMESH_Block::ID_E1y0, 0, 0, + SMESH_Block::ID_E0y1, SMESH_Block::ID_E1y1 }, + { SMESH_Block::ID_Ex00, 0, SMESH_Block::ID_Ex10, 0, + SMESH_Block::ID_Ex01, 0, SMESH_Block::ID_Ex11 } + }; + switch ( egdeMask ) { + case X | Y: sub = edge[ 0 ][ vertex ]; break; + case X | Z: sub = edge[ 1 ][ vertex ]; break; + default: sub = edge[ 2 ][ vertex ]; + } + break; + } + //case 3: + default: + sub = vertex + SMESH_Block::ID_FirstV; + } + + return nbFacets; + } + //================================================================================ + /*! + * \brief Adds intersection with an EDGE + */ + bool Hexahedron::addIntersection( const E_IntersectPoint& ip, + vector< Hexahedron* >& hexes, + int ijk[], int dIJK[] ) + { + bool added = false; + + size_t hexIndex[4] = { + _grid->CellIndex( ijk[0], ijk[1], ijk[2] ), + dIJK[0] ? _grid->CellIndex( ijk[0]+dIJK[0], ijk[1], ijk[2] ) : -1, + dIJK[1] ? _grid->CellIndex( ijk[0], ijk[1]+dIJK[1], ijk[2] ) : -1, + dIJK[2] ? _grid->CellIndex( ijk[0], ijk[1], ijk[2]+dIJK[2] ) : -1 + }; + for ( int i = 0; i < 4; ++i ) + { + if ( 0 <= hexIndex[i] && hexIndex[i] < hexes.size() && hexes[ hexIndex[i] ] ) + { + Hexahedron* h = hexes[ hexIndex[i] ]; + // check if ip is really inside the hex +#ifdef _DEBUG_ + if (( _grid->_coords[0][ h->_i ] - _grid->_tol > ip._uvw[0] ) || + ( _grid->_coords[0][ h->_i+1 ] + _grid->_tol < ip._uvw[0] ) || + ( _grid->_coords[1][ h->_j ] - _grid->_tol > ip._uvw[1] ) || + ( _grid->_coords[1][ h->_j+1 ] + _grid->_tol < ip._uvw[1] ) || + ( _grid->_coords[2][ h->_k ] - _grid->_tol > ip._uvw[2] ) || + ( _grid->_coords[2][ h->_k+1 ] + _grid->_tol < ip._uvw[2] )) + throw SALOME_Exception("ip outside a hex"); +#endif + h->_edgeIntPnts.push_back( & ip ); + added = true; + } + } + return added; + } + //================================================================================ + /*! + * \brief Finds nodes at a path from one node to another via intersections with EDGEs + */ + bool Hexahedron::findChain( _Node* n1, + _Node* n2, + _Face& quad, + vector<_Node*>& chn ) + { + chn.clear(); + chn.push_back( n1 ); + bool found = false; + do + { + found = false; + for ( size_t iP = 0; iP < quad._edgeNodes.size(); ++iP ) + if (( std::find( ++chn.begin(), chn.end(), & quad._edgeNodes[iP]) == chn.end()) && + chn.back()->IsLinked( quad._edgeNodes[ iP ]._intPoint )) + { + chn.push_back( & quad._edgeNodes[ iP ]); + found = true; + break; + } + } while ( found && chn.back() != n2 ); + + if ( chn.back() != n2 ) + chn.push_back( n2 ); + + return chn.size() > 2; + } //================================================================================ /*! * \brief Adds computed elements to the mesh @@ -1646,8 +2525,19 @@ namespace // add elements resulted from hexahedron intersection //for ( size_t i = 0; i < _volumeDefs.size(); ++i ) { - vector< const SMDS_MeshNode* >& nodes = _volumeDefs._nodes; - + vector< const SMDS_MeshNode* > nodes( _volumeDefs._nodes.size() ); + for ( size_t iN = 0; iN < nodes.size(); ++iN ) + if ( !( nodes[iN] = _volumeDefs._nodes[iN]->Node() )) + { + if ( const E_IntersectPoint* eip = _volumeDefs._nodes[iN]->EdgeIntPnt() ) + nodes[iN] = _volumeDefs._nodes[iN]->_intPoint->_node = + helper.AddNode( eip->_point.X(), + eip->_point.Y(), + eip->_point.Z() ); + else + throw SALOME_Exception("Bug: no node at intersection point"); + } + if ( !_volumeDefs._quantities.empty() ) { helper.AddPolyhedralVolume( nodes, _volumeDefs._quantities ); @@ -1679,8 +2569,11 @@ namespace */ bool Hexahedron::isInHole() const { + if ( !_vertexNodes.empty() ) + return false; + const int ijk[3] = { _i, _j, _k }; - IntersectionPoint curIntPnt; + F_IntersectPoint curIntPnt; // consider a cell to be in a hole if all links in any direction // comes OUT of geometry @@ -1698,19 +2591,19 @@ namespace { const _Link& link = _hexLinks[ iL + 4*iDir ]; // check transition of the first node of a link - const IntersectionPoint* firstIntPnt = 0; + const F_IntersectPoint* firstIntPnt = 0; if ( link._nodes[0]->Node() ) // 1st node is a hexa corner { curIntPnt._paramOnLine = coords[ ijk[ iDir ]] - coords[0]; const GridLine& line = _grid->_lines[ iDir ][ lineIndex[ iL ]]; - multiset< IntersectionPoint >::const_iterator ip = + multiset< F_IntersectPoint >::const_iterator ip = line._intPoints.upper_bound( curIntPnt ); --ip; firstIntPnt = &(*ip); } else if ( !link._intNodes.empty() ) { - firstIntPnt = link._intNodes[0]._intPoint; + firstIntPnt = link._intNodes[0].FaceIntPnt(); } if ( firstIntPnt ) @@ -1736,10 +2629,10 @@ namespace { const _Face& polygon = _polygons[iP]; gp_XYZ area (0,0,0); - SMESH_TNodeXYZ p1 ( polygon._links[ 0 ].FirstNode()->Node() ); + gp_XYZ p1 = polygon._links[ 0 ].FirstNode()->Point().XYZ(); for ( size_t iL = 0; iL < polygon._links.size(); ++iL ) { - SMESH_TNodeXYZ p2 ( polygon._links[ iL ].LastNode()->Node() ); + gp_XYZ p2 = polygon._links[ iL ].LastNode()->Point().XYZ(); area += p1 ^ p2; p1 = p2; } @@ -1764,12 +2657,12 @@ namespace _polygons[4]._links.size() != 4 || _polygons[5]._links.size() != 4 ) return false; - const SMDS_MeshNode* nodes[8]; + _Node* nodes[8]; int nbN = 0; for ( int iL = 0; iL < 4; ++iL ) { // a base node - nodes[iL] = _polygons[0]._links[iL].FirstNode()->Node(); + nodes[iL] = _polygons[0]._links[iL].FirstNode(); ++nbN; // find a top node above the base node @@ -1781,13 +2674,13 @@ namespace if ( quad->_links[i]._link == link ) { // 1st node of a link opposite to in - nodes[iL+4] = quad->_links[(i+2)%4].FirstNode()->Node(); + nodes[iL+4] = quad->_links[(i+2)%4].FirstNode(); ++nbN; break; } } if ( nbN == 8 ) - _volumeDefs.set( vector< const SMDS_MeshNode* >( nodes, nodes+8 )); + _volumeDefs.set( vector< _Node* >( nodes, nodes+8 )); return nbN == 8; } @@ -1797,10 +2690,10 @@ namespace */ bool Hexahedron::addTetra() { - const SMDS_MeshNode* nodes[4]; - nodes[0] = _polygons[0]._links[0].FirstNode()->Node(); - nodes[1] = _polygons[0]._links[1].FirstNode()->Node(); - nodes[2] = _polygons[0]._links[2].FirstNode()->Node(); + _Node* nodes[4]; + nodes[0] = _polygons[0]._links[0].FirstNode(); + nodes[1] = _polygons[0]._links[1].FirstNode(); + nodes[2] = _polygons[0]._links[2].FirstNode(); _Link* link = _polygons[0]._links[0]._link; ASSERT( link->_faces.size() > 1 ); @@ -1810,8 +2703,8 @@ namespace for ( int i = 0; i < 3; ++i ) if ( tria->_links[i]._link == link ) { - nodes[3] = tria->_links[(i+1)%3].LastNode()->Node(); - _volumeDefs.set( vector< const SMDS_MeshNode* >( nodes, nodes+4 )); + nodes[3] = tria->_links[(i+1)%3].LastNode(); + _volumeDefs.set( vector< _Node* >( nodes, nodes+4 )); return true; } @@ -1831,12 +2724,12 @@ namespace if ( iTri < 0 ) return false; // find nodes - const SMDS_MeshNode* nodes[6]; + _Node* nodes[6]; int nbN = 0; for ( int iL = 0; iL < 3; ++iL ) { // a base node - nodes[iL] = _polygons[ iTri ]._links[iL].FirstNode()->Node(); + nodes[iL] = _polygons[ iTri ]._links[iL].FirstNode(); ++nbN; // find a top node above the base node @@ -1849,13 +2742,13 @@ namespace if ( quad->_links[i]._link == link ) { // 1st node of a link opposite to in - nodes[iL+3] = quad->_links[(i+2)%4].FirstNode()->Node(); + nodes[iL+3] = quad->_links[(i+2)%4].FirstNode(); ++nbN; break; } } if ( nbN == 6 ) - _volumeDefs.set( vector< const SMDS_MeshNode* >( nodes, nodes+6 )); + _volumeDefs.set( vector< _Node* >( nodes, nodes+6 )); return ( nbN == 6 ); } @@ -1873,11 +2766,11 @@ namespace if ( iQuad < 0 ) return false; // find nodes - const SMDS_MeshNode* nodes[5]; - nodes[0] = _polygons[iQuad]._links[0].FirstNode()->Node(); - nodes[1] = _polygons[iQuad]._links[1].FirstNode()->Node(); - nodes[2] = _polygons[iQuad]._links[2].FirstNode()->Node(); - nodes[3] = _polygons[iQuad]._links[3].FirstNode()->Node(); + _Node* nodes[5]; + nodes[0] = _polygons[iQuad]._links[0].FirstNode(); + nodes[1] = _polygons[iQuad]._links[1].FirstNode(); + nodes[2] = _polygons[iQuad]._links[2].FirstNode(); + nodes[3] = _polygons[iQuad]._links[3].FirstNode(); _Link* link = _polygons[iQuad]._links[0]._link; ASSERT( link->_faces.size() > 1 ); @@ -1888,8 +2781,8 @@ namespace for ( int i = 0; i < 3; ++i ) if ( tria->_links[i]._link == link ) { - nodes[4] = tria->_links[(i+1)%3].LastNode()->Node(); - _volumeDefs.set( vector< const SMDS_MeshNode* >( nodes, nodes+5 )); + nodes[4] = tria->_links[(i+1)%3].LastNode(); + _volumeDefs.set( vector< _Node* >( nodes, nodes+5 )); return true; } @@ -1930,25 +2823,37 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, { Grid grid; - TopTools_MapOfShape faceMap; - for ( TopExp_Explorer fExp( theShape, TopAbs_FACE ); fExp.More(); fExp.Next() ) - if ( !faceMap.Add( fExp.Current() )) - faceMap.Remove( fExp.Current() ); // remove a face shared by two solids - + vector< TopoDS_Shape > faceVec; + { + TopTools_MapOfShape faceMap; + for ( TopExp_Explorer fExp( theShape, TopAbs_FACE ); fExp.More(); fExp.Next() ) + if ( faceMap.Add( fExp.Current() )) // skip a face shared by two solids + faceVec.push_back( fExp.Current() ); + } Bnd_Box shapeBox; - vector facesItersectors( faceMap.Extent() ); - TopTools_MapIteratorOfMapOfShape faceMppIt( faceMap ); - for ( int i = 0; faceMppIt.More(); faceMppIt.Next(), ++i ) + vector facesItersectors( faceVec.size() ); + map< TGeomID, vector< TGeomID > > edge2faceIDsMap; + TopExp_Explorer eExp; + for ( int i = 0; i < faceVec.size(); ++i ) { - facesItersectors[i]._face = TopoDS::Face( faceMppIt.Key() ); - facesItersectors[i]._grid = &grid; + facesItersectors[i]._face = TopoDS::Face ( faceVec[i] ); + facesItersectors[i]._faceID = grid._shapes.Add( faceVec[i] ); + facesItersectors[i]._grid = &grid; shapeBox.Add( facesItersectors[i].GetFaceBndBox() ); + + if ( _hyp->GetToAddEdges() ) + for ( eExp.Init( faceVec[i], TopAbs_EDGE ); eExp.More(); eExp.Next() ) + { + const TopoDS_Edge& edge = TopoDS::Edge( eExp.Current() ); + if ( !SMESH_Algo::isDegenerated( edge )) + edge2faceIDsMap[ grid._shapes.Add( edge )].push_back( facesItersectors[i]._faceID ); + } } vector xCoords, yCoords, zCoords; _hyp->GetCoordinates( xCoords, yCoords, zCoords, shapeBox ); - grid.SetCoordinates( xCoords, yCoords, zCoords, theShape ); + grid.SetCoordinates( xCoords, yCoords, zCoords, _hyp->GetAxisDirs(), theShape ); // check if the grid encloses the shape if ( !_hyp->IsGridBySpacing(0) || @@ -2015,12 +2920,12 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, // create volume elements Hexahedron hex( _hyp->GetSizeThreshold(), &grid ); - int nbAdded = hex.MakeElements( helper ); + int nbAdded = hex.MakeElements( helper, edge2faceIDsMap ); SMESHDS_Mesh* meshDS = theMesh.GetMeshDS(); if ( nbAdded > 0 ) { - // make all SOLIDS computed + // make all SOLIDs computed if ( SMESHDS_SubMesh* sm1 = meshDS->MeshElements( solidExp.Current()) ) { SMDS_ElemIteratorPtr volIt = sm1->GetElements(); @@ -2038,23 +2943,28 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, // remove free nodes if ( SMESHDS_SubMesh * smDS = meshDS->MeshElements( helper.GetSubShapeID() )) { - // intersection nodes + TIDSortedNodeSet nodesToRemove; + // get intersection nodes for ( int iDir = 0; iDir < 3; ++iDir ) { vector< GridLine >& lines = grid._lines[ iDir ]; for ( size_t i = 0; i < lines.size(); ++i ) { - multiset< IntersectionPoint >::iterator ip = lines[i]._intPoints.begin(); + multiset< F_IntersectPoint >::iterator ip = lines[i]._intPoints.begin(); for ( ; ip != lines[i]._intPoints.end(); ++ip ) if ( ip->_node && ip->_node->NbInverseElements() == 0 ) - meshDS->RemoveFreeNode( ip->_node, smDS, /*fromGroups=*/false ); + nodesToRemove.insert( nodesToRemove.end(), ip->_node ); } } - // grid nodes + // get grid nodes for ( size_t i = 0; i < grid._nodes.size(); ++i ) - if ( !grid._isBndNode[i] ) // nodes on boundary are already removed - if ( grid._nodes[i] && grid._nodes[i]->NbInverseElements() == 0 ) - meshDS->RemoveFreeNode( grid._nodes[i], smDS, /*fromGroups=*/false ); + if ( grid._nodes[i] && grid._nodes[i]->NbInverseElements() == 0 ) + nodesToRemove.insert( nodesToRemove.end(), grid._nodes[i] ); + + // do remove + TIDSortedNodeSet::iterator n = nodesToRemove.begin(); + for ( ; n != nodesToRemove.end(); ++n ) + meshDS->RemoveFreeNode( *n, smDS, /*fromGroups=*/false ); } return nbAdded; diff --git a/src/StdMeshers/StdMeshers_FaceSide.cxx b/src/StdMeshers/StdMeshers_FaceSide.cxx index 4e5e0478a..5382b7e87 100644 --- a/src/StdMeshers/StdMeshers_FaceSide.cxx +++ b/src/StdMeshers/StdMeshers_FaceSide.cxx @@ -251,7 +251,8 @@ StdMeshers_FaceSide::StdMeshers_FaceSide(const StdMeshers_FaceSide* theSide, */ //================================================================================ -StdMeshers_FaceSide::StdMeshers_FaceSide(UVPtStructVec& theSideNodes) +StdMeshers_FaceSide::StdMeshers_FaceSide(UVPtStructVec& theSideNodes, + const TopoDS_Face& theFace) { myEdge.resize( 1 ); myEdgeID.resize( 1, -1 ); @@ -272,12 +273,42 @@ StdMeshers_FaceSide::StdMeshers_FaceSide(UVPtStructVec& theSideNodes) if ( !myPoints.empty() ) { myPoints[0].normParam = 0; - gp_Pnt pPrev = SMESH_TNodeXYZ( myPoints[0].node ); - for ( size_t i = 1; i < myPoints.size(); ++i ) + if ( myPoints[0].node && + myPoints.back().node && + myPoints[ myNbPonits/2 ].node ) { - gp_Pnt p = SMESH_TNodeXYZ( myPoints[i].node ); - myLength += ( myPoints[i].normParam = p.Distance( pPrev )); - pPrev = p; + gp_Pnt pPrev = SMESH_TNodeXYZ( myPoints[0].node ); + for ( size_t i = 1; i < myPoints.size(); ++i ) + { + gp_Pnt p = SMESH_TNodeXYZ( myPoints[i].node ); + myLength += p.Distance( pPrev ); + myPoints[i].normParam = myLength; + pPrev = p; + } + } + else if ( !theFace.IsNull() ) + { + TopLoc_Location loc; + Handle(Geom_Surface) surf = BRep_Tool::Surface( theFace, loc ); + gp_Pnt pPrev = surf->Value( myPoints[0].u, myPoints[0].v ); + for ( size_t i = 1; i < myPoints.size(); ++i ) + { + gp_Pnt p = surf->Value( myPoints[i].u, myPoints[i].v ); + myLength += p.Distance( pPrev ); + myPoints[i].normParam = myLength; + pPrev = p; + } + } + else + { + gp_Pnt2d pPrev = myPoints[0].UV(); + for ( size_t i = 1; i < myPoints.size(); ++i ) + { + gp_Pnt2d p = myPoints[i].UV(); + myLength += p.Distance( pPrev ); + myPoints[i].normParam = myLength; + pPrev = p; + } } if ( myLength > std::numeric_limits::min() ) for ( size_t i = 1; i < myPoints.size(); ++i ) @@ -926,6 +957,18 @@ gp_Pnt2d StdMeshers_FaceSide::Value2d(double U) const return myC2d[ i ]->Value(par); } + else if ( !myPoints.empty() ) + { + int i = U * double( myPoints.size()-1 ); + while ( i > 0 && myPoints[ i ].normParam > U ) + --i; + while ( i+1 < myPoints.size() && myPoints[ i+1 ].normParam < U ) + ++i; + double r = (( U - myPoints[ i ].normParam ) / + ( myPoints[ i+1 ].normParam - myPoints[ i ].normParam )); + return ( myPoints[ i ].UV() * ( 1 - r ) + + myPoints[ i+1 ].UV() * r ); + } return myDefaultPnt2d; } diff --git a/src/StdMeshers/StdMeshers_FaceSide.hxx b/src/StdMeshers/StdMeshers_FaceSide.hxx index 4f714d4bf..c08d75cc3 100644 --- a/src/StdMeshers/StdMeshers_FaceSide.hxx +++ b/src/StdMeshers/StdMeshers_FaceSide.hxx @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -47,7 +48,6 @@ class SMESH_Mesh; class Adaptor2d_Curve2d; class Adaptor3d_Curve; class BRepAdaptor_CompCurve; -class TopoDS_Face; struct SMESH_ComputeError; class StdMeshers_FaceSide; @@ -96,7 +96,43 @@ public: /*! * \brief Create a side from an UVPtStructVec */ - StdMeshers_FaceSide(UVPtStructVec& theSideNodes); + StdMeshers_FaceSide(UVPtStructVec& theSideNodes, + const TopoDS_Face& theFace = TopoDS_Face()); + + // static "consrtuctors" + static StdMeshers_FaceSidePtr New(const TopoDS_Face& Face, + const TopoDS_Edge& Edge, + SMESH_Mesh* Mesh, + const bool IsForward, + const bool IgnoreMediumNodes, + SMESH_ProxyMesh::Ptr ProxyMesh = SMESH_ProxyMesh::Ptr()) + { return StdMeshers_FaceSidePtr + ( new StdMeshers_FaceSide( Face,Edge,Mesh,IsForward,IgnoreMediumNodes,ProxyMesh )); + } + static StdMeshers_FaceSidePtr New (const TopoDS_Face& Face, + std::list& Edges, + SMESH_Mesh* Mesh, + const bool IsForward, + const bool IgnoreMediumNodes, + SMESH_ProxyMesh::Ptr ProxyMesh = SMESH_ProxyMesh::Ptr()) + { return StdMeshers_FaceSidePtr + ( new StdMeshers_FaceSide( Face,Edges,Mesh,IsForward,IgnoreMediumNodes,ProxyMesh )); + } + static StdMeshers_FaceSidePtr New (const StdMeshers_FaceSide* Side, + const SMDS_MeshNode* Node, + const gp_Pnt2d* Pnt2d1, + const gp_Pnt2d* Pnt2d2=NULL, + const Handle(Geom2d_Curve)& C2d=NULL, + const double UFirst=0., + const double ULast=1.) + { return StdMeshers_FaceSidePtr + ( new StdMeshers_FaceSide( Side,Node,Pnt2d1,Pnt2d2,C2d,UFirst,ULast )); + } + static StdMeshers_FaceSidePtr New (UVPtStructVec& theSideNodes, + const TopoDS_Face& theFace = TopoDS_Face()) + { + return StdMeshers_FaceSidePtr( new StdMeshers_FaceSide( theSideNodes, theFace )); + } /*! * \brief Return wires of a face as StdMeshers_FaceSide's diff --git a/src/StdMeshers/StdMeshers_Geometric1D.cxx b/src/StdMeshers/StdMeshers_Geometric1D.cxx new file mode 100644 index 000000000..8d2be979c --- /dev/null +++ b/src/StdMeshers/StdMeshers_Geometric1D.cxx @@ -0,0 +1,204 @@ +// Copyright (C) 2007-2013 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// SMESH SMESH : implementaion of SMESH idl descriptions +// File : StdMeshers_Geometric1D.cxx +// Module : SMESH +// +#include "StdMeshers_Geometric1D.hxx" + +#include "SMESH_Mesh.hxx" + +#include +#include +#include +#include +#include +#include +#include + +//============================================================================= +/*! + * Constructor + */ +//============================================================================= + +StdMeshers_Geometric1D::StdMeshers_Geometric1D(int hypId, int studyId, SMESH_Gen * gen) + :StdMeshers_Reversible1D(hypId, studyId, gen) +{ + _begLength = 1.; + _ratio = 1.; + _name = "GeometricProgression"; +} + +//============================================================================= +/*! + * Sets length of the first segment + */ +//============================================================================= + +void StdMeshers_Geometric1D::SetStartLength(double length) + throw(SALOME_Exception) +{ + if ( _begLength != length ) + { + if (length <= 0) + throw SALOME_Exception(LOCALIZED("length must be positive")); + _begLength = length; + NotifySubMeshesHypothesisModification(); + } +} + +//============================================================================= +/*! + * Sets value of Common Ratio + */ +//============================================================================= + +void StdMeshers_Geometric1D::SetCommonRatio(double factor) + throw(SALOME_Exception) +{ + if ( _ratio != factor ) + { + if (factor == 0) + throw SALOME_Exception(LOCALIZED("Zero factor is not allowed")); + _ratio = factor; + NotifySubMeshesHypothesisModification(); + } +} + +//============================================================================= +/*! + * Returns length of the first segment + */ +//============================================================================= + +double StdMeshers_Geometric1D::GetStartLength() const +{ + return _begLength; +} + +//============================================================================= +/*! + * Returns value of Common Ratio + */ +//============================================================================= + +double StdMeshers_Geometric1D::GetCommonRatio() const +{ + return _ratio; +} + +//============================================================================= +/*! + * + */ +//============================================================================= + +ostream & StdMeshers_Geometric1D::SaveTo(ostream & save) +{ + save << _begLength << " " << _ratio << " "; + + StdMeshers_Reversible1D::SaveTo( save ); + + return save; +} + +//============================================================================= +/*! + * + */ +//============================================================================= + +istream & StdMeshers_Geometric1D::LoadFrom(istream & load) +{ + bool isOK = true; + isOK = (load >> _begLength); + isOK = (load >> _ratio); + + if (isOK) + StdMeshers_Reversible1D::LoadFrom( load ); + + return load; +} + +//================================================================================ +/*! + * \brief Initialize start and end length by the mesh built on the geometry + * \param theMesh - the built mesh + * \param theShape - the geometry of interest + * \retval bool - true if parameter values have been successfully defined + */ +//================================================================================ + +bool StdMeshers_Geometric1D::SetParametersByMesh(const SMESH_Mesh* theMesh, + const TopoDS_Shape& theShape) +{ + if ( !theMesh || theShape.IsNull() ) + return false; + + _begLength = _ratio = 0.; + + int nbEdges = 0; + TopTools_IndexedMapOfShape edgeMap; + TopExp::MapShapes( theShape, TopAbs_EDGE, edgeMap ); + for ( int i = 1; i <= edgeMap.Extent(); ++i ) + { + const TopoDS_Edge& edge = TopoDS::Edge( edgeMap( i )); + BRepAdaptor_Curve C( edge ); + + vector< double > params; + if ( SMESH_Algo::GetNodeParamOnEdge( theMesh->GetMeshDS(), edge, params )) + { + nbEdges++; + double l1 = GCPnts_AbscissaPoint::Length( C, params[0], params[1] ); + _begLength += l1; + if ( params.size() > 2 && l1 > 1e-100 ) + _ratio += GCPnts_AbscissaPoint::Length( C, params[1], params[2]) / l1; + else + _ratio += 1; + } + } + if ( nbEdges ) { + _begLength /= nbEdges; + _ratio /= nbEdges; + } + else { + _begLength = 1; + _ratio = 1; + } + return nbEdges; +} + +//================================================================================ +/*! + * \brief Initialize my parameter values by default parameters. + * \retval bool - true if parameter values have been successfully defined + */ +//================================================================================ + +bool StdMeshers_Geometric1D::SetParametersByDefaults(const TDefaults& dflts, + const SMESH_Mesh* /*mesh*/) +{ + return ( _begLength = dflts._elemLength ); +} + diff --git a/src/StdMeshers/StdMeshers_Geometric1D.hxx b/src/StdMeshers/StdMeshers_Geometric1D.hxx new file mode 100644 index 000000000..436c31748 --- /dev/null +++ b/src/StdMeshers/StdMeshers_Geometric1D.hxx @@ -0,0 +1,67 @@ +// Copyright (C) 2007-2013 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// SMESH SMESH : implementaion of SMESH idl descriptions +// File : StdMeshers_Geometric1D.hxx +// Module : SMESH +// +#ifndef _SMESH_Geometric1D_HXX_ +#define _SMESH_Geometric1D_HXX_ + +#include "SMESH_StdMeshers.hxx" + +#include "StdMeshers_Reversible1D.hxx" +#include "Utils_SALOME_Exception.hxx" + +class STDMESHERS_EXPORT StdMeshers_Geometric1D: public StdMeshers_Reversible1D +{ +public: + StdMeshers_Geometric1D(int hypId, int studyId, SMESH_Gen* gen); + + void SetStartLength(double length) throw(SALOME_Exception); + void SetCommonRatio(double factor) throw(SALOME_Exception); + + double GetStartLength() const; + double GetCommonRatio() const; + + virtual std::ostream & SaveTo(std::ostream & save); + virtual std::istream & LoadFrom(std::istream & load); + + /*! + * \brief Initialize start and end length by the mesh built on the geometry + * \param theMesh - the built mesh + * \param theShape - the geometry of interest + * \retval bool - true if parameter values have been successfully defined + */ + virtual bool SetParametersByMesh(const SMESH_Mesh* theMesh, const TopoDS_Shape& theShape); + + /*! + * \brief Initialize my parameter values by default parameters. + * \retval bool - true if parameter values have been successfully defined + */ + virtual bool SetParametersByDefaults(const TDefaults& dflts, const SMESH_Mesh* theMesh=0); + +protected: + double _begLength, _ratio; +}; + +#endif diff --git a/src/StdMeshers/StdMeshers_Hexa_3D.cxx b/src/StdMeshers/StdMeshers_Hexa_3D.cxx index 949ba6709..a0d205bd1 100644 --- a/src/StdMeshers/StdMeshers_Hexa_3D.cxx +++ b/src/StdMeshers/StdMeshers_Hexa_3D.cxx @@ -222,8 +222,8 @@ namespace * \brief Finds FaceQuadStruct having a side equal to a given one and rearranges * the found FaceQuadStruct::side to have the given side at a Q_BOTTOM place */ - FaceQuadStructPtr getQuadWithBottom( StdMeshers_FaceSide* side, - FaceQuadStructPtr quad[ 6 ]) + FaceQuadStructPtr getQuadWithBottom( StdMeshers_FaceSidePtr side, + FaceQuadStructPtr quad[ 6 ]) { FaceQuadStructPtr foundQuad; for ( int i = 1; i < 6; ++i ) @@ -231,7 +231,7 @@ namespace if ( !quad[i] ) continue; for ( unsigned iS = 0; iS < quad[i]->side.size(); ++iS ) { - const StdMeshers_FaceSide* side2 = quad[i]->side[iS]; + const StdMeshers_FaceSidePtr side2 = quad[i]->side[iS]; if (( side->FirstVertex().IsSame( side2->FirstVertex() ) || side->FirstVertex().IsSame( side2->LastVertex() )) && @@ -241,7 +241,7 @@ namespace { if ( iS != Q_BOTTOM ) { - vector< StdMeshers_FaceSide*> newSides; + vector< FaceQuadStruct::Side > newSides; for ( unsigned j = iS; j < quad[i]->side.size(); ++j ) newSides.push_back( quad[i]->side[j] ); for ( unsigned j = 0; j < iS; ++j ) @@ -391,7 +391,7 @@ bool StdMeshers_Hexa_3D::Compute(SMESH_Mesh & aMesh, for ( int i = 0; i < 6; ++i ) { const TopoDS_Face& F = aCubeSide[i]._quad->face; - StdMeshers_FaceSide* baseQuadSide = aCubeSide[i]._quad->side[ Q_BOTTOM ]; + StdMeshers_FaceSidePtr baseQuadSide = aCubeSide[i]._quad->side[ Q_BOTTOM ]; list baseEdges( baseQuadSide->Edges().begin(), baseQuadSide->Edges().end() ); // assure correctness of node positions on baseE: diff --git a/src/StdMeshers/StdMeshers_Prism_3D.cxx b/src/StdMeshers/StdMeshers_Prism_3D.cxx index 07e9a166d..33bd5e1dc 100644 --- a/src/StdMeshers/StdMeshers_Prism_3D.cxx +++ b/src/StdMeshers/StdMeshers_Prism_3D.cxx @@ -107,7 +107,7 @@ namespace { algo->myProxyMesh->GetMesh() != helper->GetMesh() ) algo->myProxyMesh.reset( new SMESH_ProxyMesh( *helper->GetMesh() )); - algo->myQuadStruct.reset(); + algo->myQuadList.clear(); if ( helper ) algo->_quadraticMesh = helper->GetIsQuadratic(); @@ -166,15 +166,15 @@ namespace { //================================================================================ bool setBottomEdge( const TopoDS_Edge& botE, - faceQuadStruct::Ptr& quad, + FaceQuadStruct::Ptr& quad, const TopoDS_Shape& face) { - quad->side[ QUAD_TOP_SIDE ]->Reverse(); - quad->side[ QUAD_LEFT_SIDE ]->Reverse(); + quad->side[ QUAD_TOP_SIDE ].grid->Reverse(); + quad->side[ QUAD_LEFT_SIDE ].grid->Reverse(); int edgeIndex = 0; for ( size_t i = 0; i < quad->side.size(); ++i ) { - StdMeshers_FaceSide* quadSide = quad->side[i]; + StdMeshers_FaceSidePtr quadSide = quad->side[i]; for ( int iE = 0; iE < quadSide->NbEdges(); ++iE ) if ( botE.IsSame( quadSide->Edge( iE ))) { @@ -681,7 +681,7 @@ bool StdMeshers_Prism_3D::Compute(SMESH_Mesh& theMesh, const TopoDS_Shape& theSh continue; // already computed prism } // find a source FACE of the SOLID: it's a FACE sharing a bottom EDGE with wFace - const TopoDS_Edge& wEdge = (*wQuad)->side[ QUAD_TOP_SIDE ]->Edge(0); + const TopoDS_Edge& wEdge = (*wQuad)->side[ QUAD_TOP_SIDE ].grid->Edge(0); PShapeIteratorPtr faceIt = myHelper->GetAncestors( wEdge, *myHelper->GetMesh(), TopAbs_FACE); while ( const TopoDS_Shape* f = faceIt->next() ) @@ -879,7 +879,7 @@ bool StdMeshers_Prism_3D::getWallFaces( Prism_3D::TPrismTopo & thePrism, int nbKnownFaces; do { nbKnownFaces = faceMap.Extent(); - StdMeshers_FaceSide *rightSide, *topSide; // sides of the quad + StdMeshers_FaceSidePtr rightSide, topSide; // sides of the quad for ( size_t i = 0; i < thePrism.myWallQuads.size(); ++i ) { rightSide = thePrism.myWallQuads[i].back()->side[ QUAD_RIGHT_SIDE ]; @@ -911,8 +911,8 @@ bool StdMeshers_Prism_3D::getWallFaces( Prism_3D::TPrismTopo & thePrism, { for ( size_t i = 0; i < thePrism.myWallQuads.size(); ++i ) { - StdMeshers_FaceSide* topSide = thePrism.myWallQuads[i].back()->side[ QUAD_TOP_SIDE ]; - const TopoDS_Edge & topE = topSide->Edge( 0 ); + StdMeshers_FaceSidePtr topSide = thePrism.myWallQuads[i].back()->side[ QUAD_TOP_SIDE ]; + const TopoDS_Edge & topE = topSide->Edge( 0 ); if ( topSide->NbEdges() > 1 ) return toSM( error(COMPERR_BAD_SHAPE, TCom("Side face #") << shapeID( thePrism.myWallQuads[i].back()->face ) @@ -958,8 +958,8 @@ bool StdMeshers_Prism_3D::getWallFaces( Prism_3D::TPrismTopo & thePrism, // Check that the top FACE shares all the top EDGEs for ( size_t i = 0; i < thePrism.myWallQuads.size(); ++i ) { - StdMeshers_FaceSide* topSide = thePrism.myWallQuads[i].back()->side[ QUAD_TOP_SIDE ]; - const TopoDS_Edge & topE = topSide->Edge( 0 ); + StdMeshers_FaceSidePtr topSide = thePrism.myWallQuads[i].back()->side[ QUAD_TOP_SIDE ]; + const TopoDS_Edge & topE = topSide->Edge( 0 ); if ( !myHelper->IsSubShape( topE, thePrism.myTop )) return toSM( error( TCom("Wrong source face (#") << shapeID( thePrism.myBottom ))); } @@ -1205,7 +1205,7 @@ bool StdMeshers_Prism_3D::computeWalls(const Prism_3D::TPrismTopo& thePrism) int wgt = 0; // "weight" for ( ; quad != thePrism.myWallQuads[iW].end(); ++quad ) { - StdMeshers_FaceSide* lftSide = (*quad)->side[ QUAD_LEFT_SIDE ]; + StdMeshers_FaceSidePtr lftSide = (*quad)->side[ QUAD_LEFT_SIDE ]; for ( int i = 0; i < lftSide->NbEdges(); ++i ) { ++wgt; @@ -1224,7 +1224,7 @@ bool StdMeshers_Prism_3D::computeWalls(const Prism_3D::TPrismTopo& thePrism) quad = thePrism.myWallQuads[iW].begin(); for ( ; quad != thePrism.myWallQuads[iW].end(); ++quad ) for ( int i = 0; i < NB_QUAD_SIDES; ++i ) - (*quad)->side[ i ]->SetIgnoreMediumNodes( true ); + (*quad)->side[ i ].grid->SetIgnoreMediumNodes( true ); } } @@ -1237,8 +1237,8 @@ bool StdMeshers_Prism_3D::computeWalls(const Prism_3D::TPrismTopo& thePrism) Prism_3D::TQuadList::const_iterator quad = quads.begin(); for ( ; quad != quads.end(); ++quad ) { - StdMeshers_FaceSide* rgtSide = (*quad)->side[ QUAD_RIGHT_SIDE ]; // tgt - StdMeshers_FaceSide* lftSide = (*quad)->side[ QUAD_LEFT_SIDE ]; // src + StdMeshers_FaceSidePtr rgtSide = (*quad)->side[ QUAD_RIGHT_SIDE ]; // tgt + StdMeshers_FaceSidePtr lftSide = (*quad)->side[ QUAD_LEFT_SIDE ]; // src bool swapLeftRight = ( lftSide->NbSegments( /*update=*/true ) == 0 && rgtSide->NbSegments( /*update=*/true ) > 0 ); if ( swapLeftRight ) @@ -1373,8 +1373,8 @@ bool StdMeshers_Prism_3D::computeWalls(const Prism_3D::TPrismTopo& thePrism) // to compute stuctured quad mesh on wall FACEs // --------------------------------------------------- { - const TopoDS_Edge& botE = (*quad)->side[ QUAD_BOTTOM_SIDE ]->Edge(0); - const TopoDS_Edge& topE = (*quad)->side[ QUAD_TOP_SIDE ]->Edge(0); + const TopoDS_Edge& botE = (*quad)->side[ QUAD_BOTTOM_SIDE ].grid->Edge(0); + const TopoDS_Edge& topE = (*quad)->side[ QUAD_TOP_SIDE ].grid->Edge(0); SMESH_subMesh* botSM = mesh->GetSubMesh( botE ); SMESH_subMesh* topSM = mesh->GetSubMesh( topE ); SMESH_subMesh* srcSM = botSM; @@ -2352,7 +2352,7 @@ bool StdMeshers_PrismAsBlock::Init(SMESH_MesherHelper* helper, Prism_3D::TQuadList::const_iterator quad = thePrism.myWallQuads[ iE ].begin(); for ( ; quad != thePrism.myWallQuads[ iE ].end(); ++quad ) { - const TopoDS_Edge& quadBot = (*quad)->side[ QUAD_BOTTOM_SIDE ]->Edge( 0 ); + const TopoDS_Edge& quadBot = (*quad)->side[ QUAD_BOTTOM_SIDE ].grid->Edge( 0 ); if ( !myHelper->LoadNodeColumns( faceColumns, (*quad)->face, quadBot, meshDS )) return error(COMPERR_BAD_INPUT_MESH, TCom("Can't find regular quadrangle mesh ") << "on a side face #" << MeshDS()->ShapeToIndex( (*quad)->face )); @@ -2373,7 +2373,7 @@ bool StdMeshers_PrismAsBlock::Init(SMESH_MesherHelper* helper, Prism_3D::TQuadList::const_iterator quad = thePrism.myWallQuads[ iE ].begin(); for ( ; quad != thePrism.myWallQuads[ iE ].end(); ++quad ) { - const TopoDS_Edge& quadBot = (*quad)->side[ QUAD_BOTTOM_SIDE ]->Edge( 0 ); + const TopoDS_Edge& quadBot = (*quad)->side[ QUAD_BOTTOM_SIDE ].grid->Edge( 0 ); if ( !myHelper->LoadNodeColumns( faceColumns, (*quad)->face, quadBot, meshDS )) return error(COMPERR_BAD_INPUT_MESH, TCom("Can't find regular quadrangle mesh ") << "on a side face #" << MeshDS()->ShapeToIndex( (*quad)->face )); diff --git a/src/StdMeshers/StdMeshers_Projection_2D.cxx b/src/StdMeshers/StdMeshers_Projection_2D.cxx index 6bb6adc1f..96570ecdd 100644 --- a/src/StdMeshers/StdMeshers_Projection_2D.cxx +++ b/src/StdMeshers/StdMeshers_Projection_2D.cxx @@ -1018,9 +1018,9 @@ bool StdMeshers_Projection_2D::Compute(SMESH_Mesh& theMesh, const TopoDS_Shape& map< double, const SMDS_MeshNode* >::iterator u_oldNode, u_newNode, u_newOnSeam, newEnd; set< const SMDS_MeshNode* > seamNodes; - // mapper puts on a seam edge nodes from 2 edges + // mapper changed, no more "mapper puts on a seam edge nodes from 2 edges" if ( isSeam && ! getBoundaryNodes ( sm, tgtFace, u2nodesOnSeam, seamNodes )) - RETURN_BAD_RESULT("getBoundaryNodes() failed"); + ;//RETURN_BAD_RESULT("getBoundaryNodes() failed"); SMDS_NodeIteratorPtr nIt = smDS->GetNodes(); while ( nIt->more() ) diff --git a/src/StdMeshers/StdMeshers_Propagation.cxx b/src/StdMeshers/StdMeshers_Propagation.cxx index bfe999db9..15bd1944c 100644 --- a/src/StdMeshers/StdMeshers_Propagation.cxx +++ b/src/StdMeshers/StdMeshers_Propagation.cxx @@ -64,7 +64,8 @@ namespace { /*! * \brief Return an edge from which hypotheses are propagated from */ - static TopoDS_Edge GetSource(SMESH_subMesh * submesh); + static TopoDS_Edge GetSource(SMESH_subMesh * submesh, + bool& isPropagOfDistribution); /*! * \brief Does it's main job */ @@ -90,23 +91,28 @@ StdMeshers_Propagation::StdMeshers_Propagation (int hypId, int studyId, SMESH_Ge _name = GetName(); _param_algo_dim = -1; // 1D auxiliary } +StdMeshers_PropagOfDistribution::StdMeshers_PropagOfDistribution (int hypId, + int studyId, + SMESH_Gen * gen) + : StdMeshers_Propagation(hypId, studyId, gen) { _name = GetName(); } StdMeshers_Propagation::~StdMeshers_Propagation() {} string StdMeshers_Propagation::GetName () { return "Propagation"; } +string StdMeshers_PropagOfDistribution::GetName () { return "PropagOfDistribution"; } ostream & StdMeshers_Propagation::SaveTo (ostream & save) { return save; } istream & StdMeshers_Propagation::LoadFrom (istream & load) { return load; } -ostream & operator << (ostream & save, StdMeshers_Propagation & hyp) { return hyp.SaveTo(save); } -istream & operator >> (istream & load, StdMeshers_Propagation & hyp) { return hyp.LoadFrom(load); } bool StdMeshers_Propagation::SetParametersByMesh(const SMESH_Mesh*, const TopoDS_Shape& ) { return false; } bool StdMeshers_Propagation::SetParametersByDefaults(const TDefaults&,const SMESH_Mesh*) { return false; } void StdMeshers_Propagation::SetPropagationMgr(SMESH_subMesh* subMesh) { PropagationMgr::Set( subMesh ); } /*! - * \brief Return an edge from which hypotheses are propagated from + * \brief Return an edge from which hypotheses are propagated */ -TopoDS_Edge StdMeshers_Propagation::GetPropagationSource(SMESH_Mesh& theMesh, - const TopoDS_Shape& theEdge) +TopoDS_Edge StdMeshers_Propagation::GetPropagationSource(SMESH_Mesh& theMesh, + const TopoDS_Shape& theEdge, + bool& isPropagOfDistribution) { - return PropagationMgr::GetSource(theMesh.GetSubMeshContaining( theEdge )); + return PropagationMgr::GetSource( theMesh.GetSubMeshContaining( theEdge ), + isPropagOfDistribution); } //============================================================================= @@ -126,11 +132,13 @@ namespace { struct PropagationMgrData : public EventListenerData { bool myForward; //!< true if a curve of edge in chain is codirected with one of source edge + bool myIsPropagOfDistribution; //!< type of Propagation hyp PropagationMgrData( SubMeshState state=WAIT_PROPAG_HYP ): EventListenerData(true) { - myType = state; myForward = true; + myType = state; myForward = true; myIsPropagOfDistribution = false; } void Init() { myType = WAIT_PROPAG_HYP; mySubMeshes.clear(); myForward = true; + myIsPropagOfDistribution = false; } SubMeshState State() const { return (SubMeshState) myType; @@ -217,8 +225,13 @@ namespace { const SMESH_Hypothesis* getProagationHyp (SMESH_Mesh& theMesh, const TopoDS_Shape& theEdge) { - static SMESH_HypoFilter propagHypFilter - ( SMESH_HypoFilter::HasName( StdMeshers_Propagation::GetName ())); + static SMESH_HypoFilter propagHypFilter; + if ( propagHypFilter.IsEmpty() ) + { + propagHypFilter. + Init( SMESH_HypoFilter::HasName( StdMeshers_Propagation::GetName ())). + Or ( SMESH_HypoFilter::HasName( StdMeshers_PropagOfDistribution::GetName ())); + } return theMesh.GetHypothesis( theEdge, propagHypFilter, true ); } //================================================================================ @@ -248,6 +261,10 @@ namespace { PropagationMgrData* chainData = getData( theMainSubMesh ); chainData->SetState( HAS_PROPAG_HYP ); + if ( const SMESH_Hypothesis * propagHyp = getProagationHyp( *mesh, theMainEdge )) + chainData->myIsPropagOfDistribution = + ( StdMeshers_PropagOfDistribution::GetName() == propagHyp->GetName() ); + // Edge submeshes, to which the 1D hypothesis will be propagated from theMainEdge list & chain = chainData->mySubMeshes; chain.clear(); @@ -462,17 +479,21 @@ namespace { { if ( findData( submesh )) return; DBGMSG( "PropagationMgr::Set() on " << submesh->GetId() ); - EventListenerData* data = new PropagationMgrData(); + PropagationMgrData* data = new PropagationMgrData(); submesh->SetEventListener( getListener(), data, submesh ); const SMESH_Hypothesis * propagHyp = getProagationHyp( *submesh->GetFather(), submesh->GetSubShape() ); if ( propagHyp ) + { + data->myIsPropagOfDistribution = + ( StdMeshers_PropagOfDistribution::GetName() == propagHyp->GetName() ); getListener()->ProcessEvent( SMESH_subMesh::ADD_HYP, SMESH_subMesh::ALGO_EVENT, submesh, data, propagHyp); + } } //================================================================================ /*! @@ -480,7 +501,8 @@ namespace { */ //================================================================================ - TopoDS_Edge PropagationMgr::GetSource(SMESH_subMesh * submesh) + TopoDS_Edge PropagationMgr::GetSource(SMESH_subMesh * submesh, + bool& isPropagOfDistribution) { if ( PropagationMgrData* data = findData( submesh )) { if ( data->State() == IN_CHAIN ) { @@ -489,6 +511,9 @@ namespace { TopoDS_Shape edge = sm->GetSubShape(); edge = edge.Oriented( data->myForward ? TopAbs_FORWARD : TopAbs_REVERSED ); DBGMSG( " GetSource() = edge " << sm->GetId() << " REV = " << (!data->myForward)); + isPropagOfDistribution = false; + if ( PropagationMgrData* data = findData( sm )) + isPropagOfDistribution = data->myIsPropagOfDistribution; if ( edge.ShapeType() == TopAbs_EDGE ) return TopoDS::Edge( edge ); } @@ -502,9 +527,9 @@ namespace { */ //================================================================================ - void PropagationMgr::ProcessEvent(const int event, - const int eventType, - SMESH_subMesh* subMesh, + void PropagationMgr::ProcessEvent(const int event, + const int eventType, + SMESH_subMesh* subMesh, SMESH_subMeshEventListenerData* listenerData, const SMESH_Hypothesis* hyp) { @@ -516,7 +541,8 @@ namespace { return; DBGMSG( "PropagationMgr::ProcessEvent() on " << subMesh->GetId() ); - bool isPropagHyp = ( StdMeshers_Propagation::GetName() == hyp->GetName() ); + bool isPropagHyp = ( StdMeshers_Propagation::GetName() == hyp->GetName() || + StdMeshers_PropagOfDistribution::GetName() == hyp->GetName() ); PropagationMgrData* data = static_cast( listenerData ); switch ( data->State() ) { diff --git a/src/StdMeshers/StdMeshers_Propagation.hxx b/src/StdMeshers/StdMeshers_Propagation.hxx index c5a1616f7..e8a2b6e7a 100644 --- a/src/StdMeshers/StdMeshers_Propagation.hxx +++ b/src/StdMeshers/StdMeshers_Propagation.hxx @@ -50,8 +50,6 @@ class STDMESHERS_EXPORT StdMeshers_Propagation:public SMESH_Hypothesis virtual std::ostream & SaveTo(std::ostream & save); virtual std::istream & LoadFrom(std::istream & load); - friend std::ostream & operator <<(std::ostream & save, StdMeshers_Propagation & hyp); - friend std::istream & operator >>(std::istream & load, StdMeshers_Propagation & hyp); static std::string GetName (); @@ -69,7 +67,9 @@ class STDMESHERS_EXPORT StdMeshers_Propagation:public SMESH_Hypothesis * \param theEdge - edge to which hypotheses are propagated * \retval TopoDS_Edge - source edge, also passing orientation */ - static TopoDS_Edge GetPropagationSource(SMESH_Mesh& theMesh, const TopoDS_Shape& theEdge); + static TopoDS_Edge GetPropagationSource(SMESH_Mesh& theMesh, + const TopoDS_Shape& theEdge, + bool& isPropagOfDistribution ); /*! * \brief Initialize my parameter values by the mesh built on the geometry @@ -88,4 +88,19 @@ class STDMESHERS_EXPORT StdMeshers_Propagation:public SMESH_Hypothesis virtual bool SetParametersByDefaults(const TDefaults& dflts, const SMESH_Mesh* theMesh=0); }; + +// ======================================================================= +/*! + * \brief Propagation Of Distribution hypothesis + */ +// ======================================================================= + +class STDMESHERS_EXPORT StdMeshers_PropagOfDistribution: public StdMeshers_Propagation +{ + public: + StdMeshers_PropagOfDistribution(int hypId, int studyId, SMESH_Gen * gen); + + static std::string GetName(); +}; + #endif diff --git a/src/StdMeshers/StdMeshers_QuadrangleParams.cxx b/src/StdMeshers/StdMeshers_QuadrangleParams.cxx index 5369e598c..e8ab5ad6a 100644 --- a/src/StdMeshers/StdMeshers_QuadrangleParams.cxx +++ b/src/StdMeshers/StdMeshers_QuadrangleParams.cxx @@ -87,6 +87,43 @@ void StdMeshers_QuadrangleParams::SetQuadType (StdMeshers_QuadType type) } } +//================================================================================ +/*! + * \brief Set positions of enforced nodes + */ +//================================================================================ + +void StdMeshers_QuadrangleParams:: +SetEnforcedNodes( const std::vector< TopoDS_Shape >& shapes, + const std::vector< gp_Pnt >& points ) +{ + bool isChanged = ( shapes != _enforcedVertices || + points.size() != _enforcedPoints.size() ); + for ( size_t i = 0; i < points.size() && !isChanged; ++i ) + isChanged = ( _enforcedPoints[ i ].SquareDistance( points[i] ) > 1e-100 ); + + if ( isChanged ) + { + _enforcedVertices = shapes; + _enforcedPoints = points; + NotifySubMeshesHypothesisModification(); + } +} + +//================================================================================ +/*! + * \brief Returns positions of enforced nodes + */ +//================================================================================ + +void StdMeshers_QuadrangleParams:: +GetEnforcedNodes( std::vector< TopoDS_Shape >& shapes, + std::vector< gp_Pnt >& points ) const +{ + shapes = _enforcedVertices; + points = _enforcedPoints; +} + //============================================================================= /*! * @@ -98,6 +135,13 @@ ostream & StdMeshers_QuadrangleParams::SaveTo(ostream & save) save << _triaVertexID << " UNDEFINED " << int(_quadType); else save << _triaVertexID << " " << _objEntry << " " << int(_quadType); + + save << " " << _enforcedPoints.size(); + for ( size_t i = 0; i < _enforcedPoints.size(); ++i ) + save << " " << _enforcedPoints[i].X() + << " " << _enforcedPoints[i].Y() + << " " << _enforcedPoints[i].Z(); + return save; } @@ -122,29 +166,25 @@ istream & StdMeshers_QuadrangleParams::LoadFrom(istream & load) if (isOK) _quadType = StdMeshers_QuadType(type); + // _enforcedVertices are loaded at StdMeshers_I level + // because GEOM objects are referred by study entry. + + int nbP = 0; + double x,y,z; + if ( load >> nbP && nbP > 0 ) + { + _enforcedPoints.reserve( nbP ); + while ( _enforcedPoints.size() < _enforcedPoints.capacity() ) + if ( load >> x && + load >> y && + load >> z ) + _enforcedPoints.push_back( gp_Pnt( x,y,z )); + else + break; + } return load; } -//============================================================================= -/*! - * - */ -//============================================================================= -ostream & operator <<(ostream & save, StdMeshers_QuadrangleParams & hyp) -{ - return hyp.SaveTo( save ); -} - -//============================================================================= -/*! - * - */ -//============================================================================= -istream & operator >>(istream & load, StdMeshers_QuadrangleParams & hyp) -{ - return hyp.LoadFrom( load ); -} - //================================================================================ /*! * \brief Redifined method diff --git a/src/StdMeshers/StdMeshers_QuadrangleParams.hxx b/src/StdMeshers/StdMeshers_QuadrangleParams.hxx index 9d8374daa..afc6a8523 100644 --- a/src/StdMeshers/StdMeshers_QuadrangleParams.hxx +++ b/src/StdMeshers/StdMeshers_QuadrangleParams.hxx @@ -24,9 +24,12 @@ #define _SMESH_QUADRANGLEPARAMS_HXX_ #include "SMESH_StdMeshers.hxx" - #include "SMESH_Hypothesis.hxx" -#include "Utils_SALOME_Exception.hxx" + +#include + +#include +#include enum StdMeshers_QuadType { @@ -38,8 +41,7 @@ enum StdMeshers_QuadType QUAD_NB_TYPES }; -class STDMESHERS_EXPORT StdMeshers_QuadrangleParams: - public SMESH_Hypothesis +class STDMESHERS_EXPORT StdMeshers_QuadrangleParams: public SMESH_Hypothesis { public: StdMeshers_QuadrangleParams(int hypId, int studyId, SMESH_Gen* gen); @@ -54,12 +56,13 @@ public: void SetQuadType (StdMeshers_QuadType type); StdMeshers_QuadType GetQuadType() const { return _quadType; } + void SetEnforcedNodes( const std::vector< TopoDS_Shape >& shapes, + const std::vector< gp_Pnt >& points ); + void GetEnforcedNodes( std::vector< TopoDS_Shape >& shapes, + std::vector< gp_Pnt >& points ) const; + virtual std::ostream & SaveTo(std::ostream & save); virtual std::istream & LoadFrom(std::istream & load); - friend std::ostream& operator << (std::ostream & save, - StdMeshers_QuadrangleParams & hyp); - friend std::istream& operator >> (std::istream & load, - StdMeshers_QuadrangleParams & hyp); /*! * \brief Initialize start and end length by the mesh built on the geometry @@ -78,9 +81,11 @@ public: const SMESH_Mesh* theMesh=0); protected: - int _triaVertexID; - std::string _objEntry; - StdMeshers_QuadType _quadType; + int _triaVertexID; + std::string _objEntry; + StdMeshers_QuadType _quadType; + std::vector< TopoDS_Shape > _enforcedVertices; + std::vector< gp_Pnt > _enforcedPoints; }; #endif diff --git a/src/StdMeshers/StdMeshers_Quadrangle_2D.cxx b/src/StdMeshers/StdMeshers_Quadrangle_2D.cxx index 62fadc505..127598867 100644 --- a/src/StdMeshers/StdMeshers_Quadrangle_2D.cxx +++ b/src/StdMeshers/StdMeshers_Quadrangle_2D.cxx @@ -33,13 +33,16 @@ #include "SMESH_Block.hxx" #include "SMESH_Comment.hxx" #include "SMESH_Gen.hxx" +#include "SMESH_HypoFilter.hxx" #include "SMESH_Mesh.hxx" +#include "SMESH_MeshAlgos.hxx" #include "SMESH_MesherHelper.hxx" #include "SMESH_subMesh.hxx" #include "StdMeshers_FaceSide.hxx" #include "StdMeshers_QuadrangleParams.hxx" #include "StdMeshers_ViscousLayers2D.hxx" +#include #include #include #include @@ -85,8 +88,9 @@ StdMeshers_Quadrangle_2D::StdMeshers_Quadrangle_2D (int hypId, int studyId, myTrianglePreference(false), myTriaVertexID(-1), myNeedSmooth(false), + myParams( NULL ), myQuadType(QUAD_STANDARD), - myHelper( 0 ) + myHelper( NULL ) { MESSAGE("StdMeshers_Quadrangle_2D::StdMeshers_Quadrangle_2D"); _name = "Quadrangle_2D"; @@ -119,15 +123,16 @@ bool StdMeshers_Quadrangle_2D::CheckHypothesis const TopoDS_Shape& aShape, SMESH_Hypothesis::Hypothesis_Status& aStatus) { - myTriaVertexID = -1; - myQuadType = QUAD_STANDARD; + myTriaVertexID = -1; + myQuadType = QUAD_STANDARD; myQuadranglePreference = false; - myTrianglePreference = false; - myQuadStruct.reset(); - myHelper = NULL; + myTrianglePreference = false; + myHelper = (SMESH_MesherHelper*)NULL; + myParams = NULL; + myQuadList.clear(); bool isOk = true; - aStatus = SMESH_Hypothesis::HYP_OK; + aStatus = SMESH_Hypothesis::HYP_OK; const list & hyps = GetUsedHypothesis(aMesh, aShape, false); @@ -138,11 +143,11 @@ bool StdMeshers_Quadrangle_2D::CheckHypothesis // First assigned hypothesis (if any) is processed now if (hyps.size() > 0) { aHyp = hyps.front(); - if (strcmp("QuadrangleParams", aHyp->GetName()) == 0) { - const StdMeshers_QuadrangleParams* aHyp1 = - (const StdMeshers_QuadrangleParams*)aHyp; - myTriaVertexID = aHyp1->GetTriaVertex(); - myQuadType = aHyp1->GetQuadType(); + if (strcmp("QuadrangleParams", aHyp->GetName()) == 0) + { + myParams = (const StdMeshers_QuadrangleParams*)aHyp; + myTriaVertexID = myParams->GetTriaVertex(); + myQuadType = myParams->GetQuadType(); if (myQuadType == QUAD_QUADRANGLE_PREF || myQuadType == QUAD_QUADRANGLE_PREF_REVERSED) myQuadranglePreference = true; @@ -221,22 +226,25 @@ bool StdMeshers_Quadrangle_2D::Compute (SMESH_Mesh& aMesh, FaceQuadStruct::Ptr quad = CheckNbEdges( aMesh, F, /*considerMesh=*/true ); if (!quad) return false; - myQuadStruct = quad; + myQuadList.clear(); + myQuadList.push_back( quad ); + + if ( !getEnforcedUV() ) + return false; updateDegenUV( quad ); + int n1 = quad->side[0].NbPoints(); + int n2 = quad->side[1].NbPoints(); + int n3 = quad->side[2].NbPoints(); + int n4 = quad->side[3].NbPoints(); + enum { NOT_COMPUTED = -1, COMPUTE_FAILED = 0, COMPUTE_OK = 1 }; int res = NOT_COMPUTED; if (myQuadranglePreference) { - int n1 = quad->side[0]->NbPoints(); - int n2 = quad->side[1]->NbPoints(); - int n3 = quad->side[2]->NbPoints(); - int n4 = quad->side[3]->NbPoints(); int nfull = n1+n2+n3+n4; - int ntmp = nfull/2; - ntmp = ntmp*2; - if (nfull == ntmp && ((n1 != n3) || (n2 != n4))) + if ((nfull % 2) == 0 && ((n1 != n3) || (n2 != n4))) { // special path genarating only quandrangle faces res = computeQuadPref( aMesh, F, quad ); @@ -244,10 +252,6 @@ bool StdMeshers_Quadrangle_2D::Compute (SMESH_Mesh& aMesh, } else if (myQuadType == QUAD_REDUCED) { - int n1 = quad->side[0]->NbPoints(); - int n2 = quad->side[1]->NbPoints(); - int n3 = quad->side[2]->NbPoints(); - int n4 = quad->side[3]->NbPoints(); int n13 = n1 - n3; int n24 = n2 - n4; int n13tmp = n13/2; n13tmp = n13tmp*2; @@ -275,7 +279,10 @@ bool StdMeshers_Quadrangle_2D::Compute (SMESH_Mesh& aMesh, if ( res == NOT_COMPUTED ) { - res = computeQuadDominant( aMesh, F, quad ); + if ( n1 != n3 || n2 != n4 ) + res = computeTriangles( aMesh, F, quad ); + else + res = computeQuadDominant( aMesh, F ); } if ( res == COMPUTE_OK && myNeedSmooth ) @@ -284,6 +291,83 @@ bool StdMeshers_Quadrangle_2D::Compute (SMESH_Mesh& aMesh, return ( res == COMPUTE_OK ); } +//================================================================================ +/*! + * \brief Compute quadrangles and triangles on the quad + */ +//================================================================================ + +bool StdMeshers_Quadrangle_2D::computeTriangles(SMESH_Mesh& aMesh, + const TopoDS_Face& aFace, + FaceQuadStruct::Ptr quad) +{ + int nb = quad->side[0].grid->NbPoints(); + int nr = quad->side[1].grid->NbPoints(); + int nt = quad->side[2].grid->NbPoints(); + int nl = quad->side[3].grid->NbPoints(); + + // rotate the quad to have nbNodeOut sides on TOP [and LEFT] + if ( nb > nt ) + quad->shift( nl > nr ? 3 : 2, true ); + else if ( nr > nl ) + quad->shift( 1, true ); + else if ( nl > nr ) + quad->shift( nt > nb ? 0 : 3, true ); + + if ( !setNormalizedGrid( quad )) + return false; + + if ( quad->nbNodeOut( QUAD_BOTTOM_SIDE )) + { + splitQuad( quad, 0, 1 ); + } + if ( quad->nbNodeOut( QUAD_TOP_SIDE )) + { + splitQuad( quad, 0, quad->jSize-2 ); + } + FaceQuadStruct::Ptr newQuad = myQuadList.back(); + if ( quad != newQuad ) // split done + { + // make quad be a greatest one + if ( quad->side[ QUAD_LEFT_SIDE ].NbPoints() == 2 || + quad->side[ QUAD_RIGHT_SIDE ].NbPoints() == 2 ) + quad = newQuad; + if ( !setNormalizedGrid( quad )) + return false; + } + + if ( quad->nbNodeOut( QUAD_RIGHT_SIDE )) + { + splitQuad( quad, quad->iSize-2, 0 ); + } + if ( quad->nbNodeOut( QUAD_LEFT_SIDE )) + { + splitQuad( quad, 1, 0 ); + } + + return computeQuadDominant( aMesh, aFace ); +} + +//================================================================================ +/*! + * \brief Compute quadrangles and possibly triangles on all quads of myQuadList + */ +//================================================================================ + +bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, + const TopoDS_Face& aFace) +{ + if ( !addEnforcedNodes() ) + return false; + + std::list< FaceQuadStruct::Ptr >::iterator quad = myQuadList.begin(); + for ( ; quad != myQuadList.end(); ++quad ) + if ( !computeQuadDominant( aMesh, aFace, *quad )) + return false; + + return true; +} + //================================================================================ /*! * \brief Compute quadrangles and possibly triangles @@ -294,37 +378,28 @@ bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, const TopoDS_Face& aFace, FaceQuadStruct::Ptr quad) { - // set normalized grid on unit square in parametric domain + // --- set normalized grid on unit square in parametric domain - if (!setNormalizedGrid(aMesh, aFace, quad)) + if ( !setNormalizedGrid( quad )) return false; - // --- compute 3D values on points, store points & quadrangles - - int nbdown = quad->side[0]->NbPoints(); - int nbup = quad->side[2]->NbPoints(); - - int nbright = quad->side[1]->NbPoints(); - int nbleft = quad->side[3]->NbPoints(); + // --- create nodes on points, and create quadrangles - int nbhoriz = Min(nbdown, nbup); - int nbvertic = Min(nbright, nbleft); + int nbhoriz = quad->iSize; + int nbvertic = quad->jSize; // internal mesh nodes SMESHDS_Mesh * meshDS = aMesh.GetMeshDS(); Handle(Geom_Surface) S = BRep_Tool::Surface(aFace); - int i, j, geomFaceID = meshDS->ShapeToIndex(aFace); - for (i = 1; i < nbhoriz - 1; i++) { - for (j = 1; j < nbvertic - 1; j++) { - int ij = j * nbhoriz + i; - double u = quad->uv_grid[ij].u; - double v = quad->uv_grid[ij].v; - gp_Pnt P = S->Value(u, v); - SMDS_MeshNode * node = meshDS->AddNode(P.X(), P.Y(), P.Z()); - meshDS->SetNodeOnFace(node, geomFaceID, u, v); - quad->uv_grid[ij].node = node; + int i,j, geomFaceID = meshDS->ShapeToIndex(aFace); + for (i = 1; i < nbhoriz - 1; i++) + for (j = 1; j < nbvertic - 1; j++) + { + UVPtStruct& uvPnt = quad->UVPt( i, j ); + gp_Pnt P = S->Value( uvPnt.u, uvPnt.v ); + uvPnt.node = meshDS->AddNode(P.X(), P.Y(), P.Z()); + meshDS->SetNodeOnFace( uvPnt.node, geomFaceID, uvPnt.u, uvPnt.v ); } - } // mesh faces @@ -340,21 +415,20 @@ bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, // i // [0] - i = 0; int ilow = 0; int iup = nbhoriz - 1; - if (quad->isEdgeOut[3]) { ilow++; } else { if (quad->isEdgeOut[1]) iup--; } + if (quad->nbNodeOut(3)) { ilow++; } else { if (quad->nbNodeOut(1)) iup--; } int jlow = 0; int jup = nbvertic - 1; - if (quad->isEdgeOut[0]) { jlow++; } else { if (quad->isEdgeOut[2]) jup--; } + if (quad->nbNodeOut(0)) { jlow++; } else { if (quad->nbNodeOut(2)) jup--; } // regular quadrangles for (i = ilow; i < iup; i++) { for (j = jlow; j < jup; j++) { const SMDS_MeshNode *a, *b, *c, *d; - a = quad->uv_grid[j * nbhoriz + i ].node; - b = quad->uv_grid[j * nbhoriz + i + 1].node; + a = quad->uv_grid[ j * nbhoriz + i ].node; + b = quad->uv_grid[ j * nbhoriz + i + 1].node; c = quad->uv_grid[(j + 1) * nbhoriz + i + 1].node; d = quad->uv_grid[(j + 1) * nbhoriz + i ].node; SMDS_MeshFace* face = myHelper->AddFace(a, b, c, d); @@ -364,19 +438,25 @@ bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, } } - const vector& uv_e0 = quad->side[0]->GetUVPtStruct(true,0); - const vector& uv_e1 = quad->side[1]->GetUVPtStruct(false,1); - const vector& uv_e2 = quad->side[2]->GetUVPtStruct(true,1); - const vector& uv_e3 = quad->side[3]->GetUVPtStruct(false,0); + // Boundary elements (must always be on an outer boundary of the FACE) + + const vector& uv_e0 = quad->side[0].grid->GetUVPtStruct(); + const vector& uv_e1 = quad->side[1].grid->GetUVPtStruct(); + const vector& uv_e2 = quad->side[2].grid->GetUVPtStruct(); + const vector& uv_e3 = quad->side[3].grid->GetUVPtStruct(); if (uv_e0.empty() || uv_e1.empty() || uv_e2.empty() || uv_e3.empty()) return error(COMPERR_BAD_INPUT_MESH); double eps = Precision::Confusion(); - // Boundary quadrangles - - if (quad->isEdgeOut[0]) { + int nbdown = (int) uv_e0.size(); + int nbup = (int) uv_e2.size(); + int nbright = (int) uv_e1.size(); + int nbleft = (int) uv_e3.size(); + + if (quad->nbNodeOut(0) && nbvertic == 2) + { // Down edge is out // // |___|___|___|___|___|___| @@ -393,8 +473,12 @@ bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, // number of last node of the down edge to be processed int stop = nbdown - 1; // if right edge is out, we will stop at a node, previous to the last one - if (quad->isEdgeOut[1]) stop--; - + //if (quad->nbNodeOut(1)) stop--; + if ( quad->nbNodeOut( QUAD_RIGHT_SIDE )) + quad->UVPt( nbhoriz-1, 1 ).node = uv_e1[1].node; + if ( quad->nbNodeOut( QUAD_LEFT_SIDE )) + quad->UVPt( 0, 1 ).node = uv_e3[1].node; + // for each node of the down edge find nearest node // in the first row of the regular grid and link them for (i = 0; i < stop; i++) { @@ -449,7 +533,7 @@ bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, if (face) meshDS->SetMeshElementOnShape(face, geomFaceID); } else { - splitQuad(meshDS, geomFaceID, a, b, c, d); + splitQuadFace(meshDS, geomFaceID, a, b, c, d); } // if node d is not at position g - make additional triangles @@ -468,7 +552,8 @@ bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, } } } else { - if (quad->isEdgeOut[2]) { + if (quad->nbNodeOut(2) && nbvertic == 2) + { // Up edge is out // // <-<-<-<-<-<-<-<-<-<-<-<-< -- direction of processing @@ -483,9 +568,16 @@ bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, int g = nbhoriz - 1; // last processed node in the regular grid + ilow = 0; + iup = nbhoriz - 1; + int stop = 0; // if left edge is out, we will stop at a second node - if (quad->isEdgeOut[3]) stop++; + //if (quad->nbNodeOut(3)) stop++; + if ( quad->nbNodeOut( QUAD_RIGHT_SIDE )) + quad->UVPt( nbhoriz-1, 0 ).node = uv_e1[ nbright-2 ].node; + if ( quad->nbNodeOut( QUAD_LEFT_SIDE )) + quad->UVPt( 0, 0 ).node = uv_e3[ nbleft-2 ].node; // for each node of the up edge find nearest node // in the first row of the regular grid and link them @@ -536,10 +628,10 @@ bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, if (face) meshDS->SetMeshElementOnShape(face, geomFaceID); } else { - splitQuad(meshDS, geomFaceID, a, b, c, d); + splitQuadFace(meshDS, geomFaceID, a, b, c, d); } - if (near + 1 < g) { // if d not is at g - make additional triangles + if (near + 1 < g) { // if d is not at g - make additional triangles for (int k = near + 1; k < g; k++) { c = quad->uv_grid[nbhoriz*(nbvertic - 2) + k].node; if (k + 1 > iup) @@ -557,12 +649,14 @@ bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, } // right or left boundary quadrangles - if (quad->isEdgeOut[1]) { -// MESSAGE("right edge is out"); + if (quad->nbNodeOut( QUAD_RIGHT_SIDE ) && nbhoriz == 2) + { int g = 0; // last processed node in the grid int stop = nbright - 1; - if (quad->isEdgeOut[2]) stop--; - for (i = 0; i < stop; i++) { + i = 0; + if (quad->side[ QUAD_RIGHT_SIDE ].from != i ) i++; + if (quad->side[ QUAD_RIGHT_SIDE ].to != stop ) stop--; + for ( ; i < stop; i++) { const SMDS_MeshNode *a, *b, *c, *d; a = uv_e1[i].node; b = uv_e1[i + 1].node; @@ -609,7 +703,7 @@ bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, if (face) meshDS->SetMeshElementOnShape(face, geomFaceID); } else { - splitQuad(meshDS, geomFaceID, a, b, c, d); + splitQuadFace(meshDS, geomFaceID, a, b, c, d); } if (near - 1 > g) { // if d not is at g - make additional triangles @@ -627,12 +721,14 @@ bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, } } } else { - if (quad->isEdgeOut[3]) { + if (quad->nbNodeOut(3) && nbhoriz == 2) { // MESSAGE("left edge is out"); int g = nbvertic - 1; // last processed node in the grid int stop = 0; - if (quad->isEdgeOut[0]) stop++; - for (i = nbleft - 1; i > stop; i--) { + i = nbleft - 1; + if (quad->side[3].from != stop ) stop++; + if (quad->side[3].to != i ) i--; + for (; i > stop; i--) { const SMDS_MeshNode *a, *b, *c, *d; a = uv_e3[i].node; b = uv_e3[i - 1].node; @@ -678,7 +774,7 @@ bool StdMeshers_Quadrangle_2D::computeQuadDominant(SMESH_Mesh& aMesh, if (face) meshDS->SetMeshElementOnShape(face, geomFaceID); } else { - splitQuad(meshDS, geomFaceID, a, b, c, d); + splitQuadFace(meshDS, geomFaceID, a, b, c, d); } if (near + 1 < g) { // if d not is at g - make additional triangles @@ -828,8 +924,8 @@ FaceQuadStruct::Ptr StdMeshers_Quadrangle_2D::CheckNbEdges(SMESH_Mesh & const TopoDS_Shape & aShape, const bool considerMesh) { - if ( myQuadStruct && myQuadStruct->face.IsSame( aShape )) - return myQuadStruct; + if ( !myQuadList.empty() && myQuadList.front()->face.IsSame( aShape )) + return myQuadList.front(); TopoDS_Face F = TopoDS::Face(aShape); if ( F.Orientation() >= TopAbs_INTERNAL ) F.Orientation( TopAbs_FORWARD ); @@ -852,7 +948,6 @@ FaceQuadStruct::Ptr StdMeshers_Quadrangle_2D::CheckNbEdges(SMESH_Mesh & return FaceQuadStruct::Ptr(); } FaceQuadStruct::Ptr quad( new FaceQuadStruct ); - quad->uv_grid = 0; quad->side.reserve(nbEdgesInWire.front()); quad->face = F; @@ -870,17 +965,17 @@ FaceQuadStruct::Ptr StdMeshers_Quadrangle_2D::CheckNbEdges(SMESH_Mesh & else sideEdges.push_back( *edgeIt++ ); if ( !sideEdges.empty() ) - quad->side.push_back(new StdMeshers_FaceSide(F, sideEdges, &aMesh, iSide < QUAD_TOP_SIDE, - ignoreMediumNodes, myProxyMesh)); + quad->side.push_back( StdMeshers_FaceSide::New(F, sideEdges, &aMesh, iSide < QUAD_TOP_SIDE, + ignoreMediumNodes, myProxyMesh)); else --iSide; } - const vector& UVPSleft = quad->side[0]->GetUVPtStruct(true,0); - /* vector& UVPStop = */quad->side[1]->GetUVPtStruct(false,1); - /* vector& UVPSright = */quad->side[2]->GetUVPtStruct(true,1); + const vector& UVPSleft = quad->side[0].GetUVPtStruct(true,0); + /* vector& UVPStop = */quad->side[1].GetUVPtStruct(false,1); + /* vector& UVPSright = */quad->side[2].GetUVPtStruct(true,1); const SMDS_MeshNode* aNode = UVPSleft[0].node; - gp_Pnt2d aPnt2d(UVPSleft[0].u, UVPSleft[0].v); - quad->side.push_back(new StdMeshers_FaceSide(quad->side[1], aNode, &aPnt2d)); + gp_Pnt2d aPnt2d = UVPSleft[0].UV(); + quad->side.push_back( StdMeshers_FaceSide::New( quad->side[1].grid.get(), aNode, &aPnt2d )); myNeedSmooth = ( nbDegenEdges > 0 ); return quad; } @@ -922,15 +1017,15 @@ FaceQuadStruct::Ptr StdMeshers_Quadrangle_2D::CheckNbEdges(SMESH_Mesh & } if ( !sideEdges.empty() ) { - quad->side.push_back(new StdMeshers_FaceSide(F, sideEdges, &aMesh, iSide < QUAD_TOP_SIDE, - ignoreMediumNodes, myProxyMesh)); + quad->side.push_back( StdMeshers_FaceSide::New( F, sideEdges, &aMesh, iSide < QUAD_TOP_SIDE, + ignoreMediumNodes, myProxyMesh )); ++iSide; } else if ( !SMESH_Algo::isDegenerated( *edgeIt ) && // closed EDGE myHelper->IthVertex( 0, *edgeIt ).IsSame( myHelper->IthVertex( 1, *edgeIt ))) { - quad->side.push_back(new StdMeshers_FaceSide(F, *edgeIt++, &aMesh, iSide < QUAD_TOP_SIDE, - ignoreMediumNodes, myProxyMesh)); + quad->side.push_back( StdMeshers_FaceSide::New( F, *edgeIt++, &aMesh, iSide < QUAD_TOP_SIDE, + ignoreMediumNodes, myProxyMesh)); ++iSide; } if ( quad->side.size() == 4 ) @@ -1155,36 +1250,12 @@ StdMeshers_Quadrangle_2D::CheckAnd2Dcompute (SMESH_Mesh & aMesh, if ( quad ) { // set normalized grid on unit square in parametric domain - if ( ! setNormalizedGrid( aMesh, TopoDS::Face( aShape ), quad)) + if ( ! setNormalizedGrid( quad )) quad.reset(); } return quad; } -//============================================================================= -/*! - * - */ -//============================================================================= - -faceQuadStruct::~faceQuadStruct() -{ - for (size_t i = 0; i < side.size(); i++) { - if (side[i]) { - delete side[i]; - for (size_t j = i+1; j < side.size(); j++) - if ( side[i] == side[j] ) - side[j] = 0; - } - } - side.clear(); - - if (uv_grid) { - delete [] uv_grid; - uv_grid = 0; - } -} - namespace { inline const vector& getUVPtStructIn(FaceQuadStruct::Ptr& quad, int i, int nbSeg) @@ -1192,9 +1263,9 @@ namespace bool isXConst = (i == QUAD_BOTTOM_SIDE || i == QUAD_TOP_SIDE); double constValue = (i == QUAD_BOTTOM_SIDE || i == QUAD_LEFT_SIDE) ? 0 : 1; return - quad->isEdgeOut[i] ? - quad->side[i]->SimulateUVPtStruct(nbSeg,isXConst,constValue) : - quad->side[i]->GetUVPtStruct(isXConst,constValue); + quad->nbNodeOut(i) ? + quad->side[i].grid->SimulateUVPtStruct(nbSeg,isXConst,constValue) : + quad->side[i].grid->GetUVPtStruct (isXConst,constValue); } inline gp_UV calcUV(double x, double y, const gp_UV& a0,const gp_UV& a1,const gp_UV& a2,const gp_UV& a3, @@ -1212,10 +1283,11 @@ namespace */ //============================================================================= -bool StdMeshers_Quadrangle_2D::setNormalizedGrid (SMESH_Mesh & aMesh, - const TopoDS_Face& aFace, - FaceQuadStruct::Ptr & quad) +bool StdMeshers_Quadrangle_2D::setNormalizedGrid (FaceQuadStruct::Ptr quad) { + if ( !quad->uv_grid.empty() ) + return true; + // Algorithme décrit dans "Génération automatique de maillages" // P.L. GEORGE, MASSON, § 6.4.1 p. 84-85 // traitement dans le domaine paramétrique 2d u,v @@ -1233,81 +1305,133 @@ bool StdMeshers_Quadrangle_2D::setNormalizedGrid (SMESH_Mesh & aMesh, // =down // - int nbhoriz = Min(quad->side[0]->NbPoints(), quad->side[2]->NbPoints()); - int nbvertic = Min(quad->side[1]->NbPoints(), quad->side[3]->NbPoints()); - - quad->isEdgeOut[0] = (quad->side[0]->NbPoints() > quad->side[2]->NbPoints()); - quad->isEdgeOut[1] = (quad->side[1]->NbPoints() > quad->side[3]->NbPoints()); - quad->isEdgeOut[2] = (quad->side[2]->NbPoints() > quad->side[0]->NbPoints()); - quad->isEdgeOut[3] = (quad->side[3]->NbPoints() > quad->side[1]->NbPoints()); - - UVPtStruct *uv_grid = quad->uv_grid = new UVPtStruct[nbvertic * nbhoriz]; + int nbhoriz = Min(quad->side[0].NbPoints(), quad->side[2].NbPoints()); + int nbvertic = Min(quad->side[1].NbPoints(), quad->side[3].NbPoints()); - const vector& uv_e0 = getUVPtStructIn(quad, 0, nbhoriz - 1); - const vector& uv_e1 = getUVPtStructIn(quad, 1, nbvertic - 1); - const vector& uv_e2 = getUVPtStructIn(quad, 2, nbhoriz - 1); - const vector& uv_e3 = getUVPtStructIn(quad, 3, nbvertic - 1); + if ( myQuadList.size() == 1 ) + { + // all sub-quads must have NO sides with nbNodeOut > 0 + quad->nbNodeOut(0) = Max( 0, quad->side[0].grid->NbPoints() - quad->side[2].grid->NbPoints()); + quad->nbNodeOut(1) = Max( 0, quad->side[1].grid->NbPoints() - quad->side[3].grid->NbPoints()); + quad->nbNodeOut(2) = Max( 0, quad->side[2].grid->NbPoints() - quad->side[0].grid->NbPoints()); + quad->nbNodeOut(3) = Max( 0, quad->side[3].grid->NbPoints() - quad->side[1].grid->NbPoints()); + } + int from[4] = { + quad->side[0].from, + quad->side[1].from, + quad->side[2].from, + quad->side[3].from + }; + const vector& uv_e0_vec = quad->side[ 0 ].GetUVPtStruct(); + const vector& uv_e1_vec = quad->side[ 1 ].GetUVPtStruct(); + const vector& uv_e2_vec = quad->side[ 2 ].GetUVPtStruct(); + const vector& uv_e3_vec = quad->side[ 3 ].GetUVPtStruct(); - if (uv_e0.empty() || uv_e1.empty() || uv_e2.empty() || uv_e3.empty()) + if (uv_e0_vec.empty() || uv_e1_vec.empty() || uv_e2_vec.empty() || uv_e3_vec.empty()) //return error("Can't find nodes on sides"); return error(COMPERR_BAD_INPUT_MESH); + UVPtStruct* uv_e0 = (UVPtStruct*) & uv_e0_vec[0] + from[0]; + UVPtStruct* uv_e1 = (UVPtStruct*) & uv_e1_vec[0] + from[1]; + UVPtStruct* uv_e2 = (UVPtStruct*) & uv_e2_vec[0] + from[2]; + UVPtStruct* uv_e3 = (UVPtStruct*) & uv_e3_vec[0] + from[3]; + + quad->uv_grid.resize( nbvertic * nbhoriz ); + quad->iSize = nbhoriz; + quad->jSize = nbvertic; + UVPtStruct *uv_grid = & quad->uv_grid[0]; + + quad->uv_box.Clear(); + // copy data of face boundary - { + + { // BOTTOM const int j = 0; - for (int i = 0; i < nbhoriz; i++) // down + const double x0 = uv_e0[ 0 ].normParam; + const double dx = uv_e0[ nbhoriz-1 ].normParam - uv_e0[ 0 ].normParam; + for (int i = 0; i < nbhoriz; i++) { // down + uv_e0[i].x = ( uv_e0[i].normParam - x0 ) / dx; + uv_e0[i].y = 0.; uv_grid[ j * nbhoriz + i ] = uv_e0[i]; + quad->uv_box.Add( uv_e0[i].UV() ); + } } - { + { // RIGHT const int i = nbhoriz - 1; - for (int j = 0; j < nbvertic; j++) // right + const double y0 = uv_e1[ 0 ].normParam; + const double dy = uv_e1[ nbvertic-1 ].normParam - uv_e1[ 0 ].normParam; + int j = 0, nb = nbvertic; + if ( quad->UVPt( i, j ).node ) ++j; // avoid copying from a split emulated side + for ( ; j < nb; j++) { // right + uv_e1[j].x = 1.; + uv_e1[j].y = ( uv_e1[j].normParam - y0 ) / dy; uv_grid[ j * nbhoriz + i ] = uv_e1[j]; + quad->uv_box.Add( uv_e1[j].UV() ); + } } - { + { // TOP const int j = nbvertic - 1; - for (int i = 0; i < nbhoriz; i++) // up + const double x0 = uv_e2[ 0 ].normParam; + const double dx = uv_e2[ nbhoriz-1 ].normParam - uv_e2[ 0 ].normParam; + int i = 0, nb = nbhoriz; + if ( quad->UVPt( nb-1, j ).node ) --nb; // avoid copying from a split emulated side + for (; i < nb; i++) { // up + uv_e2[i].x = ( uv_e2[i].normParam - x0 ) / dx; + uv_e2[i].y = 1.; uv_grid[ j * nbhoriz + i ] = uv_e2[i]; + quad->uv_box.Add( uv_e2[i].UV() ); + } } - { + { // LEFT const int i = 0; - for (int j = 0; j < nbvertic; j++) // left + const double y0 = uv_e3[ 0 ].normParam; + const double dy = uv_e3[ nbvertic-1 ].normParam - uv_e3[ 0 ].normParam; + int j = 0, nb = nbvertic; + if ( quad->UVPt( i, j ).node ) ++j; // avoid copying from a split emulated side + if ( quad->UVPt( i, nb-1 ).node ) --nb; + for ( ; j < nb; j++) { // left + uv_e3[j].x = 0.; + uv_e3[j].y = ( uv_e3[j].normParam - y0 ) / dy; uv_grid[ j * nbhoriz + i ] = uv_e3[j]; + quad->uv_box.Add( uv_e3[j].UV() ); + } } // normalized 2d parameters on grid - for (int i = 0; i < nbhoriz; i++) { - for (int j = 0; j < nbvertic; j++) { - int ij = j * nbhoriz + i; - // --- droite i cste : x = x0 + y(x1-x0) - double x0 = uv_e0[i].normParam; // bas - sud - double x1 = uv_e2[i].normParam; // haut - nord - // --- droite j cste : y = y0 + x(y1-y0) - double y0 = uv_e3[j].normParam; // gauche - ouest - double y1 = uv_e1[j].normParam; // droite - est + for (int i = 1; i < nbhoriz-1; i++) + { + const double x0 = uv_e0[i].x; + const double x1 = uv_e2[i].x; + for (int j = 1; j < nbvertic-1; j++) + { + const double y0 = uv_e3[j].y; + const double y1 = uv_e1[j].y; // --- intersection : x=x0+(y0+x(y1-y0))(x1-x0) double x = (x0 + y0 * (x1 - x0)) / (1 - (y1 - y0) * (x1 - x0)); double y = y0 + x * (y1 - y0); + int ij = j * nbhoriz + i; uv_grid[ij].x = x; uv_grid[ij].y = y; + uv_grid[ij].node = NULL; } } // projection on 2d domain (u,v) - gp_UV a0 (uv_e0.front().u, uv_e0.front().v); - gp_UV a1 (uv_e0.back().u, uv_e0.back().v ); - gp_UV a2 (uv_e2.back().u, uv_e2.back().v ); - gp_UV a3 (uv_e2.front().u, uv_e2.front().v); + gp_UV a0 = uv_e0[0 ].UV(); + gp_UV a1 = uv_e0[nbhoriz-1].UV(); + gp_UV a2 = uv_e2[nbhoriz-1].UV(); + gp_UV a3 = uv_e2[0 ].UV(); - for (int i = 0; i < nbhoriz; i++) + for (int i = 1; i < nbhoriz-1; i++) { - gp_UV p0( uv_e0[i].u, uv_e0[i].v ); - gp_UV p2( uv_e2[i].u, uv_e2[i].v ); - for (int j = 0; j < nbvertic; j++) + gp_UV p0 = uv_e0[i].UV(); + gp_UV p2 = uv_e2[i].UV(); + for (int j = 1; j < nbvertic-1; j++) { - gp_UV p1( uv_e1[j].u, uv_e1[j].v ); - gp_UV p3( uv_e3[j].u, uv_e3[j].v ); + gp_UV p1 = uv_e1[j].UV(); + gp_UV p3 = uv_e3[j].UV(); int ij = j * nbhoriz + i; double x = uv_grid[ij].x; @@ -1343,7 +1467,7 @@ static void shiftQuad(FaceQuadStruct::Ptr& quad, const int num) void FaceQuadStruct::shift( size_t nb, bool ori ) { if ( nb == 0 ) return; - StdMeshers_FaceSide* sideArr[4] = { side[0], side[1], side[2], side[3] }; + StdMeshers_FaceSidePtr sideArr[4] = { side[0], side[1], side[2], side[3] }; for (int i = QUAD_BOTTOM_SIDE; i < NB_QUAD_SIDES; ++i) { int id = (i + nb) % NB_QUAD_SIDES; bool wasForward = (i < QUAD_TOP_SIDE); @@ -1367,10 +1491,10 @@ static gp_UV calcUV(double x0, double x1, double y0, double y1, double x = (x0 + y0 * (x1 - x0)) / (1 - (y1 - y0) * (x1 - x0)); double y = y0 + x * (y1 - y0); - gp_UV p0 = quad->side[QUAD_BOTTOM_SIDE]->Value2d(x).XY(); - gp_UV p1 = quad->side[QUAD_RIGHT_SIDE ]->Value2d(y).XY(); - gp_UV p2 = quad->side[QUAD_TOP_SIDE ]->Value2d(x).XY(); - gp_UV p3 = quad->side[QUAD_LEFT_SIDE ]->Value2d(y).XY(); + gp_UV p0 = quad->side[QUAD_BOTTOM_SIDE].grid->Value2d(x).XY(); + gp_UV p1 = quad->side[QUAD_RIGHT_SIDE ].grid->Value2d(y).XY(); + gp_UV p2 = quad->side[QUAD_TOP_SIDE ].grid->Value2d(x).XY(); + gp_UV p3 = quad->side[QUAD_LEFT_SIDE ].grid->Value2d(y).XY(); gp_UV uv = calcUV(x,y, a0,a1,a2,a3, p0,p1,p2,p3); @@ -1387,10 +1511,10 @@ static gp_UV calcUV2(double x, double y, const gp_UV& a0, const gp_UV& a1, const gp_UV& a2, const gp_UV& a3) { - gp_UV p0 = quad->side[QUAD_BOTTOM_SIDE]->Value2d(x).XY(); - gp_UV p1 = quad->side[QUAD_RIGHT_SIDE ]->Value2d(y).XY(); - gp_UV p2 = quad->side[QUAD_TOP_SIDE ]->Value2d(x).XY(); - gp_UV p3 = quad->side[QUAD_LEFT_SIDE ]->Value2d(y).XY(); + gp_UV p0 = quad->side[QUAD_BOTTOM_SIDE].grid->Value2d(x).XY(); + gp_UV p1 = quad->side[QUAD_RIGHT_SIDE ].grid->Value2d(y).XY(); + gp_UV p2 = quad->side[QUAD_TOP_SIDE ].grid->Value2d(x).XY(); + gp_UV p3 = quad->side[QUAD_LEFT_SIDE ].grid->Value2d(y).XY(); gp_UV uv = calcUV(x,y, a0,a1,a2,a3, p0,p1,p2,p3); @@ -1418,24 +1542,37 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, bool WisF = true; int i,j,geomFaceID = meshDS->ShapeToIndex(aFace); - int nb = quad->side[0]->NbPoints(); - int nr = quad->side[1]->NbPoints(); - int nt = quad->side[2]->NbPoints(); - int nl = quad->side[3]->NbPoints(); + int nb = quad->side[0].grid->NbPoints(); + int nr = quad->side[1].grid->NbPoints(); + int nt = quad->side[2].grid->NbPoints(); + int nl = quad->side[3].grid->NbPoints(); int dh = abs(nb-nt); int dv = abs(nr-nl); - // rotate sides to be as in the picture below and to have - // dh >= dv and nt > nb - if ( dh >= dv ) - shiftQuad( quad, ( nt > nb ) ? 0 : 2 ); + if ( myForcedPnts.empty() ) + { + // rotate sides to be as in the picture below and to have + // dh >= dv and nt > nb + if ( dh >= dv ) + shiftQuad( quad, ( nt > nb ) ? 0 : 2 ); + else + shiftQuad( quad, ( nr > nl ) ? 1 : 3 ); + } else - shiftQuad( quad, ( nr > nl ) ? 1 : 3 ); + { + // rotate the quad to have nt > nb [and nr > nl] + if ( nb > nt ) + quad->shift( nr > nl ? 1 : 2, true ); + else if ( nr > nl ) + quad->shift( nb == nt ? 1 : 0, true ); + else if ( nl > nr ) + quad->shift( 3, true ); + } - nb = quad->side[0]->NbPoints(); - nr = quad->side[1]->NbPoints(); - nt = quad->side[2]->NbPoints(); - nl = quad->side[3]->NbPoints(); + nb = quad->side[0].grid->NbPoints(); + nr = quad->side[1].grid->NbPoints(); + nt = quad->side[2].grid->NbPoints(); + nl = quad->side[3].grid->NbPoints(); dh = abs(nb-nt); dv = abs(nr-nl); int nbh = Max(nb,nt); @@ -1470,6 +1607,302 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, // 0------------0 // 0 bottom 1 + const vector& uv_eb = quad->side[0].GetUVPtStruct(true,0); + const vector& uv_er = quad->side[1].GetUVPtStruct(false,1); + const vector& uv_et = quad->side[2].GetUVPtStruct(true,1); + const vector& uv_el = quad->side[3].GetUVPtStruct(false,0); + + if (uv_eb.size() != nb || uv_er.size() != nr || uv_et.size() != nt || uv_el.size() != nl) + return error(COMPERR_BAD_INPUT_MESH); + + gp_UV a0,a1,a2,a3, p0,p1,p2,p3, uv; + double x,y; + + a0 = uv_eb[ 0 ].UV(); + a1 = uv_er[ 0 ].UV(); + a2 = uv_er[ nr-1 ].UV(); + a3 = uv_et[ 0 ].UV(); + + if ( !myForcedPnts.empty() ) + { + if ( dv != 0 && dh != 0 ) + { + const int dmin = Min( dv, dh ); + + // Make a side separating domains L and Cb + StdMeshers_FaceSidePtr sideLCb; + UVPtStruct p3dom; // a point where 3 domains meat + { // dmin + vector pointsLCb( dmin+1 ); // 1--------1 + pointsLCb[0] = uv_eb[0]; // | | | + for ( int i = 1; i <= dmin; ++i ) // | |Ct| + { // | L | | + x = uv_et[ i ].normParam; // | |__| + y = uv_er[ i ].normParam; // | / | + p0 = quad->side[0].grid->Value2d( x ).XY(); // | / Cb |dmin + p1 = uv_er[ i ].UV(); // |/ | + p2 = uv_et[ i ].UV(); // 0--------0 + p3 = quad->side[3].grid->Value2d( y ).XY(); + uv = calcUV( x,y, a0,a1,a2,a3, p0,p1,p2,p3 ); + pointsLCb[ i ].u = uv.X(); + pointsLCb[ i ].v = uv.Y(); + } + sideLCb = StdMeshers_FaceSide::New( pointsLCb, aFace ); + p3dom = pointsLCb.back(); + } + // Make a side separating domains L and Ct + StdMeshers_FaceSidePtr sideLCt; + { + vector pointsLCt( nl ); + pointsLCt[0] = p3dom; + pointsLCt.back() = uv_et[ dmin ]; + x = uv_et[ dmin ].normParam; + p0 = quad->side[0].grid->Value2d( x ).XY(); + p2 = uv_et[ dmin ].UV(); + for ( int i = 1; i < nl; ++i ) + { + y = uv_er[ i + dmin ].normParam; + p1 = uv_er[ i + dmin ].UV(); + p3 = quad->side[3].grid->Value2d( y ).XY(); + uv = calcUV( x,y, a0,a1,a2,a3, p0,p1,p2,p3 ); + pointsLCt[ i ].u = uv.X(); + pointsLCt[ i ].v = uv.Y(); + } + sideLCt = StdMeshers_FaceSide::New( pointsLCt, aFace ); + } + // Make a side separating domains Cb and Ct + StdMeshers_FaceSidePtr sideCbCt; + { + vector pointsCbCt( nb ); + pointsCbCt[0] = p3dom; + pointsCbCt.back() = uv_er[ dmin ]; + y = uv_er[ dmin ].normParam; + p1 = uv_er[ dmin ].UV(); + p3 = quad->side[3].grid->Value2d( y ).XY(); + for ( int i = 1; i < nb-1; ++i ) + { + x = uv_et[ i + dmin ].normParam; + p2 = uv_et[ i + dmin ].UV(); + p0 = quad->side[0].grid->Value2d( x ).XY(); + uv = calcUV( x,y, a0,a1,a2,a3, p0,p1,p2,p3 ); + pointsCbCt[ i ].u = uv.X(); + pointsCbCt[ i ].v = uv.Y(); + } + sideCbCt = StdMeshers_FaceSide::New( pointsCbCt, aFace ); + } + // Make Cb quad + FaceQuadStruct* qCb = new FaceQuadStruct( quad->face ); + myQuadList.push_back( FaceQuadStruct::Ptr( qCb )); + qCb->side.resize(4); + qCb->side[0] = quad->side[0]; + qCb->side[1] = quad->side[1]; + qCb->side[2] = sideCbCt; + qCb->side[3] = sideLCb; + qCb->side[1].to = dmin+1; + // Make L quad + FaceQuadStruct* qL = new FaceQuadStruct( quad->face ); + myQuadList.push_back( FaceQuadStruct::Ptr( qL )); + qL->side.resize(4); + qL->side[0] = sideLCb; + qL->side[1] = sideLCt; + qL->side[2] = quad->side[2]; + qL->side[3] = quad->side[3]; + qL->side[2].to = dmin+1; + // Make Ct from the main quad + FaceQuadStruct::Ptr qCt = quad; + qCt->side[0] = sideCbCt; + qCt->side[3] = sideLCt; + qCt->side[1].from = dmin; + qCt->side[2].from = dmin; + qCt->uv_grid.clear(); + + // Connect sides + qCb->side[3].AddContact( dmin, & qCb->side[2], 0 ); + qCb->side[3].AddContact( dmin, & qCt->side[3], 0 ); + qCt->side[3].AddContact( 0, & qCt->side[0], 0 ); + qCt->side[0].AddContact( 0, & qL ->side[0], dmin ); + qL ->side[0].AddContact( dmin, & qL ->side[1], 0 ); + qL ->side[0].AddContact( dmin, & qCb->side[2], 0 ); + + if ( dh == dv ) + return computeQuadDominant( aMesh, aFace ); + else + return computeQuadPref( aMesh, aFace, qCt ); + + } // if ( dv != 0 && dh != 0 ) + + // Case dv == 0 + // + // lw nb lw = dh/2 + // +------------+ + // | | | | + // | | Ct | | + // | L | | R | + // | |____| | + // | / \ | + // | / Cb \ | + // |/ \| + // +------------+ + const int lw = dh/2; // lateral width + const int bfrom = quad->side[0].from; + const int rfrom = quad->side[1].from; + const int tfrom = quad->side[2].from; + const int lfrom = quad->side[3].from; + + const double lL = quad->side[3].Length(); + const double lLwL = quad->side[2].Length( tfrom, tfrom + lw + 1 ); + const double yCbL = lLwL / ( lLwL + lL ); + + const double lR = quad->side[1].Length(); + const double lLwR = quad->side[2].Length( nt - lw - 1, nt ); + const double yCbR = lLwR / ( lLwR + lR ); + + // Make sides separating domains Cb and L and R + StdMeshers_FaceSidePtr sideLCb, sideRCb; + UVPtStruct pTBL, pTBR; // points where 3 domains meat + { + vector pointsLCb( lw+1 ), pointsRCb( lw+1 ); + pointsLCb[0] = uv_eb[ 0 + bfrom ]; + pointsRCb[0] = uv_eb[ nb + bfrom ]; + for ( int i = 1, i2 = nt-2; i <= lw; ++i, --i2 ) + { + x = quad->side[2].Param( i ); + y = yCbL * i / lw; + p0 = quad->side[0].Value2d( x ); + p1 = quad->side[1].Value2d( y ); + p2 = uv_et[ i + tfrom ].UV(); + p3 = quad->side[3].Value2d( y ); + uv = calcUV( x,y, a0,a1,a2,a3, p0,p1,p2,p3 ); + pointsLCb[ i ].u = uv.X(); + pointsLCb[ i ].v = uv.Y(); + pointsLCb[ i ].x = x; + + x = quad->side[2].Param( i2 ); + y = yCbR * i / lw; + p0 = quad->side[0].Value2d( x ); + p2 = uv_et[ i2 + tfrom ].UV(); + uv = calcUV( x,y, a0,a1,a2,a3, p0,p1,p2,p3 ); + pointsRCb[ i ].u = uv.X(); + pointsRCb[ i ].v = uv.Y(); + pointsRCb[ i ].x = x; + } + sideLCb = StdMeshers_FaceSide::New( pointsLCb, aFace ); + sideRCb = StdMeshers_FaceSide::New( pointsRCb, aFace ); + pTBL = pointsLCb.back(); + pTBR = pointsRCb.back(); + } + // Make sides separating domains Ct and L and R + StdMeshers_FaceSidePtr sideLCt, sideRCt; + { + vector pointsLCt( nl ), pointsRCt( nl ); + pointsLCt[0] = pTBL; + pointsLCt.back() = uv_et[ lw + tfrom ]; + pointsRCt[0] = pTBR; + pointsRCt.back() = uv_et[ lw + nb - 1 + tfrom ]; + x = pTBL.x; + p0 = quad->side[0].Value2d( x ); + p2 = uv_et[ lw + tfrom ].UV(); + int iR = lw + nb - 1; + double xR = pTBR.x; + gp_UV p0R = quad->side[0].Value2d( xR ); + gp_UV p2R = uv_et[ iR + tfrom ].UV(); + for ( int i = 1; i < nl; ++i ) + { + y = yCbL + ( 1. - yCbL ) * i / nl; + p1 = quad->side[1].Value2d( y ); + p3 = quad->side[3].Value2d( y ); + uv = calcUV( x,y, a0,a1,a2,a3, p0,p1,p2,p3 ); + pointsLCt[ i ].u = uv.X(); + pointsLCt[ i ].v = uv.Y(); + + y = yCbR + ( 1. - yCbR ) * i / nl; + p1 = quad->side[1].Value2d( y ); + p3 = quad->side[3].Value2d( y ); + uv = calcUV( xR,y, a0,a1,a2,a3, p0,p1,p2,p3 ); + pointsRCt[ i ].u = uv.X(); + pointsRCt[ i ].v = uv.Y(); + } + sideLCt = StdMeshers_FaceSide::New( pointsLCt, aFace ); + sideRCt = StdMeshers_FaceSide::New( pointsRCt, aFace ); + } + // Make a side separating domains Cb and Ct + StdMeshers_FaceSidePtr sideCbCt; + { + vector pointsCbCt( nb ); + pointsCbCt[0] = pTBL; + pointsCbCt.back() = pTBR; + p1 = quad->side[1].Value2d( yCbR ); + p3 = quad->side[3].Value2d( yCbL ); + for ( int i = 1; i < nb-1; ++i ) + { + x = quad->side[2].Param( i + lw ); + y = yCbL + ( yCbR - yCbL ) * i / nb; + p2 = uv_et[ i + lw + tfrom ].UV(); + p0 = quad->side[0].Value2d( x ); + uv = calcUV( x,y, a0,a1,a2,a3, p0,p1,p2,p3 ); + pointsCbCt[ i ].u = uv.X(); + pointsCbCt[ i ].v = uv.Y(); + } + sideCbCt = StdMeshers_FaceSide::New( pointsCbCt, aFace ); + } + // Make Cb quad + FaceQuadStruct* qCb = new FaceQuadStruct( quad->face ); + myQuadList.push_back( FaceQuadStruct::Ptr( qCb )); + qCb->side.resize(4); + qCb->side[0] = quad->side[0]; + qCb->side[1] = sideRCb; + qCb->side[2] = sideCbCt; + qCb->side[3] = sideLCb; + // Make L quad + FaceQuadStruct* qL = new FaceQuadStruct( quad->face ); + myQuadList.push_back( FaceQuadStruct::Ptr( qL )); + qL->side.resize(4); + qL->side[0] = sideLCb; + qL->side[1] = sideLCt; + qL->side[2] = quad->side[2]; + qL->side[3] = quad->side[3]; + qL->side[2].to = lw+1; + // Make R quad + FaceQuadStruct* qR = new FaceQuadStruct( quad->face ); + myQuadList.push_back( FaceQuadStruct::Ptr( qR )); + qR->side.resize(4); + qR->side[0] = sideRCb; + qR->side[0].from = lw; + qR->side[0].to = -1; + qR->side[1] = quad->side[1]; + qR->side[2] = quad->side[2]; + qR->side[2].from = nb + lw + tfrom; + qR->side[3] = sideRCt; + // Make Ct from the main quad + FaceQuadStruct::Ptr qCt = quad; + qCt->side[0] = sideCbCt; + qCt->side[1] = sideRCt; + qCt->side[2].from = lw + tfrom; + qCt->side[2].to = nt - lw + tfrom; + qCt->side[3] = sideLCt; + qCt->uv_grid.clear(); + + // Connect sides + qCb->side[3].AddContact( lw, & qCb->side[2], 0 ); + qCb->side[3].AddContact( lw, & qCt->side[3], 0 ); + qCt->side[3].AddContact( 0, & qCt->side[0], 0 ); + qCt->side[0].AddContact( 0, & qL ->side[0], lw ); + qL ->side[0].AddContact( lw, & qL ->side[1], 0 ); + qL ->side[0].AddContact( lw, & qCb->side[2], 0 ); + // + qCb->side[1].AddContact( lw, & qCb->side[2], lw ); + qCb->side[1].AddContact( lw, & qCt->side[1], 0 ); + qCt->side[0].AddContact( lw, & qCt->side[1], 0 ); + qCt->side[0].AddContact( lw, & qR ->side[0], lw ); + qR ->side[3].AddContact( lw, & qR ->side[0], lw ); + qR ->side[3].AddContact( lw, & qCb->side[2], lw ); + + if ( dh == dv ) + return computeQuadDominant( aMesh, aFace ); + + + } + if ( dh > dv ) { addv = (dh-dv)/2; nbv = nbv + addv; @@ -1479,19 +1912,6 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, nbh = nbh + addh; } - const vector& uv_eb = quad->side[0]->GetUVPtStruct(true,0); - const vector& uv_er = quad->side[1]->GetUVPtStruct(false,1); - const vector& uv_et = quad->side[2]->GetUVPtStruct(true,1); - const vector& uv_el = quad->side[3]->GetUVPtStruct(false,0); - - if (uv_eb.size() != nb || uv_er.size() != nr || uv_et.size() != nt || uv_el.size() != nl) - return error(COMPERR_BAD_INPUT_MESH); - - if ( !OldVersion ) - { - // dh/2, Min(nb,nt), dh - dh/2, dv - } - // arrays for normalized params TColStd_SequenceOfReal npb, npr, npt, npl; for (i=0; i0) { // add top nodes - for (i=1; i<=dl; i++) + for (i=1; i<=dl; i++) NodesL.SetValue(i+1,nl,uv_et[i].node); // create and add needed nodes TColgp_SequenceOfXY UVtmp; @@ -1580,13 +1995,13 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, if (WisF) { SMDS_MeshFace* F = myHelper->AddFace(NodesL.Value(i,j), NodesL.Value(i+1,j), - NodesL.Value(i+1,j+1), NodesL.Value(i,j+1)); + NodesL.Value(i+1,j+1), NodesL.Value(i,j+1)); if (F) meshDS->SetMeshElementOnShape(F, geomFaceID); } else { SMDS_MeshFace* F = myHelper->AddFace(NodesL.Value(i,j), NodesL.Value(i,j+1), - NodesL.Value(i+1,j+1), NodesL.Value(i+1,j)); + NodesL.Value(i+1,j+1), NodesL.Value(i+1,j)); if (F) meshDS->SetMeshElementOnShape(F, geomFaceID); } } @@ -1598,15 +2013,15 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, UVL.Append(gp_UV (uv_el[i].u, uv_el[i].v)); } } - + // step2: create faces for right domain StdMeshers_Array2OfNode NodesR(1,dr+1,1,nr); // add right nodes - for (j=1; j<=nr; j++) + for (j=1; j<=nr; j++) NodesR.SetValue(1,j,uv_er[nr-j].node); if (dr>0) { // add top nodes - for (i=1; i<=dr; i++) + for (i=1; i<=dr; i++) NodesR.SetValue(i+1,1,uv_et[nt-1-i].node); // create and add needed nodes TColgp_SequenceOfXY UVtmp; @@ -1643,13 +2058,13 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, if (WisF) { SMDS_MeshFace* F = myHelper->AddFace(NodesR.Value(i,j), NodesR.Value(i+1,j), - NodesR.Value(i+1,j+1), NodesR.Value(i,j+1)); + NodesR.Value(i+1,j+1), NodesR.Value(i,j+1)); if (F) meshDS->SetMeshElementOnShape(F, geomFaceID); } else { SMDS_MeshFace* F = myHelper->AddFace(NodesR.Value(i,j), NodesR.Value(i,j+1), - NodesR.Value(i+1,j+1), NodesR.Value(i+1,j)); + NodesR.Value(i+1,j+1), NodesR.Value(i+1,j)); if (F) meshDS->SetMeshElementOnShape(F, geomFaceID); } } @@ -1661,7 +2076,7 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, UVR.Append(gp_UV(uv_er[i].u, uv_er[i].v)); } } - + // step3: create faces for central domain StdMeshers_Array2OfNode NodesC(1,nb,1,nbv); // add first line using NodesL @@ -1675,12 +2090,12 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, for (i=1; iAddFace(NodesC.Value(i,j), NodesC.Value(i+1,j), - NodesC.Value(i+1,j+1), NodesC.Value(i,j+1)); + NodesC.Value(i+1,j+1), NodesC.Value(i,j+1)); if (F) meshDS->SetMeshElementOnShape(F, geomFaceID); } else { SMDS_MeshFace* F = myHelper->AddFace(NodesC.Value(i,j), NodesC.Value(i,j+1), - NodesC.Value(i+1,j+1), NodesC.Value(i+1,j)); + NodesC.Value(i+1,j+1), NodesC.Value(i+1,j)); if (F) meshDS->SetMeshElementOnShape(F, geomFaceID); } } @@ -1760,13 +2175,13 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, if (WisF) { SMDS_MeshFace* F = myHelper->AddFace(NodesBRD.Value(i,j), NodesBRD.Value(i+1,j), - NodesBRD.Value(i+1,j+1), NodesBRD.Value(i,j+1)); + NodesBRD.Value(i+1,j+1), NodesBRD.Value(i,j+1)); if (F) meshDS->SetMeshElementOnShape(F, geomFaceID); } else { SMDS_MeshFace* F = myHelper->AddFace(NodesBRD.Value(i,j), NodesBRD.Value(i,j+1), - NodesBRD.Value(i+1,j+1), NodesBRD.Value(i+1,j)); + NodesBRD.Value(i+1,j+1), NodesBRD.Value(i+1,j)); if (F) meshDS->SetMeshElementOnShape(F, geomFaceID); } } @@ -1812,7 +2227,7 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, double yy1 = y1 + dy1*i; double dyy = yy1 - yy0; for (j=1; j<=nb; j++) { - double x = npt.Value(i+1+drl) + + double x = npt.Value(i+1+drl) + npb.Value(j) * (npt.Value(nt-i) - npt.Value(i+1+drl)); double y = yy0 + dyy*x; gp_UV UV = calcUV2(x, y, quad, a0, a1, a2, a3); @@ -1855,7 +2270,7 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, double yy1 = y1 + dy1*i; double dyy = yy1 - yy0; for (j=1; j<=nb; j++) { - double x = npt.Value(i+1) + + double x = npt.Value(i+1) + npb.Value(j) * (npt.Value(nt-i-drl) - npt.Value(i+1)); double y = yy0 + dyy*x; gp_UV UV = calcUV2(x, y, quad, a0, a1, a2, a3); @@ -1872,13 +2287,13 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, if (WisF) { SMDS_MeshFace* F = myHelper->AddFace(NodesC.Value(i,j), NodesC.Value(i+1,j), - NodesC.Value(i+1,j+1), NodesC.Value(i,j+1)); + NodesC.Value(i+1,j+1), NodesC.Value(i,j+1)); if (F) meshDS->SetMeshElementOnShape(F, geomFaceID); } else { SMDS_MeshFace* F = myHelper->AddFace(NodesC.Value(i,j), NodesC.Value(i,j+1), - NodesC.Value(i+1,j+1), NodesC.Value(i+1,j)); + NodesC.Value(i+1,j+1), NodesC.Value(i+1,j)); if (F) meshDS->SetMeshElementOnShape(F, geomFaceID); } } @@ -1905,13 +2320,13 @@ bool StdMeshers_Quadrangle_2D::computeQuadPref (SMESH_Mesh & aMesh, if (WisF) { SMDS_MeshFace* F = myHelper->AddFace(NodesLast.Value(i,1), NodesLast.Value(i+1,1), - NodesLast.Value(i+1,2), NodesLast.Value(i,2)); + NodesLast.Value(i+1,2), NodesLast.Value(i,2)); if (F) meshDS->SetMeshElementOnShape(F, geomFaceID); } else { SMDS_MeshFace* F = myHelper->AddFace(NodesLast.Value(i,1), NodesLast.Value(i,2), - NodesLast.Value(i+1,2), NodesLast.Value(i+1,2)); + NodesLast.Value(i+1,2), NodesLast.Value(i+1,2)); if (F) meshDS->SetMeshElementOnShape(F, geomFaceID); } } @@ -2064,12 +2479,12 @@ bool StdMeshers_Quadrangle_2D::evaluateQuadPref(SMESH_Mesh & aMesh, */ //============================================================================= -void StdMeshers_Quadrangle_2D::splitQuad(SMESHDS_Mesh * theMeshDS, - int theFaceID, - const SMDS_MeshNode* theNode1, - const SMDS_MeshNode* theNode2, - const SMDS_MeshNode* theNode3, - const SMDS_MeshNode* theNode4) +void StdMeshers_Quadrangle_2D::splitQuadFace(SMESHDS_Mesh * theMeshDS, + int theFaceID, + const SMDS_MeshNode* theNode1, + const SMDS_MeshNode* theNode2, + const SMDS_MeshNode* theNode3, + const SMDS_MeshNode* theNode4) { SMDS_MeshFace* face; if ( SMESH_TNodeXYZ( theNode1 ).SquareDistance( theNode3 ) > @@ -2100,8 +2515,8 @@ namespace SMESH_MesherHelper* helper, Handle(Geom_Surface) S) { - const vector& uv_eb = quad->side[QUAD_BOTTOM_SIDE]->GetUVPtStruct(); - const vector& uv_et = quad->side[QUAD_TOP_SIDE ]->GetUVPtStruct(); + const vector& uv_eb = quad->side[QUAD_BOTTOM_SIDE].GetUVPtStruct(); + const vector& uv_et = quad->side[QUAD_TOP_SIDE ].GetUVPtStruct(); double rBot = ( uv_eb.size() - 1 ) * uvPt.normParam; double rTop = ( uv_et.size() - 1 ) * uvPt.normParam; int iBot = int( rBot ); @@ -2112,9 +2527,9 @@ namespace gp_UV uv = calcUV(/*x,y=*/x, y, /*a0,...=*/UVs[UV_A0], UVs[UV_A1], UVs[UV_A2], UVs[UV_A3], - /*p0=*/quad->side[QUAD_BOTTOM_SIDE]->Value2d( x ).XY(), + /*p0=*/quad->side[QUAD_BOTTOM_SIDE].grid->Value2d( x ).XY(), /*p1=*/UVs[ UV_R ], - /*p2=*/quad->side[QUAD_TOP_SIDE ]->Value2d( x ).XY(), + /*p2=*/quad->side[QUAD_TOP_SIDE ].grid->Value2d( x ).XY(), /*p3=*/UVs[ UV_L ]); gp_Pnt P = S->Value( uv.X(), uv.Y() ); uvPt.u = uv.X(); @@ -2280,10 +2695,10 @@ bool StdMeshers_Quadrangle_2D::computeReduced (SMESH_Mesh & aMesh, Handle(Geom_Surface) S = BRep_Tool::Surface(aFace); int i,j,geomFaceID = meshDS->ShapeToIndex(aFace); - int nb = quad->side[0]->NbPoints(); // bottom - int nr = quad->side[1]->NbPoints(); // right - int nt = quad->side[2]->NbPoints(); // top - int nl = quad->side[3]->NbPoints(); // left + int nb = quad->side[0].grid->NbPoints(); // bottom + int nr = quad->side[1].grid->NbPoints(); // right + int nt = quad->side[2].grid->NbPoints(); // top + int nl = quad->side[3].grid->NbPoints(); // left // Simple Reduce 10->8->6->4 (3 steps) Multiple Reduce 10->4 (1 step) // @@ -2372,10 +2787,10 @@ bool StdMeshers_Quadrangle_2D::computeReduced (SMESH_Mesh & aMesh, } } - nb = quad->side[0]->NbPoints(); - nr = quad->side[1]->NbPoints(); - nt = quad->side[2]->NbPoints(); - nl = quad->side[3]->NbPoints(); + nb = quad->side[0].grid->NbPoints(); + nr = quad->side[1].grid->NbPoints(); + nt = quad->side[2].grid->NbPoints(); + nl = quad->side[3].grid->NbPoints(); dh = abs(nb-nt); dv = abs(nr-nl); int nbh = Max(nb,nt); @@ -2392,10 +2807,10 @@ bool StdMeshers_Quadrangle_2D::computeReduced (SMESH_Mesh & aMesh, nbh = nbh + addh; } - const vector& uv_eb = quad->side[0]->GetUVPtStruct(true,0); - const vector& uv_er = quad->side[1]->GetUVPtStruct(false,1); - const vector& uv_et = quad->side[2]->GetUVPtStruct(true,1); - const vector& uv_el = quad->side[3]->GetUVPtStruct(false,0); + const vector& uv_eb = quad->side[0].GetUVPtStruct(true,0); + const vector& uv_er = quad->side[1].GetUVPtStruct(false,1); + const vector& uv_et = quad->side[2].GetUVPtStruct(true,1); + const vector& uv_el = quad->side[3].GetUVPtStruct(false,0); if (uv_eb.size() != nb || uv_er.size() != nr || uv_et.size() != nt || uv_el.size() != nl) return error(COMPERR_BAD_INPUT_MESH); @@ -2646,10 +3061,10 @@ bool StdMeshers_Quadrangle_2D::computeReduced (SMESH_Mesh & aMesh, } } - nb = quad->side[0]->NbPoints(); - nr = quad->side[1]->NbPoints(); - nt = quad->side[2]->NbPoints(); - nl = quad->side[3]->NbPoints(); + nb = quad->side[0].grid->NbPoints(); + nr = quad->side[1].grid->NbPoints(); + nt = quad->side[2].grid->NbPoints(); + nl = quad->side[3].grid->NbPoints(); // number of rows and columns int nrows = nr - 1; // and also == nl - 1 @@ -2711,10 +3126,10 @@ bool StdMeshers_Quadrangle_2D::computeReduced (SMESH_Mesh & aMesh, } } - const vector& uv_eb = quad->side[0]->GetUVPtStruct(true,0); - const vector& uv_er = quad->side[1]->GetUVPtStruct(false,1); - const vector& uv_et = quad->side[2]->GetUVPtStruct(true,1); - const vector& uv_el = quad->side[3]->GetUVPtStruct(false,0); + const vector& uv_eb = quad->side[0].GetUVPtStruct(true,0); + const vector& uv_er = quad->side[1].GetUVPtStruct(false,1); + const vector& uv_et = quad->side[2].GetUVPtStruct(true,1); + const vector& uv_el = quad->side[3].GetUVPtStruct(false,0); if (uv_eb.size() != nb || uv_er.size() != nr || uv_et.size() != nt || uv_el.size() != nl) return error(COMPERR_BAD_INPUT_MESH); @@ -3234,8 +3649,7 @@ void StdMeshers_Quadrangle_2D::updateDegenUV(FaceQuadStruct::Ptr quad) // -------------------------------------------------------------------------- for ( unsigned i = 0; i < quad->side.size(); ++i ) { - StdMeshers_FaceSide* side = quad->side[i]; - const vector& uvVec = side->GetUVPtStruct(); + const vector& uvVec = quad->side[i].GetUVPtStruct(); // find which end of the side is on degenerated shape int degenInd = -1; @@ -3251,10 +3665,9 @@ void StdMeshers_Quadrangle_2D::updateDegenUV(FaceQuadStruct::Ptr quad) if ( i >= QUAD_TOP_SIDE ) isPrev = !isPrev; int i2 = ( isPrev ? ( i + 3 ) : ( i + 1 )) % 4; - StdMeshers_FaceSide* side2 = quad->side[ i2 ]; - const vector& uvVec2 = side2->GetUVPtStruct(); + const vector& uvVec2 = quad->side[ i2 ].GetUVPtStruct(); int degenInd2 = -1; - if ( uvVec[ degenInd ].node == uvVec2[0].node ) + if ( uvVec[ degenInd ].node == uvVec2.front().node ) degenInd2 = 0; else if ( uvVec[ degenInd ].node == uvVec2.back().node ) degenInd2 = uvVec2.size() - 1; @@ -3274,10 +3687,10 @@ void StdMeshers_Quadrangle_2D::updateDegenUV(FaceQuadStruct::Ptr quad) // ---------------------------------------------------------------------------- for ( unsigned i = 0; i < quad->side.size(); ++i ) { - StdMeshers_FaceSide* degSide = quad->side[i]; + StdMeshers_FaceSidePtr degSide = quad->side[i]; if ( !myHelper->IsDegenShape( degSide->EdgeID(0) )) continue; - StdMeshers_FaceSide* oppSide = quad->side[( i+2 ) % quad->side.size() ]; + StdMeshers_FaceSidePtr oppSide = quad->side[( i+2 ) % quad->side.size() ]; if ( degSide->NbSegments() == oppSide->NbSegments() ) continue; @@ -3286,11 +3699,10 @@ void StdMeshers_Quadrangle_2D::updateDegenUV(FaceQuadStruct::Ptr quad) const SMDS_MeshNode* n = uvVecDegOld[0].node; Handle(Geom2d_Curve) c2d = degSide->Curve2d(0); double f = degSide->FirstU(0), l = degSide->LastU(0); - gp_Pnt2d p1( uvVecDegOld.front().u, uvVecDegOld.front().v ); - gp_Pnt2d p2( uvVecDegOld.back().u, uvVecDegOld.back().v ); + gp_Pnt2d p1 = uvVecDegOld.front().UV(); + gp_Pnt2d p2 = uvVecDegOld.back().UV(); - delete degSide; - quad->side[i] = new StdMeshers_FaceSide( oppSide, n, &p1, &p2, c2d, f, l ); + quad->side[i] = StdMeshers_FaceSide::New( oppSide.get(), n, &p1, &p2, c2d, f, l ); } } @@ -3344,11 +3756,11 @@ void StdMeshers_Quadrangle_2D::smooth (FaceQuadStruct::Ptr quad) // set _uv of smooth nodes on FACE boundary for ( unsigned i = 0; i < quad->side.size(); ++i ) { - const vector& uvVec = quad->side[i]->GetUVPtStruct(); + const vector& uvVec = quad->side[i].GetUVPtStruct(); for ( unsigned j = 0; j < uvVec.size(); ++j ) { TSmoothNode & sNode = smooNoMap[ uvVec[j].node ]; - sNode._uv.SetCoord( uvVec[j].u, uvVec[j].v ); + sNode._uv = uvVec[j].UV(); sNode._xyz = SMESH_TNodeXYZ( uvVec[j].node ); } } @@ -3704,3 +4116,1004 @@ int StdMeshers_Quadrangle_2D::getCorners(const TopoDS_Face& theFace, return nbCorners; } + +//================================================================================ +/*! + * \brief Constructor of a side of quad + */ +//================================================================================ + +FaceQuadStruct::Side::Side(StdMeshers_FaceSidePtr theGrid) + : grid(theGrid), nbNodeOut(0), from(0), to(theGrid ? theGrid->NbPoints() : 0 ) +{ +} + +//============================================================================= +/*! + * \brief Constructor of a quad + */ +//============================================================================= + +FaceQuadStruct::FaceQuadStruct(const TopoDS_Face& F) : face( F ) +{ + side.reserve(4); +} + +//================================================================================ +/*! + * \brief Fills myForcedPnts + */ +//================================================================================ + +bool StdMeshers_Quadrangle_2D::getEnforcedUV() +{ + myForcedPnts.clear(); + if ( !myParams ) return true; // missing hypothesis + + std::vector< TopoDS_Shape > shapes; + std::vector< gp_Pnt > points; + myParams->GetEnforcedNodes( shapes, points ); + + TopTools_IndexedMapOfShape vMap; + for ( size_t i = 0; i < shapes.size(); ++i ) + if ( !shapes[i].IsNull() ) + TopExp::MapShapes( shapes[i], TopAbs_VERTEX, vMap ); + + size_t nbPoints = points.size(); + for ( int i = 1; i <= vMap.Extent(); ++i ) + points.push_back( BRep_Tool::Pnt( TopoDS::Vertex( vMap( i )))); + + // find out if all points must be in the FACE, which is so if + // myParams is a local hypothesis on the FACE being meshed + bool isStrictCheck = false; + { + SMESH_HypoFilter paramFilter( SMESH_HypoFilter::Is( myParams )); + TopoDS_Shape assignedTo; + if ( myHelper->GetMesh()->GetHypothesis( myHelper->GetSubShape(), + paramFilter, + /*ancestors=*/true, + &assignedTo )) + isStrictCheck = ( assignedTo.IsSame( myHelper->GetSubShape() )); + } + + multimap< double, ForcedPoint > sortedFP; // sort points by distance from EDGEs + + Standard_Real u1,u2,v1,v2; + const TopoDS_Face& face = TopoDS::Face( myHelper->GetSubShape() ); + const double tol = BRep_Tool::Tolerance( face ); + Handle(Geom_Surface) surf = BRep_Tool::Surface( face ); + surf->Bounds( u1,u2,v1,v2 ); + GeomAPI_ProjectPointOnSurf project; + project.Init(surf, u1,u2, v1,v2, tol ); + + for ( size_t iP = 0; iP < points.size(); ++iP ) + { + project.Perform( points[ iP ]); + if ( !project.IsDone() ) + { + if ( isStrictCheck && iP < nbPoints ) + return error + (TComm("Projection of an enforced point to the face failed - (") + << points[ iP ].X() << ", "<< points[ iP ].Y() << ", "<< points[ iP ].Z() << " )"); + continue; + } + if ( project.LowerDistance() > tol*1000 ) + { + if ( isStrictCheck && iP < nbPoints ) + return error + (COMPERR_BAD_PARMETERS, TComm("An enforced point is too far from the face, dist = ") + << project.LowerDistance() << " - (" + << points[ iP ].X() << ", "<< points[ iP ].Y() << ", "<< points[ iP ].Z() << " )"); + continue; + } + Quantity_Parameter u, v; + project.LowerDistanceParameters(u, v); + gp_Pnt2d uv( u, v ); + BRepClass_FaceClassifier clsf ( face, uv, tol ); + switch ( clsf.State() ) { + case TopAbs_IN: + { + double edgeDist = ( Min( Abs( u - u1 ), Abs( u - u2 )) + + Min( Abs( v - v1 ), Abs( v - v2 ))); + ForcedPoint fp; + fp.uv = uv.XY(); + fp.xyz = points[ iP ].XYZ(); + if ( iP >= nbPoints ) + fp.vertex = TopoDS::Vertex( vMap( iP - nbPoints + 1 )); + + sortedFP.insert( make_pair( edgeDist, fp )); + break; + } + case TopAbs_OUT: + { + if ( isStrictCheck && iP < nbPoints ) + return error + (COMPERR_BAD_PARMETERS, TComm("An enforced point is out of the face boundary - ") + << points[ iP ].X() << ", "<< points[ iP ].Y() << ", "<< points[ iP ].Z() << " )"); + break; + } + case TopAbs_ON: + { + if ( isStrictCheck && iP < nbPoints ) + return error + (COMPERR_BAD_PARMETERS, TComm("An enforced point is on the face boundary - ") + << points[ iP ].X() << ", "<< points[ iP ].Y() << ", "<< points[ iP ].Z() << " )"); + break; + } + default: + { + if ( isStrictCheck && iP < nbPoints ) + return error + (TComm("Classification of an enforced point ralative to the face boundary failed - ") + << points[ iP ].X() << ", "<< points[ iP ].Y() << ", "<< points[ iP ].Z() << " )"); + } + } + } + + multimap< double, ForcedPoint >::iterator d2uv = sortedFP.begin(); + for ( ; d2uv != sortedFP.end(); ++d2uv ) + myForcedPnts.push_back( (*d2uv).second ); + + return true; +} + +//================================================================================ +/*! + * \brief Splits quads by adding points of enforced nodes and create nodes on + * the sides shared by quads + */ +//================================================================================ + +bool StdMeshers_Quadrangle_2D::addEnforcedNodes() +{ + // if ( myForcedPnts.empty() ) + // return true; + + // make a map of quads sharing a side + map< StdMeshers_FaceSidePtr, vector< FaceQuadStruct::Ptr > > quadsBySide; + list< FaceQuadStruct::Ptr >::iterator quadIt = myQuadList.begin(); + for ( ; quadIt != myQuadList.end(); ++quadIt ) + for ( size_t iSide = 0; iSide < (*quadIt)->side.size(); ++iSide ) + quadsBySide[ (*quadIt)->side[iSide] ].push_back( *quadIt ); + + SMESH_Mesh* mesh = myHelper->GetMesh(); + SMESHDS_Mesh* meshDS = myHelper->GetMeshDS(); + const TopoDS_Face& face = TopoDS::Face( myHelper->GetSubShape() ); + Handle(Geom_Surface) surf = BRep_Tool::Surface( face ); + + for ( size_t iFP = 0; iFP < myForcedPnts.size(); ++iFP ) + { + bool isNodeEnforced = false; + + // look for a quad enclosing a enforced point + for ( quadIt = myQuadList.begin(); quadIt != myQuadList.end(); ++quadIt ) + { + FaceQuadStruct::Ptr quad = *quadIt; + int i,j; + if ( !setNormalizedGrid( quad )) + return false; + if ( !quad->findCell( myForcedPnts[ iFP ], i, j )) + continue; + + // a grid cell is found, select a node of the cell to move + // to the enforced point to and to split the quad at + multimap< double, pair< int, int > > ijByDist; + for ( int di = 0; di < 2; ++di ) + for ( int dj = 0; dj < 2; ++dj ) + { + double dist2 = ( myForcedPnts[ iFP ].uv - quad->UVPt( i+di,j+dj ).UV() ).SquareModulus(); + ijByDist.insert( make_pair( dist2, make_pair( i+di,j+dj ))); + } + // try all nodes starting from the closest one + set< FaceQuadStruct::Ptr > changedQuads; + multimap< double, pair< int, int > >::iterator d2ij = ijByDist.begin(); + for ( ; !isNodeEnforced && d2ij != ijByDist.end(); ++d2ij ) + { + i = d2ij->second.first; + j = d2ij->second.second; + + // check if a node is at a side + int iSide = -1; + if ( j == 0 ) + iSide = QUAD_BOTTOM_SIDE; + else if ( j+1 == quad->jSize ) + iSide = QUAD_TOP_SIDE; + else if ( i == 0 ) + iSide = QUAD_LEFT_SIDE; + else if ( i+1 == quad->iSize ) + iSide = QUAD_RIGHT_SIDE; + + if ( iSide > -1 ) // ----- node is at a side + { + FaceQuadStruct::Side& side = quad->side[ iSide ]; + // check if this node can be moved + if ( quadsBySide[ side ].size() < 2 ) + continue; // its a face boundary -> can't move the node + + int quadNodeIndex = ( iSide % 2 ) ? j : i; + int sideNodeIndex = side.ToSideIndex( quadNodeIndex ); + if ( side.IsForced( sideNodeIndex )) + { + // the node is already moved to another enforced point + isNodeEnforced = quad->isEqual( myForcedPnts[ iFP ], i, j ); + continue; + } + // make a node of a side forced + vector& points = (vector&) side.GetUVPtStruct(); + points[ sideNodeIndex ].u = myForcedPnts[ iFP ].U(); + points[ sideNodeIndex ].v = myForcedPnts[ iFP ].V(); + + updateSideUV( side, sideNodeIndex, quadsBySide ); + + // update adjacent sides + set< StdMeshers_FaceSidePtr > updatedSides; + updatedSides.insert( side ); + for ( size_t i = 0; i < side.contacts.size(); ++i ) + if ( side.contacts[i].point == sideNodeIndex ) + { + const vector< FaceQuadStruct::Ptr >& adjQuads = + quadsBySide[ *side.contacts[i].other_side ]; + if ( adjQuads.size() > 1 && + updatedSides.insert( * side.contacts[i].other_side ).second ) + { + updateSideUV( *side.contacts[i].other_side, + side.contacts[i].other_point, + quadsBySide ); + } + changedQuads.insert( adjQuads.begin(), adjQuads.end() ); + } + const vector< FaceQuadStruct::Ptr >& adjQuads = quadsBySide[ side ]; + changedQuads.insert( adjQuads.begin(), adjQuads.end() ); + + isNodeEnforced = true; + } + else // ------------------ node is inside the quad + { + // make a new side passing through IJ node and split the quad + int indForced, iNewSide; + if ( quad->iSize < quad->jSize ) // split vertically + { + quad->updateUV( myForcedPnts[ iFP ].uv, i, j, /*isVert=*/true ); + indForced = i; + iNewSide = splitQuad( quad, i, 0 ); + } + else + { + quad->updateUV( myForcedPnts[ iFP ].uv, i, j, /*isVert=*/false ); + indForced = j; + iNewSide = splitQuad( quad, 0, j ); + } + FaceQuadStruct::Ptr newQuad = myQuadList.back(); + FaceQuadStruct::Side& newSide = newQuad->side[ iNewSide ]; + + newSide.forced_nodes.insert( indForced ); + quad->side[( iNewSide+2 ) % 4 ].forced_nodes.insert( indForced ); + + quadsBySide[ newSide ].push_back( quad ); + quadsBySide[ newSide ].push_back( newQuad ); + + isNodeEnforced = true; + + } // end of "node is inside the quad" + + } // loop on nodes of the cell + + // remove out-of-date uv grid of changedQuads + set< FaceQuadStruct::Ptr >::iterator qIt = changedQuads.begin(); + for ( ; qIt != changedQuads.end(); ++qIt ) + (*qIt)->uv_grid.clear(); + + } // loop on quads + + if ( !isNodeEnforced ) + { + if ( !myForcedPnts[ iFP ].vertex.IsNull() ) + return error(TComm("Unable to move any node to vertex #") + <GetMeshDS()->ShapeToIndex( myForcedPnts[ iFP ].vertex )); + else + return error(TComm("Unable to move any node to point ( ") + << myForcedPnts[iFP].xyz.X() << ", " + << myForcedPnts[iFP].xyz.Y() << ", " + << myForcedPnts[iFP].xyz.Z() << " )"); + } + + } // loop on enforced points + + // Compute nodes on all sides, where not yet present + + for ( quadIt = myQuadList.begin(); quadIt != myQuadList.end(); ++quadIt ) + { + FaceQuadStruct::Ptr quad = *quadIt; + for ( int iSide = 0; iSide < 4; ++iSide ) + { + FaceQuadStruct::Side & side = quad->side[ iSide ]; + if ( side.nbNodeOut > 0 ) + continue; // emulated side + vector< FaceQuadStruct::Ptr >& quadVec = quadsBySide[ side ]; + if ( quadVec.size() <= 1 ) + continue; // outer side + + bool missedNodesOnSide = false; + const vector& points = side.grid->GetUVPtStruct(); + for ( size_t iC = 0; iC < side.contacts.size(); ++iC ) + { + const vector& oGrid = side.contacts[iC].other_side->grid->GetUVPtStruct(); + const UVPtStruct& uvPt = points[ side.contacts[iC].point ]; + if ( side.contacts[iC].other_point >= oGrid.size() || + side.contacts[iC].point >= points.size() ) + throw SALOME_Exception( "StdMeshers_Quadrangle_2D::addEnforcedNodes(): wrong contact" ); + if ( oGrid[ side.contacts[iC].other_point ].node ) + (( UVPtStruct& ) uvPt).node = oGrid[ side.contacts[iC].other_point ].node; + } + for ( size_t iP = 0; iP < points.size(); ++iP ) + if ( !points[ iP ].node ) + { + UVPtStruct& uvPnt = ( UVPtStruct& ) points[ iP ]; + gp_Pnt P = surf->Value( uvPnt.u, uvPnt.v ); + uvPnt.node = meshDS->AddNode(P.X(), P.Y(), P.Z()); + meshDS->SetNodeOnFace( uvPnt.node, myHelper->GetSubShapeID(), uvPnt.u, uvPnt.v ); + missedNodesOnSide = true; + } + if ( missedNodesOnSide ) + { + // clear uv_grid where nodes are missing + for ( size_t iQ = 0; iQ < quadVec.size(); ++iQ ) + quadVec[ iQ ]->uv_grid.clear(); + } + } + } + + return true; +} + +//================================================================================ +/*! + * \brief Splits a quad at I or J. Returns an index of a new side in the new quad + */ +//================================================================================ + +int StdMeshers_Quadrangle_2D::splitQuad(FaceQuadStruct::Ptr quad, int I, int J) +{ + FaceQuadStruct* newQuad = new FaceQuadStruct( quad->face ); + myQuadList.push_back( FaceQuadStruct::Ptr( newQuad )); + + vector points; + if ( I > 0 ) + { + points.reserve( quad->jSize ); + for ( int jP = 0; jP < quad->jSize; ++jP ) + points.push_back( quad->UVPt( I, jP )); + + newQuad->side.resize( 4 ); + newQuad->side[ QUAD_BOTTOM_SIDE ] = quad->side[ QUAD_BOTTOM_SIDE ]; + newQuad->side[ QUAD_RIGHT_SIDE ] = quad->side[ QUAD_RIGHT_SIDE ]; + newQuad->side[ QUAD_TOP_SIDE ] = quad->side[ QUAD_TOP_SIDE ]; + newQuad->side[ QUAD_LEFT_SIDE ] = StdMeshers_FaceSide::New( points, quad->face ); + + FaceQuadStruct::Side& newSide = newQuad->side[ QUAD_LEFT_SIDE ]; + FaceQuadStruct::Side& newSide2 = quad->side [ QUAD_RIGHT_SIDE ]; + + quad->side[ QUAD_RIGHT_SIDE ] = newSide; + + int iBot = quad->side[ QUAD_BOTTOM_SIDE ].ToSideIndex( I ); + int iTop = quad->side[ QUAD_TOP_SIDE ].ToSideIndex( I ); + + newSide.AddContact ( 0, & quad->side[ QUAD_BOTTOM_SIDE ], iBot ); + newSide2.AddContact( 0, & quad->side[ QUAD_BOTTOM_SIDE ], iBot ); + newSide.AddContact ( quad->jSize - 1, & quad->side[ QUAD_TOP_SIDE ], iTop ); + newSide2.AddContact( quad->jSize - 1, & quad->side[ QUAD_TOP_SIDE ], iTop ); + // cout << "Contact: L " << &newSide << " "<< newSide.NbPoints() + // << " R " << &newSide2 << " "<< newSide2.NbPoints() + // << " B " << &quad->side[ QUAD_BOTTOM_SIDE ] << " "<< quad->side[ QUAD_BOTTOM_SIDE].NbPoints() + // << " T " << &quad->side[ QUAD_TOP_SIDE ] << " "<< quad->side[ QUAD_TOP_SIDE].NbPoints()<< endl; + + newQuad->side[ QUAD_BOTTOM_SIDE ].from = iBot; + newQuad->side[ QUAD_TOP_SIDE ].from = iTop; + + quad->side[ QUAD_BOTTOM_SIDE ].to = iBot + 1; + quad->side[ QUAD_TOP_SIDE ].to = iTop + 1; + quad->uv_grid.clear(); + + return QUAD_LEFT_SIDE; + } + else if ( J > 0 ) //// split horizontally + { + points.reserve( quad->iSize ); + for ( int iP = 0; iP < quad->iSize; ++iP ) + points.push_back( quad->UVPt( iP, J )); + + newQuad->side.resize( 4 ); + newQuad->side[ QUAD_BOTTOM_SIDE ] = quad->side[ QUAD_BOTTOM_SIDE ]; + newQuad->side[ QUAD_RIGHT_SIDE ] = quad->side[ QUAD_RIGHT_SIDE ]; + newQuad->side[ QUAD_TOP_SIDE ] = StdMeshers_FaceSide::New( points, quad->face ); + newQuad->side[ QUAD_LEFT_SIDE ] = quad->side[ QUAD_LEFT_SIDE ]; + + FaceQuadStruct::Side& newSide = newQuad->side[ QUAD_TOP_SIDE ]; + FaceQuadStruct::Side& newSide2 = quad->side [ QUAD_BOTTOM_SIDE ]; + + quad->side[ QUAD_BOTTOM_SIDE ] = newSide; + + int iLft = quad->side[ QUAD_LEFT_SIDE ].ToSideIndex( J ); + int iRgt = quad->side[ QUAD_RIGHT_SIDE ].ToSideIndex( J ); + + newSide.AddContact ( 0, & quad->side[ QUAD_LEFT_SIDE ], iLft ); + newSide2.AddContact( 0, & quad->side[ QUAD_LEFT_SIDE ], iLft ); + newSide.AddContact ( quad->iSize - 1, & quad->side[ QUAD_RIGHT_SIDE ], iRgt ); + newSide2.AddContact( quad->iSize - 1, & quad->side[ QUAD_RIGHT_SIDE ], iRgt ); + // cout << "Contact: T " << &newSide << " "<< newSide.NbPoints() + // << " B " << &newSide2 << " "<< newSide2.NbPoints() + // << " L " << &quad->side[ QUAD_LEFT_SIDE ] << " "<< quad->side[ QUAD_LEFT_SIDE].NbPoints() + // << " R " << &quad->side[ QUAD_RIGHT_SIDE ] << " "<< quad->side[ QUAD_RIGHT_SIDE].NbPoints()<< endl; + + newQuad->side[ QUAD_RIGHT_SIDE ].to = iRgt+1; + newQuad->side[ QUAD_LEFT_SIDE ].to = iLft+1; + + quad->side[ QUAD_RIGHT_SIDE ].from = iRgt; + quad->side[ QUAD_LEFT_SIDE ].from = iLft; + quad->uv_grid.clear(); + + return QUAD_TOP_SIDE; + } +} + +//================================================================================ +/*! + * \brief Updates UV of a side after moving its node + */ +//================================================================================ + +void StdMeshers_Quadrangle_2D::updateSideUV( FaceQuadStruct::Side& side, + int iForced, + const TQuadsBySide& quadsBySide, + int * iNext) +{ + if ( !iNext ) + { + side.forced_nodes.insert( iForced ); + + // update parts of the side before and after iForced + + set::iterator iIt = side.forced_nodes.upper_bound( iForced ); + int iEnd = Min( side.NbPoints()-1, ( iIt == side.forced_nodes.end() ) ? int(1e7) : *iIt ); + if ( iForced + 1 < iEnd ) + updateSideUV( side, iForced, quadsBySide, &iEnd ); + + iIt = side.forced_nodes.lower_bound( iForced ); + int iBeg = Max( 0, ( iIt == side.forced_nodes.begin() ) ? 0 : *--iIt ); + if ( iForced - 1 > iBeg ) + updateSideUV( side, iForced, quadsBySide, &iBeg ); + + return; + } + + const int iFrom = Min ( iForced, *iNext ); + const int iTo = Max ( iForced, *iNext ) + 1; + const int sideSize = iTo - iFrom; + + vector points[4]; + + // get from the quads grid points adjacent to the side + // to make two sides of another temporary quad + vector< FaceQuadStruct::Ptr > quads = quadsBySide.find( side )->second; // copy! + for ( int is2nd = 0; is2nd < 2; ++is2nd ) + { + points[ is2nd ].reserve( sideSize ); + int nbLoops = 0; + while ( points[is2nd].size() < sideSize ) + { + int iCur = iFrom + points[is2nd].size() - int( !points[is2nd].empty() ); + + // look for a quad adjacent to iCur-th point of the side + for ( size_t iQ = 0; iQ < quads.size(); ++iQ ) + { + FaceQuadStruct::Ptr q = quads[ iQ ]; + if ( !q ) continue; + size_t iS; + for ( iS = 0; iS < q->side.size(); ++iS ) + if ( side.grid == q->side[ iS ].grid ) + break; + bool isOut; + if ( !q->side[ iS ].IsReversed() ) + isOut = ( q->side[ iS ].from > iCur || q->side[ iS ].to-1 <= iCur ); + else + isOut = ( q->side[ iS ].to >= iCur || q->side[ iS ].from <= iCur ); + if ( isOut ) + continue; + + // found - copy points + int i,j,di,dj,nb; + if ( iS % 2 ) // right ot left + { + i = ( iS == QUAD_LEFT_SIDE ) ? 1 : q->iSize-2; + j = q->side[ iS ].ToQuadIndex( iCur ); + di = 0; + dj = ( q->side[ iS ].IsReversed() ) ? -1 : +1; + nb = ( q->side[ iS ].IsReversed() ) ? j+1 : q->jSize-j; + } + else // bottom or top + { + i = q->side[ iS ].ToQuadIndex( iCur ); + j = ( iS == QUAD_BOTTOM_SIDE ) ? 1 : q->jSize-2; + di = ( q->side[ iS ].IsReversed() ) ? -1 : +1; + dj = 0; + nb = ( q->side[ iS ].IsReversed() ) ? i+1 : q->iSize-i; + } + if ( !points[is2nd].empty() ) + { + gp_UV lastUV = points[is2nd].back().UV(); + gp_UV quadUV = q->UVPt( i, j ).UV(); + if ( ( lastUV - quadUV ).SquareModulus() > 1e-10 ) + continue; // quad is on the other side of the side + i += di; j += dj; --nb; + } + for ( ; nb > 0 ; --nb ) + { + points[ is2nd ].push_back( q->UVPt( i, j )); + if ( points[is2nd].size() >= sideSize ) + break; + i += di; j += dj; + } + quads[ iQ ].reset(); // not to use this quad anymore + + if ( points[is2nd].size() >= sideSize ) + break; + } // loop on quads + + if ( nbLoops++ > quads.size() ) + throw SALOME_Exception( "StdMeshers_Quadrangle_2D::updateSideUV() bug: infinite loop" ); + + } // while ( points[is2nd].size() < sideSize ) + } // two loops to fill points[0] and points[1] + + // points for other pair of opposite sides of the temporary quad + + enum { L,R,B,T }; // side index of points[] + + points[B].push_back( points[L].front() ); + points[B].push_back( side.GetUVPtStruct()[ iFrom ]); + points[B].push_back( points[R].front() ); + + points[T].push_back( points[L].back() ); + points[T].push_back( side.GetUVPtStruct()[ iTo-1 ]); + points[T].push_back( points[R].back() ); + + // make the temporary quad + FaceQuadStruct::Ptr tmpQuad( new FaceQuadStruct( TopoDS::Face( myHelper->GetSubShape() ))); + tmpQuad->side.push_back( StdMeshers_FaceSide::New( points[B] )); // bottom + tmpQuad->side.push_back( StdMeshers_FaceSide::New( points[R] )); // right + tmpQuad->side.push_back( StdMeshers_FaceSide::New( points[T] )); + tmpQuad->side.push_back( StdMeshers_FaceSide::New( points[L] )); + + // compute new UV of the side + setNormalizedGrid( tmpQuad ); + gp_UV uv = tmpQuad->UVPt(1,0).UV(); + tmpQuad->updateUV( uv, 1,0, /*isVertical=*/true ); + + // update UV of the side + vector& sidePoints = (vector&) side.GetUVPtStruct(); + for ( int i = iFrom; i < iTo; ++i ) + sidePoints[ i ] = tmpQuad->UVPt( 1, i-iFrom ); +} + +//================================================================================ +/*! + * \brief Finds indices of a grid quad enclosing the given enforced UV + */ +//================================================================================ + +bool FaceQuadStruct::findCell( const gp_XY& UV, int & I, int & J ) +{ + // setNormalizedGrid() must be called before! + if ( uv_box.IsOut( UV )) + return false; + + // find an approximate position + double x = 0.5, y = 0.5; + gp_XY t0 = UVPt( iSize - 1, 0 ).UV(); + gp_XY t1 = UVPt( 0, jSize - 1 ).UV(); + gp_XY t2 = UVPt( 0, 0 ).UV(); + SMESH_MeshAlgos::GetBarycentricCoords( UV, t0, t1, t2, x, y ); + x = Min( 1., Max( 0., x )); + y = Min( 1., Max( 0., y )); + + // precise the position + //int i, j; + normPa2IJ( x,y, I,J ); + if ( !isNear( UV, I,J )) + { + // look for the most close IJ by traversing uv_grid in the middle + double dist2, minDist2 = ( UV - UVPt( I,J ).UV() ).SquareModulus(); + for ( int isU = 0; isU < 2; ++isU ) + { + int ind1 = isU ? 0 : iSize / 2; + int ind2 = isU ? jSize / 2 : 0; + int di1 = isU ? Max( 2, iSize / 20 ) : 0; + int di2 = isU ? 0 : Max( 2, jSize / 20 ); + int i,nb = isU ? iSize / di1 : jSize / di2; + for ( i = 0; i < nb; ++i, ind1 += di1, ind2 += di2 ) + if (( dist2 = ( UV - UVPt( ind1,ind2 ).UV() ).SquareModulus() ) < minDist2 ) + { + I = ind1; + J = ind2; + if ( isNear( UV, I,J )) + return true; + minDist2 = ( UV - UVPt( I,J ).UV() ).SquareModulus(); + } + } + if ( !isNear( UV, I,J, Max( iSize, jSize ) /2 )) + return false; + } + return true; +} + +//================================================================================ +/*! + * \brief Find indices (i,j) of a point in uv_grid by normalized parameters (x,y) + */ +//================================================================================ + +void FaceQuadStruct::normPa2IJ(double X, double Y, int & I, int & J ) +{ + + I = Min( int ( iSize * X ), iSize - 2 ); + J = Min( int ( jSize * Y ), jSize - 2 ); + + int oldI, oldJ; + do + { + oldI = I, oldJ = J; + while ( X <= UVPt( I,J ).x && I != 0 ) + --I; + while ( X > UVPt( I+1,J ).x && I+1 < iSize ) + ++I; + while ( Y <= UVPt( I,J ).y && J != 0 ) + --J; + while ( Y > UVPt( I,J+1 ).y && J+1 < jSize ) + ++J; + } while ( oldI != I || oldJ != J ); +} + +//================================================================================ +/*! + * \brief Looks for UV in quads around a given (I,J) and precise (I,J) + */ +//================================================================================ + +bool FaceQuadStruct::isNear( const gp_XY& UV, int & I, int & J, int nbLoops ) +{ + if ( I+1 >= iSize ) I = iSize - 2; + if ( J+1 >= jSize ) J = jSize - 2; + + double bcI, bcJ; + gp_XY uvI, uvJ, uv0, uv1; + for ( int iLoop = 0; iLoop < nbLoops; ++iLoop ) + { + int oldI = I, oldJ = J; + + uvI = UVPt( I+1, J ).UV(); + uvJ = UVPt( I, J+1 ).UV(); + uv0 = UVPt( I, J ).UV(); + SMESH_MeshAlgos::GetBarycentricCoords( UV, uvI, uvJ, uv0, bcI, bcJ ); + if ( bcI >= 0. && bcJ >= 0. && bcI + bcJ <= 1.) + return true; + + if ( I > 0 && bcI < 0. ) --I; + if ( I+1 < iSize && bcI > 1. ) ++I; + if ( J > 0 && bcJ < 0. ) --J; + if ( J+1 < jSize && bcJ > 1. ) ++J; + + uv1 = UVPt( I+1,J+1).UV(); + if ( I != oldI || J != oldJ ) + { + uvI = UVPt( I+1, J ).UV(); + uvJ = UVPt( I, J+1 ).UV(); + } + SMESH_MeshAlgos::GetBarycentricCoords( UV, uvI, uvJ, uv1, bcI, bcJ ); + if ( bcI >= 0. && bcJ >= 0. && bcI + bcJ <= 1.) + return true; + + if ( I > 0 && bcI > 1. ) --I; + if ( I+1 < iSize && bcI < 0. ) ++I; + if ( J > 0 && bcJ > 1. ) --J; + if ( J+1 < jSize && bcJ < 0. ) ++J; + + if ( I == oldI && J == oldJ ) + return false; + + if ( iLoop+1 == nbLoops ) + { + uvI = UVPt( I+1, J ).UV(); + uvJ = UVPt( I, J+1 ).UV(); + uv0 = UVPt( I, J ).UV(); + SMESH_MeshAlgos::GetBarycentricCoords( UV, uvI, uvJ, uv0, bcI, bcJ ); + if ( bcI >= 0. && bcJ >= 0. && bcI + bcJ <= 1.) + return true; + + uv1 = UVPt( I+1,J+1).UV(); + SMESH_MeshAlgos::GetBarycentricCoords( UV, uvI, uvJ, uv1, bcI, bcJ ); + if ( bcI >= 0. && bcJ >= 0. && bcI + bcJ <= 1.) + return true; + } + } + return false; +} + +//================================================================================ +/*! + * \brief Checks if a given UV is equal to a given frid point + */ +//================================================================================ + +bool FaceQuadStruct::isEqual( const gp_XY& UV, int I, int J ) +{ + TopLoc_Location loc; + Handle(Geom_Surface) surf = BRep_Tool::Surface( face, loc ); + gp_Pnt p1 = surf->Value( UV.X(), UV.Y() ); + gp_Pnt p2 = surf->Value( UVPt( I,J ).u, UVPt( I,J ).v ); + + double dist2 = 1e100; + for ( int di = -1; di < 2; di += 2 ) + { + int i = I + di; + if ( i < 0 || i+1 >= iSize ) continue; + for ( int dj = -1; dj < 2; dj += 2 ) + { + int j = J + dj; + if ( j < 0 || j+1 >= jSize ) continue; + + dist2 = Min( dist2, + p2.SquareDistance( surf->Value( UVPt( i,j ).u, UVPt( i,j ).v ))); + } + } + double tol2 = dist2 / 1000.; + return p1.SquareDistance( p2 ) < tol2; +} + +//================================================================================ +/*! + * \brief Recompute UV of grid points around a moved point in one direction + */ +//================================================================================ + +void FaceQuadStruct::updateUV( const gp_XY& UV, int I, int J, bool isVertical ) +{ + UVPt( I, J ).u = UV.X(); + UVPt( I, J ).v = UV.Y(); + + if ( isVertical ) + { + // above J + if ( J+1 < jSize-1 ) + { + gp_UV a0 = UVPt( 0, J ).UV(); + gp_UV a1 = UVPt( iSize-1, J ).UV(); + gp_UV a2 = UVPt( iSize-1, jSize-1 ).UV(); + gp_UV a3 = UVPt( 0, jSize-1 ).UV(); + + gp_UV p0 = UVPt( I, J ).UV(); + gp_UV p2 = UVPt( I, jSize-1 ).UV(); + const double y0 = UVPt( I, J ).y, dy = 1. - y0; + for (int j = J+1; j < jSize-1; j++) + { + gp_UV p1 = UVPt( iSize-1, j ).UV(); + gp_UV p3 = UVPt( 0, j ).UV(); + + UVPtStruct& uvPt = UVPt( I, j ); + gp_UV uv = calcUV( uvPt.x, ( uvPt.y - y0 ) / dy, a0,a1,a2,a3, p0,p1,p2,p3); + uvPt.u = uv.X(); + uvPt.v = uv.Y(); + } + } + // under J + if ( J-1 > 0 ) + { + gp_UV a0 = UVPt( 0, 0 ).UV(); + gp_UV a1 = UVPt( iSize-1, 0 ).UV(); + gp_UV a2 = UVPt( iSize-1, J ).UV(); + gp_UV a3 = UVPt( 0, J ).UV(); + + gp_UV p0 = UVPt( I, 0 ).UV(); + gp_UV p2 = UVPt( I, J ).UV(); + const double y0 = 0., dy = UVPt( I, J ).y - y0; + for (int j = 1; j < J; j++) + { + gp_UV p1 = UVPt( iSize-1, j ).UV(); + gp_UV p3 = UVPt( 0, j ).UV(); + + UVPtStruct& uvPt = UVPt( I, j ); + gp_UV uv = calcUV( uvPt.x, ( uvPt.y - y0 ) / dy, a0,a1,a2,a3, p0,p1,p2,p3); + uvPt.u = uv.X(); + uvPt.v = uv.Y(); + } + } + } + else // horizontally + { + // before I + if ( I-1 > 0 ) + { + gp_UV a0 = UVPt( 0, 0 ).UV(); + gp_UV a1 = UVPt( I, 0 ).UV(); + gp_UV a2 = UVPt( I, jSize-1 ).UV(); + gp_UV a3 = UVPt( 0, jSize-1 ).UV(); + + gp_UV p1 = UVPt( I, J ).UV(); + gp_UV p3 = UVPt( 0, J ).UV(); + const double x0 = 0., dx = UVPt( I, J ).x - x0; + for (int i = 1; i < I; i++) + { + gp_UV p0 = UVPt( i, 0 ).UV(); + gp_UV p2 = UVPt( i, jSize-1 ).UV(); + + UVPtStruct& uvPt = UVPt( i, J ); + gp_UV uv = calcUV(( uvPt.x - x0 ) / dx , uvPt.y, a0,a1,a2,a3, p0,p1,p2,p3); + uvPt.u = uv.X(); + uvPt.v = uv.Y(); + } + } + // after I + if ( I+1 < iSize-1 ) + { + gp_UV a0 = UVPt( I, 0 ).UV(); + gp_UV a1 = UVPt( iSize-1, 0 ).UV(); + gp_UV a2 = UVPt( iSize-1, jSize-1 ).UV(); + gp_UV a3 = UVPt( I, jSize-1 ).UV(); + + gp_UV p1 = UVPt( iSize-1, J ).UV(); + gp_UV p3 = UVPt( I, J ).UV(); + const double x0 = UVPt( I, J ).x, dx = 1. - x0; + for (int i = I+1; i < iSize-1; i++) + { + gp_UV p0 = UVPt( i, 0 ).UV(); + gp_UV p2 = UVPt( i, jSize-1 ).UV(); + + UVPtStruct& uvPt = UVPt( i, J ); + gp_UV uv = calcUV(( uvPt.x - x0 ) / dx , uvPt.y, a0,a1,a2,a3, p0,p1,p2,p3); + uvPt.u = uv.X(); + uvPt.v = uv.Y(); + } + } + } +} + +//================================================================================ +/*! + * \brief Side copying + */ +//================================================================================ + +FaceQuadStruct::Side& FaceQuadStruct::Side::operator=(const Side& otherSide) +{ + grid = otherSide.grid; + from = otherSide.from; + to = otherSide.to; + forced_nodes = otherSide.forced_nodes; + contacts = otherSide.contacts; + nbNodeOut = otherSide.nbNodeOut; + + for ( size_t iC = 0; iC < contacts.size(); ++iC ) + { + FaceQuadStruct::Side* oSide = contacts[iC].other_side; + for ( size_t iOC = 0; iOC < oSide->contacts.size(); ++iOC ) + if ( oSide->contacts[iOC].other_side == & otherSide ) + { + // cout << "SHIFT old " << &otherSide << " " << otherSide.NbPoints() + // << " -> new " << this << " " << this->NbPoints() << endl; + oSide->contacts[iOC].other_side = this; + } + } +} + +//================================================================================ +/*! + * \brief Converts node index of a quad to node index of this side + */ +//================================================================================ + +int FaceQuadStruct::Side::ToSideIndex( int quadNodeIndex ) const +{ + return ( from > to ) ? ( from - quadNodeIndex ) : ( quadNodeIndex + from ); +} + +//================================================================================ +/*! + * \brief Converts node index of this side to node index of a quad + */ +//================================================================================ + +int FaceQuadStruct::Side::ToQuadIndex( int sideNodeIndex ) const +{ + return ( from > to ) ? ( from - sideNodeIndex ) : ( sideNodeIndex - from ); +} + +//================================================================================ +/*! + * \brief Checks if a node is enforced + * \param [in] nodeIndex - an index of a node in a size + * \return bool - \c true if the node is forced + */ +//================================================================================ + +bool FaceQuadStruct::Side::IsForced( int nodeIndex ) const +{ + if ( nodeIndex < 0 || nodeIndex >= grid->NbPoints() ) + throw SALOME_Exception( " FaceQuadStruct::Side::IsForced(): wrong index" ); + + if ( forced_nodes.count( nodeIndex ) ) + return true; + + for ( size_t i = 0; i < this->contacts.size(); ++i ) + if ( contacts[ i ].point == nodeIndex && + contacts[ i ].other_side->forced_nodes.count( contacts[ i ].other_point )) + return true; + + return false; +} + +//================================================================================ +/*! + * \brief Sets up a contact between this and another side + */ +//================================================================================ + +void FaceQuadStruct::Side::AddContact( int ip, Side* side, int iop ) +{ + if ( ip >= GetUVPtStruct().size() || + iop >= side->GetUVPtStruct().size() ) + throw SALOME_Exception( "FaceQuadStruct::Side::AddContact(): wrong point" ); + { + contacts.resize( contacts.size() + 1 ); + Contact& c = contacts.back(); + c.point = ip; + c.other_side = side; + c.other_point = iop; + } + { + side->contacts.resize( side->contacts.size() + 1 ); + Contact& c = side->contacts.back(); + c.point = iop; + c.other_side = this; + c.other_point = ip; + } +} + +//================================================================================ +/*! + * \brief Returns a normalized parameter of a point indexed within a quadrangle + */ +//================================================================================ + +double FaceQuadStruct::Side::Param( int i ) const +{ + const vector& points = GetUVPtStruct(); + return (( points[ from + i ].normParam - points[ from ].normParam ) / + ( points[ to - 1 ].normParam - points[ from ].normParam )); +} + +//================================================================================ +/*! + * \brief Returns UV by a parameter normalized within a quadrangle + */ +//================================================================================ + +gp_XY FaceQuadStruct::Side::Value2d( double x ) const +{ + const vector& points = GetUVPtStruct(); + double u = ( points[ from ].normParam + + x * ( points[ to-1 ].normParam - points[ from ].normParam )); + return grid->Value2d( u ).XY(); +} + +//================================================================================ +/*! + * \brief Returns side length + */ +//================================================================================ + +double FaceQuadStruct::Side::Length(int theFrom, int theTo) const +{ + const vector& points = GetUVPtStruct(); + double r = ( points[ Max( to, theTo )-1 ].normParam - + points[ Max( from, theFrom ) ].normParam ); + return r * grid->Length(); +} diff --git a/src/StdMeshers/StdMeshers_Quadrangle_2D.hxx b/src/StdMeshers/StdMeshers_Quadrangle_2D.hxx index 54a2a0664..9fe6e120a 100644 --- a/src/StdMeshers/StdMeshers_Quadrangle_2D.hxx +++ b/src/StdMeshers/StdMeshers_Quadrangle_2D.hxx @@ -30,31 +30,77 @@ #include "SMESH_Algo.hxx" #include "SMESH_ProxyMesh.hxx" #include "SMESH_StdMeshers.hxx" +#include "StdMeshers_FaceSide.hxx" #include "StdMeshers_QuadrangleParams.hxx" #include +#include class SMDS_MeshNode; class SMESH_Mesh; class SMESH_MesherHelper; class SMESH_ProxyMesh; -class StdMeshers_FaceSide; struct uvPtStruct; enum TSideID { QUAD_BOTTOM_SIDE=0, QUAD_RIGHT_SIDE, QUAD_TOP_SIDE, QUAD_LEFT_SIDE, NB_QUAD_SIDES }; typedef uvPtStruct UVPtStruct; -typedef struct faceQuadStruct +struct FaceQuadStruct { - std::vector< StdMeshers_FaceSide*> side; - bool isEdgeOut[4]; // true, if an EDGE has more nodes, than an opposite one - UVPtStruct* uv_grid; - TopoDS_Face face; - ~faceQuadStruct(); - void shift( size_t nb, bool keepUnitOri ); - typedef boost::shared_ptr Ptr; -} FaceQuadStruct; + struct Side // a side of FaceQuadStruct + { + struct Contact // contact of two sides + { + int point; // index of a grid point of this side where two sides meat + Side* other_side; + int other_point; + }; + StdMeshers_FaceSidePtr grid; + int from, to; // indices of grid points used by the quad + std::set forced_nodes; // indices of forced grid points + std::vector contacts; // contacts with sides of other quads + int nbNodeOut; // nb of missing nodes on an opposite shorter side + + Side(StdMeshers_FaceSidePtr theGrid = StdMeshers_FaceSidePtr()); + Side& operator=(const Side& otherSide); + operator StdMeshers_FaceSidePtr() { return grid; } + operator const StdMeshers_FaceSidePtr() const { return grid; } + void AddContact( int ip, Side* side, int iop ); + int ToSideIndex( int quadNodeIndex ) const; + int ToQuadIndex( int sideNodeIndex ) const; + bool IsForced( int nodeIndex ) const; + bool IsReversed() const { return nbNodeOut ? false : to < from; } + int NbPoints() const { return Abs( to - from ); } + double Param( int nodeIndex ) const; + double Length( int from=-1, int to=-1) const; + gp_XY Value2d( double x ) const; + // some sortcuts + const vector& GetUVPtStruct(bool isXConst=0, double constValue=0) const + { return nbNodeOut ? + grid->SimulateUVPtStruct( NbPoints()-nbNodeOut-1, isXConst, constValue ) : + grid->GetUVPtStruct( isXConst, constValue ); + } + }; + + std::vector< Side > side; + std::vector< UVPtStruct> uv_grid; + int iSize, jSize; + TopoDS_Face face; + Bnd_B2d uv_box; + + FaceQuadStruct ( const TopoDS_Face& F = TopoDS_Face() ); + UVPtStruct& UVPt( int i, int j ) { return uv_grid[ i + j * iSize ]; } + void shift ( size_t nb, bool keepUnitOri ); + int & nbNodeOut( int iSide ) { return side[ iSide ].nbNodeOut; } + bool findCell ( const gp_XY& uv, int & i, int & j ); + bool isNear ( const gp_XY& uv, int & i, int & j, int nbLoops=1 ); + bool isEqual ( const gp_XY& uv, int i, int j ); + void normPa2IJ( double x, double y, int & i, int & j ); + void updateUV ( const gp_XY& uv, int i, int j, bool isVertical ); + + typedef boost::shared_ptr Ptr; +}; class STDMESHERS_EXPORT StdMeshers_Quadrangle_2D: public SMESH_2D_Algo { @@ -89,16 +135,17 @@ protected: std::vector& aNbNodes, bool& IsQuadratic); - bool setNormalizedGrid(SMESH_Mesh& aMesh, - const TopoDS_Face& aFace, - FaceQuadStruct::Ptr& quad); + bool setNormalizedGrid(FaceQuadStruct::Ptr quad); - void splitQuad(SMESHDS_Mesh *theMeshDS, - const int theFaceID, - const SMDS_MeshNode* theNode1, - const SMDS_MeshNode* theNode2, - const SMDS_MeshNode* theNode3, - const SMDS_MeshNode* theNode4); + void splitQuadFace(SMESHDS_Mesh * theMeshDS, + const int theFaceID, + const SMDS_MeshNode* theNode1, + const SMDS_MeshNode* theNode2, + const SMDS_MeshNode* theNode3, + const SMDS_MeshNode* theNode4); + + bool computeQuadDominant(SMESH_Mesh& aMesh, + const TopoDS_Face& aFace); bool computeQuadDominant(SMESH_Mesh& aMesh, const TopoDS_Face& aFace, @@ -108,6 +155,10 @@ protected: const TopoDS_Face& aFace, FaceQuadStruct::Ptr quad); + bool computeTriangles(SMESH_Mesh& aMesh, + const TopoDS_Face& aFace, + FaceQuadStruct::Ptr quad); + bool evaluateQuadPref(SMESH_Mesh& aMesh, const TopoDS_Shape& aShape, std::vector& aNbNodes, @@ -129,20 +180,43 @@ protected: int & theNbDegenEdges, const bool considerMesh); + bool getEnforcedUV(); + + bool addEnforcedNodes(); + + int splitQuad(FaceQuadStruct::Ptr quad, int i, int j); + + typedef std::map< StdMeshers_FaceSidePtr, std::vector< FaceQuadStruct::Ptr > > TQuadsBySide; + void updateSideUV( FaceQuadStruct::Side& side, + int iForced, + const TQuadsBySide& quads, + int * iNext=NULL); + + + // Fields - // true if QuadranglePreference hypothesis is assigned that forces - // construction of quadrangles if the number of nodes on opposite edges - // is not the same in the case where the global number of nodes on edges - // is even bool myQuadranglePreference; bool myTrianglePreference; int myTriaVertexID; bool myNeedSmooth; - - StdMeshers_QuadType myQuadType; - SMESH_MesherHelper* myHelper; // tool for working with quadratic elements - SMESH_ProxyMesh::Ptr myProxyMesh; - FaceQuadStruct::Ptr myQuadStruct; + const StdMeshers_QuadrangleParams* myParams; + StdMeshers_QuadType myQuadType; + + SMESH_MesherHelper* myHelper; + SMESH_ProxyMesh::Ptr myProxyMesh; + std::list< FaceQuadStruct::Ptr > myQuadList; + + struct ForcedPoint + { + gp_XY uv; + gp_XYZ xyz; + TopoDS_Vertex vertex; + + double U() const { return uv.X(); } + double V() const { return uv.Y(); } + operator const gp_XY& () { return uv; } + }; + std::vector< ForcedPoint > myForcedPnts; }; #endif diff --git a/src/StdMeshers/StdMeshers_Regular_1D.cxx b/src/StdMeshers/StdMeshers_Regular_1D.cxx index 5377a6fa5..3470e50a2 100644 --- a/src/StdMeshers/StdMeshers_Regular_1D.cxx +++ b/src/StdMeshers/StdMeshers_Regular_1D.cxx @@ -37,6 +37,7 @@ #include "SMESH_subMeshEventListener.hxx" #include "StdMeshers_Adaptive1D.hxx" #include "StdMeshers_Arithmetic1D.hxx" +#include "StdMeshers_Geometric1D.hxx" #include "StdMeshers_AutomaticLength.hxx" #include "StdMeshers_Deflection1D.hxx" #include "StdMeshers_Distribution.hxx" @@ -89,12 +90,14 @@ StdMeshers_Regular_1D::StdMeshers_Regular_1D(int hypId, int studyId, _compatibleHypothesis.push_back("StartEndLength"); _compatibleHypothesis.push_back("Deflection1D"); _compatibleHypothesis.push_back("Arithmetic1D"); + _compatibleHypothesis.push_back("GeometricProgression"); _compatibleHypothesis.push_back("FixedPoints1D"); _compatibleHypothesis.push_back("AutomaticLength"); _compatibleHypothesis.push_back("Adaptive1D"); - - _compatibleHypothesis.push_back("QuadraticMesh"); // auxiliary !!! - _compatibleHypothesis.push_back("Propagation"); // auxiliary !!! + // auxiliary: + _compatibleHypothesis.push_back("QuadraticMesh"); + _compatibleHypothesis.push_back("Propagation"); + _compatibleHypothesis.push_back("PropagOfDistribution"); } //============================================================================= @@ -223,6 +226,21 @@ bool StdMeshers_Regular_1D::CheckHypothesis( SMESH_Mesh& aMesh, aStatus = SMESH_Hypothesis::HYP_OK; } + else if (hypName == "GeometricProgression") + { + const StdMeshers_Geometric1D * hyp = + dynamic_cast (theHyp); + ASSERT(hyp); + _value[ BEG_LENGTH_IND ] = hyp->GetStartLength(); + _value[ END_LENGTH_IND ] = hyp->GetCommonRatio(); + ASSERT( _value[ BEG_LENGTH_IND ] > 0 && _value[ END_LENGTH_IND ] > 0 ); + _hypType = GEOMETRIC_1D; + + _revEdgesIDs = hyp->GetReversedEdges(); + + aStatus = SMESH_Hypothesis::HYP_OK; + } + else if (hypName == "FixedPoints1D") { _fpHyp = dynamic_cast (theHyp); ASSERT(_fpHyp); @@ -616,6 +634,58 @@ bool StdMeshers_Regular_1D::computeInternalParameters(SMESH_Mesh & theMesh, double f = theFirstU, l = theLastU; + // Propagation Of Distribution + // + if ( !_mainEdge.IsNull() && _isPropagOfDistribution ) + { + TopoDS_Edge mainEdge = TopoDS::Edge( _mainEdge ); // should not be a reference! + _gen->Compute( theMesh, mainEdge, /*aShapeOnly=*/true, /*anUpward=*/true); + + SMESHDS_SubMesh* smDS = theMesh.GetMeshDS()->MeshElements( mainEdge ); + if ( !smDS ) + return error("No mesh on the source edge of Propagation Of Distribution"); + if ( smDS->NbNodes() < 1 ) + return true; // 1 segment + + vector< double > mainEdgeParams; + if ( ! SMESH_Algo::GetNodeParamOnEdge( theMesh.GetMeshDS(), mainEdge, mainEdgeParams )) + return error("Bad node parameters on the source edge of Propagation Of Distribution"); + + vector< double > segLen( mainEdgeParams.size() - 1 ); + double totalLen = 0; + BRepAdaptor_Curve mainEdgeCurve( mainEdge ); + for ( size_t i = 1; i < mainEdgeParams.size(); ++i ) + { + segLen[ i-1 ] = GCPnts_AbscissaPoint::Length( mainEdgeCurve, + mainEdgeParams[i-1], + mainEdgeParams[i]); + totalLen += segLen[ i-1 ]; + } + for ( size_t i = 0; i < segLen.size(); ++i ) + segLen[ i ] *= theLength / totalLen; + + size_t iSeg = theReverse ? segLen.size()-1 : 0; + size_t dSeg = theReverse ? -1 : +1; + double param = theFirstU; + int nbParams = 0; + for ( int i = 0, nb = segLen.size()-1; i < nb; ++i, iSeg += dSeg ) + { + GCPnts_AbscissaPoint Discret( theC3d, segLen[ iSeg ], param ); + if ( !Discret.IsDone() ) break; + param = Discret.Parameter(); + theParams.push_back( param ); + ++nbParams; + } + if ( nbParams != segLen.size()-1 ) + return error( SMESH_Comment("Can't divide into ") << segLen.size() << " segements"); + + compensateError( segLen[ theReverse ? segLen.size()-1 : 0 ], + segLen[ theReverse ? 0 : segLen.size()-1 ], + f, l, theLength, theC3d, theParams, true ); + return true; + } + + switch( _hypType ) { case LOCAL_LENGTH: @@ -824,9 +894,54 @@ bool StdMeshers_Regular_1D::computeInternalParameters(SMESH_Mesh & theMesh, return true; } + case GEOMETRIC_1D: { + + double a1 = _value[ BEG_LENGTH_IND ], an; + double q = _value[ END_LENGTH_IND ]; + + double U1 = theReverse ? l : f; + double Un = theReverse ? f : l; + double param = U1; + double eltSize = a1; + if ( theReverse ) + eltSize = -eltSize; + + int nbParams = 0; + while ( true ) { + // computes a point on a curve at the distance + // from the point of parameter . + GCPnts_AbscissaPoint Discret( theC3d, eltSize, param ); + if ( !Discret.IsDone() ) break; + param = Discret.Parameter(); + if ( f < param && param < l ) + theParams.push_back( param ); + else + break; + an = eltSize; + eltSize *= q; + ++nbParams; + } + if ( nbParams > 1 ) + { + if ( Abs( param - Un ) < 0.2 * Abs( param - theParams.back() )) + { + compensateError( a1, eltSize, U1, Un, theLength, theC3d, theParams ); + } + else if ( Abs( Un - theParams.back() ) < + 0.2 * Abs( theParams.back() - *(--theParams.rbegin()))) + { + theParams.pop_back(); + compensateError( a1, an, U1, Un, theLength, theC3d, theParams ); + } + } + if (theReverse) theParams.reverse(); // NPAL18025 + + return true; + } + case FIXED_POINTS_1D: { const std::vector& aPnts = _fpHyp->GetPoints(); - const std::vector& nbsegs = _fpHyp->GetNbSegments(); + const std::vector& nbsegs = _fpHyp->GetNbSegments(); int i = 0; TColStd_SequenceOfReal Params; for(; i on this edge; diff --git a/src/StdMeshers/StdMeshers_Regular_1D.hxx b/src/StdMeshers/StdMeshers_Regular_1D.hxx index fbb9d0380..1298c80a6 100644 --- a/src/StdMeshers/StdMeshers_Regular_1D.hxx +++ b/src/StdMeshers/StdMeshers_Regular_1D.hxx @@ -102,7 +102,7 @@ protected: StdMeshers_SegmentLengthAroundVertex* getVertexHyp(SMESH_Mesh & theMesh, const TopoDS_Vertex & theV); - enum HypothesisType { LOCAL_LENGTH, MAX_LENGTH, NB_SEGMENTS, BEG_END_LENGTH, DEFLECTION, ARITHMETIC_1D, FIXED_POINTS_1D, ADAPTIVE, NONE }; + enum HypothesisType { LOCAL_LENGTH, MAX_LENGTH, NB_SEGMENTS, BEG_END_LENGTH, DEFLECTION, ARITHMETIC_1D, FIXED_POINTS_1D, ADAPTIVE, GEOMETRIC_1D, NONE }; enum ValueIndex { SCALE_FACTOR_IND = 0, @@ -140,6 +140,7 @@ protected: // a source of propagated hypothesis, is set by CheckHypothesis() // always called before Compute() TopoDS_Shape _mainEdge; + bool _isPropagOfDistribution; }; #endif diff --git a/src/StdMeshers/StdMeshers_Reversible1D.cxx b/src/StdMeshers/StdMeshers_Reversible1D.cxx new file mode 100644 index 000000000..05d25be98 --- /dev/null +++ b/src/StdMeshers/StdMeshers_Reversible1D.cxx @@ -0,0 +1,99 @@ +// Copyright (C) 2007-2013 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// SMESH SMESH : implementaion of SMESH idl descriptions +// File : StdMeshers_Reversible1D.cxx +// Module : SMESH +// + +#include "StdMeshers_Reversible1D.hxx" + +//============================================================================= +/*! + * + */ +//============================================================================= + +StdMeshers_Reversible1D::StdMeshers_Reversible1D(int hypId, int studyId, SMESH_Gen * gen) + :SMESH_Hypothesis(hypId, studyId, gen) +{ + _param_algo_dim = 1; +} + +//============================================================================= +/*! + * + */ +//============================================================================= + +void StdMeshers_Reversible1D::SetReversedEdges( const std::vector& ids ) +{ + if ( ids != _edgeIDs ) + { + _edgeIDs = ids; + NotifySubMeshesHypothesisModification(); + } +} + +//============================================================================= +/*! + * + */ +//============================================================================= + +std::ostream & StdMeshers_Reversible1D::SaveTo(std::ostream & save) +{ + save << " " << _edgeIDs.size() << " "; + + if ( !_edgeIDs.empty() ) + { + for ( size_t i = 0; i < _edgeIDs.size(); i++) + save << " " << _edgeIDs[i]; + save << " " << _objEntry << " "; + } + + return save; +} + +//============================================================================= +/*! + * + */ +//============================================================================= + +std::istream & StdMeshers_Reversible1D::LoadFrom(std::istream & load) +{ + bool isOK; + int intVal; + + isOK = (load >> intVal); + if (isOK && intVal > 0) { + _edgeIDs.reserve( intVal ); + for (int i = 0; i < _edgeIDs.capacity() && isOK; i++) { + isOK = (load >> intVal); + if ( isOK ) _edgeIDs.push_back( intVal ); + } + isOK = (load >> _objEntry); + } + + return load; +} diff --git a/src/StdMeshers/StdMeshers_Reversible1D.hxx b/src/StdMeshers/StdMeshers_Reversible1D.hxx new file mode 100644 index 000000000..e393e2046 --- /dev/null +++ b/src/StdMeshers/StdMeshers_Reversible1D.hxx @@ -0,0 +1,59 @@ +// Copyright (C) 2007-2013 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// SMESH SMESH : implementaion of SMESH idl descriptions +// File : StdMeshers_Reversible1D.hxx +// Module : SMESH +// +#ifndef _SMESH_Reversible1D_HXX_ +#define _SMESH_Reversible1D_HXX_ + +#include "SMESH_StdMeshers.hxx" +#include "SMESH_Hypothesis.hxx" + +#include + +/*! + * \brief A base of reversible 1D hypotheses + */ +class STDMESHERS_EXPORT StdMeshers_Reversible1D : public SMESH_Hypothesis +{ +public: + StdMeshers_Reversible1D(int hypId, int studyId, SMESH_Gen* gen); + + void SetReversedEdges( const std::vector& ids); + + void SetObjectEntry( const char* entry ) { _objEntry = entry; } + + const char* GetObjectEntry() { return _objEntry.c_str(); } + + const std::vector& GetReversedEdges() const { return _edgeIDs; } + + virtual std::ostream & SaveTo(std::ostream & save); + virtual std::istream & LoadFrom(std::istream & load); + +protected: + std::vector _edgeIDs; + std::string _objEntry; +}; + +#endif diff --git a/src/StdMeshers/StdMeshers_UseExisting_1D2D.cxx b/src/StdMeshers/StdMeshers_UseExisting_1D2D.cxx index ee2eb57f6..53b58035c 100644 --- a/src/StdMeshers/StdMeshers_UseExisting_1D2D.cxx +++ b/src/StdMeshers/StdMeshers_UseExisting_1D2D.cxx @@ -25,6 +25,9 @@ // #include "StdMeshers_UseExisting_1D2D.hxx" +#include "SMESH_Mesh.hxx" +#include "SMESH_subMesh.hxx" + //======================================================================= //function : StdMeshers_UseExisting_1D //purpose : @@ -56,10 +59,11 @@ bool StdMeshers_UseExisting_1D::CheckHypothesis(SMESH_Mesh& , //purpose : //======================================================================= -bool StdMeshers_UseExisting_1D::Compute(SMESH_Mesh&, const TopoDS_Shape&) +bool StdMeshers_UseExisting_1D::Compute(SMESH_Mesh& mesh, const TopoDS_Shape& edge) { // This algorithm exists to allow mesh generation by mesh // edition functions in TUI mode + mesh.GetSubMesh( edge )->SetIsAlwaysComputed( true ); return true; } @@ -110,10 +114,11 @@ bool StdMeshers_UseExisting_2D::CheckHypothesis(SMESH_Mesh& , //purpose : //======================================================================= -bool StdMeshers_UseExisting_2D::Compute(SMESH_Mesh&, const TopoDS_Shape&) +bool StdMeshers_UseExisting_2D::Compute(SMESH_Mesh& mesh, const TopoDS_Shape& face) { // This algorithm exists to allow mesh generation by mesh edition // functions in TUI mode + mesh.GetSubMesh( face )->SetIsAlwaysComputed( true ); return true; } diff --git a/src/StdMeshers/StdMeshers_ViscousLayers.cxx b/src/StdMeshers/StdMeshers_ViscousLayers.cxx index e38c311ad..c2a3c954b 100644 --- a/src/StdMeshers/StdMeshers_ViscousLayers.cxx +++ b/src/StdMeshers/StdMeshers_ViscousLayers.cxx @@ -35,6 +35,7 @@ #include "SMESH_ControlsDef.hxx" #include "SMESH_Gen.hxx" #include "SMESH_Group.hxx" +#include "SMESH_HypoFilter.hxx" #include "SMESH_Mesh.hxx" #include "SMESH_MeshAlgos.hxx" #include "SMESH_MesherHelper.hxx" @@ -63,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -382,8 +384,10 @@ namespace VISCOUS_3D { TopoDS_Shape _solid; const StdMeshers_ViscousLayers* _hyp; + TopoDS_Shape _hypShape; _MeshOfSolid* _proxyMesh; set _reversedFaceIds; + set _ignoreFaceIds; double _stepSize, _stepSizeCoeff; const SMDS_MeshNode* _stepSizeNodes[2]; @@ -393,10 +397,10 @@ namespace VISCOUS_3D // iteration over the map is 5 time longer than over the vector vector< _LayerEdge* > _edges; - // key: an id of shape (EDGE or VERTEX) shared by a FACE with - // layers and a FACE w/o layers + // key: an id of shape (EDGE or VERTEX) shared by a FACE with + // layers and a FACE w/o layers // value: the shape (FACE or EDGE) to shrink mesh on. - // _LayerEdge's basing on nodes on key shape are inflated along the value shape + // _LayerEdge's basing on nodes on key shape are inflated along the value shape map< TGeomID, TopoDS_Shape > _shrinkShape2Shape; // FACE's WOL, srink on which is forbiden due to algo on the adjacent SOLID @@ -414,7 +418,9 @@ namespace VISCOUS_3D _SolidData(const TopoDS_Shape& s=TopoDS_Shape(), const StdMeshers_ViscousLayers* h=0, - _MeshOfSolid* m=0) :_solid(s), _hyp(h), _proxyMesh(m) {} + const TopoDS_Shape& hs=TopoDS_Shape(), + _MeshOfSolid* m=0) + :_solid(s), _hyp(h), _hypShape(hs), _proxyMesh(m) {} ~_SolidData(); Handle(Geom_Curve) CurveForSmooth( const TopoDS_Edge& E, @@ -517,7 +523,6 @@ namespace VISCOUS_3D SMESH_ComputeErrorPtr _error; vector< _SolidData > _sdVec; - set _ignoreShapeIds; int _tmpFaceID; }; //-------------------------------------------------------------------------------- @@ -617,7 +622,7 @@ namespace VISCOUS_3D // StdMeshers_ViscousLayers::StdMeshers_ViscousLayers(int hypId, int studyId, SMESH_Gen* gen) :SMESH_Hypothesis(hypId, studyId, gen), - _isToIgnoreShapes(18), _nbLayers(1), _thickness(1), _stretchFactor(1) + _isToIgnoreShapes(1), _nbLayers(1), _thickness(1), _stretchFactor(1) { _name = StdMeshers_ViscousLayers::GetHypType(); _param_algo_dim = -3; // auxiliary hyp used by 3D algos @@ -686,7 +691,7 @@ std::ostream & StdMeshers_ViscousLayers::SaveTo(std::ostream & save) << " " << _thickness << " " << _stretchFactor << " " << _shapeIds.size(); - for ( unsigned i = 0; i < _shapeIds.size(); ++i ) + for ( size_t i = 0; i < _shapeIds.size(); ++i ) save << " " << _shapeIds[i]; save << " " << !_isToIgnoreShapes; // negate to keep the behavior in old studies. return save; @@ -788,7 +793,7 @@ namespace // get average dir of edges going fromV gp_XYZ edgeDir; //if ( edges.size() > 1 ) - for ( unsigned i = 0; i < edges.size(); ++i ) + for ( size_t i = 0; i < edges.size(); ++i ) { edgeDir = getEdgeDir( edges[i], fromV ); double size2 = edgeDir.SquareModulus(); @@ -909,9 +914,9 @@ namespace py = new ofstream(fname); *py << "import SMESH" << endl << "from salome.smesh import smeshBuilder" << endl - << "smesh = smeshBuilder.New(salome.myStudy)" << endl + << "smesh = smeshBuilder.New(salome.myStudy)" << endl << "meshSO = smesh.GetCurrentStudy().FindObjectID('0:1:2:3')" << endl - << "mesh = smesh.Mesh( meshSO.GetObject() )"<GetGen(); + SMESH_HypoFilter filter; for ( int i = 1; i <= allSolids.Extent(); ++i ) { // find StdMeshers_ViscousLayers hyp assigned to the i-th solid @@ -1122,10 +1128,14 @@ bool _ViscousBuilder::findSolidsWithLayers() viscHyp = dynamic_cast( *hyp ); if ( viscHyp ) { + TopoDS_Shape hypShape; + filter.Init( filter.Is( viscHyp )); + _mesh->GetHypothesis( allSolids(i), filter, true, &hypShape ); + _MeshOfSolid* proxyMesh = _ViscousListener::GetSolidMesh( _mesh, allSolids(i), /*toCreate=*/true); - _sdVec.push_back( _SolidData( allSolids(i), viscHyp, proxyMesh )); + _sdVec.push_back( _SolidData( allSolids(i), viscHyp, hypShape, proxyMesh )); _sdVec.back()._index = getMeshDS()->ShapeToIndex( allSolids(i)); } } @@ -1144,44 +1154,69 @@ bool _ViscousBuilder::findSolidsWithLayers() bool _ViscousBuilder::findFacesWithLayers() { + SMESH_MesherHelper helper( *_mesh ); + TopExp_Explorer exp; + TopTools_IndexedMapOfShape solids; + // collect all faces to ignore defined by hyp - vector ignoreFaces; - for ( unsigned i = 0; i < _sdVec.size(); ++i ) + for ( size_t i = 0; i < _sdVec.size(); ++i ) { + solids.Add( _sdVec[i]._solid ); + vector ids = _sdVec[i]._hyp->GetBndShapes(); - for ( unsigned i = 0; i < ids.size(); ++i ) + if ( _sdVec[i]._hyp->IsToIgnoreShapes() ) // FACEs to ignore are given { - const TopoDS_Shape& s = getMeshDS()->IndexToShape( ids[i] ); - if ( !s.IsNull() && s.ShapeType() == TopAbs_FACE ) + for ( size_t ii = 0; ii < ids.size(); ++ii ) { - _ignoreShapeIds.insert( ids[i] ); - ignoreFaces.push_back( s ); + const TopoDS_Shape& s = getMeshDS()->IndexToShape( ids[ii] ); + if ( !s.IsNull() && s.ShapeType() == TopAbs_FACE ) + _sdVec[i]._ignoreFaceIds.insert( ids[ii] ); + } + } + else // FACEs with layers are given + { + exp.Init( _sdVec[i]._solid, TopAbs_FACE ); + for ( ; exp.More(); exp.Next() ) + { + TGeomID faceInd = getMeshDS()->ShapeToIndex( exp.Current() ); + if ( find( ids.begin(), ids.end(), faceInd ) == ids.end() ) + _sdVec[i]._ignoreFaceIds.insert( faceInd ); } } - } - // ignore internal faces - SMESH_MesherHelper helper( *_mesh ); - TopExp_Explorer exp; - for ( unsigned i = 0; i < _sdVec.size(); ++i ) - { - exp.Init( _sdVec[i]._solid.Oriented( TopAbs_FORWARD ), TopAbs_FACE ); - for ( ; exp.More(); exp.Next() ) + // ignore internal FACEs if inlets and outlets are specified { - TGeomID faceInd = getMeshDS()->ShapeToIndex( exp.Current() ); - if ( helper.NbAncestors( exp.Current(), *_mesh, TopAbs_SOLID ) > 1 ) - { - _ignoreShapeIds.insert( faceInd ); - ignoreFaces.push_back( exp.Current() ); - if ( helper.IsReversedSubMesh( TopoDS::Face( exp.Current() ))) + TopTools_IndexedDataMapOfShapeListOfShape solidsOfFace; + if ( _sdVec[i]._hyp->IsToIgnoreShapes() ) + TopExp::MapShapesAndAncestors( _sdVec[i]._hypShape, + TopAbs_FACE, TopAbs_SOLID, solidsOfFace); + + exp.Init( _sdVec[i]._solid.Oriented( TopAbs_FORWARD ), TopAbs_FACE ); + for ( ; exp.More(); exp.Next() ) + { + const TopoDS_Face& face = TopoDS::Face( exp.Current() ); + if ( helper.NbAncestors( face, *_mesh, TopAbs_SOLID ) < 2 ) + continue; + + const TGeomID faceInd = getMeshDS()->ShapeToIndex( face ); + if ( _sdVec[i]._hyp->IsToIgnoreShapes() ) + { + int nbSolids = solidsOfFace.FindFromKey( face ).Extent(); + if ( nbSolids > 1 ) + _sdVec[i]._ignoreFaceIds.insert( faceInd ); + } + + if ( helper.IsReversedSubMesh( face )) + { _sdVec[i]._reversedFaceIds.insert( faceInd ); + } } } } // Find faces to shrink mesh on (solution 2 in issue 0020832); TopTools_IndexedMapOfShape shapes; - for ( unsigned i = 0; i < _sdVec.size(); ++i ) + for ( size_t i = 0; i < _sdVec.size(); ++i ) { shapes.Clear(); TopExp::MapShapes(_sdVec[i]._solid, TopAbs_EDGE, shapes); @@ -1201,18 +1236,35 @@ bool _ViscousBuilder::findFacesWithLayers() // check presence of layers on them int ignore[2]; for ( int j = 0; j < 2; ++j ) - ignore[j] = _ignoreShapeIds.count ( getMeshDS()->ShapeToIndex( FF[j] )); - if ( ignore[0] == ignore[1] ) continue; // nothing interesting + ignore[j] = _sdVec[i]._ignoreFaceIds.count ( getMeshDS()->ShapeToIndex( FF[j] )); + if ( ignore[0] == ignore[1] ) + continue; // nothing interesting TopoDS_Shape fWOL = FF[ ignore[0] ? 0 : 1 ]; + // check presence of layers on fWOL within an adjacent SOLID + PShapeIteratorPtr sIt = helper.GetAncestors( fWOL, *_mesh, TopAbs_SOLID ); + while ( const TopoDS_Shape* solid = sIt->next() ) + if ( !solid->IsSame( _sdVec[i]._solid )) + { + int iSolid = solids.FindIndex( *solid ); + int iFace = getMeshDS()->ShapeToIndex( fWOL ); + if ( iSolid > 0 && !_sdVec[ iSolid-1 ]._ignoreFaceIds.count( iFace )) + { + _sdVec[i]._noShrinkFaces.insert( iFace ); + fWOL.Nullify(); + } + } // add edge to maps - TGeomID edgeInd = getMeshDS()->ShapeToIndex( edge ); - _sdVec[i]._shrinkShape2Shape.insert( make_pair( edgeInd, fWOL )); + if ( !fWOL.IsNull()) + { + TGeomID edgeInd = getMeshDS()->ShapeToIndex( edge ); + _sdVec[i]._shrinkShape2Shape.insert( make_pair( edgeInd, fWOL )); + } } } // Exclude from _shrinkShape2Shape FACE's that can't be shrinked since // the algo of the SOLID sharing the FACE does not support it set< string > notSupportAlgos; notSupportAlgos.insert("Hexa_3D"); - for ( unsigned i = 0; i < _sdVec.size(); ++i ) + for ( size_t i = 0; i < _sdVec.size(); ++i ) { TopTools_MapOfShape noShrinkVertices; map< TGeomID, TopoDS_Shape >::iterator e2f = _sdVec[i]._shrinkShape2Shape.begin(); @@ -1229,7 +1281,7 @@ bool _ViscousBuilder::findFacesWithLayers() SMESH_Algo* algo = _mesh->GetGen()->GetAlgo( *_mesh, *solid ); if ( !algo || !notSupportAlgos.count( algo->GetName() )) continue; notShrinkFace = true; - for ( unsigned j = 0; j < _sdVec.size(); ++j ) + for ( size_t j = 0; j < _sdVec.size(); ++j ) { if ( _sdVec[j]._solid.IsSame( *solid ) ) if ( _sdVec[j]._shrinkShape2Shape.count( edgeID )) @@ -1262,10 +1314,10 @@ bool _ViscousBuilder::findFacesWithLayers() } } } - + // Find the SHAPE along which to inflate _LayerEdge based on VERTEX - for ( unsigned i = 0; i < _sdVec.size(); ++i ) + for ( size_t i = 0; i < _sdVec.size(); ++i ) { shapes.Clear(); TopExp::MapShapes(_sdVec[i]._solid, TopAbs_VERTEX, shapes); @@ -1279,11 +1331,12 @@ bool _ViscousBuilder::findFacesWithLayers() while ( fIt->more()) { const TopoDS_Shape* f = fIt->next(); - const int fID = getMeshDS()->ShapeToIndex( *f ); if ( helper.IsSubShape( *f, _sdVec[i]._solid ) ) { totalNbFaces++; - if ( _ignoreShapeIds.count ( fID ) && ! _sdVec[i]._noShrinkFaces.count( fID )) + const int fID = getMeshDS()->ShapeToIndex( *f ); + if ( _sdVec[i]._ignoreFaceIds.count ( fID ) && + !_sdVec[i]._noShrinkFaces.count( fID )) facesWOL.push_back( *f ); } } @@ -1293,42 +1346,42 @@ bool _ViscousBuilder::findFacesWithLayers() switch ( facesWOL.size() ) { case 1: + { + helper.SetSubShape( facesWOL[0] ); + if ( helper.IsRealSeam( vInd )) // inflate along a seam edge? { - helper.SetSubShape( facesWOL[0] ); - if ( helper.IsRealSeam( vInd )) // inflate along a seam edge? + TopoDS_Shape seamEdge; + PShapeIteratorPtr eIt = helper.GetAncestors(vertex, *_mesh, TopAbs_EDGE); + while ( eIt->more() && seamEdge.IsNull() ) { - TopoDS_Shape seamEdge; - PShapeIteratorPtr eIt = helper.GetAncestors(vertex, *_mesh, TopAbs_EDGE); - while ( eIt->more() && seamEdge.IsNull() ) - { - const TopoDS_Shape* e = eIt->next(); - if ( helper.IsRealSeam( *e ) ) - seamEdge = *e; - } - if ( !seamEdge.IsNull() ) - { - _sdVec[i]._shrinkShape2Shape.insert( make_pair( vInd, seamEdge )); - break; - } + const TopoDS_Shape* e = eIt->next(); + if ( helper.IsRealSeam( *e ) ) + seamEdge = *e; + } + if ( !seamEdge.IsNull() ) + { + _sdVec[i]._shrinkShape2Shape.insert( make_pair( vInd, seamEdge )); + break; } - _sdVec[i]._shrinkShape2Shape.insert( make_pair( vInd, facesWOL[0] )); - break; } + _sdVec[i]._shrinkShape2Shape.insert( make_pair( vInd, facesWOL[0] )); + break; + } case 2: + { + // find an edge shared by 2 faces + PShapeIteratorPtr eIt = helper.GetAncestors(vertex, *_mesh, TopAbs_EDGE); + while ( eIt->more()) { - // find an edge shared by 2 faces - PShapeIteratorPtr eIt = helper.GetAncestors(vertex, *_mesh, TopAbs_EDGE); - while ( eIt->more()) + const TopoDS_Shape* e = eIt->next(); + if ( helper.IsSubShape( *e, facesWOL[0]) && + helper.IsSubShape( *e, facesWOL[1])) { - const TopoDS_Shape* e = eIt->next(); - if ( helper.IsSubShape( *e, facesWOL[0]) && - helper.IsSubShape( *e, facesWOL[1])) - { - _sdVec[i]._shrinkShape2Shape.insert( make_pair( vInd, *e )); break; - } + _sdVec[i]._shrinkShape2Shape.insert( make_pair( vInd, *e )); break; } - break; } + break; + } default: return error("Not yet supported case", _sdVec[i]._index); } @@ -1351,10 +1404,10 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) subIds = data._noShrinkFaces; TopExp_Explorer exp( data._solid, TopAbs_FACE ); for ( ; exp.More(); exp.Next() ) - if ( ! _ignoreShapeIds.count( getMeshDS()->ShapeToIndex( exp.Current() ))) { SMESH_subMesh* fSubM = _mesh->GetSubMesh( exp.Current() ); - faceIds.insert( fSubM->GetId() ); + if ( ! data._ignoreFaceIds.count( getMeshDS()->ShapeToIndex( exp.Current() ))) + faceIds.insert( fSubM->GetId() ); SMESH_subMeshIteratorPtr subIt = fSubM->getDependsOnIterator(/*includeSelf=*/true, /*complexShapeFirst=*/false); while ( subIt->more() ) @@ -1368,7 +1421,7 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) for (; s2s != data._shrinkShape2Shape.end(); ++s2s ) { TGeomID shapeInd = s2s->first; - for ( unsigned i = 0; i < _sdVec.size(); ++i ) + for ( size_t i = 0; i < _sdVec.size(); ++i ) { if ( _sdVec[i]._index == data._index ) continue; map< TGeomID, TopoDS_Shape >::iterator s2s2 = _sdVec[i]._shrinkShape2Shape.find( shapeInd ); @@ -1475,7 +1528,7 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) // Set target nodes into _Simplex and _2NearEdges TNode2Edge::iterator n2e; - for ( unsigned i = 0; i < data._edges.size(); ++i ) + for ( size_t i = 0; i < data._edges.size(); ++i ) { if ( data._edges[i]->IsOnEdge()) for ( int j = 0; j < 2; ++j ) @@ -1489,7 +1542,7 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) data._edges[i]->_2neibors->_edges[j] = n2e->second; } else - for ( unsigned j = 0; j < data._edges[i]->_simplices.size(); ++j ) + for ( size_t j = 0; j < data._edges[i]->_simplices.size(); ++j ) { _Simplex& s = data._edges[i]->_simplices[j]; s._nNext = data._n2eMap[ s._nNext ]->_nodes.back(); @@ -1573,7 +1626,7 @@ bool _ViscousBuilder::sortEdges( _SolidData& data, SMESH_MesherHelper helper( *_mesh ); bool ok = true; - for ( unsigned iS = 0; iS < edgesByGeom.size(); ++iS ) + for ( size_t iS = 0; iS < edgesByGeom.size(); ++iS ) { vector<_LayerEdge*>& eS = edgesByGeom[iS]; if ( eS.empty() ) continue; @@ -1617,7 +1670,7 @@ bool _ViscousBuilder::sortEdges( _SolidData& data, if ( eE.empty() ) continue; if ( eE[0]->_sWOL.IsNull() ) { - for ( unsigned i = 0; i < eE.size() && !needSmooth; ++i ) + for ( size_t i = 0; i < eE.size() && !needSmooth; ++i ) needSmooth = ( eE[i]->_cosin > 0.1 ); } else @@ -1625,7 +1678,7 @@ bool _ViscousBuilder::sortEdges( _SolidData& data, 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 ( unsigned i = 0; i < eE.size() && !needSmooth; ++i ) + 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 ); @@ -1664,7 +1717,7 @@ bool _ViscousBuilder::sortEdges( _SolidData& data, } // then the rest _LayerEdge's - for ( unsigned iS = 0; iS < edgesByGeom.size(); ++iS ) + for ( size_t iS = 0; iS < edgesByGeom.size(); ++iS ) { vector<_LayerEdge*>& eVec = edgesByGeom[iS]; data._edges.insert( data._edges.end(), eVec.begin(), eVec.end() ); @@ -1870,9 +1923,9 @@ bool _ViscousBuilder::setEdgeData(_LayerEdge& edge, if ( posType == SMDS_TOP_FACE ) { - getSimplices( node, edge._simplices, _ignoreShapeIds, &data ); + getSimplices( node, edge._simplices, data._ignoreFaceIds, &data ); double avgNormProj = 0, avgLen = 0; - for ( unsigned i = 0; i < edge._simplices.size(); ++i ) + for ( size_t i = 0; i < edge._simplices.size(); ++i ) { gp_XYZ vec = edge._pos.back() - SMESH_TNodeXYZ( edge._simplices[i]._nPrev ); avgNormProj += edge._normal * vec; @@ -2103,7 +2156,7 @@ void _ViscousBuilder::getSimplices( const SMDS_MeshNode* node, void _ViscousBuilder::makeGroupOfLE() { #ifdef _DEBUG_ - for ( unsigned i = 0 ; i < _sdVec.size(); ++i ) + for ( size_t i = 0 ; i < _sdVec.size(); ++i ) { if ( _sdVec[i]._edges.empty() ) continue; // string name = SMESH_Comment("_LayerEdge's_") << i; @@ -2113,10 +2166,10 @@ void _ViscousBuilder::makeGroupOfLE() // SMESHDS_Mesh* mDS = _mesh->GetMeshDS(); dumpFunction( SMESH_Comment("make_LayerEdge_") << i ); - for ( unsigned j = 0 ; j < _sdVec[i]._edges.size(); ++j ) + for ( size_t j = 0 ; j < _sdVec[i]._edges.size(); ++j ) { _LayerEdge* le = _sdVec[i]._edges[j]; - for ( unsigned iN = 1; iN < le->_nodes.size(); ++iN ) + for ( size_t iN = 1; iN < le->_nodes.size(); ++iN ) dumpCmd(SMESH_Comment("mesh.AddEdge([ ") <_nodes[iN-1]->GetID() << ", " << le->_nodes[iN]->GetID() <<"])"); //gDS->SMDSGroup().Add( mDS->AddEdge( le->_nodes[iN-1], le->_nodes[iN])); @@ -2124,7 +2177,7 @@ void _ViscousBuilder::makeGroupOfLE() dumpFunctionEnd(); dumpFunction( SMESH_Comment("makeNormals") << i ); - for ( unsigned j = 0 ; j < _sdVec[i]._edges.size(); ++j ) + for ( size_t j = 0 ; j < _sdVec[i]._edges.size(); ++j ) { _LayerEdge& edge = *_sdVec[i]._edges[j]; SMESH_TNodeXYZ nXYZ( edge._nodes[0] ); @@ -2178,7 +2231,7 @@ bool _ViscousBuilder::inflate(_SolidData& data) auto_ptr searcher ( SMESH_MeshAlgos::GetElementSearcher( *getMeshDS(), data._proxyMesh->GetFaces( data._solid )) ); - for ( unsigned i = 0; i < data._edges.size(); ++i ) + for ( size_t i = 0; i < data._edges.size(); ++i ) { if ( data._edges[i]->IsOnEdge() ) continue; data._edges[i]->FindIntersection( *searcher, intersecDist, data._epsilon ); @@ -2213,7 +2266,7 @@ bool _ViscousBuilder::inflate(_SolidData& data) // Elongate _LayerEdge's dumpFunction(SMESH_Comment("inflate")<SetNewLength( curThick, helper ); } @@ -2229,7 +2282,7 @@ bool _ViscousBuilder::inflate(_SolidData& data) if ( nbSteps > 0 ) { dumpFunction(SMESH_Comment("invalidate")<InvalidateStep( nbSteps+1 ); } @@ -2241,7 +2294,7 @@ bool _ViscousBuilder::inflate(_SolidData& data) // Evaluate achieved thickness avgThick = 0; - for ( unsigned i = 0; i < data._edges.size(); ++i ) + for ( size_t i = 0; i < data._edges.size(); ++i ) avgThick += data._edges[i]->_len; avgThick /= data._edges.size(); #ifdef __myDEBUG @@ -2289,7 +2342,7 @@ bool _ViscousBuilder::smoothAndCheck(_SolidData& data, TopoDS_Face F; int iBeg, iEnd = 0; - for ( unsigned iS = 0; iS < data._endEdgeToSmooth.size(); ++iS ) + for ( size_t iS = 0; iS < data._endEdgeToSmooth.size(); ++iS ) { iBeg = iEnd; iEnd = data._endEdgeToSmooth[ iS ]; @@ -2355,7 +2408,7 @@ bool _ViscousBuilder::smoothAndCheck(_SolidData& data, { _LayerEdge* edge = data._edges[i]; SMESH_TNodeXYZ tgtXYZ( edge->_nodes.back() ); - for ( unsigned j = 0; j < edge->_simplices.size(); ++j ) + for ( size_t j = 0; j < edge->_simplices.size(); ++j ) if ( !edge->_simplices[j].IsForward( edge->_nodes[0], &tgtXYZ )) { cout << "Bad simplex ( " << edge->_nodes[0]->GetID()<< " "<< tgtXYZ._node->GetID() @@ -2384,7 +2437,7 @@ bool _ViscousBuilder::smoothAndCheck(_SolidData& data, const SMDS_MeshElement* closestFace = 0; int iLE = 0; #endif - for ( unsigned i = 0; i < data._edges.size(); ++i ) + for ( size_t i = 0; i < data._edges.size(); ++i ) { if ( data._edges[i]->FindIntersection( *searcher, dist, data._epsilon, &intFace )) return false; @@ -2695,7 +2748,7 @@ 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; @@ -2712,7 +2765,7 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, } // look for a _LayerEdge containg tgt2 // _LayerEdge* neiborEdge = 0; -// unsigned di = 0; // check _edges[i+di] and _edges[i-di] +// size_t di = 0; // check _edges[i+di] and _edges[i-di] // while ( !neiborEdge && ++di <= data._edges.size() ) // { // if ( i+di < data._edges.size() && data._edges[i+di]->_nodes.back() == tgt2 ) @@ -2751,7 +2804,7 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, TLEdge2LEdgeSet edge2CloseEdge; const double eps = data._epsilon * data._epsilon; - for ( unsigned i = 0; i < data._edges.size(); ++i ) + for ( size_t i = 0; i < data._edges.size(); ++i ) { _LayerEdge* edge = data._edges[i]; if ( !edge->IsOnEdge() || !edge->_sWOL.IsNull() ) continue; @@ -2922,7 +2975,7 @@ bool _ViscousBuilder::updateNormals( _SolidData& data, // 2) Check absence of intersections // TODO? - for ( unsigned i = 0 ; i < tmpFaces.size(); ++i ) + for ( size_t i = 0 ; i < tmpFaces.size(); ++i ) delete tmpFaces[i]; return true; @@ -2948,7 +3001,7 @@ bool _LayerEdge::FindIntersection( SMESH_ElementSearcher& searcher, bool segmentIntersected = false; distance = Precision::Infinite(); int iFace = -1; // intersected face - for ( unsigned j = 0 ; j < suspectFaces.size() && !segmentIntersected; ++j ) + for ( size_t j = 0 ; j < suspectFaces.size() && !segmentIntersected; ++j ) { const SMDS_MeshElement* face = suspectFaces[j]; if ( face->GetNodeIndex( _nodes.back() ) >= 0 || @@ -3236,7 +3289,7 @@ bool _LayerEdge::Smooth(int& badNb) // compute new position for the last _pos gp_XYZ newPos (0,0,0); - for ( unsigned i = 0; i < _simplices.size(); ++i ) + for ( size_t i = 0; i < _simplices.size(); ++i ) newPos += SMESH_TNodeXYZ( _simplices[i]._nPrev ); newPos /= _simplices.size(); @@ -3259,11 +3312,11 @@ bool _LayerEdge::Smooth(int& badNb) // count quality metrics (orientation) of tetras around _tgtNode int nbOkBefore = 0; SMESH_TNodeXYZ tgtXYZ( _nodes.back() ); - for ( unsigned i = 0; i < _simplices.size(); ++i ) + for ( size_t i = 0; i < _simplices.size(); ++i ) nbOkBefore += _simplices[i].IsForward( _nodes[0], &tgtXYZ ); int nbOkAfter = 0; - for ( unsigned i = 0; i < _simplices.size(); ++i ) + for ( size_t i = 0; i < _simplices.size(); ++i ) nbOkAfter += _simplices[i].IsForward( _nodes[0], &newPos ); if ( nbOkAfter < nbOkBefore ) @@ -3389,14 +3442,14 @@ bool _ViscousBuilder::refine(_SolidData& data) gp_XY uv; bool isOnEdge; - for ( unsigned i = 0; i < data._edges.size(); ++i ) + for ( size_t i = 0; i < data._edges.size(); ++i ) { _LayerEdge& edge = *data._edges[i]; // get accumulated length of segments vector< double > segLen( edge._pos.size() ); segLen[0] = 0.0; - for ( unsigned j = 1; j < edge._pos.size(); ++j ) + 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 @@ -3444,8 +3497,8 @@ bool _ViscousBuilder::refine(_SolidData& data) // create intermediate nodes double hSum = 0, hi = h0/f; - unsigned iSeg = 1; - for ( unsigned iStep = 1; iStep < edge._nodes.size(); ++iStep ) + size_t iSeg = 1; + for ( size_t iStep = 1; iStep < edge._nodes.size(); ++iStep ) { // compute an intermediate position hi *= f; @@ -3512,7 +3565,7 @@ bool _ViscousBuilder::refine(_SolidData& data) if ( !getMeshDS()->IsEmbeddedMode() ) // Log node movement - for ( unsigned i = 0; i < data._edges.size(); ++i ) + for ( size_t i = 0; i < data._edges.size(); ++i ) { _LayerEdge& edge = *data._edges[i]; SMESH_TNodeXYZ p ( edge._nodes.back() ); @@ -3526,7 +3579,7 @@ bool _ViscousBuilder::refine(_SolidData& data) TopExp_Explorer exp( data._solid, TopAbs_FACE ); for ( ; exp.More(); exp.Next() ) { - if ( _ignoreShapeIds.count( getMeshDS()->ShapeToIndex( exp.Current() ))) + if ( data._ignoreFaceIds.count( getMeshDS()->ShapeToIndex( exp.Current() ))) continue; SMESHDS_SubMesh* fSubM = getMeshDS()->MeshElements( exp.Current() ); SMDS_ElemIteratorPtr fIt = fSubM->GetElements(); @@ -3577,7 +3630,7 @@ bool _ViscousBuilder::shrink() // make map of (ids of FACEs to shrink mesh on) to (_SolidData containing _LayerEdge's // inflated along FACE or EDGE) map< TGeomID, _SolidData* > f2sdMap; - for ( unsigned i = 0 ; i < _sdVec.size(); ++i ) + for ( size_t i = 0 ; i < _sdVec.size(); ++i ) { _SolidData& data = _sdVec[i]; TopTools_MapOfShape FFMap; @@ -3684,7 +3737,7 @@ bool _ViscousBuilder::shrink() // Replace source nodes by target nodes in mesh faces to shrink const SMDS_MeshNode* nodes[20]; - for ( unsigned i = 0; i < lEdges.size(); ++i ) + for ( size_t i = 0; i < lEdges.size(); ++i ) { _LayerEdge& edge = *lEdges[i]; const SMDS_MeshNode* srcNode = edge._nodes[0]; @@ -3712,7 +3765,7 @@ bool _ViscousBuilder::shrink() vector< _SmoothNode > nodesToSmooth( smoothNodes.size() ); { const bool sortSimplices = isConcaveFace; - for ( unsigned i = 0; i < smoothNodes.size(); ++i ) + for ( size_t i = 0; i < smoothNodes.size(); ++i ) { const SMDS_MeshNode* n = smoothNodes[i]; nodesToSmooth[ i ]._node = n; @@ -3728,7 +3781,7 @@ bool _ViscousBuilder::shrink() // Find EDGE's to shrink and set simpices to LayerEdge's set< _Shrinker1D* > eShri1D; { - for ( unsigned i = 0; i < lEdges.size(); ++i ) + for ( size_t i = 0; i < lEdges.size(); ++i ) { _LayerEdge* edge = lEdges[i]; if ( edge->_sWOL.ShapeType() == TopAbs_EDGE ) @@ -3801,7 +3854,7 @@ bool _ViscousBuilder::shrink() int oldBadNb = badNb; badNb = 0; moved = false; - for ( unsigned i = 0; i < nodesToSmooth.size(); ++i ) + for ( size_t i = 0; i < nodesToSmooth.size(); ++i ) { moved |= nodesToSmooth[i].Smooth( badNb,surface,helper,refSign, smoothType, /*set3D=*/isConcaveFace); @@ -3870,7 +3923,7 @@ bool _ViscousBuilder::shrink() case 3: smoothType = _SmoothNode::ANGULAR; break; } dumpFunction(SMESH_Comment("shrinkFace")<first<<"_st"<<++smooStep); // debug - for ( unsigned i = 0; i < nodesToSmooth.size(); ++i ) + for ( size_t i = 0; i < nodesToSmooth.size(); ++i ) { nodesToSmooth[i].Smooth( badNb,surface,helper,refSign, smoothType,/*set3D=*/st==1 ); @@ -3883,7 +3936,7 @@ bool _ViscousBuilder::shrink() if ( !getMeshDS()->IsEmbeddedMode() ) // Log node movement - for ( unsigned i = 0; i < nodesToSmooth.size(); ++i ) + for ( size_t i = 0; i < nodesToSmooth.size(); ++i ) { SMESH_TNodeXYZ p ( nodesToSmooth[i]._node ); getMeshDS()->MoveNode( nodesToSmooth[i]._node, p.X(), p.Y(), p.Z() ); @@ -3937,7 +3990,7 @@ bool _ViscousBuilder::prepareEdgeToShrink( _LayerEdge& edge, // if ( faceSubMesh->Contains( f )) // faces.push_back( f ); // } - // for ( unsigned i = 0; i < faces.size(); ++i ) + // for ( size_t i = 0; i < faces.size(); ++i ) // { // const int nbNodes = faces[i]->NbCornerNodes(); // for ( int j = 0; j < nbNodes; ++j ) @@ -4397,11 +4450,11 @@ bool _SmoothNode::Smooth(int& badNb, // count quality metrics (orientation) of triangles around the node int nbOkBefore = 0; gp_XY tgtUV = helper.GetNodeUV( face, _node ); - for ( unsigned i = 0; i < _simplices.size(); ++i ) + for ( size_t i = 0; i < _simplices.size(); ++i ) nbOkBefore += _simplices[i].IsForward( tgtUV, _node, face, helper, refSign ); int nbOkAfter = 0; - for ( unsigned i = 0; i < _simplices.size(); ++i ) + for ( size_t i = 0; i < _simplices.size(); ++i ) nbOkAfter += _simplices[i].IsForward( newPos, _node, face, helper, refSign ); if ( nbOkAfter < nbOkBefore ) @@ -4496,7 +4549,7 @@ gp_XY _SmoothNode::computeAngularPos(vector& uv, _SolidData::~_SolidData() { - for ( unsigned i = 0; i < _edges.size(); ++i ) + for ( size_t i = 0; i < _edges.size(); ++i ) { if ( _edges[i] && _edges[i]->_2neibors ) delete _edges[i]->_2neibors; @@ -4569,7 +4622,7 @@ void _Shrinker1D::AddEdge( const _LayerEdge* e, SMESH_MesherHelper& helper ) { // remove target node of the _LayerEdge from _nodes int nbFound = 0; - for ( unsigned i = 0; i < _nodes.size(); ++i ) + for ( size_t i = 0; i < _nodes.size(); ++i ) if ( !_nodes[i] || _nodes[i] == tgtNode0 || _nodes[i] == tgtNode1 ) _nodes[i] = 0, nbFound++; if ( nbFound == _nodes.size() ) @@ -4607,7 +4660,7 @@ void _Shrinker1D::Compute(bool set3D, SMESH_MesherHelper& helper) l = helper.GetNodeU( E, _edges[1]->_nodes.back(), _nodes.back() ); double totLen = GCPnts_AbscissaPoint::Length( aCurve, f, l ); - for ( unsigned i = 0; i < _nodes.size(); ++i ) + for ( size_t i = 0; i < _nodes.size(); ++i ) { if ( !_nodes[i] ) continue; double len = totLen * _normPar[i]; @@ -4629,7 +4682,7 @@ void _Shrinker1D::Compute(bool set3D, SMESH_MesherHelper& helper) if ( _edges[1] ) l = helper.GetNodeU( E, _edges[1]->_nodes.back(), _nodes.back() ); - for ( unsigned i = 0; i < _nodes.size(); ++i ) + for ( size_t i = 0; i < _nodes.size(); ++i ) { if ( !_nodes[i] ) continue; double u = f * ( 1-_normPar[i] ) + l * _normPar[i]; @@ -4648,7 +4701,7 @@ void _Shrinker1D::Compute(bool set3D, SMESH_MesherHelper& helper) void _Shrinker1D::RestoreParams() { if ( _done ) - for ( unsigned i = 0; i < _nodes.size(); ++i ) + for ( size_t i = 0; i < _nodes.size(); ++i ) { if ( !_nodes[i] ) continue; SMDS_EdgePosition* pos = static_cast( _nodes[i]->GetPosition() ); @@ -4701,7 +4754,7 @@ bool _ViscousBuilder::addBoundaryElements() { SMESH_MesherHelper helper( *_mesh ); - for ( unsigned i = 0; i < _sdVec.size(); ++i ) + for ( size_t i = 0; i < _sdVec.size(); ++i ) { _SolidData& data = _sdVec[i]; TopTools_IndexedMapOfShape geomEdges; @@ -4772,7 +4825,7 @@ bool _ViscousBuilder::addBoundaryElements() { const TopoDS_Shape* pF = fIt->next(); if ( helper.IsSubShape( *pF, data._solid) && - !_ignoreShapeIds.count( e2f->first )) + !data._ignoreFaceIds.count( e2f->first )) F = *pF; } } @@ -4788,7 +4841,7 @@ bool _ViscousBuilder::addBoundaryElements() // Make faces const int dj1 = reverse ? 0 : 1; const int dj2 = reverse ? 1 : 0; - for ( unsigned j = 1; j < ledges.size(); ++j ) + for ( size_t j = 1; j < ledges.size(); ++j ) { vector< const SMDS_MeshNode*>& nn1 = ledges[j-dj1]->_nodes; vector< const SMDS_MeshNode*>& nn2 = ledges[j-dj2]->_nodes; diff --git a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx index 937c79066..c2b58de8d 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx +++ b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -49,14 +50,14 @@ #include #include #include +#include #include #include #include +#include #include #include #include -#include -#include #define SPACING 6 #define MARGIN 11 @@ -162,7 +163,7 @@ namespace StdMeshersGUI myStepSpin->SetValue( myStep = 1. ); // 3) Coodrinates/Spacing group - QFrame* csFrame = new QFrame( this ); + QFrame* csFrame = new QFrame( this ); QVBoxLayout* scLay = new QVBoxLayout( csFrame ); scLay->setMargin( 0 ); scLay->setSpacing( SPACING ); @@ -610,7 +611,12 @@ QFrame* StdMeshersGUI_CartesianParamCreator::buildFrame() argGroupLayout->addWidget( myThreshold, row, 1 ); row++; - // 2) Grid definition + // 2) "Implement edges" + myAddEdges = new QCheckBox( tr("ADD_EDGES"), GroupC1 ); + argGroupLayout->addWidget( myAddEdges, row, 0, 1, 2 ); + row++; + + // 3) Grid definition QTabWidget* tabWdg = new QTabWidget( fr ); myAxisTabs[ 0 ] = new StdMeshersGUI::GridAxisTab( tabWdg, 0 ); myAxisTabs[ 1 ] = new StdMeshersGUI::GridAxisTab( tabWdg, 1 ); @@ -637,6 +643,8 @@ void StdMeshersGUI_CartesianParamCreator::retrieveParams() const else myThreshold->setText( varName ); + myAddEdges->setChecked( h->GetToAddEdges() ); + for ( int ax = 0; ax < 3; ++ax ) { if ( h->IsGridBySpacing( ax )) @@ -653,7 +661,8 @@ void StdMeshersGUI_CartesianParamCreator::retrieveParams() const } } if ( dlg() ) - dlg()->setMinimumSize( dlg()->minimumSizeHint().width(), dlg()->minimumSizeHint().height() ); + dlg()->setMinimumSize( dlg()->minimumSizeHint().width(), + dlg()->minimumSizeHint().height() ); } QString StdMeshersGUI_CartesianParamCreator::storeParams() const @@ -668,6 +677,7 @@ QString StdMeshersGUI_CartesianParamCreator::storeParams() const h->SetVarParameter( myThreshold->text().toLatin1().constData(), "SetSizeThreshold" ); h->SetSizeThreshold( myThreshold->text().toDouble() ); + h->SetToAddEdges( myAddEdges->isChecked() ); for ( int ax = 0; ax < 3; ++ax ) { diff --git a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h index 5a02d5ca1..1187bb90b 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h +++ b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h @@ -39,18 +39,19 @@ #include #include -class SMESHGUI_SpinBox; -class QLineEdit; +class QAbstractItemModel; class QButtonGroup; -class QTreeWidgetItem; -class QString; -class QWidget; -class QTreeWidget; +class QCheckBox; +class QLineEdit; class QListWidget; -class QStyleOptionViewItem; -class QModelIndex; -class QAbstractItemModel; class QListWidgetItem; +class QModelIndex; +class QString; +class QStyleOptionViewItem; +class QTreeWidget; +class QTreeWidgetItem; +class QWidget; +class SMESHGUI_SpinBox; namespace StdMeshersGUI { @@ -136,6 +137,7 @@ protected: private: QLineEdit* myName; SMESHGUI_SpinBox* myThreshold; + QCheckBox* myAddEdges; StdMeshersGUI::GridAxisTab* myAxisTabs[3]; }; diff --git a/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx b/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx index ca774d105..342f66fc4 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx +++ b/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx @@ -523,6 +523,23 @@ QString StdMeshersGUI_StdHypothesisCreator::storeParams() const h->SetObjectEntry( w->GetMainShapeEntry() ); } } + else if( hypType()=="GeometricProgression" ) + { + StdMeshers::StdMeshers_Geometric1D_var h = + StdMeshers::StdMeshers_Geometric1D::_narrow( hypothesis() ); + + StdMeshersGUI_SubShapeSelectorWdg* w = + widget< StdMeshersGUI_SubShapeSelectorWdg >( 2 ); + + h->SetVarParameter( params[0].text(), "SetStartLength" ); + h->SetStartLength( params[0].myValue.toDouble() ); + h->SetVarParameter( params[1].text(), "SetCommonRatio" ); + h->SetCommonRatio( params[1].myValue.toDouble() ); + if (w) { + h->SetReversedEdges( w->GetListOfIDs() ); + h->SetObjectEntry( w->GetMainShapeEntry() ); + } + } else if( hypType()=="FixedPoints1D" ) { StdMeshers::StdMeshers_FixedPoints1D_var h = @@ -704,9 +721,9 @@ QString StdMeshersGUI_StdHypothesisCreator::storeParams() const h->SetStretchFactor ( params[2].myValue.toDouble() ); if ( StdMeshersGUI_SubShapeSelectorWdg* idsWg = - widget< StdMeshersGUI_SubShapeSelectorWdg >( 3 )) + widget< StdMeshersGUI_SubShapeSelectorWdg >( 4 )) { - h->SetIgnoreFaces( idsWg->GetListOfIDs() ); + h->SetFaces( idsWg->GetListOfIDs(), params[3].myValue.toInt() ); } } else if( hypType()=="ViscousLayers2D" ) @@ -878,6 +895,41 @@ bool StdMeshersGUI_StdHypothesisCreator::stdParams( ListOfStdParams& p ) const customWidgets()->append ( aDirectionWidget ); } + else if( hypType()=="GeometricProgression" ) + { + StdMeshers::StdMeshers_Geometric1D_var h = + StdMeshers::StdMeshers_Geometric1D::_narrow( hyp ); + + item.myName = tr( "SMESH_START_LENGTH_PARAM" ); + if(!initVariableName( hyp, item, "SetStartLength" )) + item.myValue = h->GetStartLength(); + p.append( item ); + + customWidgets()->append (0); + + item.myName = tr( "SMESH_COMMON_RATIO" ); + if(!initVariableName( hyp, item, "SetCommonRatio" )) + item.myValue = h->GetCommonRatio(); + p.append( item ); + + customWidgets()->append (0); + + item.myName = tr( "SMESH_REVERSED_EDGES" ); + p.append( item ); + + StdMeshersGUI_SubShapeSelectorWdg* aDirectionWidget = + new StdMeshersGUI_SubShapeSelectorWdg(); + QString aGeomEntry = SMESHGUI_GenericHypothesisCreator::getShapeEntry(); + QString aMainEntry = SMESHGUI_GenericHypothesisCreator::getMainShapeEntry(); + if ( aGeomEntry == "" ) + aGeomEntry = h->GetObjectEntry(); + + aDirectionWidget->SetGeomShapeEntry( aGeomEntry ); + aDirectionWidget->SetMainShapeEntry( aMainEntry ); + aDirectionWidget->SetListOfIDs( h->GetReversedEdges() ); + aDirectionWidget->showPreview( true ); + customWidgets()->append ( aDirectionWidget ); + } else if( hypType()=="FixedPoints1D" ) { @@ -1192,7 +1244,19 @@ bool StdMeshersGUI_StdHypothesisCreator::stdParams( ListOfStdParams& p ) const QString aMainEntry = SMESHGUI_GenericHypothesisCreator::getMainShapeEntry(); if ( !aMainEntry.isEmpty() ) { - item.myName = tr( "SMESH_FACES_WO_LAYERS" ); + item.myName = tr( "TO_IGNORE_FACES_OR_NOT" ); + p.append( item ); + + StdMeshersGUI_RadioButtonsGrpWdg* ignoreWdg = new StdMeshersGUI_RadioButtonsGrpWdg(""); + ignoreWdg->setButtonLabels ( QStringList() + << tr("NOT_TO_IGNORE_FACES") + << tr("TO_IGNORE_FACES") ); + ignoreWdg->setChecked( h->GetIsToIgnoreFaces() ); + connect(ignoreWdg->getButtonGroup(),SIGNAL(buttonClicked(int)),this,SLOT(onValueChanged())); + customWidgets()->append( ignoreWdg ); + + item.myName = + tr( h->GetIsToIgnoreFaces() ? "SMESH_FACES_WO_LAYERS" : "SMESH_FACES_WITH_LAYERS" ); p.append( item ); StdMeshersGUI_SubShapeSelectorWdg* idsWg = @@ -1200,7 +1264,7 @@ bool StdMeshersGUI_StdHypothesisCreator::stdParams( ListOfStdParams& p ) const idsWg->SetGeomShapeEntry( aMainEntry ); idsWg->SetMainShapeEntry( aMainEntry ); - idsWg->SetListOfIDs( h->GetIgnoreFaces() ); + idsWg->SetListOfIDs( h->GetFaces() ); idsWg->showPreview( true ); customWidgets()->append ( idsWg ); } @@ -1325,6 +1389,13 @@ void StdMeshersGUI_StdHypothesisCreator::attuneStdWidget (QWidget* w, const int) { sb->RangeStepAndValidator( VALUE_SMALL, VALUE_MAX, 1.0, "parametric_precision" ); } + else if( hypType()=="GeometricProgression" ) + { + if (sb->objectName() == tr("SMESH_START_LENGTH_PARAM")) + sb->RangeStepAndValidator( VALUE_SMALL, VALUE_MAX, 1.0, "length_precision" ); + else if (sb->objectName() == tr("SMESH_COMMON_RATIO")) + sb->RangeStepAndValidator( -VALUE_MAX, VALUE_MAX, 0.5, "len_tol_precision" ); + } else if( hypType()=="MaxLength" ) { sb->RangeStepAndValidator( VALUE_SMALL, VALUE_MAX, 1.0, "length_precision" ); @@ -1423,6 +1494,7 @@ QString StdMeshersGUI_StdHypothesisCreator::hypTypeName( const QString& t ) cons types.insert( "Deflection1D", "DEFLECTION1D" ); types.insert( "Adaptive1D", "ADAPTIVE1D" ); types.insert( "Arithmetic1D", "ARITHMETIC_1D" ); + types.insert( "GeometricProgression", "GEOMETRIC_1D" ); types.insert( "FixedPoints1D", "FIXED_POINTS_1D" ); types.insert( "AutomaticLength", "AUTOMATIC_LENGTH" ); types.insert( "ProjectionSource1D", "PROJECTION_SOURCE_1D" ); @@ -1596,12 +1668,15 @@ void StdMeshersGUI_StdHypothesisCreator::valueChanged( QWidget* paramWidget) toCopyGroups->setEnabled( true ); } } - else if ( hypType() == "ViscousLayers2D" && paramWidget->inherits("QButtonGroup")) + else if ( hypType().startsWith( "ViscousLayers" ) && paramWidget->inherits("QButtonGroup")) { if ( QLabel* label = getLabel(4) ) { bool toIgnore = widget< StdMeshersGUI_RadioButtonsGrpWdg >( 3 )->checkedId(); - label->setText( tr( toIgnore ? "SMESH_EDGES_WO_LAYERS" : "SMESH_EDGES_WITH_LAYERS" )); + if ( hypType() == "ViscousLayers2D" ) + label->setText( tr( toIgnore ? "SMESH_EDGES_WO_LAYERS" : "SMESH_EDGES_WITH_LAYERS" )); + else + label->setText( tr( toIgnore ? "SMESH_FACES_WO_LAYERS" : "SMESH_FACES_WITH_LAYERS" )); } } } diff --git a/src/StdMeshersGUI/StdMeshers_images.ts b/src/StdMeshersGUI/StdMeshers_images.ts index a729340e2..537118cf5 100644 --- a/src/StdMeshersGUI/StdMeshers_images.ts +++ b/src/StdMeshersGUI/StdMeshers_images.ts @@ -7,6 +7,10 @@ ICON_DLG_ARITHMETIC_1D mesh_hypo_length.png + + ICON_DLG_GEOMETRIC_1D + mesh_hypo_length.png + ICON_DLG_FIXED_POINTS_1D mesh_hypo_length.png @@ -23,10 +27,6 @@ ICON_DLG_ADAPTIVE1D mesh_hypo_length.png - - ICON_DLG_GEOMETRIC_1D - mesh_hypo_length.png - ICON_DLG_LAYER_DISTRIBUTION mesh_hypo_layer_distribution.png @@ -163,6 +163,10 @@ ICON_SMESH_TREE_HYPO_Arithmetic1D mesh_tree_hypo_length.png + + ICON_SMESH_TREE_HYPO_Geometric1D + mesh_tree_hypo_length.png + ICON_SMESH_TREE_HYPO_AutomaticLength mesh_tree_hypo_length.png @@ -231,6 +235,10 @@ ICON_SMESH_TREE_HYPO_Propagation mesh_tree_hypo_length.png + + ICON_SMESH_TREE_HYPO_PropagOfDistribution + mesh_tree_hypo_length.png + ICON_SMESH_TREE_HYPO_QuadranglePreference mesh_tree_algo_quad.png diff --git a/src/StdMeshersGUI/StdMeshers_msg_en.ts b/src/StdMeshersGUI/StdMeshers_msg_en.ts index c9982405e..f2ccb30f8 100644 --- a/src/StdMeshersGUI/StdMeshers_msg_en.ts +++ b/src/StdMeshersGUI/StdMeshers_msg_en.ts @@ -15,6 +15,18 @@ TO_IGNORE_EDGES Edges without layers (inlets and oulets) + + TO_IGNORE_FACES_OR_NOT + Specified faces are + + + NOT_TO_IGNORE_FACES + Faces with layers (walls) + + + TO_IGNORE_FACES + Faces without layers (inlets and oulets) + @default @@ -23,13 +35,21 @@ Arithmetic 1D - SMESH_ARITHMETIC_1D_PARAM - Arithmetic Reason + SMESH_ARITHMETIC_1D_TITLE + Hypothesis Construction - SMESH_ARITHMETIC_1D_TITLE + SMESH_GEOMETRIC_1D_HYPOTHESIS + Geometric Progression + + + SMESH_GEOMETRIC_1D_TITLE Hypothesis Construction + + SMESH_COMMON_RATIO + Common Ratio + SMESH_AUTOMATIC_LENGTH_HYPOTHESIS Automatic Length @@ -493,6 +513,10 @@ THRESHOLD Threshold + + ADD_EDGES + Implement Edges + AXIS_X Axis X diff --git a/src/StdMeshersGUI/StdMeshers_msg_fr.ts b/src/StdMeshersGUI/StdMeshers_msg_fr.ts index 4f3de7883..dba900057 100755 --- a/src/StdMeshersGUI/StdMeshers_msg_fr.ts +++ b/src/StdMeshersGUI/StdMeshers_msg_fr.ts @@ -3,6 +3,18 @@ @default + + SMESH_COMMON_RATIO + Common Ratio + + + SMESH_GEOMETRIC_1D_TITLE + Hypothesis Construction + + + SMESH_GEOMETRIC_1D_HYPOTHESIS + Geometric Progression + SMESH_EDGES_WITH_LAYERS Edges with layers @@ -475,6 +487,10 @@ StdMeshersGUI_CartesianParamCreator + + ADD_EDGES + Implement Edges + THRESHOLD Seuil diff --git a/src/StdMeshers_I/CMakeLists.txt b/src/StdMeshers_I/CMakeLists.txt index 51cbd0c0d..73bacce5c 100644 --- a/src/StdMeshers_I/CMakeLists.txt +++ b/src/StdMeshers_I/CMakeLists.txt @@ -129,8 +129,10 @@ ENDIF(SALOME_SMESH_ENABLE_MEFISTO) SET(StdMeshersEngine_SOURCES StdMeshers_i.cxx StdMeshers_LocalLength_i.cxx + StdMeshers_Reversible1D_i.cxx StdMeshers_StartEndLength_i.cxx StdMeshers_Arithmetic1D_i.cxx + StdMeshers_Geometric1D_i.cxx StdMeshers_FixedPoints1D_i.cxx StdMeshers_NumberOfSegments_i.cxx StdMeshers_Deflection1D_i.cxx diff --git a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx index 5221f2cec..3db734054 100644 --- a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx +++ b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx @@ -224,6 +224,28 @@ void StdMeshers_CartesianParameters3D_i::GetGridSpacing(SMESH::string_array_out } } +//======================================================================= +//function : SetToAddEdges +//purpose : Enables implementation of geometrical edges into the mesh. +//======================================================================= + +void StdMeshers_CartesianParameters3D_i::SetToAddEdges(CORBA::Boolean toAdd) +{ + GetImpl()->SetToAddEdges( toAdd ); + SMESH::TPythonDump() << _this() << ".SetToAddEdges( " << toAdd << " )"; +} + +//======================================================================= +//function : GetToAddEdges +//purpose : Returns true if implementation of geometrical edges into the +// mesh is enabled +//======================================================================= + +CORBA::Boolean StdMeshers_CartesianParameters3D_i::GetToAddEdges() +{ + return GetImpl()->GetToAddEdges(); +} + //======================================================================= //function : IsGridBySpacing //purpose : Return true if the grid is defined by spacing functions and diff --git a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx index a1f00f605..abdda05f4 100644 --- a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx +++ b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx @@ -84,6 +84,14 @@ class STDMESHERS_I_EXPORT StdMeshers_CartesianParameters3D_i: SMESH::double_array_out xInternalPoints, CORBA::Short axis) throw (SALOME::SALOME_Exception); + /*! + * \brief Enables implementation of geometrical edges into the mesh. If this feature + * is disabled, sharp edges of the shape are lost ("smoothed") in the mesh if + * they don't coincide with the grid lines + */ + void SetToAddEdges(CORBA::Boolean toAdd); + CORBA::Boolean GetToAddEdges(); + /*! * \brief Return true if the grid is defined by spacing functions and * not by node coordinates diff --git a/src/StdMeshers_I/StdMeshers_Geometric1D_i.cxx b/src/StdMeshers_I/StdMeshers_Geometric1D_i.cxx new file mode 100644 index 000000000..b3183e805 --- /dev/null +++ b/src/StdMeshers_I/StdMeshers_Geometric1D_i.cxx @@ -0,0 +1,143 @@ +// Copyright (C) 2007-2013 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// SMESH SMESH_I : idl implementation based on 'SMESH' unit's calsses +// File : StdMeshers_Geometric1D_i.cxx +// Module : SMESH +// +#include "StdMeshers_Geometric1D_i.hxx" +#include "SMESH_Gen_i.hxx" +#include "SMESH_Gen.hxx" +#include "SMESH_PythonDump.hxx" + +#include +#include + +//============================================================================= +/*! + * StdMeshers_Geometric1D_i::StdMeshers_Geometric1D_i + * + * Constructor + */ +//============================================================================= + +StdMeshers_Geometric1D_i::StdMeshers_Geometric1D_i( PortableServer::POA_ptr thePOA, + int theStudyId, + ::SMESH_Gen* theGenImpl ) + : SALOME::GenericObj_i( thePOA ), + SMESH_Hypothesis_i( thePOA ), + StdMeshers_Reversible1D_i( this ) +{ + myBaseImpl = new ::StdMeshers_Geometric1D( theGenImpl->GetANewId(), + theStudyId, + theGenImpl ); +} + +//============================================================================= +/*! + * Sets parameter value + */ +//============================================================================= + +void StdMeshers_Geometric1D_i::SetStartLength( CORBA::Double theLength ) + throw (SALOME::SALOME_Exception) +{ + try { + this->GetImpl()->SetStartLength( theLength ); + } + catch ( SALOME_Exception& S_ex ) { + THROW_SALOME_CORBA_EXCEPTION( S_ex.what(), SALOME::BAD_PARAM ); + } + // Update Python script + SMESH::TPythonDump() + << _this() << ".SetStartLength( " << SMESH::TVar(theLength) << " )"; +} + +//============================================================================= +/*! + * Sets parameter value + */ +//============================================================================= + +void StdMeshers_Geometric1D_i::SetCommonRatio( CORBA::Double factor ) + throw (SALOME::SALOME_Exception) +{ + try { + this->GetImpl()->SetCommonRatio( factor ); + } + catch ( SALOME_Exception& S_ex ) { + THROW_SALOME_CORBA_EXCEPTION( S_ex.what(), SALOME::BAD_PARAM ); + } + // Update Python script + SMESH::TPythonDump() + << _this() << ".SetCommonRatio( " << SMESH::TVar(factor) << " )"; +} + +//============================================================================= +/*! + * Returns length of the first segment + */ +//============================================================================= + +CORBA::Double StdMeshers_Geometric1D_i::GetStartLength() +{ + return this->GetImpl()->GetStartLength(); +} + +//============================================================================= +/*! + * Returns value of Common Ratio + */ +//============================================================================= + +CORBA::Double StdMeshers_Geometric1D_i::GetCommonRatio() +{ + return this->GetImpl()->GetCommonRatio(); +} + +//============================================================================= +/*! + * StdMeshers_Geometric1D_i::GetImpl + * + * Get implementation + */ +//============================================================================= + +::StdMeshers_Geometric1D* StdMeshers_Geometric1D_i::GetImpl() +{ + return ( ::StdMeshers_Geometric1D* )myBaseImpl; +} + +//================================================================================ +/*! + * \brief Verify whether hypothesis supports given entity type + * \param type - dimension (see SMESH::Dimension enumeration) + * \retval CORBA::Boolean - TRUE if dimension is supported, FALSE otherwise + * + * Verify whether hypothesis supports given entity type (see SMESH::Dimension enumeration) + */ +//================================================================================ + +CORBA::Boolean StdMeshers_Geometric1D_i::IsDimSupported(::SMESH::Dimension type) +{ + return type == SMESH::DIM_1D; +} diff --git a/src/StdMeshers_I/StdMeshers_Geometric1D_i.hxx b/src/StdMeshers_I/StdMeshers_Geometric1D_i.hxx new file mode 100644 index 000000000..3786dd581 --- /dev/null +++ b/src/StdMeshers_I/StdMeshers_Geometric1D_i.hxx @@ -0,0 +1,65 @@ +// Copyright (C) 2007-2013 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// SMESH SMESH_I : idl implementation based on 'SMESH' unit's calsses +// File : StdMeshers_Geometric1D_i.hxx +// Module : SMESH +// +#ifndef _SMESH_Geometric1D_I_HXX_ +#define _SMESH_Geometric1D_I_HXX_ + +#include "SMESH_StdMeshers_I.hxx" + +#include +#include CORBA_SERVER_HEADER(SMESH_BasicHypothesis) + +#include "SMESH_Hypothesis_i.hxx" +#include "StdMeshers_Geometric1D.hxx" +#include "StdMeshers_Reversible1D_i.hxx" + +// ====================================================== +// Geometric 1D hypothesis +// ====================================================== +class STDMESHERS_I_EXPORT StdMeshers_Geometric1D_i: + public virtual POA_StdMeshers::StdMeshers_Geometric1D, + public virtual SMESH_Hypothesis_i, + public virtual StdMeshers_Reversible1D_i +{ + public: + // Constructor + StdMeshers_Geometric1D_i( PortableServer::POA_ptr thePOA, + int theStudyId, + ::SMESH_Gen* theGenImpl ); + + void SetStartLength(CORBA::Double length) throw(SALOME::SALOME_Exception); + void SetCommonRatio(CORBA::Double factor) throw(SALOME::SALOME_Exception); + + CORBA::Double GetStartLength(); + CORBA::Double GetCommonRatio(); + + virtual ::CORBA::Boolean IsDimSupported(::SMESH::Dimension type); + + // Get implementation + ::StdMeshers_Geometric1D* GetImpl(); +}; + +#endif diff --git a/src/StdMeshers_I/StdMeshers_ObjRefUlils.cxx b/src/StdMeshers_I/StdMeshers_ObjRefUlils.cxx index 88a0163ec..6b92e8b2d 100644 --- a/src/StdMeshers_I/StdMeshers_ObjRefUlils.cxx +++ b/src/StdMeshers_I/StdMeshers_ObjRefUlils.cxx @@ -37,7 +37,7 @@ using namespace std; //purpose : Return study entry of GEOM Object //======================================================================= -std::string StdMeshers_ObjRefUlils::GeomObjectToEntry(GEOM::GEOM_Object_ptr& theGeomObject) +std::string StdMeshers_ObjRefUlils::GeomObjectToEntry(GEOM::GEOM_Object_ptr theGeomObject) { if ( CORBA::is_nil( theGeomObject )) return "NULL_OBJECT"; @@ -109,13 +109,16 @@ void StdMeshers_ObjRefUlils::SaveToStream( const TopoDS_Shape& theShape, ostream */ //================================================================================ -TopoDS_Shape StdMeshers_ObjRefUlils::LoadFromStream( istream & stream) +TopoDS_Shape StdMeshers_ObjRefUlils::LoadFromStream( istream & stream, + std::string* entry) { - if (SMESH_Gen_i* gen = SMESH_Gen_i::GetSMESHGen()) { - SALOMEDS::Study_var study = gen->GetCurrentStudy(); - if ( ! study->_is_nil() ) { - string str; - if (stream >> str) { + string str; + if (stream >> str) { + if ( entry ) + * entry = str; + if (SMESH_Gen_i* gen = SMESH_Gen_i::GetSMESHGen()) { + SALOMEDS::Study_var study = gen->GetCurrentStudy(); + if ( ! study->_is_nil() ) { SALOMEDS::SObject_wrap sobj = study->FindObjectID( str.c_str() ); CORBA::Object_var obj = gen->SObjectToObject( sobj ); GEOM::GEOM_Object_var geom = GEOM::GEOM_Object::_narrow( obj ); @@ -123,6 +126,8 @@ TopoDS_Shape StdMeshers_ObjRefUlils::LoadFromStream( istream & stream) } } } + if ( entry ) + entry->clear(); return TopoDS_Shape(); } diff --git a/src/StdMeshers_I/StdMeshers_ObjRefUlils.hxx b/src/StdMeshers_I/StdMeshers_ObjRefUlils.hxx index c7d6ed865..32ae9b3e3 100644 --- a/src/StdMeshers_I/StdMeshers_ObjRefUlils.hxx +++ b/src/StdMeshers_I/StdMeshers_ObjRefUlils.hxx @@ -68,7 +68,7 @@ public: /*! * \brief Return study entry of GEOM Object */ - static std::string GeomObjectToEntry(GEOM::GEOM_Object_ptr& theGeomObject); + static std::string GeomObjectToEntry(GEOM::GEOM_Object_ptr theGeomObject); /*! * \brief Return GEOM Object by its study entry or TopoDS_Shape @@ -89,7 +89,7 @@ public: * \param stream - the stream * \retval TopoDS_Shape - resulting shape */ - static TopoDS_Shape LoadFromStream( std::istream & stream ); + static TopoDS_Shape LoadFromStream( std::istream & stream, std::string* entry=NULL ); /*! * \brief Store the CORBA object in the stream diff --git a/src/StdMeshers_I/StdMeshers_Propagation_i.cxx b/src/StdMeshers_I/StdMeshers_Propagation_i.cxx index 919d2f698..a468e81c8 100644 --- a/src/StdMeshers_I/StdMeshers_Propagation_i.cxx +++ b/src/StdMeshers_I/StdMeshers_Propagation_i.cxx @@ -23,7 +23,6 @@ // SMESH SMESH_I : idl implementation based on 'SMESH' unit's calsses // File : StdMeshers_Propagation_i.cxx // Module : SMESH -// $Header$ // #include "StdMeshers_Propagation_i.hxx" #include "SMESH_Gen.hxx" @@ -43,8 +42,8 @@ using namespace std; StdMeshers_Propagation_i::StdMeshers_Propagation_i (PortableServer::POA_ptr thePOA, int theStudyId, ::SMESH_Gen* theGenImpl ) - : SALOME::GenericObj_i( thePOA ), - SMESH_Hypothesis_i( thePOA ) + : SALOME::GenericObj_i( thePOA ), + SMESH_Hypothesis_i( thePOA ) { MESSAGE( "StdMeshers_Propagation_i::StdMeshers_Propagation_i" ); myBaseImpl = new ::StdMeshers_Propagation(theGenImpl->GetANewId(), @@ -52,41 +51,49 @@ StdMeshers_Propagation_i::StdMeshers_Propagation_i (PortableServer::POA_ptr theP theGenImpl); } -//============================================================================= +//================================================================================ /*! - * StdMeshers_Propagation_i::~StdMeshers_Propagation_i + * \brief Verify whether hypothesis supports given entity type + * \param type - dimension (see SMESH::Dimension enumeration) + * \retval CORBA::Boolean - TRUE if dimension is supported, FALSE otherwise * - * Destructor + * Verify whether hypothesis supports given entity type (see SMESH::Dimension enumeration) */ -//============================================================================= -StdMeshers_Propagation_i::~StdMeshers_Propagation_i() +//================================================================================ +CORBA::Boolean StdMeshers_Propagation_i::IsDimSupported( SMESH::Dimension type ) { - MESSAGE( "StdMeshers_Propagation_i::~StdMeshers_Propagation_i" ); + return type == SMESH::DIM_1D; } //============================================================================= /*! - * StdMeshers_Propagation_i::GetImpl + * StdMeshers_PropagOfDistribution_i::StdMeshers_PropagOfDistribution_i * - * Get implementation + * Constructor */ //============================================================================= -::StdMeshers_Propagation* StdMeshers_Propagation_i::GetImpl() +StdMeshers_PropagOfDistribution_i:: +StdMeshers_PropagOfDistribution_i (PortableServer::POA_ptr thePOA, + int theStudyId, + ::SMESH_Gen* theGenImpl ) + : SALOME::GenericObj_i( thePOA ), + SMESH_Hypothesis_i( thePOA ) { - MESSAGE( "StdMeshers_Propagation_i::GetImpl" ); - return ( ::StdMeshers_Propagation* )myBaseImpl; + myBaseImpl = new ::StdMeshers_PropagOfDistribution(theGenImpl->GetANewId(), + theStudyId, + theGenImpl); } //================================================================================ /*! - * \brief Verify whether hypothesis supports given entity type - * \param type - dimension (see SMESH::Dimension enumeration) - * \retval CORBA::Boolean - TRUE if dimension is supported, FALSE otherwise - * + * \brief Verify whether hypothesis supports given entity type + * \param type - dimension (see SMESH::Dimension enumeration) + * \retval CORBA::Boolean - TRUE if dimension is supported, FALSE otherwise + * * Verify whether hypothesis supports given entity type (see SMESH::Dimension enumeration) */ -//================================================================================ -CORBA::Boolean StdMeshers_Propagation_i::IsDimSupported( SMESH::Dimension type ) +//================================================================================ +CORBA::Boolean StdMeshers_PropagOfDistribution_i::IsDimSupported( SMESH::Dimension type ) { return type == SMESH::DIM_1D; } diff --git a/src/StdMeshers_I/StdMeshers_Propagation_i.hxx b/src/StdMeshers_I/StdMeshers_Propagation_i.hxx index 0b303abfd..19b35e9f9 100644 --- a/src/StdMeshers_I/StdMeshers_Propagation_i.hxx +++ b/src/StdMeshers_I/StdMeshers_Propagation_i.hxx @@ -23,7 +23,6 @@ // SMESH SMESH_I : idl implementation based on 'SMESH' unit's calsses // File : StdMeshers_Propagation_i.hxx // Module : SMESH -// $Header$ // #ifndef _SMESH_PROPAGATION_I_HXX_ #define _SMESH_PROPAGATION_I_HXX_ @@ -41,6 +40,7 @@ class SMESH_Gen; // ====================================================== // Propagation hypothesis // ====================================================== + class STDMESHERS_I_EXPORT StdMeshers_Propagation_i: public virtual POA_StdMeshers::StdMeshers_Propagation, public virtual SMESH_Hypothesis_i @@ -50,11 +50,24 @@ public: StdMeshers_Propagation_i (PortableServer::POA_ptr thePOA, int theStudyId, ::SMESH_Gen* theGenImpl); - // Destructor - virtual ~StdMeshers_Propagation_i(); + + // Verify whether hypothesis supports given entity type + CORBA::Boolean IsDimSupported( SMESH::Dimension type ); +}; + +// ====================================================== +// Propagation of Distribution hypothesis +// ====================================================== - // Get implementation - ::StdMeshers_Propagation* GetImpl(); +class STDMESHERS_I_EXPORT StdMeshers_PropagOfDistribution_i: + public virtual POA_StdMeshers::StdMeshers_PropagOfDistribution, + public virtual SMESH_Hypothesis_i +{ +public: + // Constructor + StdMeshers_PropagOfDistribution_i (PortableServer::POA_ptr thePOA, + int theStudyId, + ::SMESH_Gen* theGenImpl); // Verify whether hypothesis supports given entity type CORBA::Boolean IsDimSupported( SMESH::Dimension type ); diff --git a/src/StdMeshers_I/StdMeshers_QuadrangleParams_i.cxx b/src/StdMeshers_I/StdMeshers_QuadrangleParams_i.cxx index ba32f927a..2c2c3ac54 100644 --- a/src/StdMeshers_I/StdMeshers_QuadrangleParams_i.cxx +++ b/src/StdMeshers_I/StdMeshers_QuadrangleParams_i.cxx @@ -21,14 +21,17 @@ // Module : SMESH #include "StdMeshers_QuadrangleParams_i.hxx" -#include "SMESH_Gen_i.hxx" + #include "SMESH_Gen.hxx" +#include "SMESH_Gen_i.hxx" #include "SMESH_PythonDump.hxx" +#include "StdMeshers_ObjRefUlils.hxx" -#include "Utils_CorbaException.hxx" -#include "utilities.h" +#include +#include -#include +#include +#include "SMESH_TryCatch.hxx" using namespace std; @@ -82,13 +85,11 @@ void StdMeshers_QuadrangleParams_i::SetTriaVertex(CORBA::Long vertID) this->GetImpl()->SetTriaVertex( vertID ); } catch ( SALOME_Exception& S_ex ) { - THROW_SALOME_CORBA_EXCEPTION( S_ex.what(), - SALOME::BAD_PARAM ); + THROW_SALOME_CORBA_EXCEPTION( S_ex.what(), SALOME::BAD_PARAM ); } // Update Python script - SMESH::TPythonDump() << _this() << ".SetTriaVertex( " - << vertID << " )"; + SMESH::TPythonDump() << _this() << ".SetTriaVertex( " << vertID << " )"; } //============================================================================= @@ -147,8 +148,7 @@ char* StdMeshers_QuadrangleParams_i::GetObjectEntry() entry = this->GetImpl()->GetObjectEntry(); } catch ( SALOME_Exception& S_ex ) { - THROW_SALOME_CORBA_EXCEPTION( S_ex.what(), - SALOME::BAD_PARAM ); + THROW_SALOME_CORBA_EXCEPTION( S_ex.what(), SALOME::BAD_PARAM ); } return CORBA::string_dup( entry ); } @@ -162,12 +162,6 @@ char* StdMeshers_QuadrangleParams_i::GetObjectEntry() //============================================================================= void StdMeshers_QuadrangleParams_i::SetQuadType(StdMeshers::QuadType type) { - //static char* quadTypes[5] = {"StdMeshers.QUAD_STANDARD", - // "StdMeshers.QUAD_TRIANGLE_PREF", - // "StdMeshers.QUAD_QUADRANGLE_PREF", - // "StdMeshers.QUAD_QUADRANGLE_PREF_REVERSED", - // "StdMeshers.QUAD_REDUCED"}; - MESSAGE("StdMeshers_QuadrangleParams_i::SetQuadType"); ASSERT(myBaseImpl); @@ -199,7 +193,6 @@ void StdMeshers_QuadrangleParams_i::SetQuadType(StdMeshers::QuadType type) quadType = "UNKNOWN"; } SMESH::TPythonDump() << _this() << ".SetQuadType( " << quadType << " )"; - //SMESH::TPythonDump() << _this() << ".SetQuadType( " << quadTypes[int(type)] << " )"; } //============================================================================= @@ -216,6 +209,100 @@ StdMeshers::QuadType StdMeshers_QuadrangleParams_i::GetQuadType() return StdMeshers::QuadType(int(this->GetImpl()->GetQuadType())); } +//================================================================================ +/*! + * \brief Set positions of enforced nodes + */ +//================================================================================ + +void StdMeshers_QuadrangleParams_i::SetEnforcedNodes(const GEOM::ListOfGO& theVertices, + const SMESH::nodes_array& thePoints) + throw ( SALOME::SALOME_Exception ) +{ + try { + std::vector< TopoDS_Shape > shapes; + std::vector< gp_Pnt > points; + shapes.reserve( theVertices.length() ); + points.reserve( thePoints.length() ); + + myShapeEntries.clear(); + + for ( size_t i = 0; i < theVertices.length(); ++i ) + { + if ( CORBA::is_nil( theVertices[i] )) + continue; + CORBA::String_var entry = theVertices[i]->GetStudyEntry(); + if ( !entry.in() || !entry.in()[0] ) + THROW_SALOME_CORBA_EXCEPTION( "Not published enforced vertex shape", SALOME::BAD_PARAM ); + + shapes.push_back( StdMeshers_ObjRefUlils::GeomObjectToShape( theVertices[i].in() )); + myShapeEntries.push_back( entry.in() ); + } + for ( size_t i = 0; i < thePoints.length(); ++i ) + { + points.push_back( gp_Pnt( thePoints[i].x, thePoints[i].y, thePoints[i].z )); + } + this->GetImpl()->SetEnforcedNodes( shapes, points ); + } + catch ( SALOME_Exception& S_ex ) { + THROW_SALOME_CORBA_EXCEPTION( S_ex.what(), SALOME::BAD_PARAM ); + } + // Update Python script + SMESH::TPythonDump() << _this() << ".SetEnforcedNodes( " + << theVertices << ", " << thePoints << " )"; +} + +//================================================================================ +/*! + * \brief Returns positions of enforced nodes + */ +//================================================================================ + +void StdMeshers_QuadrangleParams_i::GetEnforcedNodes(GEOM::ListOfGO_out theVertices, + SMESH::nodes_array_out thePoints) +{ + SMESH_TRY; + + std::vector< TopoDS_Shape > shapes; + std::vector< gp_Pnt > points; + this->GetImpl()->GetEnforcedNodes( shapes, points ); + + theVertices = new GEOM::ListOfGO; + thePoints = new SMESH::nodes_array; + + size_t i = 0; + theVertices->length( myShapeEntries.size() ); + for ( i = 0; i < myShapeEntries.size(); ++i ) + theVertices[i] = + StdMeshers_ObjRefUlils::EntryOrShapeToGeomObject( myShapeEntries[i], shapes[i] ); + + thePoints->length( points.size() ); + for ( i = 0; i < points.size(); ++i ) + { + thePoints[i].x = points[i].X(); + thePoints[i].y = points[i].Y(); + thePoints[i].z = points[i].Z(); + } + SMESH_CATCH( SMESH::doNothing ); +} + +//================================================================================ +/*! + * \brief Returns study entries of shapes defining enforced nodes + */ +//================================================================================ + +SMESH::string_array* StdMeshers_QuadrangleParams_i::GetEnfVertices() +{ + SMESH::string_array_var arr = new SMESH::string_array; + arr->length( myShapeEntries.size() ); + + for ( size_t i = 0; i < myShapeEntries.size(); ++i ) + arr[ i ] = myShapeEntries[ i ].c_str(); + + return arr._retn(); +} + //============================================================================= /*! * StdMeshers_QuadrangleParams_i::GetImpl @@ -243,3 +330,58 @@ CORBA::Boolean StdMeshers_QuadrangleParams_i::IsDimSupported( SMESH::Dimension t { return type == SMESH::DIM_2D; } + +//================================================================================ +/*! + * \brief Write parameters in a string + * \retval char* - resulting string + */ +//================================================================================ + +char* StdMeshers_QuadrangleParams_i::SaveTo() +{ + ASSERT( myBaseImpl ); + std::ostringstream os; + + os << "ENTRIES: " << myShapeEntries.size(); + for ( size_t i = 0; i < myShapeEntries.size(); ++i ) + StdMeshers_ObjRefUlils::SaveToStream( myShapeEntries[ i ], os ); + os << " "; + + myBaseImpl->SaveTo( os ); + + return CORBA::string_dup( os.str().c_str() ); +} + +//================================================================================ +/*! + * \brief Retrieve parameters from the string + * \param theStream - the input string + */ +//================================================================================ + +void StdMeshers_QuadrangleParams_i::LoadFrom( const char* theStream ) +{ + ASSERT( myBaseImpl ); + + bool hasEntries = ( strncmp( "ENTRIES: ", theStream, 9 ) == 0 ); + std::istringstream is( theStream + ( hasEntries ? 9 : 0 )); + + if ( hasEntries ) + { + int nb = 0; + if ( is >> nb && nb > 0 ) + { + std::vector< TopoDS_Shape > shapes; + std::vector< gp_Pnt > points; + myShapeEntries.resize( nb ); + + for ( int i = 0; i < nb; ++i ) + shapes.push_back( StdMeshers_ObjRefUlils::LoadFromStream( is, & myShapeEntries[i] )); + + GetImpl()->SetEnforcedNodes( shapes, points ); + } + } + + myBaseImpl->LoadFrom( is ); +} diff --git a/src/StdMeshers_I/StdMeshers_QuadrangleParams_i.hxx b/src/StdMeshers_I/StdMeshers_QuadrangleParams_i.hxx index 4596712a0..6a6e645d5 100644 --- a/src/StdMeshers_I/StdMeshers_QuadrangleParams_i.hxx +++ b/src/StdMeshers_I/StdMeshers_QuadrangleParams_i.hxx @@ -19,7 +19,6 @@ // File : StdMeshers_QuadrangleParams_i.hxx // Author : Sergey KUUL, OCC // Module : SMESH -// $Header$ #ifndef _SMESH_QUADRANGLEPARAMS_I_HXX_ #define _SMESH_QUADRANGLEPARAMS_I_HXX_ @@ -47,13 +46,6 @@ public: // Destructor virtual ~StdMeshers_QuadrangleParams_i(); - // Set length - //void SetLength( CORBA::Double theLength, CORBA::Boolean theIsStart ) - // throw ( SALOME::SALOME_Exception ); - - // Get length - //CORBA::Double GetLength(CORBA::Boolean theIsStart); - // Set base vertex for triangles void SetTriaVertex (CORBA::Long vertID); @@ -72,11 +64,31 @@ public: // Get the type of quadrangulation StdMeshers::QuadType GetQuadType(); + // Set positions of enforced nodes + void SetEnforcedNodes(const GEOM::ListOfGO& vertices, + const SMESH::nodes_array& points) throw ( SALOME::SALOME_Exception ); + + // Returns positions of enforced nodes + void GetEnforcedNodes(GEOM::ListOfGO_out vertices, SMESH::nodes_array_out points); + + // Returns entries of shapes defining enforced nodes + SMESH::string_array* GetEnfVertices(); + + // Get implementation ::StdMeshers_QuadrangleParams* GetImpl(); // Verify whether hypothesis supports given entity type CORBA::Boolean IsDimSupported( SMESH::Dimension type ); + + // Redefined Persistence + virtual char* SaveTo(); + virtual void LoadFrom( const char* theStream ); + + protected: + + std::vector myShapeEntries; + }; #endif diff --git a/src/StdMeshers_I/StdMeshers_Reversible1D_i.cxx b/src/StdMeshers_I/StdMeshers_Reversible1D_i.cxx new file mode 100644 index 000000000..20a8c9d4a --- /dev/null +++ b/src/StdMeshers_I/StdMeshers_Reversible1D_i.cxx @@ -0,0 +1,143 @@ +// Copyright (C) 2007-2013 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// SMESH SMESH_I : idl implementation based on 'SMESH' unit's calsses +// File : StdMeshers_Reversible1D_i.cxx +// Module : SMESH +// +#include "StdMeshers_Reversible1D_i.hxx" +#include "SMESH_PythonDump.hxx" + +#include +#include + +#include CORBA_SERVER_HEADER(SMESH_Hypothesis) + +//================================================================================ +/*! + * \brief Constructor + */ +//================================================================================ + +StdMeshers_Reversible1D_i::StdMeshers_Reversible1D_i( SMESH_Hypothesis_i* reversible ) + : myHyp( reversible ) +{ +} + +//============================================================================= +/*! + * StdMeshers_Reversible1D_i::SetReversedEdges + * + * Set edges to reverse + */ +//============================================================================= + +void StdMeshers_Reversible1D_i::SetReversedEdges( const SMESH::long_array& theIds ) +{ + try { + std::vector ids( theIds.length() ); + CORBA::Long iEnd = theIds.length(); + for ( CORBA::Long i = 0; i < iEnd; i++ ) + ids[ i ] = theIds[ i ]; + + this->GetImpl()->SetReversedEdges( ids ); + } + catch ( SALOME_Exception& S_ex ) { + THROW_SALOME_CORBA_EXCEPTION( S_ex.what(), SALOME::BAD_PARAM ); + } + + // Update Python script + SMESH::TPythonDump() << myHyp->_this() << ".SetReversedEdges( " << theIds << " )"; +} + +//============================================================================= +/*! + * StdMeshers_Reversible1D_i::SetObjectEntry + * + * Set the Entry for the Main Object + */ +//============================================================================= + +void StdMeshers_Reversible1D_i::SetObjectEntry( const char* theEntry ) +{ + string entry(theEntry); // actually needed as theEntry is spoiled by moment of dumping + try { + this->GetImpl()->SetObjectEntry( entry.c_str() ); + // Update Python script + SMESH::TPythonDump() << myHyp->_this() << ".SetObjectEntry( \"" << entry.c_str() << "\" )"; + } + catch ( SALOME_Exception& S_ex ) { + THROW_SALOME_CORBA_EXCEPTION( S_ex.what(),SALOME::BAD_PARAM ); + } +} + +//============================================================================= +/*! + * StdMeshers_Reversible1D_i::GetObjectEntry + * + * Set the Entry for the Main Object + */ +//============================================================================= + +char* StdMeshers_Reversible1D_i::GetObjectEntry() +{ + const char* entry; + try { + entry = this->GetImpl()->GetObjectEntry(); + } + catch ( SALOME_Exception& S_ex ) { + THROW_SALOME_CORBA_EXCEPTION( S_ex.what(), SALOME::BAD_PARAM ); + } + return CORBA::string_dup( entry ); +} + +//============================================================================= +/*! + * StdMeshers_Reversible1D_i::GetReversedEdges + * + * Get reversed edges + */ +//============================================================================= + +SMESH::long_array* StdMeshers_Reversible1D_i::GetReversedEdges() +{ + SMESH::long_array_var anArray = new SMESH::long_array; + std::vector ids = this->GetImpl()->GetReversedEdges(); + anArray->length( ids.size() ); + for ( CORBA::Long i = 0; i < ids.size(); i++) + anArray [ i ] = ids [ i ]; + + return anArray._retn(); +} + +//============================================================================= +/*! + * StdMeshers_Reversible1D_i::GetImpl + * + * Get implementation + */ +//============================================================================= + +::StdMeshers_Reversible1D* StdMeshers_Reversible1D_i::GetImpl() +{ + return ( ::StdMeshers_Reversible1D* )myHyp->GetImpl(); +} diff --git a/src/StdMeshers_I/StdMeshers_Reversible1D_i.hxx b/src/StdMeshers_I/StdMeshers_Reversible1D_i.hxx new file mode 100644 index 000000000..8d47a7c21 --- /dev/null +++ b/src/StdMeshers_I/StdMeshers_Reversible1D_i.hxx @@ -0,0 +1,66 @@ +// Copyright (C) 2007-2013 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// SMESH SMESH_I : idl implementation based on 'SMESH' unit's calsses +// File : StdMeshers_Reversible1D_i.hxx +// Module : SMESH +// +#ifndef _SMESH_Reversible1D_I_HXX_ +#define _SMESH_Reversible1D_I_HXX_ + +#include "SMESH_StdMeshers_I.hxx" + +#include +#include CORBA_SERVER_HEADER(SMESH_BasicHypothesis) + +#include "SMESH_Hypothesis_i.hxx" +#include "StdMeshers_Reversible1D.hxx" + +// ====================================================== +// Common metrhods of Reversible 1D hypotheses +// ====================================================== +class STDMESHERS_I_EXPORT StdMeshers_Reversible1D_i: + public virtual POA_StdMeshers::Reversible1D +{ + public: + StdMeshers_Reversible1D_i( SMESH_Hypothesis_i* reversible ); + + //Set Reversed Edges + void SetReversedEdges( const SMESH::long_array& theIDs); + + //Get Reversed Edges + SMESH::long_array* GetReversedEdges(); + + //Set the Entry of the Object + void SetObjectEntry( const char* theEntry); + + //Get Object Entry + char* GetObjectEntry(); + + // Get implementation + ::StdMeshers_Reversible1D* GetImpl(); + + private: + SMESH_Hypothesis_i* myHyp; +}; + +#endif diff --git a/src/StdMeshers_I/StdMeshers_ViscousLayers_i.cxx b/src/StdMeshers_I/StdMeshers_ViscousLayers_i.cxx index 5ad82c7ec..8b3050421 100644 --- a/src/StdMeshers_I/StdMeshers_ViscousLayers_i.cxx +++ b/src/StdMeshers_I/StdMeshers_ViscousLayers_i.cxx @@ -78,15 +78,18 @@ StdMeshers_ViscousLayers_i::~StdMeshers_ViscousLayers_i() */ //================================================================================ -void StdMeshers_ViscousLayers_i::SetIgnoreFaces(const ::SMESH::long_array& faceIDs) -throw ( SALOME::SALOME_Exception ) +void StdMeshers_ViscousLayers_i::SetFaces(const ::SMESH::long_array& faceIDs, + CORBA::Boolean toIgnore) + throw ( SALOME::SALOME_Exception ) { vector ids( faceIDs.length() ); for ( unsigned i = 0; i < ids.size(); ++i ) if (( ids[i] = faceIDs[i] ) < 1 ) THROW_SALOME_CORBA_EXCEPTION( "Invalid face id", SALOME::BAD_PARAM ); - GetImpl()->SetBndShapes( ids, /*toIgnore=*/true ); - SMESH::TPythonDump() << _this() << ".SetIgnoreFaces( " << faceIDs << " )"; + + GetImpl()->SetBndShapes( ids, toIgnore ); + + SMESH::TPythonDump() << _this() << ".SetFaces( " << faceIDs << ", " << toIgnore << " )"; } //================================================================================ @@ -95,16 +98,13 @@ throw ( SALOME::SALOME_Exception ) */ //================================================================================ -SMESH::long_array* StdMeshers_ViscousLayers_i::GetIgnoreFaces() +SMESH::long_array* StdMeshers_ViscousLayers_i::GetFaces() { + vector idsVec = GetImpl()->GetBndShapes(); SMESH::long_array_var ids = new SMESH::long_array; - if ( GetImpl()->IsToIgnoreShapes() ) - { - vector idsVec = GetImpl()->GetBndShapes(); - ids->length( idsVec.size() ); - for ( unsigned i = 0; i < idsVec.size(); ++i ) - ids[i] = idsVec[i]; - } + ids->length( idsVec.size() ); + for ( unsigned i = 0; i < idsVec.size(); ++i ) + ids[i] = idsVec[i]; return ids._retn(); } @@ -114,6 +114,47 @@ SMESH::long_array* StdMeshers_ViscousLayers_i::GetIgnoreFaces() */ //================================================================================ +SMESH::long_array* StdMeshers_ViscousLayers_i::GetIgnoreFaces() +{ + if ( GetImpl()->IsToIgnoreShapes() ) + return this->GetFaces(); + return new SMESH::long_array; +} + +//================================================================================ +/*! + * \brief + */ +//================================================================================ + +CORBA::Boolean StdMeshers_ViscousLayers_i::GetIsToIgnoreFaces() +{ + return GetImpl()->IsToIgnoreShapes(); +} + +//================================================================================ +/*! + * \brief + */ +//================================================================================ + +void StdMeshers_ViscousLayers_i::SetIgnoreFaces(const ::SMESH::long_array& faceIDs) +throw ( SALOME::SALOME_Exception ) +{ + vector ids( faceIDs.length() ); + for ( unsigned i = 0; i < ids.size(); ++i ) + if (( ids[i] = faceIDs[i] ) < 1 ) + THROW_SALOME_CORBA_EXCEPTION( "Invalid face id", SALOME::BAD_PARAM ); + GetImpl()->SetBndShapes( ids, /*toIgnore=*/true ); + SMESH::TPythonDump() << _this() << ".SetIgnoreFaces( " << faceIDs << " )"; +} + +//================================================================================ +/*! + * \brief + */ +//================================================================================ + void StdMeshers_ViscousLayers_i::SetTotalThickness(::CORBA::Double thickness) throw ( SALOME::SALOME_Exception ) { diff --git a/src/StdMeshers_I/StdMeshers_ViscousLayers_i.hxx b/src/StdMeshers_I/StdMeshers_ViscousLayers_i.hxx index 59c8d324c..9a6b760e3 100644 --- a/src/StdMeshers_I/StdMeshers_ViscousLayers_i.hxx +++ b/src/StdMeshers_I/StdMeshers_ViscousLayers_i.hxx @@ -51,6 +51,11 @@ class STDMESHERS_I_EXPORT StdMeshers_ViscousLayers_i: void SetIgnoreFaces(const ::SMESH::long_array& faceIDs) throw ( SALOME::SALOME_Exception ); SMESH::long_array* GetIgnoreFaces(); + void SetFaces(const SMESH::long_array& faceIDs, + CORBA::Boolean toIgnore) throw (SALOME::SALOME_Exception); + SMESH::long_array* GetFaces(); + CORBA::Boolean GetIsToIgnoreFaces(); + void SetTotalThickness(::CORBA::Double thickness) throw ( SALOME::SALOME_Exception ); ::CORBA::Double GetTotalThickness(); diff --git a/src/StdMeshers_I/StdMeshers_i.cxx b/src/StdMeshers_I/StdMeshers_i.cxx index 3cc3ac050..2d98e11c4 100644 --- a/src/StdMeshers_I/StdMeshers_i.cxx +++ b/src/StdMeshers_I/StdMeshers_i.cxx @@ -35,6 +35,7 @@ #include "StdMeshers_AutomaticLength_i.hxx" #include "StdMeshers_StartEndLength_i.hxx" #include "StdMeshers_Arithmetic1D_i.hxx" +#include "StdMeshers_Geometric1D_i.hxx" #include "StdMeshers_FixedPoints1D_i.hxx" #include "StdMeshers_NumberOfSegments_i.hxx" #include "StdMeshers_Deflection1D_i.hxx" @@ -136,6 +137,8 @@ STDMESHERS_I_EXPORT aCreator = new StdHypothesisCreator_i; else if (strcmp(aHypName, "Propagation") == 0) aCreator = new StdHypothesisCreator_i; + else if (strcmp(aHypName, "PropagOfDistribution") == 0) + aCreator = new StdHypothesisCreator_i; else if (strcmp(aHypName, "MaxElementArea") == 0) aCreator = new StdHypothesisCreator_i; else if (strcmp(aHypName, "MaxElementVolume") == 0) @@ -150,6 +153,8 @@ STDMESHERS_I_EXPORT aCreator = new StdHypothesisCreator_i; else if (strcmp(aHypName, "Arithmetic1D") == 0) aCreator = new StdHypothesisCreator_i; + else if (strcmp(aHypName, "GeometricProgression") == 0) + aCreator = new StdHypothesisCreator_i; else if (strcmp(aHypName, "AutomaticLength") == 0) aCreator = new StdHypothesisCreator_i; else if (strcmp(aHypName, "QuadranglePreference") == 0) -- 2.30.2