Geometry definition#
In XLiFE++ all canonical geometry classes (say Segment
, Rectangle
, Disk
, Sphere
, … )
inherit from the Geometry
class. Combined geometries are directly managed in Geometry
as a vector of Geometry
pointers.
Canonical geometries have the following inheritance diagram:
To define a canonical geometry object (1D, 2D or 3D), a general constructor based on a key/value system is provided:
For instance:
Segment s(_v1 = 0., _v2 =1., ...);
There are a lot of available parameters (or keys) for each geometry object that can be given in any order. Some keys are parts of a group of keys. In that case, every key of the group has to be set.
For instance, in the following example, to define a triangle, the three vertices of the triangles with the keys _v1
, _v2
, _v3
must be given and none can be forgotten.
Triangle tri(_v1 = Point(0.,0.), _v2 = Point(1.,0.), _v3 = Point(0.,1.), ...);
There are 3 kinds of parameters (plus 1 single parameter):
First, some parameters are dedicated to geometry definition. This part is different for each geometry and will be explained in following subsections.
-
Secondly, there are 2 parameters dedicated to mesh process such (fitted to the GMSH mesh generator):
the
_nnodes
key to specify the number of nodes on each geometry edge (one value by edge or smaller number of values according to properties of symmetry of the geometry, or a unique value for all edges)the
_hsteps
key to specify the local mesh step on each geometry vertex (one value by vertex or or a unique value for all vertices)
These parameters are optional and only one of them is to be used.
Triangle tri(_v1 = Point(0.,0.), _v2 = Point(1.,0.), _v3 = Point(0.,1.), _nnodes = {11,15,11});
What is the difference between
_nnodes
and_hsteps
? It is as in the GMSH documentation.When the parameter
_nnodes
is used, the number of nodes of a regular mesh on an edge is set. As a result, the mesh step is constant on the edge. Using this parameter, the mesh can be refined near a gemetry edge.When the parameter
_hsteps
is used, the mesh step near a vertex is set. If the mesh step is the same for both vertices of the edge, then this is a regular mesh (equivalent to define the number of nodes in this case). If the mesh step is different on vertices of an edge, it varies progressively to fit the expected value on vertices. Using this parameter, the mesh can be refined near a geometry vertex.
-
Thirdly, there are some parameters dedicated to definition of geometrical domains. These keys are all optional:
_domain_name
is used to set the name of the main domain of the geometry. The main domain depends on the type of mesh (for a mesh a cube with triangles, the main domain will be the whole border, whereas with tetrahedra, it is the cube itself)._side_names
is used to set the names of every side domain. It can be given either a list of strings (orStrings
object) or a singleString
if it is the same name for every side domain.
Default values are empty strings. When a domain has an empty name, it is not built. For some geometries (cylinders and cones), there is an additional parameter.
At last, the
_type
key is dedicated to geometries fitted to the subdivision mesh generator (See Unstructured subdivision generator (internal) for details).
Let’s summarize information about these keys:
key |
authorized types |
examples |
---|---|---|
|
|
|
|
single real value, explicit list of real values or |
|
|
single (unsigned) integer value, explicit list of integer values or |
|
|
|
|
|
single (unsigned) integer value, or |
|
Note
For 1D or 2D canonical geometries, there exits a Parametrization
(see The Parametrization class) object that can be accessed:
Triangle tri(_v1 = Point(0.,0.) _v2 = Point(1.,0.),_v3 = Point(0.,1.));
Parametrization& par = tri.parametrization();
Point m = par(0.5,0.5);
Parametrization parameter is set on interval \([0,1]\) for 1D geometries and on square \([0,1]\times [0,1]\) for 2D geometries.
In the following, we will see how to define each canonical geometries, before showing how to define more complicated ones.
Combining geometries#
A composite geometry is a geometry defined from a list of canonical or loop geometries. It is for example the right way to define holes in your mesh, or to define multi-domains geometries. It’s easy to define composite geometries using the operators + and -.
Let’s see a first example:
Rectangle r(_xmin=-3, _xmax=3, _ymin=-2, _ymax=2
, _nnodes=Numbers(33,22), _domain_name="Omega");
Ellipse e(_center=Point(0,0), _xlength=1, _ylength=0.5, _nnodes=11);
Geometry gm=r-e;
Geometry gp=r+e;
Composite geometry: an ellipse inside a rectangle (gm and gp)
Composite geometry: an ellipse inside a rectangle (gm and gp)
In both cases, the ellipse is geometrically inside the rectangle. This hole will be meshed if the operator + is used, and not meshed if the operator - is used. Both operators can detect if a geometry is inside another geometry, in most of the cases, but the operator - always consider the second geometry as a hole of the first one, even if it is geometrically wrong !
Important
If domain name for the right hand side of the operator + is forgotten, it will not be stored, so that the geometry still will have a hole.
These operators work with any geometries as far as geometrical inclusion is easy enough to detect.
Using operators + and - to define composite geometries is not restricted to 2 components. Composite geometries with any number of components may be defined, and some of them can be loop geometries:
Ellipse e1(_center=Point(0.,0.), _v1=Point(4,0.), _v2=Point(0.,5.)
, _nnodes=12, _domain_name="Omega1");
Point a(-1.5,-4.); Point b(1.5,-4.); Point c(2.,-3.5); Point d(2.,3.5);
Point e(1.5,4.); Point f(-1.5,4.); Point g(-2.,3.5); Point h(-2.,-3.5);
Segment s1(_v1=a, _v2=b, _nnodes=21, _domain_name="AB");
CircArc c1(_center=Point(3.5,0.5), _v1=b, _v2=c, _nnodes=5, _domain_name="BC");
Segment s2(_v1=c, _v2=d, _nnodes=11, _domain_name="CD");
CircArc c2(_center=Point(3.5,1.5), _v1=d, _v2=e, _nnodes=5, _domain_name="DE");
Segment s3(_v1=e, _v2=f, _nnodes=21, _domain_name="EF");
CircArc c3(_center=Point(0.5,1.5), _v1=f, _v2=g, _nnodes=5, _domain_name="FG");
Segment s4(_v1=g, _v2=h, _nnodes=11, _domain_name="GH");
CircArc c4(_center=Point(0.5,0.5), _v1=h, _v2=a, _nnodes=5, _domain_name="HA");
Geometry sf1=(surfaceFrom(s1+c1+s2+c2+s3+c3+s4+c4,"Omega2");
Ellipse e2(_center=Point(1.,2.), _v1=Point(1.5,2.), _v2=Point(1.,3.)
, _nnodes=12, _domain_name="Omega3");
Ellipse e3(_center=Point(0.,0.), _v1=Point(0.5,0.), _v2=Point(0.,1.)
, _nnodes=12, _domain_name="Omega4");
Rectangle r2(_xmin=5., _xmax=6., _ymin=0., _ymax=1., _nnodes=6
, _domain_name="Omega5");
Segment s5(_v1=Point(5.3,0.5), _v2=Point(5.7,0.5), _nnodes=5);
CircArc c5(_center=Point(5.5,0.5), _v1=Point(5.7,0.5), _v2=Point (5.5,0.7)
, _nnodes=5);
CircArc c6(_center=Point(5.5,0.5), _v1=Point (5.5,0.7), _v2=Point(5.3,0.5)
, _nnodes=5);
Geometry sf2=surfaceFrom(s5+c5+c6,"Omega6");
Geometry gmulti=(e1+sf1)-(e2+e3)+r2-sf2;
Composite geometry with multiple components and inclusions between components
Composite geometry with multiple components and inclusions between components
Warning
When at least 2 components share several vertices, several edges and/or several surfaces, everything works fine, shared geometrical entities are not duplicated.
Composite geometry with edges shared by components
Composite geometry with edges shared by components
As far as composite geometries are concerned, XLiFE++ detects inclusions between canonical components. It is not always the case if components are loop geometries. Let’s take the previous example, but this time, every domain has to be meshed.
Geometry gmulti2=(e1+sf1)+(e2+e3)+r2+sf2;
Composite geometry with multiple components and inclusions between components. Some inclusions are not detected correctly:
Both holes of the rounded rectangle are not taken into account, whereas the half disk is correctly managed. Indeed, XLiFE++ can in most of the cases determine if a loop geometry is inside a canonical geometry but it can’t determine if a canonical geometry is inside a loop geometry.
How to solve this problem ? By forcing it with the unary ! operator, and rewriting the composite expression if necessary, as in the following:
Geometry gmulti3=e1+(sf1+!(e2+e3)+r2+sf2);
Composite geometry with multiple components and inclusions between components with forced inclusions
Composite geometry with multiple components and inclusions between components with forced inclusions
Writing sf1+!(e2+e3) tells explicitely that the right operand (e2+e3) is forced be inside the left operand (sf1).
Danger
When at least two components intersect and the intersection has same dimension (2 surfaces whose intersection is a surface, for instance), the resulting mesh will not be generated properly. In this case, definition of the geometry must be reconsidered.
Partial inclusion is forbidden:
Definition of a geometry from its boundary#
A loop geometry is a geometry defined by its boundaries. For example, instead of defining a triangle, the surface inside the closed boundary composed of 3 segments can be defined. With XLiFE++ geometry engine, the following routines are available for 2D or 3D geometries:
Geometry planeSurfaceFrom(const Geometry& boundary, String domName = String());
Geometry ruledSurfaceFrom(const Geometry& boundary, String domName = String());
Geometry volumeFrom(const Geometry& boundary, String domName = String());
The first argument must be a “composite” geometry defined from curve boundaries (2D case) or surface boundaries (3D case) such that the result is closed.
Let’s see an example using segments and circular arcs to define a mesh on a rectangle with rounded corners:
Point a(-1.5,-4.); Point b(1.5,-4.); Point c(2.,-3.5); Point d(2.,3.5);
Point e(1.5,4.); Point f(-1.5,4.); Point g(-2.,3.5); Point h(-2.,-3.5);
Segment s1(_v1=a, _v2=b, _nnodes=21, _domain_name="AB");
CircArc c1(_center=Point(3.5,0.5), _v1=b, _v2=c, _nnodes=5, _domain_name="BC");
Segment s2(_v1=c, _v2=d, _nnodes=11, _domain_name="CD");
CircArc c2(_center=Point(3.5,1.5), _v1=d, _v2=e, _nnodes=5, _domain_name="DE");
Segment s3(_v1=e, _v2=f, _nnodes=21, _domain_name="EF");
CircArc c3(_center=Point(0.5,1.5), _v1=f, _v2=g, _nnodes=5, _domain_name="FG");
Segment s4(_v1=g, _v2=h, _nnodes=11, _domain_name="GH");
CircArc c4(_center=Point(0.5,0.5), _v1=h, _v2=a, _nnodes=5, _domain_name="HA");
Geometry g=planeSurfaceFrom(s1+c1+s2+c2+s3+c3+s4+c4,"Omega");
The planeSurfaceFrom
routine (or shorter surfaceFrom
) is devoted to define surfaces from their boundaries. Segments and circular arcs must be defined with the same orientation (clockwise or counter-clockwise).
With such definitions of segments s1, s2, s3 and s4 and circular arcs c1, c2, c3 and c4, in previous example, the following definitions are right :
Geometry g=planeSurfaceFrom(s2+c2+s3+c3+s4+c4+s1+c1, "Omega");
Geometry g=planeSurfaceFrom(s1+s2+s3+s4+c1+c2+c3+c4, "Omega");
The order of components here, and also the first component, has no meaning, but they all are oriented in the same way.
Rectangular geometry with rounded corners, defined with the surfaceFrom routine
Rectangular geometry with rounded corners, defined with the surfaceFrom routine
Here is an example of a 3D geometry basically composed of a cube and a pyramid sharing one face:
Point a(0,0,0); Point b(2,0,0); Point c(2,2,0); Point d(0,2,0);
Point e(0,0,2); Point f(2,0,2); Point g(2,2,2); Point h(0,2,2)
Point i(4,1,1);
SquareGeo s1(_v1=a, _v2=b, _v4=e, _nnodes=11, _domain_name="S1");
SquareGeo s2(_v1=d, _v2=c, _v4=h, _nnodes=11, _domain_name="S2");
SquareGeo s3(_v1=a, _v2=b, _v4=d, _nnodes=11, _domain_name="S3");
SquareGeo s4(_v1=e, _v2=f, _v4=h, _nnodes=11, _domain_name="S4");
SquareGeo s5(_v1=a, _v2=d, _v4=e, _nnodes=11, _domain_name="S5");
Triangle t1(_v1=b, _v2=c, _v3=i, _nnodes=11, _domain_name="T1");
Triangle t2(_v1=c, _v2=g, _v3=i, _nnodes=11, _domain_name="T2");
Triangle t3(_v1=g, _v2=f, _v4=i, _nnodes=11, _domain_name="T3");
Triangle t4(_v1=f, _v2=b, _v4=i, _nnodes=11, _domain_name="T4");
Geometry vf=volumeFrom(s1+s2+s3+s4+s5+t1+t2+t3);
3D geometry defined with the volumeFrom routine
Important
3D loop geometries can be defined by a mix of 2D loop geometries and 2D canonical geometries.
Warning
Although C++ authorizes it, do not write loop geometries as follows: volumeFrom(Rectangle(a,b,d,11,11,"R1")+...);
. You have to define the rectangle \(r_1\) instead, as in the previous example.
Cracking geometries#
Gmsh allows to crack domains (1D cracks in 2D meshes, 1D or 2D cracks in 3D meshes). Cracks can be opened (boundary nodes of the crack are duplicated as the other nodes) or closed crack (boundary nodes of the crack are not duplicated).
There is a general routine defining both opened and closed cracks through 2 additional optional arguments: Geometry::crack()
, default behavior being closed cracks.
As an alternative, it can be used the functions closedCrack()
(only the geometry in argument) and openCrack()
(the geometry and a domain name) to define a closed or an opened crack.
The domain name is the boundary domain of the geometry to be cracked (opened). Let’s see following examples to understand this.
There are two ways to define a geometry with a crack inside it: the direct one and the indirect one.
Defining cracks directly
This is the way you should always do to define a crack. A crack is a geometry inside a geometry of bigger dimension. So the geometry to be cracked must be defined as a meshed “hole” inside the container geometry.
Point x1(0,0,0), x2(1,0,0), x3(1,1,0), x4(0,1,0), x5(0.2,0.2,0), x6(0.8,0.8,0)
Rectangle rrect8(_v1=x1, _v2=x2, _v4=x4, _domain_name="Omega", _side_names="Gamma");
Segment scrack(_v1=x5, _v2=x6, _nnodes=3, _domain_name="Crack", _side_names="Sigma");
openCrack(scrack, "Sigma");
Mesh m(rrect8+scrack, _generator=gmsh);
Open crack.
Open crack.
A side name is given to both ends of the segment. This name will be given to the routine openCrack()
to tell which ends are to be opened. Here, it is both.
Defining cracks indirectly
This way is called indirect, compared to the previous one, insofar as you have to link the geometry you want to crack to the boundaries of the parent geometry and define surfaces from their boundaries:
Point x1(0,0,0), x2(1,0,0), x3(1,1,0), x4(0,1,0);
Point x5(0.2,0.2,0), x6(0.8,0.8,0), x7(0.2,0,0), x8(0.8,1,0);
Segment s1(_v1=x1, _v2=x7, _domain_name="Gamma");
Segment s2(_v1=x2, _v2=x7, _domain_name="Gamma");
Segment s3(_v1=x3, _v2=x2, _domain_name="Gamma");
Segment s4(_v1=x8, _v2=x3, _domain_name="Gamma");
Segment s5(_v1=x8, _v2=x4, _domain_name="Gamma");
Segment s6(_v1=x4, _v2=x1, _domain_name="Gamma");
Segment s7(_v1=x7, _v2=x5);
Segment s8(_v1=x5, _v2=x6, _nnodes=3, _domain_name="Crack");
Segment s9(_v1=x6, _v2=x8);
crack(s8);
Geometry sf1=surfaceFrom(s7+s8+s9+s5+s6+s1, "Omega1");
Geometry sf2=surfaceFrom(s7+s8+s9+s4+s3+s2, "Omega2");
Mesh m(sf1+sf2, _generator=gmsh);
Closed crack.
Closed crack.
Important
In this example, surfaces have different domain names. You can also give the same domain name.
Which is better ?
direct way |
indirect way |
|
---|---|---|
1D crack in 2D mesh |
100% safe |
100% safe |
2D crack in 3D mesh |
not 100% safe |
100% safe |
1D crack in 3D mesh |
to be tested |
to be tested |
Hint
Gmsh team is currently working on improving their crack engine to be 100% whatever the case.
A look at the mesh file
Let’s see the resulting mesh file for the indirect example above:
$MeshFormat
2.2 0 8
$EndMeshFormat
$PhysicalNames
4
1 1 "Crack"
1 2 "Gamma"
2 3 "Omega1"
2 4 "Omega2"
$EndPhysicalNames
$Nodes
18
1 0.2 0.2 0
2 0.8 0.8 0
3 0.4999999999991927 0.4999999999991927 0
4 0.8 1 0
5 0 1 0
6 0 0 0
7 0.2 0 0
8 1 1 0
9 1 0 0
10 0.4999999999991927 0.4999999999991927 0
11 0.4000000000000001 0.8999999999999999 0
12 0.09999999999935429 0.5999999999998384 0
13 0.6188775510203053 0.8489795918366316 0
14 0.1178571428570276 0.1714285714285426 0
15 0.5999999999991926 0.1 0
16 0.8999999999993543 0.3999999999998387 0
17 0.3811224489791758 0.1510204081631623 0
18 0.8821428571427419 0.8285714285713998 0
$EndNodes
Bounds of the cracked domain are not duplicated (nodes 1 and 2), whereas the middle node of the cracked domain is duplicated (nodes 3 and 10):
$Elements
36
1 1 2 1 2 1 3
2 1 2 1 2 3 2
3 1 2 2 4 4 5
4 1 2 2 5 5 6
5 1 2 2 6 6 7
6 1 2 2 8 4 8
7 1 2 2 9 8 9
8 1 2 2 10 9 7
9 1 2 1 11 1 10
10 1 2 1 11 10 2
11 2 2 3 7 2 13 3
12 2 2 3 7 1 3 12
13 2 2 3 7 3 11 12
14 2 2 3 7 3 13 11
15 2 2 3 7 2 4 13
16 2 2 3 7 7 1 14
17 2 2 3 7 7 14 6
18 2 2 3 7 5 12 11
19 2 2 3 7 1 12 14
20 2 2 3 7 4 5 11
21 2 2 3 7 4 11 13
22 2 2 3 7 5 6 12
23 2 2 3 7 6 14 12
24 2 2 4 11 1 10 17
25 2 2 4 11 2 16 10
26 2 2 4 11 10 16 15
27 2 2 4 11 10 15 17
28 2 2 4 11 7 1 17
29 2 2 4 11 2 4 18
30 2 2 4 11 4 8 18
31 2 2 4 11 9 15 16
32 2 2 4 11 7 15 9
33 2 2 4 11 7 17 15
34 2 2 4 11 2 18 16
35 2 2 4 11 8 9 16
36 2 2 4 11 8 16 18
$EndElements
Segments are also duplicated. If you’re familiar with the msh file format, by reading elements 11 and 24 for instance, we can deduce that domain “Omega1” has geometrical reference 7 and domain “Omega2” has geometrical reference 11. These references will be used with the cracked domain name to name sides of the crack, namely “Crack_7” and “Crack_11”.