Meshed plate with Gmsh

This example connects NuSA’s preprocessing and analysis layers in one complete workflow:

geometry -> Gmsh -> triangle mesh -> LinearTriangleModel
    -> solve -> StaticResult -> recovered stress field

It is the recommended reference when a problem contains more triangles than is practical to create manually.

Problem

The model is a square plate with side length 0.3 and thickness 0.01. The left edge is fixed in both translational degrees of freedom and a total horizontal load of 6000 is distributed equally across the nodes on the right edge.

The material parameters are:

  • Young’s modulus E = 200e9

  • Poisson ratio nu = 0.3

  • thickness t = 0.01

The plate is discretized with constant-strain triangular elements.

1. Create the geometry

Modeler stores a small 2D geometry description and delegates mesh generation to the external Gmsh executable:

from nusa.mesh import Modeler

modeler = Modeler()
modeler.add_rectangle((0.0, 0.0), (0.3, 0.3), esize=0.05)
coordinates, connectivity = modeler.generate_mesh()

coordinates contains the compacted mesh points and connectivity contains zero-based three-node triangle indices.

For example, one connectivity row

[4, 7, 3]

means that one finite element is formed by mesh points 4, 7 and 3.

2. Convert the mesh to NuSA objects

Mesh points become nusa.node.Node objects:

nodes = [Node(tuple(point[:2])) for point in coordinates]

Each triangle is then converted to a nusa.element.LinearTriangle:

elements = [
    LinearTriangle(
        (nodes[int(i)], nodes[int(j)], nodes[int(k)]),
        E=200e9,
        nu=0.3,
        t=0.01,
    )
    for i, j, k in connectivity
]

The mesher deliberately does not create a finite-element Model itself. This keeps preprocessing independent from analysis and makes the conversion step explicit.

3. Build the finite-element problem

model = LinearTriangleModel("Meshed square plate")
model.add_nodes(nodes)
model.add_elements(elements)

Boundary conditions and loads can be selected from geometry. Here, nodes on the minimum x-coordinate are fixed and nodes on the maximum x-coordinate share the total horizontal force:

xmin = coordinates[:, 0].min()
xmax = coordinates[:, 0].max()

loaded_nodes = [node for node in nodes if np.isclose(node.x, xmax)]
force_per_node = 6000.0 / len(loaded_nodes)

for node in nodes:
    if np.isclose(node.x, xmin):
        model.add_constraint(node, ux=0.0, uy=0.0)
    if np.isclose(node.x, xmax):
        model.add_force(node, (force_per_node, 0.0))

Using np.isclose is preferable to exact floating-point comparisons when selecting mesh boundaries.

4. Solve

The solve stage is unchanged from manually created models:

result = model.solve()

The mesh is now frozen into the returned nusa.result.StaticResult through its node coordinates and connectivity.

5. Post-process a stress field

A CST element produces element stress and strain components. NuSA can recover those values to nodes and compute the plane-stress von Mises field:

von_mises = result.nodal_field("von_mises_stress")

or plot it directly:

result.plot_nodal_field("von_mises_stress")

The current nodal recovery policy is the arithmetic average of adjacent element values. See Post-processing for details.

Visualizing preprocessing and results

The three visualization stages remain separate:

modeler.plot_mesh()                  # generated mesh
plot_model(model)                    # FE problem definition
result.plot_nodal_field(
    "von_mises_stress"
)                                    # solved field

This mirrors the ownership model used throughout NuSA:

Modeler -> mesh
Model   -> finite-element problem
Result  -> solved state

Complete executable example

The following script is the same meshed-plate example maintained under examples/:

 1# -*- coding: utf-8 -*-
 2# ***********************************
 3#  Author: Pedro Jorge De Los Santos
 4#  E-mail: delossantosmfq@gmail.com
 5#  License: MIT License
 6# ***********************************
 7
 8import matplotlib.pyplot as plt
 9import numpy as np
10
11from nusa import LinearTriangle, LinearTriangleModel, Node, plot_model
12from nusa.mesh import Modeler
13
14
15E = 200e9
16NU = 0.3
17THICKNESS = 0.01
18TOTAL_FORCE = 6000.0
19
20
21def build_model(esize=0.05, gmsh_executable="gmsh"):
22    """Build a meshed square plate using Gmsh-generated CST elements."""
23    modeler = Modeler()
24    modeler.add_rectangle((0.0, 0.0), (0.3, 0.3), esize=esize)
25    coordinates, connectivity = modeler.generate_mesh(
26        gmsh_executable=gmsh_executable,
27    )
28
29    nodes = [Node(tuple(point[:2])) for point in coordinates]
30    elements = [
31        LinearTriangle(
32            (nodes[int(i)], nodes[int(j)], nodes[int(k)]),
33            E=E,
34            nu=NU,
35            t=THICKNESS,
36        )
37        for i, j, k in connectivity
38    ]
39
40    model = LinearTriangleModel("Meshed square plate")
41    model.add_nodes(nodes)
42    model.add_elements(elements)
43
44    xmin = coordinates[:, 0].min()
45    xmax = coordinates[:, 0].max()
46    loaded_nodes = [node for node in nodes if np.isclose(node.x, xmax)]
47    nodal_force = TOTAL_FORCE / len(loaded_nodes)
48
49    for node in nodes:
50        if np.isclose(node.x, xmin):
51            model.add_constraint(node, ux=0.0, uy=0.0)
52        if np.isclose(node.x, xmax):
53            model.add_force(node, (nodal_force, 0.0))
54
55    return model, modeler
56
57
58def main():
59    model, modeler = build_model()
60
61    modeler.plot_mesh()
62    plot_model(model)
63
64    result = model.solve()
65    result.plot_nodal_field("von_mises_stress")
66    plt.show()
67    return result
68
69
70if __name__ == "__main__":
71    main()

Gmsh requirement

Generating a new mesh requires the Gmsh command-line executable. Before running this example, the following should succeed in the same terminal or notebook environment:

gmsh --version

Loading an existing triangular mesh with generate_mesh_from_file() only requires meshio and does not invoke Gmsh.

See Mesh utilities for installation notes, geometry helpers, custom executable selection, and troubleshooting.