Generating a shell model with the Gmsh Python API #

Objectives

This notebook illustrates the use of the Gmsh Python API for generating a shell model of a curved I-beam model.

../../../_images/curved_beam_mesh.png

Download sources

Geometric parameters#

We consider a I-shaped constant cross-section for the beam with bottom and top flange widths wb and wt and of height h. The arch will be a portion of a circular arc of total angle opening \(2\theta\) and chord length \(L\). The circular arc radius is therefore given by \(R=\dfrac{L}{2\sin(\theta)}\) and the arch rise by \(f=R-\dfrac{L}{2\tan(\theta)}\).

import gmsh
import numpy as np

filename = "I_beam"
# I-beam profile
wb = 0.2  # bottom flange width
wt = 0.3  # top flange width
h = 0.5  # web height
# Arch geometry
theta = np.pi / 6  # arch opening half-angle
L = 10.0  # chord length
R = L / 2 / np.sin(theta)  # arch radius
f = R - L / 2 / np.tan(theta)  # arch rise

Defining points and lines for the left-hand side cross-section#

We first begin by initializing Gmsh ang use the built-in geo CAD kernel since we won’t require any boolean operation which require the use of the Open Cascade kernel.

gmsh.initialize()
geom = gmsh.model.geo

Next, we define the points and lines of the left-hand side cross-section. To facilitate their definition, these objects are first defined for a vertical cross-section located in the \(X=0\) plane and centered on the origin \((0,0,0)\). We split them into a group of bottom_points for the bottom_flange and a group of top_points for the top_flange. The web then connects the middle points of each group. The start_section contains the web, bottom and top flange lines.

Note

Note that Gmsh requires passing a mesh density for each point definition. However, this lcar value will not be used in the end since we will later define the number of points on each line using the TransfiniteCurve command.

lcar = 0.1  # characteristic mesh size density (will not be used)
bottom_points = [
    geom.addPoint(0, -wb / 2, -h / 2, lcar),
    geom.addPoint(0, 0, -h / 2, lcar),
    geom.addPoint(0, wb / 2, -h / 2, lcar),
]
top_points = [
    geom.addPoint(0, -wt / 2, h / 2, lcar),
    geom.addPoint(0, 0, h / 2, lcar),
    geom.addPoint(0, wt / 2, h / 2, lcar),
]
bottom_flange = [
    geom.addLine(bottom_points[0], bottom_points[1]),
    geom.addLine(bottom_points[1], bottom_points[2]),
]
web = [geom.addLine(bottom_points[1], top_points[1])]
top_flange = [
    geom.addLine(top_points[0], top_points[1]),
    geom.addLine(top_points[1], top_points[2]),
]
start_section = bottom_flange + web + top_flange

Cross-section rotation and extrusion#

Now, we rotate this section by an angle equal to \(-\theta\) around the \(Y\) axis to obtain our initial starting section for the arch. The rotate command requires a dimTags first argument i.e. a list of tuple of the form (dim, label) where dim is the entity dimension (dim=1 here for lines) and label is the label entity. The following arguments are then x, y, z, ax, ay, az, angle defining a rotation of angle angle around an axis of revolution passing through a point (x, y, z) along a direction (ax, ay, az).

dimTags = [(1, l) for l in start_section]
geom.rotate(dimTags, 0, 0, 0, 0, 1, 0, -theta)

We will now extrude the previously defined cross-section along a circular arc. The extrude command is used for extruding along a line, here we will need the ̀revolve command to extrude the cross-section by following a rotation of angle \(2\theta\) which is defined similarly to the rotate command. Here, the axis follows the direction \((0, 1, 0)\) and passes through the point \((L/2, 0, -(R-f))\). We also pass a list of layers and corresponding numbers of elements generated during extrusion. Here, we have only one layer (normalized height = 1.0) of 50 elements.

The revolve command returns a list of dimTags corresponding to the newly created entities. For instance, when extruding a line, this will create a surface and three new lines. The output stores these entities as follows: \([\text{end line}, \text{ surface}, \text{ lateral line }1,\text{ lateral line }2]\). We loop on the various entities composing the starting cross-section and append the newly created end-lines and surfaces in corresponding lists. Note that we need to call synchronize to update the corresponding Gmsh model with the newly created entities.

end_bottom_flange = []
end_top_flange = []
end_web = []
surfaces = []
for ini, end in zip(
    [bottom_flange, web, top_flange], [end_bottom_flange, end_web, end_top_flange]
):
    for l in ini:
        outDimTags = geom.revolve(
            [(1, l)],
            L / 2,
            0,
            -(R - f),
            0,
            1,
            0,
            2 * theta,
            numElements=[50],
            heights=[1.0],
        )
        end.append(outDimTags[0][1])
        surfaces.append(outDimTags[1][1])
        geom.synchronize()
end_section = end_bottom_flange + end_web + end_top_flange

Tagging and mesh generation#

We finish by specifying the number of elements for the flange and web discretization using the setTransfiniteCurve command. We also affect the physical tag 1 for the left-hand side cross-section and 2 for the right-hand side cross section. We do not forget to also add physical tags to the surfaces otherwise they well later be ignored when generating the mesh.

for f in bottom_flange + top_flange + end_bottom_flange + end_top_flange:
    geom.mesh.setTransfiniteCurve(f, 6)
for w in web + end_web:
    geom.mesh.setTransfiniteCurve(w, 11)

gmsh.model.addPhysicalGroup(2, surfaces, 1)
gmsh.model.addPhysicalGroup(1, start_section, 1)
gmsh.model.addPhysicalGroup(1, end_section, 2)
2

We can now call the mesh generate function (up to dimension dim=2 since we don’t have any 3D solid element here). The generated mesh is then exported to a .msh. We finish by the finalize command since we are finished with the Gmsh API.

Tip

You can export the corresponding geo file by writing to a file with a .geo_unrolled extension.

gmsh.model.mesh.generate(dim=2)
gmsh.write(filename + ".msh")

gmsh.finalize()
Info    : Meshing 1D...
Info    : [  0%] Meshing curve 1 (Line)
Info    : [ 10%] Meshing curve 2 (Line)
Info    : [ 20%] Meshing curve 3 (Line)
Info    : [ 20%] Meshing curve 4 (Line)
Info    : [ 30%] Meshing curve 5 (Line)
Info    : [ 40%] Meshing curve 6 (Extruded)
Info    : [ 40%] Meshing curve 7 (Extruded)
Info    : [ 50%] Meshing curve 8 (Extruded)
Info    : [ 50%] Meshing curve 10 (Extruded)
Info    : [ 60%] Meshing curve 12 (Extruded)
Info    : [ 70%] Meshing curve 14 (Extruded)
Info    : [ 70%] Meshing curve 16 (Extruded)
Info    : [ 80%] Meshing curve 18 (Extruded)
Info    : [ 90%] Meshing curve 19 (Extruded)
Info    : [ 90%] Meshing curve 22 (Extruded)
Info    : [100%] Meshing curve 24 (Extruded)
Info    : Done meshing 1D (Wall 0.000240038s, CPU 0.000352s)
Info    : Meshing 2D...
Info    : [  0%] Meshing surface 9 (Extruded)
Info    : [ 20%] Meshing surface 13 (Extruded)
Info    : [ 40%] Meshing surface 17 (Extruded)
Info    : [ 60%] Meshing surface 21 (Extruded)
Info    : [ 80%] Meshing surface 25 (Extruded)
Info    : Done meshing 2D (Wall 0.00232941s, CPU 0.001437s)
Info    : 617 nodes 1439 elements
Info    : Writing 'I_beam.msh'...
Info    : Done writing 'I_beam.msh'