From cd3ffac3fabc68b4d1dee2ad199302f04b20d2c8 Mon Sep 17 00:00:00 2001 From: eap Date: Tue, 4 Feb 2014 13:02:26 +0000 Subject: [PATCH] 22359: Body Fitting algorithm: grid orientation 22358: Body Fitting algorithm: origin point of grid defined by spacing --- doc/salome/examples/cartesian_algo.py | 37 + .../gui/SMESH/images/cartesian3D_hyp.png | Bin 34405 -> 60056 bytes .../SMESH/images/cartesian_implement_edge.png | Bin 0 -> 9506 bytes doc/salome/gui/SMESH/input/cartesian_algo.doc | 82 +- idl/SMESH_BasicHypothesis.idl | 33 +- src/SMESH_I/SMESH_2smeshpy.cxx | 6 +- src/SMESH_SWIG/StdMeshersBuilder.py | 62 +- .../StdMeshers_CartesianParameters3D.cxx | 277 ++++++- .../StdMeshers_CartesianParameters3D.hxx | 22 +- src/StdMeshers/StdMeshers_Cartesian_3D.cxx | 501 +++++++----- .../StdMeshersGUI_CartesianParamCreator.cxx | 725 +++++++++++++++++- .../StdMeshersGUI_CartesianParamCreator.h | 30 +- src/StdMeshersGUI/StdMeshers_msg_en.ts | 24 + .../StdMeshers_CartesianParameters3D_i.cxx | 133 +++- .../StdMeshers_CartesianParameters3D_i.hxx | 24 + 15 files changed, 1723 insertions(+), 233 deletions(-) create mode 100644 doc/salome/gui/SMESH/images/cartesian_implement_edge.png diff --git a/doc/salome/examples/cartesian_algo.py b/doc/salome/examples/cartesian_algo.py index dc0dc23cf..e5651cb67 100644 --- a/doc/salome/examples/cartesian_algo.py +++ b/doc/salome/examples/cartesian_algo.py @@ -50,3 +50,40 @@ print "nb hexahedra",mesh.NbHexas() print "nb tetrahedra",mesh.NbTetras() print "nb polyhedra",mesh.NbPolyhedrons() print + +# Example of customization of dirtections of the grid axes + +# make a box with non-orthogonal edges +xDir = geompy.MakeVectorDXDYDZ( 1.0, 0.1, 0.0, "xDir" ) +yDir = geompy.MakeVectorDXDYDZ(-0.1, 1.0, 0.0, "yDir" ) +zDir = geompy.MakeVectorDXDYDZ( 0.2, 0.3, 1.0, "zDir" ) +face = geompy.MakePrismVecH( xDir, yDir, 1.0 ) +box = geompy.MakePrismVecH( face, zDir, 1.0, theName="box" ) + +spc = "0.1" # spacing + +# default axes +mesh = smesh.Mesh( box, "custom axes") +algo = mesh.BodyFitted() +algo.SetGrid( spc, spc, spc, 10000 ) +mesh.Compute() +print "Default axes" +print " nb hex:",mesh.NbHexas() + +# set axes using edges of the box +algo.SetAxesDirs( xDir, [-0.1,1,0], zDir ) +mesh.Compute() +print "Manual axes" +print " nb hex:",mesh.NbHexas() + +# set optimal orthogonal axes +algo.SetOptimalAxesDirs( isOrthogonal=True ) +mesh.Compute() +print "Optimal orthogonal axes" +print " nb hex:",mesh.NbHexas() + +# set optimal non-orthogonal axes +algo.SetOptimalAxesDirs( isOrthogonal=False ) +mesh.Compute() +print "Optimal non-orthogonal axes" +print " nb hex:",mesh.NbHexas() diff --git a/doc/salome/gui/SMESH/images/cartesian3D_hyp.png b/doc/salome/gui/SMESH/images/cartesian3D_hyp.png index b8373ed6ddbafd52276f5e5ba8c2b7cd0c7da88a..c9a605fea0ca0cec4e0d8ba14290e00db423cac9 100644 GIT binary patch literal 60056 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 Ph~S;q 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 diff --git a/doc/salome/gui/SMESH/images/cartesian_implement_edge.png b/doc/salome/gui/SMESH/images/cartesian_implement_edge.png new file mode 100644 index 0000000000000000000000000000000000000000..59ac9ba6f2ae5190392094022591c7235642b50b GIT binary patch literal 9506 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 MZ9}aBjk{0(4=Uv5b^rhX literal 0 HcmV?d00001 diff --git a/doc/salome/gui/SMESH/input/cartesian_algo.doc b/doc/salome/gui/SMESH/input/cartesian_algo.doc index 3240150c7..dfb9acad4 100644 --- a/doc/salome/gui/SMESH/input/cartesian_algo.doc +++ b/doc/salome/gui/SMESH/input/cartesian_algo.doc @@ -31,7 +31,7 @@ nodes are inside and some outside. To apply this algorithm when you define your mesh, select Body Fitting in the list of 3D algorithms and click "Add Hypothesis" button and "Body Fitting Parameters"" menu - item. Dialog of Body Fitting Parameters +item. Dialog of Body Fitting Parameters hypothesis will appear.
@@ -42,34 +42,64 @@ To apply this algorithm when you define your mesh, select Body This dialog allows to define
    -
  • \b Name of the algorithm
  • -
  • Minimal size of a cell truncated by the geometry boundary. If the - size of a truncated grid cell is \b Threshold times less than a - initial cell size, then a mesh element is not created.
  • -
  • Cartesian structured grid. Each grid axis is defined - individually. Definition mode chooses a way of grid - definition:
      -
    • You can specify the \b Coordinates of grid nodes. \b Insert button - inserts a node at distance \b Step (negative or positive) from a - selected node. \b Delete button removes a selected node. Double - click on a coordinate in the list enables its edition. A grid - defined by \b Coordinates should enclose the geometry, else the - algorithm will fail.
    • -
    • You can define the \b Spacing of a grid as an algebraic formula - f(t) where \a t is a position along a grid axis - normalized at [0.0,1.0]. The whole range of geometry can be - divided into sub-ranges with their own spacing formulas to apply; +
    • \b Name of the algorithm.
    • +
    • Minimal size of a cell truncated by the geometry boundary. If the + size of a truncated grid cell is \b Threshold times less than a + initial cell size, then a mesh element is not created.
    • +
    • Implement Edges check-box activates incorporation of + geometrical edges in the mesh. +\image html cartesian_implement_edge.png "'Implement Edges' switched off (left) and on (right)" +
    • Cartesian structured grid. Location of nodes along each grid axis + is defined individually. Definition mode chooses a way of + grid definition: +
        +
      • You can specify the \b Coordinates of grid nodes. \b Insert button + inserts a node at distance \b Step (negative or positive) from a + selected node. \b Delete button removes a selected node. Double + click on a coordinate in the list enables its edition. + \b Note that node coordinates are measured along directions of + axes that can differ from the directions of the Global Coordinate + System.
      • +
      • You can define the \b Spacing of a grid as an algebraic formula + f(t) where \a t is a position along a grid axis + normalized at [0.0,1.0]. The whole range of geometry can be + divided into sub-ranges with their own spacing formulas to apply; \a t varies between 0.0 and 1.0 within each sub-range. \b Insert button - divides a selected range into two ones. \b Delete button adds the - selected sub-range to the previous one. Double click on a range in - the list enables edition of its right boundary. Double click on a - function in the list enables its edition. -
      -
    • + divides a selected range into two ones. \b Delete button adds the + selected sub-range to the previous one. Double click on a range in + the list enables edition of its right boundary. Double click on a + function in the list enables its edition. +
    +
  • +
  • Coordinates of a Fixed Point. They allow to exactly + locate a grid node in a direction defined by spacing. If all the three + directions are defined by spacing, then there will be a mesh node at + the Fixed Point. If two directions are defined by spacing, + then there will be at least a link between mesh nodes passing through + the Fixed Point. If only one direction is defined by spacing, + then there will be at least an element facet passing through + the Fixed Point. If no directions are defined by spacing, + Fixed Point is disabled.
  • +
  • Directions of Axes. You can set up almost any + directions of grid axes that can help in generation as many as + possible hexahedral elements. +
      +
    • Orthogonal Axes check-box, if activated, keeps the + axes orthogonal during their modification.
    • +
    • Selection buttons enable snapping corresponding axes to + direction of a geometrical edge selected in the Object + Browser. Edge direction is defined by coordinates of its end + points.
    • +
    • Optimal Axes button runs an algorithm that tries to + set the axes so that a number of generated hexahedra to be + maximal.
    • +
    • Reset button returns the axes in a default position + parallel to the axes of the Global Coordinate System.
    • +

-See Also a sample TUI Script of a -\ref tui_cartesian_algo "Usage of Body Fitting algorithm". +See Also a sample TUI Script of a +\ref tui_cartesian_algo "Usage of Body Fitting algorithm". */ diff --git a/idl/SMESH_BasicHypothesis.idl b/idl/SMESH_BasicHypothesis.idl index 186b06681..27cfbd368 100644 --- a/idl/SMESH_BasicHypothesis.idl +++ b/idl/SMESH_BasicHypothesis.idl @@ -942,8 +942,9 @@ module StdMeshers /*! * interface of "Body fitting Parameters" hypothesis. * This hypothesis specifies - * - Definition of the Cartesian grid * - Size threshold + * - Definition of the Cartesian grid + * - Direction of grid axes */ interface StdMeshers_CartesianParameters3D : SMESH::SMESH_Hypothesis { @@ -965,8 +966,8 @@ module StdMeshers /*! * Set coordinates of nodes along an axis (countered from zero) */ - void SetGrid(in SMESH::double_array coords, - in short axis) raises (SALOME::SALOME_Exception); + void SetGrid(in SMESH::double_array coords, + in short axis) raises (SALOME::SALOME_Exception); SMESH::double_array GetGrid(in short axis) raises (SALOME::SALOME_Exception); /*! @@ -985,6 +986,22 @@ module StdMeshers void GetGridSpacing(out SMESH::string_array spaceFunctions, out SMESH::double_array internalPoints, in short axis) raises (SALOME::SALOME_Exception); + /*! + * Set custom direction of axes + */ + void SetAxesDirs(in SMESH::DirStruct x, + in SMESH::DirStruct y, + in SMESH::DirStruct z ) raises (SALOME::SALOME_Exception); + void GetAxesDirs(out SMESH::DirStruct x, + out SMESH::DirStruct y, + out SMESH::DirStruct z ); + /*! + * Set/unset a fixed point, at which a node will be created provided that grid + * is defined by spacing in all directions + */ + void SetFixedPoint(in SMESH::PointStruct p, in boolean toUnset); + boolean GetFixedPoint(out SMESH::PointStruct p); + /*! * 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 @@ -993,6 +1010,16 @@ module StdMeshers void SetToAddEdges(in boolean toAdd); boolean GetToAddEdges(); + /*! + * Returns axes at which a number of generated hexahedra is maximal + */ + void ComputeOptimalAxesDirs(in GEOM::GEOM_Object shape, + in boolean isOrthogonal, + out SMESH::DirStruct x, + out SMESH::DirStruct y, + out SMESH::DirStruct z ) + raises (SALOME::SALOME_Exception); + /*! * \brief Computes node coordinates by spacing functions * \param x0 - lower coordinate diff --git a/src/SMESH_I/SMESH_2smeshpy.cxx b/src/SMESH_I/SMESH_2smeshpy.cxx index 15e2a183f..b8358a795 100644 --- a/src/SMESH_I/SMESH_2smeshpy.cxx +++ b/src/SMESH_I/SMESH_2smeshpy.cxx @@ -2632,6 +2632,8 @@ Handle(_pyHypothesis) _pyHypothesis::NewHypothesis( const Handle(_pyCommand)& th hyp->SetConvMethodAndType( "SetGrid", "Cartesian_3D"); for ( int iArg = 0; iArg < 4; ++iArg ) hyp->setCreationArg( iArg+1, "[]"); + hyp->AddAccumulativeMethod( "SetGrid" ); + hyp->AddAccumulativeMethod( "SetGridSpacing" ); } else { @@ -3114,7 +3116,9 @@ void _pyComplexParamHypo::Process( const Handle(_pyCommand)& theCommand) myCurCrMethod->myArgs[ iArg ] += "]"; } myArgCommands.push_back( theCommand ); - rememberCmdOfParameter( theCommand ); + //rememberCmdOfParameter( theCommand ); -- these commands are marked as + // accumulative, else, if the creation + // is not converted, commands for axes 1 and 2 are lost return; } } diff --git a/src/SMESH_SWIG/StdMeshersBuilder.py b/src/SMESH_SWIG/StdMeshersBuilder.py index 4e5b2d217..265a9801b 100644 --- a/src/SMESH_SWIG/StdMeshersBuilder.py +++ b/src/SMESH_SWIG/StdMeshersBuilder.py @@ -597,7 +597,7 @@ class StdMeshersBuilder_Quadrangle(Mesh_Algorithm): # 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. + # @param enfPoints: 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], ...]). @@ -1415,7 +1415,7 @@ class StdMeshersBuilder_Cartesian_3D(Mesh_Algorithm): if not self.mesh.IsUsedHypothesis( self.hyp, self.geom ): self.mesh.AddHypothesis( self.hyp, self.geom ) - for axis, gridDef in enumerate( [xGridDef, yGridDef, zGridDef]): + for axis, gridDef in enumerate( [xGridDef, yGridDef, zGridDef] ): if not gridDef: raise ValueError, "Empty grid definition" if isinstance( gridDef, str ): self.hyp.SetGridSpacing( [gridDef], [], axis ) @@ -1429,6 +1429,64 @@ class StdMeshersBuilder_Cartesian_3D(Mesh_Algorithm): self.hyp.SetSizeThreshold( sizeThreshold ) return self.hyp + ## Defines custom directions of axes of the grid + # @param xAxis either SMESH.DirStruct or a vector, or 3 vector components + # @param yAxis either SMESH.DirStruct or a vector, or 3 vector components + # @param zAxis either SMESH.DirStruct or a vector, or 3 vector components + def SetAxesDirs( self, xAxis, yAxis, zAxis ): + import GEOM + if hasattr( xAxis, "__getitem__" ): + xAxis = self.mesh.smeshpyD.MakeDirStruct( xAxis[0],xAxis[1],xAxis[2] ) + elif isinstance( xAxis, GEOM._objref_GEOM_Object ): + xAxis = self.mesh.smeshpyD.GetDirStruct( xAxis ) + if hasattr( yAxis, "__getitem__" ): + yAxis = self.mesh.smeshpyD.MakeDirStruct( yAxis[0],yAxis[1],yAxis[2] ) + elif isinstance( yAxis, GEOM._objref_GEOM_Object ): + yAxis = self.mesh.smeshpyD.GetDirStruct( yAxis ) + if hasattr( zAxis, "__getitem__" ): + zAxis = self.mesh.smeshpyD.MakeDirStruct( zAxis[0],zAxis[1],zAxis[2] ) + elif isinstance( zAxis, GEOM._objref_GEOM_Object ): + zAxis = self.mesh.smeshpyD.GetDirStruct( zAxis ) + if not self.hyp: + self.hyp = self.Hypothesis("CartesianParameters3D") + if not self.mesh.IsUsedHypothesis( self.hyp, self.geom ): + self.mesh.AddHypothesis( self.hyp, self.geom ) + self.hyp.SetAxesDirs( xAxis, yAxis, zAxis ) + return self.hyp + + ## Automatically defines directions of axes of the grid at which + # a number of generated hexahedra is maximal + # @param isOrthogonal defines whether the axes mush be orthogonal + def SetOptimalAxesDirs(self, isOrthogonal=True): + if not self.hyp: + self.hyp = self.Hypothesis("CartesianParameters3D") + if not self.mesh.IsUsedHypothesis( self.hyp, self.geom ): + self.mesh.AddHypothesis( self.hyp, self.geom ) + x,y,z = self.hyp.ComputeOptimalAxesDirs( self.geom, isOrthogonal ) + self.hyp.SetAxesDirs( x,y,z ) + return self.hyp + + ## Sets/unsets a fixed point. The algorithm makes a plane of the grid pass + # through the fixed point in each direction at which the grid is defined + # by spacing + # @param p coordinates of the fixed point. Either SMESH.PointStruct or + # 3 components of coordinates. + # @param toUnset defines whether the fixed point is defined or removed. + def SetFixedPoint( self, p, toUnset=False ): + import SMESH + if toUnset: + if not self.hyp: return + p = SMESH.PointStruct(0,0,0) + if hasattr( p, "__getitem__" ): + p = SMESH.PointStruct( p[0],p[1],p[2] ) + if not self.hyp: + self.hyp = self.Hypothesis("CartesianParameters3D") + if not self.mesh.IsUsedHypothesis( self.hyp, self.geom ): + self.mesh.AddHypothesis( self.hyp, self.geom ) + self.hyp.SetFixedPoint( p, toUnset ) + return self.hyp + + pass # end of StdMeshersBuilder_Cartesian_3D class ## Defines a stub 1D algorithm, which enables "manual" creation of nodes and diff --git a/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx b/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx index 369d4e424..00cd8615e 100644 --- a/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx +++ b/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx @@ -32,10 +32,24 @@ #include "utilities.h" +#include #include +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include using namespace std; @@ -66,6 +80,11 @@ StdMeshers_CartesianParameters3D::StdMeshers_CartesianParameters3D(int h _axisDirs[6] = 0.; _axisDirs[7] = 0.; _axisDirs[8] = 1.; + + _fixedPoint[0] = 0.; + _fixedPoint[1] = 0.; + _fixedPoint[2] = 0.; + SetFixedPoint( _fixedPoint, /*toUnset=*/true ); } @@ -73,6 +92,22 @@ namespace { const char* axisName[3] = { "X", "Y", "Z" }; + typedef std::pair< double, std::pair< double, double > > TCooTriple; + +#define gpXYZ( cTriple ) gp_XYZ( (cTriple).first, (cTriple).second.first, (cTriple).second.second ) + + //================================================================================ + /*! + * \brief Compare two normals + */ + //================================================================================ + + bool sameDir( const TCooTriple& n1, const TCooTriple& n2 ) + { + gp_XYZ xyz1 = gpXYZ( n1 ), xyz2 = gpXYZ( n2 ); + return ( xyz1 - xyz2 ).Modulus() < 0.01; + } + //================================================================================ /*! * \brief Checks validity of an axis index, throws in case of invalidity @@ -177,6 +212,36 @@ void StdMeshers_CartesianParameters3D::SetGridSpacing(std::vector& xSpac NotifySubMeshesHypothesisModification(); } +//======================================================================= +//function : SetFixedPoint +//purpose : * Set/unset a fixed point, at which a node will be created provided that grid +// * is defined by spacing in all directions +//======================================================================= + +void StdMeshers_CartesianParameters3D::SetFixedPoint(const double p[3], bool toUnset) +{ + if ( toUnset != Precision::IsInfinite( _fixedPoint[0] )) + NotifySubMeshesHypothesisModification(); + + if ( toUnset ) + _fixedPoint[0] = Precision::Infinite(); + else + std::copy( &p[0], &p[0]+3, &_fixedPoint[0] ); +} + +//======================================================================= +//function : GetFixedPoint +//purpose : Returns either false or (true + point coordinates) +//======================================================================= + +bool StdMeshers_CartesianParameters3D::GetFixedPoint(double p[3]) +{ + if ( Precision::IsInfinite( _fixedPoint[0] )) + return false; + std::copy( &_fixedPoint[0], &_fixedPoint[0]+3, &p[0] ); +} + + //======================================================================= //function : SetSizeThreshold //purpose : Set size threshold @@ -322,9 +387,199 @@ void StdMeshers_CartesianParameters3D::GetCoordinates(std::vector& xNode zNodes = _coords[2]; } +//======================================================================= +//function : ComputeOptimalAxesDirs +//purpose : Returns axes at which number of hexahedra is maximal +//======================================================================= + +void StdMeshers_CartesianParameters3D:: +ComputeOptimalAxesDirs(const TopoDS_Shape& shape, + const bool isOrthogonal, + double dirCoords[9]) +{ + for ( int i = 0; i < 9; ++i ) dirCoords[i] = 0.; + dirCoords[0] = dirCoords[4] = dirCoords[8] = 1.; + + if ( shape.IsNull() ) return; + + TopLoc_Location loc; + TopExp_Explorer exp; + + // get external FACEs of the shape + TopTools_MapOfShape faceMap; + for ( exp.Init( shape, TopAbs_FACE ); exp.More(); exp.Next() ) + if ( !faceMap.Add( exp.Current() )) + faceMap.Remove( exp.Current() ); + + // sort areas of planar faces by normal direction + + std::multimap< TCooTriple, double > areasByNormal; + + TopTools_MapIteratorOfMapOfShape fIt ( faceMap ); + for ( ; fIt.More(); fIt.Next() ) + { + const TopoDS_Face& face = TopoDS::Face( fIt.Key() ); + Handle(Geom_Surface) surf = BRep_Tool::Surface( face, loc ); + if ( surf.IsNull() ) continue; + + GeomLib_IsPlanarSurface check( surf, 1e-5 ); + if ( !check.IsPlanar() ) continue; + + GProp_GProps SProps; + BRepGProp::SurfaceProperties( face, SProps ); + double area = SProps.Mass(); + + gp_Pln pln = check.Plan(); + gp_Dir norm = pln.Axis().Direction().Transformed( loc ); + if ( norm.X() < -1e-3 ) { // negative X + norm.Reverse(); + } else if ( norm.X() < 1e-3 ) { // zero X + if ( norm.Y() < -1e-3 ) { // negative Y + norm.Reverse(); + } else if ( norm.Y() < 1e-3 ) { // zero X && zero Y + if ( norm.Y() < -1e-3 ) // negative Z + norm.Reverse(); + } + } + TCooTriple coo3( norm.X(), make_pair( norm.Y(), norm.Z() )); + areasByNormal.insert( make_pair( coo3, area )); + } + + // group coplanar normals and sort groups by sum area + + std::multimap< double, vector< const TCooTriple* > > normsByArea; + std::multimap< TCooTriple, double >::iterator norm2a = areasByNormal.begin(); + const TCooTriple* norm1 = 0; + double sumArea = 0; + vector< const TCooTriple* > norms; + for ( int iF = 1; norm2a != areasByNormal.end(); ++norm2a, ++iF ) + { + + if ( !norm1 || !sameDir( *norm1, norm2a->first )) + { + if ( !norms.empty() ) + { + normsByArea.insert( make_pair( sumArea, norms )); + norms.clear(); + } + norm1 = & norm2a->first; + sumArea = norm2a->second; + norms.push_back( norm1 ); + } + else + { + sumArea += norm2a->second; + norms.push_back( & norm2a->first ); + } + if ( iF == areasByNormal.size() ) + normsByArea.insert( make_pair( sumArea, norms )); + } + + // try to set dirs by planar faces + + gp_XYZ normDirs[3]; // normals to largest planes + + if ( !normsByArea.empty() ) + { + norm1 = normsByArea.rbegin()->second[0]; + normDirs[0] = gpXYZ( *norm1 ); + + if ( normsByArea.size() == 1 ) + { + normDirs[1] = normDirs[0]; + if ( Abs( normDirs[0].Y() ) < 1e-100 && + Abs( normDirs[0].Z() ) < 1e-100 ) // normDirs[0] || OX + normDirs[1].SetY( normDirs[0].Y() + 1. ); + else + normDirs[1].SetX( normDirs[0].X() + 1. ); + } + else + { + // look for 2 other directions + gp_XYZ testDir = normDirs[0], minDir, maxDir; + for ( int is2nd = 0; is2nd < 2; ++is2nd ) + { + double maxMetric = 0, minMetric = 1e100; + std::multimap< double, vector< const TCooTriple* > >::iterator a2n; + for ( a2n = normsByArea.begin(); a2n != normsByArea.end(); ++a2n ) + { + gp_XYZ n = gpXYZ( *( a2n->second[0]) ); + double dot = Abs( n * testDir ); + double metric = ( 1. - dot ) * ( isOrthogonal ? 1 : a2n->first ); + if ( metric > maxMetric ) + { + maxDir = n; + maxMetric = metric; + } + if ( metric < minMetric ) + { + minDir = n; + minMetric = metric; + } + } + if ( is2nd ) + { + normDirs[2] = minDir; + } + else + { + normDirs[1] = maxDir; + normDirs[2] = normDirs[0] ^ normDirs[1]; + if ( isOrthogonal || normsByArea.size() < 3 ) + break; + testDir = normDirs[2]; + } + } + } + if ( isOrthogonal || normsByArea.size() == 1 ) + { + normDirs[2] = normDirs[0] ^ normDirs[1]; + normDirs[1] = normDirs[2] ^ normDirs[0]; + } + } + else + { + return; + } + + gp_XYZ dirs[3]; + dirs[0] = normDirs[0] ^ normDirs[1]; + dirs[1] = normDirs[1] ^ normDirs[2]; + dirs[2] = normDirs[2] ^ normDirs[0]; + + dirs[0].Normalize(); + dirs[1].Normalize(); + dirs[2].Normalize(); + + // Select dirs for X, Y and Z axes + int iX = ( Abs( dirs[0].X() ) > Abs( dirs[1].X() )) ? 0 : 1; + if ( Abs( dirs[iX].X() ) < Abs( dirs[2].X() )) + iX = 2; + int iY = ( iX == 0 ) ? 1 : (( Abs( dirs[0].Y() ) > Abs( dirs[1].Y() )) ? 0 : 1 ); + if ( Abs( dirs[iY].Y() ) < Abs( dirs[2].Y() ) && iX != 2 ) + iY = 2; + int iZ = 3 - iX - iY; + + if ( dirs[iX].X() < 0 ) dirs[iX].Reverse(); + if ( dirs[iY].Y() < 0 ) dirs[iY].Reverse(); + gp_XYZ zDir = dirs[iX] ^ dirs[iY]; + if ( dirs[iZ] * zDir < 0 ) + dirs[iZ].Reverse(); + + dirCoords[0] = dirs[iX].X(); + dirCoords[1] = dirs[iX].Y(); + dirCoords[2] = dirs[iX].Z(); + dirCoords[3] = dirs[iY].X(); + dirCoords[4] = dirs[iY].Y(); + dirCoords[5] = dirs[iY].Z(); + dirCoords[6] = dirs[iZ].X(); + dirCoords[7] = dirs[iZ].Y(); + dirCoords[8] = dirs[iZ].Z(); +} + //======================================================================= //function : SetAxisDirs -//purpose : Sets directions of axes +//purpose : Sets custom direction of axes //======================================================================= void StdMeshers_CartesianParameters3D::SetAxisDirs(const double* the9DirComps) @@ -349,6 +604,10 @@ void StdMeshers_CartesianParameters3D::SetAxisDirs(const double* the9DirComps) y.IsParallel( z, M_PI / 180. )) throw SALOME_Exception("Parallel axis directions"); + gp_Vec normXY = x ^ y, normYZ = y ^ z; + if ( normXY.IsParallel( normYZ, M_PI / 180. )) + throw SALOME_Exception("Axes lie in one plane"); + bool isChanged = false; for ( int i = 0; i < 9; ++i ) { @@ -450,6 +709,14 @@ std::ostream & StdMeshers_CartesianParameters3D::SaveTo(std::ostream & save) } save << _toAddEdges << " "; + save.setf( save.scientific ); + save.precision( 12 ); + for ( int i = 0; i < 9; ++i ) + save << _axisDirs[i] << " "; + + for ( int i = 0; i < 3; ++i ) + save << _fixedPoint[i] << " "; + return save; } @@ -500,7 +767,13 @@ std::istream & StdMeshers_CartesianParameters3D::LoadFrom(std::istream & load) } } - load >> _toAddEdges; + ok = ( load >> _toAddEdges ); + + for ( int i = 0; i < 9 && ok; ++i ) + ok = ( load >> _axisDirs[i]); + + for ( int i = 0; i < 3 && ok ; ++i ) + ok = ( load >> _fixedPoint[i]); return load; } diff --git a/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx b/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx index 98970956b..3ef431cc9 100644 --- a/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx +++ b/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx @@ -78,6 +78,13 @@ public: bool IsGridBySpacing(const int axis) const throw ( SALOME_Exception ); + /*! + * Set/unset a fixed point, at which a node will be created provided that grid + * is defined by spacing in all directions + */ + void SetFixedPoint(const double p[3], bool toUnset); + bool GetFixedPoint(double p[3]); + /*! * \brief Computes node coordinates by spacing functions * \param x0 - lower coordinate @@ -101,13 +108,21 @@ public: std::vector& zNodes, const Bnd_Box& bndBox) const throw ( SALOME_Exception ); + /*! + * \brief Set custom direction of axes + */ void SetAxisDirs(const double* the9DirComps) throw ( SALOME_Exception ); const double* GetAxisDirs() const { return _axisDirs; } - + /*! + * \brief Returns axes at which number of hexahedra is maximal + */ + static void ComputeOptimalAxesDirs(const TopoDS_Shape& shape, + const bool isOrthogonal, + double dirCoords[9]); /*! * 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 \athreshold times less than the size of the initial hexahedron. */ void SetSizeThreshold(const double threshold) throw ( SALOME_Exception ); /*! @@ -150,7 +165,8 @@ public: std::vector _spaceFunctions[3]; std::vector _internalPoints[3]; - double _axisDirs[9]; + double _axisDirs [9]; + double _fixedPoint[3]; double _sizeThreshold; bool _toAddEdges; diff --git a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx index 36b2e2d48..b8c4a92ff 100644 --- a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx +++ b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx @@ -37,12 +37,17 @@ #include #include +#include + #include #include #include #include +#include #include +#include #include +#include #include #include #include @@ -67,6 +72,7 @@ #include #include #include +#include #include #include #include @@ -218,12 +224,9 @@ namespace */ 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 ); }; // -------------------------------------------------------------------------- /*! @@ -276,6 +279,9 @@ namespace gp_XYZ _axes [3]; // axis directions vector< GridLine > _lines [3]; // in 3 directions double _tol, _minCellSize; + gp_XYZ _origin; + gp_Mat _invB; // inverted basis of _axes + //bool _isOrthogonalAxes; vector< const SMDS_MeshNode* > _nodes; // mesh nodes at grid nodes vector< const F_IntersectPoint* > _gridIntP; // grid node intersection with geometry @@ -301,7 +307,8 @@ namespace const vector& yCoords, const vector& zCoords, const double* axesDirs, - const TopoDS_Shape& shape ); + const Bnd_Box& bndBox ); + void ComputeUVW(const gp_XYZ& p, double uvw[3]); void ComputeNodes(SMESH_MesherHelper& helper); }; #ifdef ELLIPSOLID_WORKAROUND @@ -646,7 +653,7 @@ namespace inline void locateValue( int & i, double val, const vector& values, int& di, double tol ) { - val += values[0]; // input \a val is measured from 0. + //val += values[0]; // input \a val is measured from 0. if ( i > values.size()-2 ) i = values.size()-2; else @@ -721,16 +728,6 @@ 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 */ @@ -793,11 +790,12 @@ namespace const vector& yCoords, const vector& zCoords, const double* axesDirs, - const TopoDS_Shape& shape) + const Bnd_Box& shapeBox) { _coords[0] = xCoords; _coords[1] = yCoords; _coords[2] = zCoords; + _axes[0].SetCoord( axesDirs[0], axesDirs[1], axesDirs[2]); @@ -807,6 +805,16 @@ namespace _axes[2].SetCoord( axesDirs[6], axesDirs[7], axesDirs[8]); + _axes[0].Normalize(); + _axes[1].Normalize(); + _axes[2].Normalize(); + + _invB.SetCols( _axes[0], _axes[1], _axes[2] ); + _invB.Invert(); + + // _isOrthogonalAxes = ( Abs( _axes[0] * _axes[1] ) < 1e-20 && + // Abs( _axes[1] * _axes[2] ) < 1e-20 && + // Abs( _axes[2] * _axes[0] ) < 1e-20 ); // compute tolerance _minCellSize = Precision::Infinite(); @@ -821,21 +829,37 @@ namespace } if ( _minCellSize < Precision::Confusion() ) throw SMESH_ComputeError (COMPERR_ALGO_FAILED, - SMESH_Comment("Too small cell size: ") << _tol ); + SMESH_Comment("Too small cell size: ") << _minCellSize ); _tol = _minCellSize / 1000.; - // attune grid extremities to shape bounding box computed by vertices - Bnd_Box shapeBox; - for ( TopExp_Explorer vExp( shape, TopAbs_VERTEX ); vExp.More(); vExp.Next() ) - shapeBox.Add( BRep_Tool::Pnt( TopoDS::Vertex( vExp.Current() ))); - + // attune grid extremities to shape bounding box + double sP[6]; // aXmin, aYmin, aZmin, aXmax, aYmax, aZmax shapeBox.Get(sP[0],sP[1],sP[2],sP[3],sP[4],sP[5]); double* cP[6] = { &_coords[0].front(), &_coords[1].front(), &_coords[2].front(), &_coords[0].back(), &_coords[1].back(), &_coords[2].back() }; for ( int i = 0; i < 6; ++i ) if ( fabs( sP[i] - *cP[i] ) < _tol ) - *cP[i] = sP[i] + _tol/1000. * ( i < 3 ? +1 : -1 ); + *cP[i] = sP[i];// + _tol/1000. * ( i < 3 ? +1 : -1 ); + + for ( int iDir = 0; iDir < 3; ++iDir ) + { + if ( _coords[iDir][0] - sP[iDir] > _tol ) + { + _minCellSize = Min( _minCellSize, _coords[iDir][0] - sP[iDir] ); + _coords[iDir].insert( _coords[iDir].begin(), sP[iDir] + _tol/1000.); + } + if ( sP[iDir+3] - _coords[iDir].back() > _tol ) + { + _minCellSize = Min( _minCellSize, sP[iDir+3] - _coords[iDir].back() ); + _coords[iDir].push_back( sP[iDir+3] - _tol/1000.); + } + } + _tol = _minCellSize / 1000.; + + _origin = ( _coords[0][0] * _axes[0] + + _coords[1][0] * _axes[1] + + _coords[2][0] * _axes[2] ); // create lines for ( int iDir = 0; iDir < 3; ++iDir ) // loop on 3 line directions @@ -843,17 +867,34 @@ namespace LineIndexer li = GetLineIndexer( iDir ); _lines[iDir].resize( li.NbLines() ); double len = _coords[ iDir ].back() - _coords[iDir].front(); - gp_Vec dir( iDir==0, iDir==1, iDir==2 ); for ( ; li.More(); ++li ) { GridLine& gl = _lines[iDir][ li.LineIndex() ]; - gl._line.SetLocation(gp_Pnt(_coords[0][li.I()], _coords[1][li.J()], _coords[2][li.K()])); - gl._line.SetDirection( dir ); + gl._line.SetLocation( _coords[0][li.I()] * _axes[0] + + _coords[1][li.J()] * _axes[1] + + _coords[2][li.K()] * _axes[2] ); + gl._line.SetDirection( _axes[ iDir ]); gl._length = len; } } } //================================================================================ + /* + * Computes coordinates of a point in the grid CS + */ + void Grid::ComputeUVW(const gp_XYZ& P, double UVW[3]) + { + // gp_XYZ p = P - _origin; + // UVW[ 0 ] = p.X() * _invB( 1, 1 ) + p.Y() * _invB( 1, 2 ) + p.Z() * _invB( 1, 3 ); + // UVW[ 1 ] = p.X() * _invB( 2, 1 ) + p.Y() * _invB( 2, 2 ) + p.Z() * _invB( 2, 3 ); + // UVW[ 2 ] = p.X() * _invB( 3, 1 ) + p.Y() * _invB( 3, 2 ) + p.Z() * _invB( 3, 3 ); + // UVW[ 0 ] += _coords[0][0]; + // UVW[ 1 ] += _coords[1][0]; + // UVW[ 2 ] += _coords[2][0]; + gp_XYZ p = P * _invB; + p.Coord( UVW[0], UVW[1], UVW[2] ); + } + //================================================================================ /* * Creates all nodes */ @@ -882,12 +923,16 @@ namespace nIndex0 = NodeIndex( li.I(), li.J(), li.K() ); GridLine& line = _lines[ iDir ][ li.LineIndex() ]; + const gp_XYZ lineLoc = line._line.Location().XYZ(); + const gp_XYZ lineDir = line._line.Direction().XYZ(); line.RemoveExcessIntPoints( _tol ); multiset< F_IntersectPoint >& intPnts = _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(); + const double* nodeCoord = & coords[0]; + const double* coord0 = nodeCoord; + const double* coordEnd = coord0 + coords.size(); double nodeParam = 0; for ( ; ip != intPnts.end(); ++ip ) { @@ -910,10 +955,11 @@ namespace // create a mesh node on a GridLine at ip if it does not coincide with a grid node if ( nodeParam > ip->_paramOnLine + _tol ) { - li.SetIndexOnLine( 0 ); - double xyz[3] = { _coords[0][ li.I() ], _coords[1][ li.J() ], _coords[2][ li.K() ]}; - xyz[ li._iConst ] += ip->_paramOnLine; - ip->_node = helper.AddNode( xyz[0], xyz[1], xyz[2] ); + // li.SetIndexOnLine( 0 ); + // double xyz[3] = { _coords[0][ li.I() ], _coords[1][ li.J() ], _coords[2][ li.K() ]}; + // xyz[ li._iConst ] += ip->_paramOnLine; + gp_XYZ xyz = lineLoc + ip->_paramOnLine * lineDir; + ip->_node = helper.AddNode( xyz.X(), xyz.Y(), xyz.Z() ); ip->_indexOnLine = nodeCoord-coord0-1; } // create a mesh node at ip concident with a grid node @@ -922,9 +968,10 @@ namespace int nodeIndex = nIndex0 + nShift * ( nodeCoord-coord0 ); if ( !_nodes[ nodeIndex ] ) { - li.SetIndexOnLine( nodeCoord-coord0 ); - double xyz[3] = { _coords[0][ li.I() ], _coords[1][ li.J() ], _coords[2][ li.K() ]}; - _nodes [ nodeIndex ] = helper.AddNode( xyz[0], xyz[1], xyz[2] ); + //li.SetIndexOnLine( nodeCoord-coord0 ); + //double xyz[3] = { _coords[0][ li.I() ], _coords[1][ li.J() ], _coords[2][ li.K() ]}; + gp_XYZ xyz = lineLoc + nodeParam * lineDir; + _nodes [ nodeIndex ] = helper.AddNode( xyz.X(), xyz.Y(), xyz.Z() ); _gridIntP[ nodeIndex ] = & * ip; } if ( _gridIntP[ nodeIndex ] ) @@ -951,7 +998,13 @@ namespace { size_t nodeIndex = NodeIndex( x, y, z ); if ( !isNodeOut[ nodeIndex ] && !_nodes[ nodeIndex] ) - _nodes[ nodeIndex ] = helper.AddNode( _coords[0][x], _coords[1][y], _coords[2][z] ); + { + //_nodes[ nodeIndex ] = helper.AddNode( _coords[0][x], _coords[1][y], _coords[2][z] ); + gp_XYZ xyz = ( _coords[0][x] * _axes[0] + + _coords[1][y] * _axes[1] + + _coords[2][z] * _axes[2] ); + _nodes[ nodeIndex ] = helper.AddNode( xyz.X(), xyz.Y(), xyz.Z() ); + } } #ifdef _MY_DEBUG_ @@ -1000,72 +1053,72 @@ namespace */ bool FaceGridIntersector::IsInGrid(const Bnd_Box& gridBox) { - double x0,y0,z0, x1,y1,z1; - const Bnd_Box& faceBox = GetFaceBndBox(); - faceBox.Get(x0,y0,z0, x1,y1,z1); - - if ( !gridBox.IsOut( gp_Pnt( x0,y0,z0 )) && - !gridBox.IsOut( gp_Pnt( x1,y1,z1 ))) - return true; - - double X0,Y0,Z0, X1,Y1,Z1; - gridBox.Get(X0,Y0,Z0, X1,Y1,Z1); - double faceP[6] = { x0,y0,z0, x1,y1,z1 }; - double gridP[6] = { X0,Y0,Z0, X1,Y1,Z1 }; - gp_Dir axes[3] = { gp::DX(), gp::DY(), gp::DZ() }; - for ( int iDir = 0; iDir < 6; ++iDir ) - { - if ( iDir < 3 && gridP[ iDir ] <= faceP[ iDir ] ) continue; - if ( iDir >= 3 && gridP[ iDir ] >= faceP[ iDir ] ) continue; - - // check if the face intersects a side of a gridBox + // double x0,y0,z0, x1,y1,z1; + // const Bnd_Box& faceBox = GetFaceBndBox(); + // faceBox.Get(x0,y0,z0, x1,y1,z1); + + // if ( !gridBox.IsOut( gp_Pnt( x0,y0,z0 )) && + // !gridBox.IsOut( gp_Pnt( x1,y1,z1 ))) + // return true; + + // double X0,Y0,Z0, X1,Y1,Z1; + // gridBox.Get(X0,Y0,Z0, X1,Y1,Z1); + // double faceP[6] = { x0,y0,z0, x1,y1,z1 }; + // double gridP[6] = { X0,Y0,Z0, X1,Y1,Z1 }; + // gp_Dir axes[3] = { gp::DX(), gp::DY(), gp::DZ() }; + // for ( int iDir = 0; iDir < 6; ++iDir ) + // { + // if ( iDir < 3 && gridP[ iDir ] <= faceP[ iDir ] ) continue; + // if ( iDir >= 3 && gridP[ iDir ] >= faceP[ iDir ] ) continue; - gp_Pnt p = iDir < 3 ? gp_Pnt( X0,Y0,Z0 ) : gp_Pnt( X1,Y1,Z1 ); - gp_Ax1 norm( p, axes[ iDir % 3 ] ); - if ( iDir < 3 ) norm.Reverse(); + // // check if the face intersects a side of a gridBox - gp_XYZ O = norm.Location().XYZ(), N = norm.Direction().XYZ(); + // gp_Pnt p = iDir < 3 ? gp_Pnt( X0,Y0,Z0 ) : gp_Pnt( X1,Y1,Z1 ); + // gp_Ax1 norm( p, axes[ iDir % 3 ] ); + // if ( iDir < 3 ) norm.Reverse(); - TopLoc_Location loc = _face.Location(); - Handle(Poly_Triangulation) aPoly = BRep_Tool::Triangulation(_face,loc); - if ( !aPoly.IsNull() ) - { - if ( !loc.IsIdentity() ) - { - norm.Transform( loc.Transformation().Inverted() ); - O = norm.Location().XYZ(), N = norm.Direction().XYZ(); - } - const double deflection = aPoly->Deflection(); + // gp_XYZ O = norm.Location().XYZ(), N = norm.Direction().XYZ(); - const TColgp_Array1OfPnt& nodes = aPoly->Nodes(); - for ( int i = nodes.Lower(); i <= nodes.Upper(); ++i ) - if (( nodes( i ).XYZ() - O ) * N > _grid->_tol + deflection ) - return false; - } - else - { - BRepAdaptor_Surface surf( _face ); - double u0, u1, v0, v1, du, dv, u, v; - BRepTools::UVBounds( _face, u0, u1, v0, v1); - if ( surf.GetType() == GeomAbs_Plane ) { - du = u1 - u0, dv = v1 - v0; - } - else { - du = surf.UResolution( _grid->_minCellSize / 10. ); - dv = surf.VResolution( _grid->_minCellSize / 10. ); - } - for ( u = u0, v = v0; u <= u1 && v <= v1; u += du, v += dv ) - { - gp_Pnt p = surf.Value( u, v ); - if (( p.XYZ() - O ) * N > _grid->_tol ) - { - TopAbs_State state = GetCurveFaceIntersector()->ClassifyUVPoint(gp_Pnt2d( u, v )); - if ( state == TopAbs_IN || state == TopAbs_ON ) - return false; - } - } - } - } + // TopLoc_Location loc = _face.Location(); + // Handle(Poly_Triangulation) aPoly = BRep_Tool::Triangulation(_face,loc); + // if ( !aPoly.IsNull() ) + // { + // if ( !loc.IsIdentity() ) + // { + // norm.Transform( loc.Transformation().Inverted() ); + // O = norm.Location().XYZ(), N = norm.Direction().XYZ(); + // } + // const double deflection = aPoly->Deflection(); + + // const TColgp_Array1OfPnt& nodes = aPoly->Nodes(); + // for ( int i = nodes.Lower(); i <= nodes.Upper(); ++i ) + // if (( nodes( i ).XYZ() - O ) * N > _grid->_tol + deflection ) + // return false; + // } + // else + // { + // BRepAdaptor_Surface surf( _face ); + // double u0, u1, v0, v1, du, dv, u, v; + // BRepTools::UVBounds( _face, u0, u1, v0, v1); + // if ( surf.GetType() == GeomAbs_Plane ) { + // du = u1 - u0, dv = v1 - v0; + // } + // else { + // du = surf.UResolution( _grid->_minCellSize / 10. ); + // dv = surf.VResolution( _grid->_minCellSize / 10. ); + // } + // for ( u = u0, v = v0; u <= u1 && v <= v1; u += du, v += dv ) + // { + // gp_Pnt p = surf.Value( u, v ); + // if (( p.XYZ() - O ) * N > _grid->_tol ) + // { + // TopAbs_State state = GetCurveFaceIntersector()->ClassifyUVPoint(gp_Pnt2d( u, v )); + // if ( state == TopAbs_IN || state == TopAbs_ON ) + // return false; + // } + // } + // } + // } return true; } //============================================================================= @@ -1139,7 +1192,7 @@ namespace if ( _bndBox.IsOut( gridLine._line )) continue; intersector._intPoints.clear(); - (intersector.*interFunction)( gridLine ); + (intersector.*interFunction)( gridLine ); // <- intersection with gridLine for ( size_t i = 0; i < intersector._intPoints.size(); ++i ) _intersections.push_back( make_pair( &gridLine, intersector._intPoints[i] )); } @@ -1188,7 +1241,7 @@ namespace */ void FaceLineIntersector::IntersectWithCylinder(const GridLine& gridLine) { - IntAna_IntConicQuad linCylinder( gridLine._line,_cylinder); + IntAna_IntConicQuad linCylinder( gridLine._line, _cylinder ); if ( linCylinder.IsDone() && linCylinder.NbPoints() > 0 ) { _w = linCylinder.ParamOnConic(1); @@ -1644,6 +1697,23 @@ namespace } // loop on _edgeIntPnts } + else if ( 3 < _nbCornerNodes && _nbCornerNodes < 8 ) // _nbIntNodes == 0 + { + _Link split; + // create sub-links (_splits) of whole links + for ( int iLink = 0; iLink < 12; ++iLink ) + { + _Link& link = _hexLinks[ iLink ]; + link._splits.clear(); + if ( link._nodes[ 0 ]->Node() && link._nodes[ 1 ]->Node() ) + { + split._nodes[ 0 ] = link._nodes[0]; + split._nodes[ 1 ] = link._nodes[1]; + link._splits.push_back( split ); + } + } + } + } //================================================================================ /*! @@ -1877,8 +1947,8 @@ namespace { curLink = freeLinks[ iL ]; freeLinks[ iL ] = 0; - polygon._links.push_back( *curLink ); --nbFreeLinks; + polygon._links.push_back( *curLink ); } } while ( curLink ); } @@ -1983,16 +2053,23 @@ namespace } // if there are intersections with EDGEs - if ( polygon._links.size() < 3 || + if ( polygon._links.size() < 2 || polygon._links[0].LastNode() != polygon._links.back().FirstNode() ) return; // closed polygon not found -> invalid polyhedron - // add polygon to its links - for ( size_t iL = 0; iL < polygon._links.size(); ++iL ) + if ( polygon._links.size() == 2 ) { - polygon._links[ iL ]._link->_faces.reserve( 2 ); - polygon._links[ iL ]._link->_faces.push_back( &polygon ); - polygon._links[ iL ].Reverse(); + _polygons.pop_back(); + } + else + { + // add polygon to its links + 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 ); + polygon._links[ iL ].Reverse(); + } } } // while ( nbFreeLinks > 0 ) @@ -2096,12 +2173,12 @@ namespace if ( hex ) { intHexInd[ nbIntHex++ ] = i; - if ( hex->_nbIntNodes > 0 ) continue; - init( hex->_i, hex->_j, hex->_k ); + if ( hex->_nbIntNodes > 0 ) continue; // treat intersected hex later + this->init( hex->_i, hex->_j, hex->_k ); } else { - init( i ); + this->init( i ); } if ( _nbCornerNodes == 8 && ( _nbBndNodes < _nbCornerNodes || !isInHole() )) { @@ -2124,7 +2201,10 @@ namespace { // all intersection of hex with geometry are at grid nodes hex = new Hexahedron( *this ); - hex->init( i ); + //hex->init( i ); + hex->_i = _i; + hex->_j = _j; + hex->_k = _k; intHexInd.push_back(0); intHexInd[ nbIntHex++ ] = i; } @@ -2168,39 +2248,27 @@ namespace // 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._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 ) + for ( int i = 1; i < planes._zProjs.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(); @@ -2209,6 +2277,8 @@ namespace const TGeomID edgeID = e2fIt->first; const TopoDS_Edge & E = TopoDS::Edge( _grid->_shapes( edgeID )); BRepAdaptor_Curve curve( E ); + TopoDS_Vertex v1 = helper.IthVertex( 0, E, false ); + TopoDS_Vertex v2 = helper.IthVertex( 1, E, false ); ip._faceIDs = e2fIt->second; ip._shapeID = edgeID; @@ -2226,34 +2296,34 @@ namespace 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; + double zLen = _grid->_coords[ iDirZ ].back() - _grid->_coords[ iDirZ ][0]; + //double zFactor = _grid->_axes[ iDirZ ] * planes._zNorm; int dIJK[3], d000[3] = { 0,0,0 }; + double o[3] = { _grid->_coords[0][0], + _grid->_coords[1][0], + _grid->_coords[2][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 ); + double zProj1 = planes._zNorm * ( p1 - _grid->_origin ); + + _grid->ComputeUVW( p1, ip._uvw ); + int iX1 = int(( ip._uvw[iDirX] - o[iDirX]) / xLen * (_grid->_coords[ iDirX ].size() - 1)); + int iY1 = int(( ip._uvw[iDirY] - o[iDirY]) / yLen * (_grid->_coords[ iDirY ].size() - 1)); + int iZ1 = int(( ip._uvw[iDirZ] - o[iDirZ]) / zLen * (_grid->_coords[ iDirZ ].size() - 1)); + locateValue( iX1, ip._uvw[iDirX], _grid->_coords[ iDirX ], dIJK[ iDirX ], tol ); + locateValue( iY1, ip._uvw[iDirY], _grid->_coords[ iDirY ], dIJK[ iDirY ], tol ); + locateValue( iZ1, ip._uvw[iDirZ], _grid->_coords[ iDirZ ], dIJK[ iDirZ ], tol ); int ijk[3]; // grid index where a segment intersect a plane 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 )) @@ -2264,7 +2334,7 @@ namespace // 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] ); + double zProj2 = planes._zNorm * ( p2 - _grid->_origin ); int iZ2 = iZ1; locateValue( iZ2, zProj2, planes._zProjs, dIJK[ iDirZ ], tol ); @@ -2275,14 +2345,11 @@ namespace { 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 ); + curve, planes._zNorm, _grid->_origin ); + _grid->ComputeUVW( ip._point.XYZ(), ip._uvw ); + locateValue( ijk[iDirX], ip._uvw[iDirX], _grid->_coords[iDirX], dIJK[iDirX], tol ); + locateValue( ijk[iDirY], ip._uvw[iDirY], _grid->_coords[iDirY], dIJK[iDirY], tol ); ijk[ iDirZ ] = 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 ); @@ -2303,15 +2370,11 @@ namespace // 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->ComputeUVW( p1, ip._uvw ); + locateValue( ijk[iDirX], ip._uvw[iDirX], _grid->_coords[iDirX], dIJK[iDirX], tol ); + locateValue( ijk[iDirY], ip._uvw[iDirY], _grid->_coords[iDirY], dIJK[iDirY], tol ); + ijk[ iDirZ ] = iZ1; _grid->_edgeIntP.push_back( ip ); if ( !addIntersection( _grid->_edgeIntP.back(), hexes, ijk, d000 )) _grid->_edgeIntP.pop_back(); @@ -2356,8 +2419,6 @@ namespace * \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, @@ -2381,7 +2442,7 @@ namespace //================================================================================ /*! - * \brief Returns index of a hexahedron sub-entities holding a point + * \brief Returns indices of a hexahedron sub-entities holding a point * \param [in] ip - intersection point * \param [out] facets - 0-3 facets holding a point * \param [out] sub - index of a vertex or an edge holding a point @@ -2466,7 +2527,7 @@ namespace }; for ( int i = 0; i < 4; ++i ) { - if ( 0 <= hexIndex[i] && hexIndex[i] < hexes.size() && hexes[ hexIndex[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 @@ -2789,6 +2850,90 @@ namespace return false; } + //================================================================================ + /*! + * \brief computes exact bounding box with axes parallel to given ones + */ + //================================================================================ + + void getExactBndBox( const vector< TopoDS_Shape >& faceVec, + const double* axesDirs, + Bnd_Box& shapeBox ) + { + BRep_Builder b; + TopoDS_Compound allFacesComp; + b.MakeCompound( allFacesComp ); + for ( size_t iF = 0; iF < faceVec.size(); ++iF ) + b.Add( allFacesComp, faceVec[ iF ] ); + + double sP[6]; // aXmin, aYmin, aZmin, aXmax, aYmax, aZmax + shapeBox.Get(sP[0],sP[1],sP[2],sP[3],sP[4],sP[5]); + double farDist = 0; + for ( int i = 0; i < 6; ++i ) + farDist = Max( farDist, 10 * sP[i] ); + + gp_XYZ axis[3] = { gp_XYZ( axesDirs[0], axesDirs[1], axesDirs[2] ), + gp_XYZ( axesDirs[3], axesDirs[4], axesDirs[5] ), + gp_XYZ( axesDirs[6], axesDirs[7], axesDirs[8] ) }; + axis[0].Normalize(); + axis[1].Normalize(); + axis[2].Normalize(); + + gp_Mat basis( axis[0], axis[1], axis[2] ); + gp_Mat bi = basis.Inverted(); + + gp_Pnt pMin, pMax; + for ( int iDir = 0; iDir < 3; ++iDir ) + { + gp_XYZ axis0 = axis[ iDir ]; + gp_XYZ axis1 = axis[ ( iDir + 1 ) % 3 ]; + gp_XYZ axis2 = axis[ ( iDir + 2 ) % 3 ]; + for ( int isMax = 0; isMax < 2; ++isMax ) + { + double shift = isMax ? farDist : -farDist; + gp_XYZ orig = shift * axis0; + gp_XYZ norm = axis1 ^ axis2; + gp_Pln pln( orig, norm ); + norm = pln.Axis().Direction().XYZ(); + BRepBuilderAPI_MakeFace plane( pln, -farDist, farDist, -farDist, farDist ); + + gp_Pnt& pAxis = isMax ? pMax : pMin; + gp_Pnt pPlane, pFaces; + double dist = GEOMUtils::GetMinDistance( plane, allFacesComp, pPlane, pFaces ); + if ( dist < 0 ) + { + Bnd_B3d bb; + gp_XYZ corner; + for ( int i = 0; i < 2; ++i ) { + corner.SetCoord( 1, sP[ i*3 ]); + for ( int j = 0; j < 2; ++j ) { + corner.SetCoord( 2, sP[ i*3 + 1 ]); + for ( int k = 0; k < 2; ++k ) + { + corner.SetCoord( 3, sP[ i*3 + 2 ]); + corner *= bi; + bb.Add( corner ); + } + } + } + corner = isMax ? bb.CornerMax() : bb.CornerMin(); + pAxis.SetCoord( iDir+1, corner.Coord( iDir+1 )); + } + else + { + gp_XYZ pf = pFaces.XYZ() * bi; + pAxis.SetCoord( iDir+1, pf.Coord( iDir+1 ) ); + } + } + } // loop on 3 axes + + shapeBox.SetVoid(); + shapeBox.Add( pMin ); + shapeBox.Add( pMax ); + + return; + } + } // namespace //============================================================================= @@ -2826,14 +2971,19 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, 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 + TopExp_Explorer fExp; + for ( fExp.Init( theShape, TopAbs_FACE ); fExp.More(); fExp.Next() ) + if ( !faceMap.Add( fExp.Current() )) + faceMap.Remove( fExp.Current() ); // remove a face shared by two solids + + for ( fExp.ReInit(); fExp.More(); fExp.Next() ) + if ( faceMap.Contains( fExp.Current() )) faceVec.push_back( fExp.Current() ); } - Bnd_Box shapeBox; vector facesItersectors( faceVec.size() ); map< TGeomID, vector< TGeomID > > edge2faceIDsMap; TopExp_Explorer eExp; + Bnd_Box shapeBox; for ( int i = 0; i < faceVec.size(); ++i ) { facesItersectors[i]._face = TopoDS::Face ( faceVec[i] ); @@ -2850,32 +3000,13 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, } } + getExactBndBox( faceVec, _hyp->GetAxisDirs(), shapeBox ); + vector xCoords, yCoords, zCoords; _hyp->GetCoordinates( xCoords, yCoords, zCoords, shapeBox ); - grid.SetCoordinates( xCoords, yCoords, zCoords, _hyp->GetAxisDirs(), theShape ); - - // check if the grid encloses the shape - if ( !_hyp->IsGridBySpacing(0) || - !_hyp->IsGridBySpacing(1) || - !_hyp->IsGridBySpacing(2) ) - { - Bnd_Box gridBox; - gridBox.Add( gp_Pnt( xCoords[0], yCoords[0], zCoords[0] )); - gridBox.Add( gp_Pnt( xCoords.back(), yCoords.back(), zCoords.back() )); - double x0,y0,z0, x1,y1,z1; - shapeBox.Get(x0,y0,z0, x1,y1,z1); - if ( gridBox.IsOut( gp_Pnt( x0,y0,z0 )) || - gridBox.IsOut( gp_Pnt( x1,y1,z1 ))) - for ( size_t i = 0; i < facesItersectors.size(); ++i ) - { - if ( !facesItersectors[i].IsInGrid( gridBox )) - return error("The grid doesn't enclose the geometry"); -#ifdef ELLIPSOLID_WORKAROUND - delete facesItersectors[i]._surfaceInt, facesItersectors[i]._surfaceInt = 0; -#endif - } - } + grid.SetCoordinates( xCoords, yCoords, zCoords, _hyp->GetAxisDirs(), shapeBox ); + if ( _computeCanceled ) return false; #ifdef WITH_TBB diff --git a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx index c2b58de8d..be926470e 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx +++ b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx @@ -25,18 +25,33 @@ // SMESH includes #include "StdMeshersGUI_CartesianParamCreator.h" -#include -#include -#include -#include +#include "SMESHGUI.h" +#include "SMESHGUI_Utils.h" +#include "SMESHGUI_VTKUtils.h" +#include "SMESHGUI_HypothesesUtils.h" +#include "SMESHGUI_SpinBox.h" +#include "SMESHGUI_MeshEditPreview.h" // IDL includes #include CORBA_SERVER_HEADER(SMESH_BasicHypothesis) // SALOME GUI includes -#include -#include +#include #include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include // Qt includes #include @@ -201,11 +216,18 @@ namespace StdMeshersGUI connect( myInsertBtn, SIGNAL( clicked() ), SLOT( onInsert() )); connect( myDeleteBtn, SIGNAL( clicked() ), SLOT( onDelete() )); connect( myModeGroup, SIGNAL( buttonClicked ( int )), SLOT( onMode(int))); + connect( myModeGroup, SIGNAL( buttonClicked ( int )), SIGNAL( gridModeChanged(int))); connect( mySpacingTreeWdg, SIGNAL( itemSelectionChanged()), SLOT( updateButtons() )); connect( myCoordList, SIGNAL( itemSelectionChanged()), SLOT( updateButtons() )); connect( myStepSpin, SIGNAL( valueChanged(double)), SLOT( onStepChange() )); } + //================================================================================ + /*! + * \brief SLOT onInsert + */ + //================================================================================ + void GridAxisTab::onInsert() { if ( isGridBySpacing() ) @@ -254,6 +276,12 @@ namespace StdMeshersGUI updateButtons(); } + //================================================================================ + /*! + * \brief SLOT onDelete + */ + //================================================================================ + void GridAxisTab::onDelete() { if ( isGridBySpacing() ) @@ -283,6 +311,12 @@ namespace StdMeshersGUI updateButtons(); } + //================================================================================ + /*! + * \brief SLOT onMode + */ + //================================================================================ + void GridAxisTab::onMode(int isSpacing) { mySpacingTreeWdg->setShown( isSpacing ); @@ -313,6 +347,12 @@ namespace StdMeshersGUI updateButtons(); } + //================================================================================ + /*! + * \brief SLOT onStepChange + */ + //================================================================================ + void GridAxisTab::onStepChange() { if ( fabs( myStepSpin->GetValue() ) < 1e-100 ) @@ -323,6 +363,12 @@ namespace StdMeshersGUI myStep = myStepSpin->GetValue(); } + //================================================================================ + /*! + * \brief Enables/disables buttons + */ + //================================================================================ + void GridAxisTab::updateButtons() { bool insertEnable = false, deleteEnable = false; @@ -347,6 +393,12 @@ namespace StdMeshersGUI myDeleteBtn->setEnabled( deleteEnable ); } + //================================================================================ + /*! + * \brief Inserts coordinates into myCoordList + */ + //================================================================================ + void GridAxisTab::setCoordinates( SMESH::double_array_var coords ) { myCoordList->clear(); @@ -357,6 +409,12 @@ namespace StdMeshersGUI onMode( COORD_BUT ); } + //================================================================================ + /*! + * \brief Sets spacing got from hypothesis + */ + //================================================================================ + void GridAxisTab::setSpacing( SMESH::string_array_var funs, SMESH::double_array_var points ) { mySpacingTreeWdg->clear(); @@ -370,11 +428,23 @@ namespace StdMeshersGUI onMode( SPACING_BUT ); } + //================================================================================ + /*! + * \brief Checks grid definintion mode + */ + //================================================================================ + bool GridAxisTab::isGridBySpacing() const { return ( myModeGroup->checkedId() == SPACING_BUT ); } + //================================================================================ + /*! + * \brief Returns coordinates to set to a hypothesis + */ + //================================================================================ + SMESH::double_array* GridAxisTab::getCoordinates() { SMESH::double_array_var coords = new SMESH::double_array; @@ -385,6 +455,12 @@ namespace StdMeshersGUI return coords._retn(); } + //================================================================================ + /*! + * \brief Returms spacing to set to a hypothesis + */ + //================================================================================ + void GridAxisTab::getSpacing(SMESH::string_array_out funs, SMESH::double_array_out points) const { @@ -404,6 +480,12 @@ namespace StdMeshersGUI } + //================================================================================ + /*! + * \brief Verifies parameters + */ + //================================================================================ + bool GridAxisTab::checkParams(QString& msg, SMESH::SMESH_Hypothesis_var& hyp) const { if ( isGridBySpacing() ) @@ -432,6 +514,12 @@ namespace StdMeshersGUI return true; } + //================================================================================ + /*! + * \brief LineDelegate constructor + */ + //================================================================================ + LineDelegate::LineDelegate( QWidget* parent ): QItemDelegate( parent ), mySpacingTreeWdg( qobject_cast( parent )), @@ -439,6 +527,12 @@ namespace StdMeshersGUI { } + //================================================================================ + /*! + * \brief Creates an editor depending on a current item + */ + //================================================================================ + QWidget* LineDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& opt, const QModelIndex& index) const @@ -472,6 +566,12 @@ namespace StdMeshersGUI return w; } + //================================================================================ + /*! + * \brief Limit value range in the spin of a neighbor range + */ + //================================================================================ + void LineDelegate::setEditorData ( QWidget * editor, const QModelIndex & index ) const { if ( mySpacingTreeWdg && index.column() == 0 ) @@ -494,6 +594,13 @@ namespace StdMeshersGUI QItemDelegate::setEditorData( editor, index ); } } + + //================================================================================ + /*! + * \brief + */ + //================================================================================ + void LineDelegate::setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const @@ -530,6 +637,53 @@ namespace StdMeshersGUI } // namespace StdMeshersGUI +namespace +{ + const double theAngTol = M_PI / 180.; + + //================================================================================ + /*! + * \brief Set variables to groups of spin boxes + */ + //================================================================================ + + void setText( const QString& vars, SMESHGUI_SpinBox** spins ) + { + QStringList varList = vars.split( ':' ); + for ( int i = 0; i < 3 && i < varList.count(); ++i ) + if ( !varList[i].isEmpty() ) + spins[i]->setText( varList[i] ); + } + + //================================================================================ + /*! + * \brief Computes more 2 axes by one + * \param [in] iOk - index of a given axis + * \param [in,out] dirs - directions of 3 axes + */ + //================================================================================ + + void get3Dirs( int iOk, gp_XYZ dirs[3] ) + { + dirs[ ( iOk+1 ) % 3 ] = dirs[ iOk ]; + + if ( Abs( dirs[ iOk ].Y() ) < 1e-100 && + Abs( dirs[ iOk ].Z() ) < 1e-100 ) + // dirs[ iOk ] || OX + dirs[ ( iOk+1 ) % 3 ].SetY( dirs[ iOk ].Y() + 1. ); + else + dirs[ ( iOk+1 ) % 3 ].SetX( dirs[ iOk ].X() + 1. ); + + dirs[( iOk+2 ) % 3] = dirs[ iOk ] ^ dirs[ ( iOk+1 ) % 3 ]; + dirs[( iOk+1 ) % 3] = dirs[ ( iOk+2 ) % 3 ] ^ dirs[ iOk ]; + } +} + +//================================================================================ +/*! + * \brief StdMeshersGUI_CartesianParamCreator constructor + */ +//================================================================================ StdMeshersGUI_CartesianParamCreator::StdMeshersGUI_CartesianParamCreator(const QString& aHypType) : StdMeshersGUI_StdHypothesisCreator( aHypType ), @@ -538,8 +692,23 @@ StdMeshersGUI_CartesianParamCreator::StdMeshersGUI_CartesianParamCreator(const Q myAxisTabs[0] = 0; myAxisTabs[1] = 0; myAxisTabs[2] = 0; + + myAxesPreview = new SMESHGUI_MeshEditPreview( SMESH::GetViewWindow( SMESHGUI::GetSMESHGUI() )); + myAxesPreview->SetArrowShapeAndNb( /*nbArrows=*/3, + /*headLength=*/0.1, + /*headRadius=*/0.01, + /*start=*/0., + /*labels=*/"XYZ"); + + myDirTic[0] = myDirTic[1] = myDirTic[2] = 0; } +//================================================================================ +/*! + * \brief StdMeshersGUI_CartesianParamCreator destructor + */ +//================================================================================ + StdMeshersGUI_CartesianParamCreator::~StdMeshersGUI_CartesianParamCreator() { if ( myAxisTabs[0] ) delete myAxisTabs[0]; @@ -548,8 +717,16 @@ StdMeshersGUI_CartesianParamCreator::~StdMeshersGUI_CartesianParamCreator() myAxisTabs[0] = 0; myAxisTabs[1] = 0; myAxisTabs[2] = 0; + + delete myAxesPreview; } +//================================================================================ +/*! + * \brief Validate parameters + */ +//================================================================================ + bool StdMeshersGUI_CartesianParamCreator::checkParams( QString& msg ) const { if( !SMESHGUI_GenericHypothesisCreator::checkParams( msg ) ) @@ -568,9 +745,22 @@ bool StdMeshersGUI_CartesianParamCreator::checkParams( QString& msg ) const if ( !myAxisTabs[1]->checkParams( msg, hyp )) return false; if ( !myAxisTabs[2]->checkParams( msg, hyp )) return false; + StdMeshersGUI_CartesianParamCreator* me = (StdMeshersGUI_CartesianParamCreator*) this; + if ( !me->updateAxesPreview() ) + { + msg = tr("INVALID_AXES_DIR"); + return false; + } + return true; } +//================================================================================ +/*! + * \brief Create widgets + */ +//================================================================================ + QFrame* StdMeshersGUI_CartesianParamCreator::buildFrame() { QFrame* fr = new QFrame(); @@ -625,10 +815,154 @@ QFrame* StdMeshersGUI_CartesianParamCreator::buildFrame() tabWdg->addTab( myAxisTabs[ 1 ], tr( "AXIS_Y" ) ); tabWdg->addTab( myAxisTabs[ 2 ], tr( "AXIS_Z" ) ); argGroupLayout->addWidget( tabWdg, row, 0, 1, 2 ); + row++; + + QPixmap aPix = SMESHGUI::resourceMgr()->loadPixmap("SMESH", tr("ICON_SELECT")); + + // 4) Fixed point + myFixedPointGrp = new QGroupBox( tr("FIXED_POINT"), fr ); + myFixedPointGrp->setCheckable( true ); + //QPushButton* pointBtn = new QPushButton( QIcon(aPix), "", myFixedPointGrp ); + QLabel* pXLbl = new QLabel( tr("SMESH_X"), myFixedPointGrp ); + QLabel* pYLbl = new QLabel( tr("SMESH_Y"), myFixedPointGrp ); + QLabel* pZLbl = new QLabel( tr("SMESH_Z"), myFixedPointGrp ); + for ( int i = 0; i < 3; ++i ) + { + myPointSpin[i] = new SMESHGUI_SpinBox( myFixedPointGrp ); + myPointSpin[i]->RangeStepAndValidator( -1e20, 1e20, 10 ); + myPointSpin[i]->SetValue( 0. ); + } + QHBoxLayout* aFixedPointLay = new QHBoxLayout( myFixedPointGrp ); + aFixedPointLay->addWidget( pXLbl, 0, Qt::AlignRight ); + aFixedPointLay->addWidget( myPointSpin[0], 1 ); + aFixedPointLay->addWidget( pYLbl, 0, Qt::AlignRight ); + aFixedPointLay->addWidget( myPointSpin[1], 1 ); + aFixedPointLay->addWidget( pZLbl, 0, Qt::AlignRight ); + aFixedPointLay->addWidget( myPointSpin[2], 1 ); + argGroupLayout->addWidget( myFixedPointGrp, row, 0, 1, 2 ); + row++; + + // 5) Axes direction + QGroupBox* axesDirGrp = new QGroupBox( tr("AXES_DIRECTION"), fr ); + QGridLayout* axisDirLay = new QGridLayout( axesDirGrp ); + axisDirLay->setSpacing( SPACING ); + axisDirLay->setMargin( MARGIN ); + axisDirLay->setColumnStretch( 0, 2 ); + // is orthogonal + myOrthogonalChk = new QCheckBox( tr("ORTHOGONAL_AXES"), axesDirGrp ); + axisDirLay->addWidget( myOrthogonalChk, 0, 0, 1, 7 ); + // axes + QLabel* axisLbl[3]; + axisLbl[0] = new QLabel( tr( "AXIS_X"), axesDirGrp ); + axisLbl[1] = new QLabel( tr( "AXIS_Y"), axesDirGrp ); + axisLbl[2] = new QLabel( tr( "AXIS_Z"), axesDirGrp ); + QLabel* dLbl[3]; + myAxisBtnGrp = new QButtonGroup( axesDirGrp ); + SMESHGUI_SpinBox** spins[3] = { &myXDirSpin[0], &myYDirSpin[0], &myZDirSpin[0] }; + for ( int i = 0; i < 3; ++i ) + { + QPushButton* axisBtn = new QPushButton( QIcon(aPix), "", axesDirGrp ); + axisBtn->setCheckable( true ); + myAxisBtnGrp->addButton( axisBtn, i ); + myXDirSpin[i] = new SMESHGUI_SpinBox( axesDirGrp ); + myYDirSpin[i] = new SMESHGUI_SpinBox( axesDirGrp ); + myZDirSpin[i] = new SMESHGUI_SpinBox( axesDirGrp ); + myXDirSpin[i]->RangeStepAndValidator( -1, 1, 0.1, "len_tol_precision" ); + myYDirSpin[i]->RangeStepAndValidator( -1, 1, 0.1, "len_tol_precision" ); + myZDirSpin[i]->RangeStepAndValidator( -1, 1, 0.1, "len_tol_precision" ); + dLbl[0] = new QLabel( tr("SMESH_DX"), axesDirGrp ); + dLbl[1] = new QLabel( tr("SMESH_DY"), axesDirGrp ); + dLbl[2] = new QLabel( tr("SMESH_DZ"), axesDirGrp ); + axisDirLay->addWidget( axisLbl[i], i+1, 0 ); + axisDirLay->addWidget( axisBtn, i+1, 1 ); + axisDirLay->addWidget( dLbl[0], i+1, 2 ); + axisDirLay->addWidget( dLbl[1], i+1, 4 ); + axisDirLay->addWidget( dLbl[2], i+1, 6 ); + axisDirLay->addWidget( myXDirSpin[i], 1, 3+i*2 ); + axisDirLay->addWidget( myYDirSpin[i], 2, 3+i*2 ); + axisDirLay->addWidget( myZDirSpin[i], 3, 3+i*2 ); + } + axisDirLay->setColumnStretch( 3, 10 ); + axisDirLay->setColumnStretch( 5, 10 ); + axisDirLay->setColumnStretch( 7, 10 ); + + // set optimal axes + QPushButton* optimBtn = new QPushButton( tr("OPTIMAL_AXES"), axesDirGrp ); + QPushButton* resetBtn = new QPushButton( tr("RESET_AXES"), axesDirGrp ); + axisDirLay->addWidget( optimBtn, 4, 0, 1, 4 ); + axisDirLay->addWidget( resetBtn, 4, 4, 1, 4 ); + + argGroupLayout->addWidget( axesDirGrp, row, 0, 1, 2 ); + row++; + + // Signals + + LightApp_SelectionMgr* selMgr = SMESH::GetSelectionMgr( SMESHGUI::GetSMESHGUI() ); + + connect( selMgr, SIGNAL( currentSelectionChanged()), SLOT( onSelectionChange())); + connect( myOrthogonalChk, SIGNAL( toggled(bool)), SLOT( onOrthogonalAxes(bool))); + connect( optimBtn, SIGNAL( clicked(bool)), SLOT( onOptimalAxes(bool))); + connect( resetBtn, SIGNAL( clicked(bool)), SLOT( onResetAxes(bool))); + for ( int i = 0; i < 3; ++i ) + { + connect( myXDirSpin[i], SIGNAL(valueChanged (const QString&)), + this, SLOT (onAxisDirChange(const QString&)) ); + connect( myYDirSpin[i], SIGNAL(valueChanged (const QString&)), + this, SLOT (onAxisDirChange(const QString&)) ); + connect( myZDirSpin[i], SIGNAL(valueChanged (const QString&)), + this, SLOT (onAxisDirChange(const QString&)) ); + connect( myAxisTabs[i], SIGNAL(gridModeChanged(int)), + this, SLOT (onGridModeChanged(int))); + } + + // Show axes + myAxesLen = 1; + myOrigin[0] = myOrigin[1] = myOrigin[2] = 0.; + TopoDS_Shape shape; + QString shapeEntry = getMainShapeEntry(); + if ( !shapeEntry.isEmpty() ) + { + // find origin + Handle(SALOME_InteractiveObject) io = + new SALOME_InteractiveObject( shapeEntry.toStdString().c_str(), "GEOM" ); + GEOM::GEOM_Object_var geomObj = SMESH::IObjectToInterface( io ); + if ( GEOMBase::GetShape( geomObj, shape ) && !shape.IsNull()) + { + Bnd_Box box; + BRepBndLib::Add( shape, box ); + double max[3]; + if ( !box.IsVoid() ) + { + box.Get( myOrigin[0], myOrigin[1], myOrigin[2], max[0], max[1], max[2] ); + gp_Pnt o( myOrigin[0], myOrigin[1], myOrigin[2] ); + gp_Pnt x( max[0], max[1], max[2] ); + myAxesLen = o.Distance( x ); + + double step = 1e20; + while ( step > myAxesLen / 5 ) + step /= 10; + myPointSpin[0]->SetStep( step ); + myPointSpin[1]->SetStep( step ); + myPointSpin[2]->SetStep( step ); + } + } + } + myAxisBtnGrp->button(0)->setEnabled( !shape.IsNull() ); + myAxisBtnGrp->button(1)->setEnabled( !shape.IsNull() ); + myAxisBtnGrp->button(2)->setEnabled( !shape.IsNull() ); + optimBtn->setEnabled( !shape.IsNull() ); + + updateAxesPreview(); return fr; } +//================================================================================ +/*! + * \brief Tranfer parameters from hypothesis to widgets + */ +//================================================================================ + void StdMeshersGUI_CartesianParamCreator::retrieveParams() const { StdMeshers::StdMeshers_CartesianParameters3D_var h = @@ -645,6 +979,7 @@ void StdMeshersGUI_CartesianParamCreator::retrieveParams() const myAddEdges->setChecked( h->GetToAddEdges() ); + // grid definition for ( int ax = 0; ax < 3; ++ax ) { if ( h->IsGridBySpacing( ax )) @@ -660,11 +995,62 @@ void StdMeshersGUI_CartesianParamCreator::retrieveParams() const myAxisTabs[ax]->setCoordinates( coords ); } } + + // fixed point + SMESH::PointStruct fp; + StdMeshersGUI_CartesianParamCreator* me = (StdMeshersGUI_CartesianParamCreator*) this; + if ( h->GetFixedPoint( fp )) + { + me->myPointSpin[0]->SetValue( fp.x ); + me->myPointSpin[1]->SetValue( fp.y ); + me->myPointSpin[2]->SetValue( fp.z ); + setText( getVariableName("GetFixedPoint"), &me->myPointSpin[0] ); + myFixedPointGrp->setChecked( true ); + } + else + { + myFixedPointGrp->setChecked( false ); + } + + // axes directions + SMESHGUI_SpinBox** spins[3] = { &me->myXDirSpin[0], &me->myYDirSpin[0], &me->myZDirSpin[0] }; + SMESH::DirStruct axisDir[3]; + h->GetAxesDirs( axisDir[0], + axisDir[1], + axisDir[2]); + QString vars = getVariableName("GetAxesDirs"); + for ( int i = 0; i < 3; ++i ) + { + spins[i][0]->SetValue( axisDir[i].PS.x ); + spins[i][1]->SetValue( axisDir[i].PS.y ); + spins[i][2]->SetValue( axisDir[i].PS.z ); + setText( vars, spins[i] ); + + // cut off 3 used vars + if ( !vars.isEmpty() ) + { + int ind = -1; + for ( int j = 0; j < 3; ++j ) + if (( ind = vars.indexOf(':', ind+1 )) < 0 ) + break; + if ( ind < 0 ) + vars.clear(); + else + vars.remove( 0, ind+1 ); + } + } + if ( dlg() ) dlg()->setMinimumSize( dlg()->minimumSizeHint().width(), dlg()->minimumSizeHint().height() ); } +//================================================================================ +/*! + * \brief Tranfer parameters from widgets to hypothesis + */ +//================================================================================ + QString StdMeshersGUI_CartesianParamCreator::storeParams() const { StdMeshers::StdMeshers_CartesianParameters3D_var h = @@ -675,10 +1061,12 @@ QString StdMeshersGUI_CartesianParamCreator::storeParams() const if( isCreation() ) SMESH::SetName( SMESH::FindSObject( h ), myName->text().toLatin1().constData() ); + // threshold h->SetVarParameter( myThreshold->text().toLatin1().constData(), "SetSizeThreshold" ); h->SetSizeThreshold( myThreshold->text().toDouble() ); h->SetToAddEdges( myAddEdges->isChecked() ); + // grid for ( int ax = 0; ax < 3; ++ax ) { if ( myAxisTabs[ax]->isGridBySpacing()) @@ -694,6 +1082,40 @@ QString StdMeshersGUI_CartesianParamCreator::storeParams() const h->SetGrid( coords, ax ); } } + + // fixed point + QStringList params; + params << myPointSpin[0]->text(); + params << myPointSpin[1]->text(); + params << myPointSpin[2]->text(); + h->SetVarParameter( params.join(":").toLatin1().constData(), "SetFixedPoint" ); + params.clear(); + + SMESH::PointStruct ps; + ps.x = myPointSpin[0]->GetValue(); + ps.y = myPointSpin[1]->GetValue(); + ps.z = myPointSpin[2]->GetValue(); + h->SetFixedPoint( ps, !myFixedPointGrp->isEnabled() || !myFixedPointGrp->isChecked() ); + + // axes directions + SMESHGUI_SpinBox* const * spins[3] = { &myXDirSpin[0], &myYDirSpin[0], &myZDirSpin[0] }; + for ( int ax = 0; ax < 3; ++ax ) + { + params << spins[ax][0]->text(); + params << spins[ax][1]->text(); + params << spins[ax][2]->text(); + } + h->SetVarParameter( params.join(":").toLatin1().constData(), "SetAxesDirs" ); + + SMESH::DirStruct axDir[3]; + for ( int ax = 0; ax < 3; ++ax ) + { + axDir[ax].PS.x = spins[ax][0]->GetValue(); + axDir[ax].PS.y = spins[ax][1]->GetValue(); + axDir[ax].PS.z = spins[ax][2]->GetValue(); + } + h->SetAxesDirs( axDir[0], axDir[1], axDir[2] ); + } catch(const SALOME::SALOME_Exception& ex) { @@ -702,7 +1124,298 @@ QString StdMeshersGUI_CartesianParamCreator::storeParams() const return ""; } +//================================================================================ +/*! + * \brief Returns a name of help page + */ +//================================================================================ + QString StdMeshersGUI_CartesianParamCreator::helpPage() const { return "cartesian_algo_page.html#cartesian_hyp_anchor"; } + +//================================================================================ +/*! + * \brief Show axes if they are OK + */ +//================================================================================ + +bool StdMeshersGUI_CartesianParamCreator::updateAxesPreview() +{ + bool isOk = true; + gp_Ax1 axes[3]; + SMESHGUI_SpinBox** spins[3] = { &myXDirSpin[0], &myYDirSpin[0], &myZDirSpin[0] }; + for ( int i = 0; i < 3 && isOk; ++i ) + { + gp_XYZ dir( spins[i][0]->GetValue(), + spins[i][1]->GetValue(), + spins[i][2]->GetValue()); + if (( isOk = ( dir.Modulus() > 1e-100 ))) + axes[i].SetDirection( gp_Dir( dir )); + + axes[i].SetLocation ( gp_Pnt( myOrigin[0], + myOrigin[1], + myOrigin[2])); + } + gp_Vec norm01 = axes[0].Direction().XYZ() ^ axes[1].Direction().XYZ(); + gp_Vec norm12 = axes[1].Direction().XYZ() ^ axes[2].Direction().XYZ(); + if ( isOk ) + isOk = ( !axes[0].Direction().IsParallel( axes[1].Direction(), theAngTol ) && + !axes[1].Direction().IsParallel( axes[2].Direction(), theAngTol ) && + !axes[2].Direction().IsParallel( axes[0].Direction(), theAngTol ) && + !norm01.IsParallel( norm12, theAngTol ) ); + if ( isOk ) + myAxesPreview->SetArrows( axes, myAxesLen ); + + myAxesPreview->SetVisibility( isOk ); + + return isOk; +} + +//================================================================================ +/*! + * \brief Makes axes orthogonal if necessary + */ +//================================================================================ + +void StdMeshersGUI_CartesianParamCreator::onOrthogonalAxes(bool isOrtho) +{ + if ( !isOrtho ) + { + updateAxesPreview(); + return; + } + + std::multimap< int, int > ageOfAxis; + gp_XYZ dirs[3]; + SMESHGUI_SpinBox** spins[3] = { &myXDirSpin[0], &myYDirSpin[0], &myZDirSpin[0] }; + int nbOk = 0, isOk; + for ( int iAx = 0; iAx < 3; ++iAx ) + { + dirs[iAx].SetCoord( spins[iAx][0]->GetValue(), + spins[iAx][1]->GetValue(), + spins[iAx][2]->GetValue()); + if (( isOk = ( dirs[iAx].Modulus() > 1e-100 ))) + ageOfAxis.insert( std::make_pair( myDirTic[iAx], iAx )); + else + ageOfAxis.insert( std::make_pair( -1, iAx )); + nbOk += isOk; + } + switch ( nbOk ) + { + case 0: + { + dirs[0].SetCoord( 1, 0, 0 ); + dirs[1].SetCoord( 0, 1, 0 ); + dirs[2].SetCoord( 0, 0, 1 ); + break; + } + case 1: + { + int iOk = ageOfAxis.rbegin()->second; + get3Dirs( iOk, dirs ); + break; + } + default: + std::multimap< int, int >::reverse_iterator ag2ax = ageOfAxis.rbegin(); + int iOk1 = ag2ax->second; + int iOk2 = (++ag2ax)->second; + int iKo = (++ag2ax)->second; + if ( gp_Vec( dirs[ iOk1 ]).IsParallel( gp_Vec( dirs[ iOk2 ]), theAngTol )) + std::swap( iOk2, iKo ); + if ( gp_Vec( dirs[ iOk1 ]).IsParallel( gp_Vec( dirs[ iOk2 ]), theAngTol )) + { + get3Dirs( iOk1, dirs ); + } + else + { + dirs[ iKo ] = dirs[ iOk1 ] ^ dirs[ iOk2 ]; + dirs[ iOk2 ] = dirs[ iKo ] ^ dirs[ iOk1 ]; + if ( ( iOk1+1 ) % 3 != iOk2 ) + dirs[ iKo ].Reverse(); + } + } + + for ( int iAx = 0; iAx < 3; ++iAx ) + { + double size = dirs[iAx].Modulus(); + if ( size > 1e-100 ) + dirs[iAx] /= size; + for (int i = 0; i < 3; ++i ) + { + bool isBlocked = spins[iAx][i]->blockSignals( true ); + spins[iAx][i]->SetValue( dirs[iAx].Coord( i+1 )); + spins[iAx][i]->blockSignals( isBlocked ); + } + } + + updateAxesPreview(); +} + +//================================================================================ +/*! + * \brief Increment myDirTic and update the preview of axes + */ +//================================================================================ + +void StdMeshersGUI_CartesianParamCreator::onAxisDirChange(const QString&) +{ + QObject* changedSpin = sender(); + SMESHGUI_SpinBox** spins[3] = { &myXDirSpin[0], &myYDirSpin[0], &myZDirSpin[0] }; + for ( int iAx = 0; iAx < 3; ++iAx ) + if ( spins[iAx][0] == changedSpin || + spins[iAx][1] == changedSpin || + spins[iAx][2] == changedSpin ) + { + myDirTic[ iAx ] = 1 + Max( Max( myDirTic[0], myDirTic[1] ), myDirTic[2] ); + break; + } + + onOrthogonalAxes( myOrthogonalChk->isChecked() ); +} + +//================================================================================ +/*! + * \brief Sets axis direction by a selected EDGE + */ +//================================================================================ + +void StdMeshersGUI_CartesianParamCreator::onSelectionChange() +{ + int iAxis = myAxisBtnGrp->checkedId(); + if ( iAxis < 0 ) + return; + + SALOME_ListIO aList; + SMESHGUI::GetSMESHGUI()->selectionMgr()->selectedObjects(aList); + + TopoDS_Shape edge, shape; + for( SALOME_ListIteratorOfListIO anIt( aList ); anIt.More(); anIt.Next() ) + { + GEOM::GEOM_Object_var go = SMESH::IObjectToInterface( anIt.Value() ); + if ( GEOMBase::GetShape( go, shape ) && shape.ShapeType() == TopAbs_EDGE ) + { + if ( !edge.IsNull() ) + return; // several EDGEs selected + edge = shape; + } + } + if ( edge.IsNull() ) + return; + + TopoDS_Shape vv[2]; + TopoDS_Iterator vIt( edge ); + for ( ; vIt.More() && vv[1].IsNull(); vIt.Next() ) + vv[ !vv[0].IsNull() ] = vIt.Value(); + + gp_Pnt pp[2]; + if ( !GEOMBase::VertexToPoint( vv[0], pp[0] ) || + !GEOMBase::VertexToPoint( vv[1], pp[1] )) + return; + + SMESHGUI_SpinBox** spins[3] = { &myXDirSpin[0], &myYDirSpin[0], &myZDirSpin[0] }; + + gp_Vec newDir( pp[0], pp[1] ); + gp_Vec curDir( spins[iAxis][0]->GetValue(), + spins[iAxis][1]->GetValue(), + spins[iAxis][2]->GetValue()); + if ( newDir * curDir < 0 ) + newDir.Reverse(); + + double size = newDir.Magnitude(); + if ( size < 1e-100 ) + return; + newDir /= size; + + for (int i = 0; i < 3; ++i ) + { + bool isBlocked = spins[iAxis][i]->blockSignals( true ); + spins[iAxis][i]->SetValue( newDir.Coord( i+1 )); + spins[iAxis][i]->blockSignals( isBlocked ); + } + myDirTic[ iAxis ] = 1 + Max( Max( myDirTic[0], myDirTic[1] ), myDirTic[2] ); + + onOrthogonalAxes( myOrthogonalChk->isChecked() ); +} + +//================================================================================ +/*! + * \brief Sets axes at which number of hexahedra is maximal + */ +//================================================================================ + +void StdMeshersGUI_CartesianParamCreator::onOptimalAxes(bool) +{ + StdMeshers::StdMeshers_CartesianParameters3D_var h = + StdMeshers::StdMeshers_CartesianParameters3D::_narrow( hypothesis() ); + if ( h->_is_nil() ) + return; + + QString shapeEntry = getMainShapeEntry(); + if ( shapeEntry.isEmpty() ) + return; + + Handle(SALOME_InteractiveObject) io = + new SALOME_InteractiveObject( shapeEntry.toStdString().c_str(), "GEOM" ); + GEOM::GEOM_Object_var geomObj = SMESH::IObjectToInterface( io ); + if ( geomObj->_is_nil() ) + return; + + SMESH::DirStruct axDirs[3]; + h->ComputeOptimalAxesDirs( geomObj, + myOrthogonalChk->isChecked(), + axDirs[0], + axDirs[1], + axDirs[2]); + + SMESHGUI_SpinBox** spins[3] = { &myXDirSpin[0], &myYDirSpin[0], &myZDirSpin[0] }; + for ( int iAx = 0; iAx < 3; ++iAx ) + { + double coords[3] = { axDirs[iAx].PS.x, axDirs[iAx].PS.y, axDirs[iAx].PS.z }; + for (int i = 0; i < 3; ++i ) + { + bool isBlocked = spins[iAx][i]->blockSignals( true ); + spins[iAx][i]->SetValue( coords[ i ]); + spins[iAx][i]->blockSignals( isBlocked ); + } + } + updateAxesPreview(); +} + +//================================================================================ +/*! + * \brief Sets axes || to the axes of global CS + */ +//================================================================================ + +void StdMeshersGUI_CartesianParamCreator::onResetAxes(bool) +{ + SMESHGUI_SpinBox** spins[3] = { &myXDirSpin[0], &myYDirSpin[0], &myZDirSpin[0] }; + for ( int iAx = 0; iAx < 3; ++iAx ) + { + for (int i = 0; i < 3; ++i ) + { + bool isBlocked = spins[iAx][i]->blockSignals( true ); + spins[iAx][i]->SetValue( iAx == i ? 1. : 0. ); + spins[iAx][i]->blockSignals( isBlocked ); + } + myDirTic[iAx] = 0; + } + updateAxesPreview(); +} + +//================================================================================ +/*! + * \brief SLOT called when the grid definintion mode changes + */ +//================================================================================ + +void StdMeshersGUI_CartesianParamCreator::onGridModeChanged(int) +{ + bool haveSpacing = ( myAxisTabs[0]->isGridBySpacing() || + myAxisTabs[1]->isGridBySpacing() || + myAxisTabs[2]->isGridBySpacing() ); + + myFixedPointGrp->setEnabled( haveSpacing ); +} diff --git a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h index 1187bb90b..1a9fcd51e 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h +++ b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h @@ -42,6 +42,7 @@ class QAbstractItemModel; class QButtonGroup; class QCheckBox; +class QGroupBox; class QLineEdit; class QListWidget; class QListWidgetItem; @@ -51,6 +52,7 @@ class QStyleOptionViewItem; class QTreeWidget; class QTreeWidgetItem; class QWidget; +class SMESHGUI_MeshEditPreview; class SMESHGUI_SpinBox; namespace StdMeshersGUI @@ -79,6 +81,9 @@ namespace StdMeshersGUI SMESH::double_array* getCoordinates(); void getSpacing(SMESH::string_array_out funs, SMESH::double_array_out points) const; + signals: + void gridModeChanged(int); + private slots: void onInsert(); void onDelete(); @@ -126,19 +131,40 @@ public: StdMeshersGUI_CartesianParamCreator( const QString& aHypType ); virtual ~StdMeshersGUI_CartesianParamCreator(); - virtual bool checkParams( QString& ) const; - virtual QString helpPage() const; + virtual bool checkParams( QString& ) const; + virtual QString helpPage() const; protected: virtual QFrame* buildFrame(); virtual void retrieveParams() const; virtual QString storeParams() const; +private slots: + bool updateAxesPreview(); + void onOrthogonalAxes(bool); + void onAxisDirChange(const QString&); + void onSelectionChange(); + void onOptimalAxes(bool); + void onResetAxes(bool); + void onGridModeChanged(int); + private: QLineEdit* myName; SMESHGUI_SpinBox* myThreshold; QCheckBox* myAddEdges; + StdMeshersGUI::GridAxisTab* myAxisTabs[3]; + QGroupBox* myFixedPointGrp; + SMESHGUI_SpinBox* myPointSpin[3]; + QCheckBox* myOrthogonalChk; + QButtonGroup* myAxisBtnGrp; + SMESHGUI_SpinBox* myXDirSpin[3]; + SMESHGUI_SpinBox* myYDirSpin[3]; + SMESHGUI_SpinBox* myZDirSpin[3]; + SMESHGUI_MeshEditPreview* myAxesPreview; + double myOrigin[3]; + double myAxesLen; + int myDirTic[3]; }; #endif // STDMESHERSGUI_CartesianParamCreator_H diff --git a/src/StdMeshersGUI/StdMeshers_msg_en.ts b/src/StdMeshersGUI/StdMeshers_msg_en.ts index f2ccb30f8..8b94762e7 100644 --- a/src/StdMeshersGUI/StdMeshers_msg_en.ts +++ b/src/StdMeshersGUI/StdMeshers_msg_en.ts @@ -529,6 +529,30 @@ AXIS_Z Axis Z + + INVALID_AXES_DIR + Invalid directions of axes + + + FIXED_POINT + Fixed Point + + + AXES_DIRECTION + Directions of Axes + + + ORTHOGONAL_AXES + Orthogonal Axes + + + OPTIMAL_AXES + Optimal Axes + + + RESET_AXES + Reset + StdMeshersGUI::GridAxisTab diff --git a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx index 3db734054..5b383a0f5 100644 --- a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx +++ b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx @@ -224,6 +224,102 @@ void StdMeshers_CartesianParameters3D_i::GetGridSpacing(SMESH::string_array_out } } +//======================================================================= +//function : SetAxesDirs +//purpose : Set custom direction of axes +//======================================================================= + +void StdMeshers_CartesianParameters3D_i::SetAxesDirs(const SMESH::DirStruct& xDir, + const SMESH::DirStruct& yDir, + const SMESH::DirStruct& zDir) + throw (SALOME::SALOME_Exception) +{ + double coords[9]; + coords[0] = xDir.PS.x; + coords[1] = xDir.PS.y; + coords[2] = xDir.PS.z; + coords[3] = yDir.PS.x; + coords[4] = yDir.PS.y; + coords[5] = yDir.PS.z; + coords[6] = zDir.PS.x; + coords[7] = zDir.PS.y; + coords[8] = zDir.PS.z; + try { + this->GetImpl()->SetAxisDirs(coords); + + SMESH::TPythonDump() << _this() << ".SetAxesDirs( " + << xDir << ", " + << yDir << ", " + << zDir << " )"; + } + catch ( SALOME_Exception& S_ex ) { + THROW_SALOME_CORBA_EXCEPTION( S_ex.what(), SALOME::BAD_PARAM ); + } +} + +//======================================================================= +//function : GetAxesDirs +//purpose : Returns direction of axes +//======================================================================= + +void StdMeshers_CartesianParameters3D_i::GetAxesDirs(SMESH::DirStruct& xDir, + SMESH::DirStruct& yDir, + SMESH::DirStruct& zDir) +{ + const double* coords = GetImpl()->GetAxisDirs(); + xDir.PS.x = coords[0]; + xDir.PS.y = coords[1]; + xDir.PS.z = coords[2]; + yDir.PS.x = coords[3]; + yDir.PS.y = coords[4]; + yDir.PS.z = coords[5]; + zDir.PS.x = coords[6]; + zDir.PS.y = coords[7]; + zDir.PS.z = coords[8]; +} + +//======================================================================= +//function : SetFixedPoint +//purpose : * Set/unset a fixed point, at which a node will be created provided that grid +// * is defined by spacing in all directions +//======================================================================= + +void StdMeshers_CartesianParameters3D_i::SetFixedPoint(const SMESH::PointStruct& ps, + CORBA::Boolean toUnset) +{ + double p[3] = { ps.x, ps.y, ps.z }; + GetImpl()->SetFixedPoint( p, toUnset ); + + if ( toUnset ) + SMESH::TPythonDump() << _this() << ".SetFixedPoint([0,0,0], True)"; + else + SMESH::TPythonDump() << _this() << ".SetFixedPoint(" << p << ", " << toUnset << " )"; +} + +//======================================================================= +//function : GetFixedPoint +//purpose : Returns a fixed point +//======================================================================= + +CORBA::Boolean StdMeshers_CartesianParameters3D_i::GetFixedPoint(SMESH::PointStruct& ps) +{ + double p[3]; + if ( GetImpl()->GetFixedPoint( p ) ) + { + ps.x = p[0]; + ps.y = p[1]; + ps.z = p[2]; + return true; + } + else + { + ps.x = 0.; + ps.y = 0.; + ps.z = 0.; + } + return false; +} + //======================================================================= //function : SetToAddEdges //purpose : Enables implementation of geometrical edges into the mesh. @@ -248,7 +344,7 @@ CORBA::Boolean StdMeshers_CartesianParameters3D_i::GetToAddEdges() //======================================================================= //function : IsGridBySpacing -//purpose : Return true if the grid is defined by spacing functions and +//purpose : Return true if the grid is defined by spacing functions and // not by node coordinates //======================================================================= @@ -257,6 +353,37 @@ CORBA::Boolean StdMeshers_CartesianParameters3D_i::IsGridBySpacing(CORBA::Short return this->GetImpl()->IsGridBySpacing(axis); } +//======================================================================= +//function : ComputeOptimalAxesDirs +//purpose : Returns axes at which number of hexahedra is maximal +//======================================================================= + +void StdMeshers_CartesianParameters3D_i:: +ComputeOptimalAxesDirs(GEOM::GEOM_Object_ptr go, + CORBA::Boolean isOrthogonal, + SMESH::DirStruct& xDir, + SMESH::DirStruct& yDir, + SMESH::DirStruct& zDir) + throw (SALOME::SALOME_Exception) +{ + TopoDS_Shape shape = SMESH_Gen_i::GetSMESHGen()->GeomObjectToShape( go ); + if ( shape.IsNull() ) + THROW_SALOME_CORBA_EXCEPTION( "Null shape", SALOME::BAD_PARAM ); + + double c[9]; + ::StdMeshers_CartesianParameters3D::ComputeOptimalAxesDirs( shape, isOrthogonal, c ); + + xDir.PS.x = c[0]; + xDir.PS.y = c[1]; + xDir.PS.z = c[2]; + yDir.PS.x = c[3]; + yDir.PS.y = c[4]; + yDir.PS.z = c[5]; + zDir.PS.x = c[6]; + zDir.PS.y = c[7]; + zDir.PS.z = c[8]; +} + //======================================================================= //function : ComputeCoordinates //purpose : Computes node coordinates by spacing functions @@ -268,13 +395,13 @@ StdMeshers_CartesianParameters3D_i::ComputeCoordinates(CORBA::Double const SMESH::string_array& spaceFuns, const SMESH::double_array& points, const char* axisName ) - throw (SALOME::SALOME_Exception) + throw (SALOME::SALOME_Exception) { vector xFuns; vector xPoints, coords; _array2vec( spaceFuns, xFuns, (const char*) ); _array2vec( points, xPoints, ); - + try { this->GetImpl()->ComputeCoordinates( x0, x1, xFuns, xPoints, coords, axisName ); } diff --git a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx index abdda05f4..74df5de91 100644 --- a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx +++ b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx @@ -83,6 +83,22 @@ class STDMESHERS_I_EXPORT StdMeshers_CartesianParameters3D_i: void GetGridSpacing(SMESH::string_array_out xSpaceFunctions, SMESH::double_array_out xInternalPoints, CORBA::Short axis) throw (SALOME::SALOME_Exception); + /*! + * Set custom direction of axes + */ + void SetAxesDirs(const SMESH::DirStruct& x, + const SMESH::DirStruct& y, + const SMESH::DirStruct& z) throw (SALOME::SALOME_Exception); + void GetAxesDirs(SMESH::DirStruct& x, + SMESH::DirStruct& y, + SMESH::DirStruct& z); + /*! + * Set/unset a fixed point, at which a node will be created provided that grid + * is defined by spacing in all directions + */ + void SetFixedPoint(const ::SMESH::PointStruct& p, CORBA::Boolean toUnset); + CORBA::Boolean GetFixedPoint(::SMESH::PointStruct& p); + /*! * \brief Enables implementation of geometrical edges into the mesh. If this feature @@ -98,6 +114,14 @@ class STDMESHERS_I_EXPORT StdMeshers_CartesianParameters3D_i: */ CORBA::Boolean IsGridBySpacing(CORBA::Short axis); + /*! + * Returns axes at which number of hexahedra is maximal + */ + void ComputeOptimalAxesDirs(GEOM::GEOM_Object_ptr shape, + CORBA::Boolean isOrthogonal, + SMESH::DirStruct& x, + SMESH::DirStruct& y, + SMESH::DirStruct& z) throw (SALOME::SALOME_Exception); /*! * \brief Computes node coordinates by spacing functions * \param x0 - lower coordinate -- 2.39.2