How to Mesh a Plate with a Hole for FEA in Salome
Most FEA problems I see from beginners are not solver problems. They are mesh problems. The geometry is fine, the material is fine, the loads look right, and then the peak stress changes by 30 percent every time the mesh is regenerated. Salome is one of the best free tools for getting this part right, because it gives you full control over geometry groups, local mesh sizes and export formats, and it can be scripted in Python from start to finish.
In this tutorial we will mesh a classic test case, a flat plate with a central hole under tension, in Salome. We will build the geometry, create named groups for the boundary conditions, generate a second-order tetrahedral mesh with a refined zone around the hole, check the quality, and export the result as MED (for Code_Aster) and UNV (for other solvers). At the end you will also have a hand-calculated target stress so you know whether the solver result that comes out of this mesh is believable.
What You Need
Download the Salome platform from the official site (salome-platform.org). The steps below follow the Salome 9 series, which uses Python 3 and the Geometry and Mesh modules described here. If you want to solve with Code_Aster inside the same environment, salome_meca bundles Salome, Code_Aster and the AsterStudy interface, but the meshing workflow is identical in both. The NETGEN mesher used in this tutorial ships with Salome, so there is nothing else to install.
The Example Part
We will use these example inputs. They are not taken from a real component, they are simply round numbers that make the hand check easy:
- Plate length L = 200 mm, width W = 50 mm, thickness t = 5 mm
- Central hole diameter d = 10 mm, so d/W = 0.2
- Axial tensile load F = 10 kN on one end, the other end fixed
The net cross section at the hole is (50 - 10) x 5 = 200 mm2, so the nominal net-section stress is 10000 / 200 = 50.0 MPa. Using Heywood's formula for a finite-width plate, Kt = 2 + (1 - d/W)^3 = 2.512. The Peterson polynomial fit, Kt = 3.00 - 3.13(d/W) + 3.66(d/W)^2 - 1.53(d/W)^3, gives 2.508. Both formulas therefore predict a peak stress at the edge of the hole of about 125 MPa (125.6 and 125.4 MPa). That number is our target. A good mesh should land close to it, and a bad mesh will usually come in low because it cannot resolve the steep gradient at the hole edge.

Stress concentration factor Kt versus d/W from Heywood's formula and the Peterson polynomial fit, with the example case marked. Plot by Bugo Center, made with Python and Matplotlib.
Step 1: Build the Geometry
Start Salome and activate the Geometry module. Create the plate with New Entity, Primitives, Box, using the "two points" or "dimensions" option with DX = 200, DY = 50, DZ = 5. Then create a cylinder with its base point at (100, 25, 0), the Z axis as direction, radius 5 and height 5. Use Operations, Boolean, Cut with the box as the main object and the cylinder as the tool. Rename the result to Plate.
Before going further, open Measures, What is and check that the result is a single solid. If you see more than one solid, or the cut failed silently, fix it now. Every later step depends on a clean solid.
Step 2: Create Groups for Boundary Conditions
This is the step people skip, and they regret it later. Groups are named subsets of the geometry (faces, edges or volumes) that are carried into the mesh and then into the solver. In Code_Aster, every boundary condition and load is applied to a group name, so you want them defined on the geometry where they survive remeshing.
Use New Entity, Group, Create Group with shape type Face, and select:
- fixed: the end face at x = 0
- load: the end face at x = 200
- hole: the cylindrical face of the hole
Also create a volume group called plate containing the solid, which is where the material will be assigned. The hole group is not used for a boundary condition here, but we will use it to control the local mesh size, and later it is handy for extracting stresses along the hole edge.
Step 3: Mesh with NETGEN 1D-2D-3D
Switch to the Mesh module and choose Mesh, Create Mesh with Plate as the geometry. On the 3D tab, select the algorithm NETGEN 1D-2D-3D and add the hypothesis NETGEN 3D Parameters. Reasonable starting values for this part are:
- Max Size: 4 mm
- Min Size: 0.5 mm
- Fineness: Fine
- Second Order: enabled
- Optimize: enabled
Then open the Local Sizes tab, click "On Face" and pick the hole group, with a size of 0.8 mm. The hole circumference is pi x 10 = 31.4 mm, so 0.8 mm gives roughly 39 element edges around the hole. That is a sensible starting point for a stress concentration. Through the 5 mm thickness, the global size alone would give only one or two elements, which is why the local size matters.
Second-order elements are not optional for stress work with tetrahedra. Linear tetrahedra are too stiff in bending and badly underpredict stress gradients. With quadratic tetrahedra (10 nodes), you get far better results for the same number of elements.
Click Apply and Close, then right-click the mesh and choose Compute. The information dialog at the end shows the number of nodes and elements of each type. Write these numbers down, because you will compare them in the convergence check below.
Step 4: Transfer the Groups to the Mesh
A geometry group is not automatically a mesh group. Select the mesh and use Mesh, Create Groups from Geometry, then select all four geometry groups (fixed, load, hole, plate). Salome creates face groups made of the boundary triangles and a volume group made of the tetrahedra. In the object browser you should now see them under the mesh, with element counts next to each one.
Check them visually. Hide everything except one group at a time and confirm that the fixed group is really the face at x = 0, and so on. Applying a load to the wrong face is a very common and very silent error.
Step 5: Check Mesh Quality
Right-click the mesh and open Controls, Volume Controls, Aspect Ratio 3D. Salome colors every element by its aspect ratio and shows a histogram. For a NETGEN mesh with optimization switched on, most elements should be close to the ideal value, with only a small tail. If you see a cluster of very distorted elements, they are usually at a small sliver face or a tangent edge in the geometry. Fix the geometry rather than fighting the mesher.
Also look at Face Controls on the hole group to see how regular the surface triangles are. Uneven triangles on the hole surface will show up as a noisy stress plot right where you care about the result.
Step 6: Script It in Python
Once the GUI workflow is clear, put it in a script so you can rerun it with different sizes. Salome records your GUI actions with File, Dump Study, which is the most reliable way to get the exact API calls for your version. A condensed version of the workflow looks like this:
import salome
salome.salome_init()
import GEOM, SMESH
from salome.geom import geomBuilder
from salome.smesh import smeshBuilder
geompy = geomBuilder.New()
smesh = smeshBuilder.New()
box = geompy.MakeBoxDXDYDZ(200, 50, 5)
cyl = geompy.MakeCylinder(geompy.MakeVertex(100, 25, 0),
geompy.MakeVectorDXDYDZ(0, 0, 1), 5, 5)
plate = geompy.MakeCut(box, cyl)
geompy.addToStudy(plate, "Plate")
def face_group(name, point):
g = geompy.CreateGroup(plate, geompy.ShapeType["FACE"])
geompy.UnionList(g, [geompy.GetFaceNearPoint(plate, point)])
geompy.addToStudyInFather(plate, g, name)
return g
g_fixed = face_group("fixed", geompy.MakeVertex(0, 25, 2.5))
g_load = face_group("load", geompy.MakeVertex(200, 25, 2.5))
g_hole = face_group("hole", geompy.MakeVertex(95, 25, 2.5))
mesh = smesh.Mesh(plate, "PlateMesh")
netgen = mesh.Tetrahedron(algo=smeshBuilder.NETGEN_1D2D3D)
p = netgen.Parameters()
p.SetMaxSize(4.0)
p.SetMinSize(0.5)
p.SetFineness(3) # 0 very coarse ... 4 very fine
p.SetSecondOrder(1)
p.SetOptimize(1)
p.SetLocalSizeOnShape(g_hole, 0.8)
mesh.Compute()
for g, name in ((g_fixed, "fixed"), (g_load, "load"), (g_hole, "hole")):
mesh.GroupOnGeom(g, name, SMESH.FACE)
mesh.GroupOnGeom(plate, "plate", SMESH.VOLUME)
print(mesh.NbNodes(), mesh.NbTetras())
mesh.ExportMED("/home/user/plate_hole.med")
mesh.ExportUNV("/home/user/plate_hole.unv")
Run it from the Salome Python console, or with File, Load Script. Change the paths to suit your system. If any call behaves differently in your release, do the step once in the GUI and compare with the Dump Study output. That is faster than searching the documentation.
Step 7: Export the Mesh
For Code_Aster, export with File, Export, MED file. MED keeps the group names, so in AsterStudy or in your command file you can refer directly to GROUP_MA='fixed' and GROUP_MA='load'. For other solvers, UNV is the usual exchange format. CalculiX users typically convert UNV to an .inp file with a converter script, and Elmer reads UNV through ElmerGrid. Whatever path you use, open the converted mesh once and confirm that the group names and element types arrived intact, especially that the elements are still quadratic.
Step 8: A Simple Convergence Check
After you solve the case, read the maximum principal or axial stress at the edge of the hole and compare it with the target of about 125 MPa. Then halve the local size on the hole (0.8 to 0.4 mm), remesh, solve again and compare. If the peak changes by only a few percent, the mesh is converged for this purpose. If it keeps climbing, keep refining locally, not globally. The far-field stress in the plate does not need a fine mesh at all, which is the whole point of local sizing.
Keep in mind what the hand calculation can and cannot tell you. The Kt formulas assume a long plate with a remote uniform stress and linear elastic material. Our plate is 200 mm long, so with the hole at the center the end effects from the fixed face have died out by the time they reach the hole. If you move the hole close to a support, the formula stops being a valid check, and the convergence study becomes your only guide.
Common Mistakes
- Groups created on the mesh only. They disappear the moment you remesh. Create them on the geometry.
- Linear tetrahedra for stress. Always switch on Second Order for stress results with tetra meshes.
- Only one element through the thickness. Use a local size on the hole and check the section with a clipping plane.
- Units confusion. Salome has no units. If you model in millimetres, use N and MPa in the solver consistently.
- Trusting the first result. One mesh is one data point. Two meshes are the minimum for any stress you plan to report.
Where to Go Next
If you have not tried FEA in FreeCAD yet, our tutorial on how to run a cantilever beam FEA in FreeCAD with CalculiX follows the same idea of checking the solver against a hand calculation. The same discipline applies to CFD meshes, where the near-wall resolution controls the result, as described in how to calculate first cell height for a target y+ in OpenFOAM. And if you prefer thermofluids scripting in Python, see how to calculate adiabatic flame temperature with Python and Cantera.
Meshing is not glamorous, but it is where most of the accuracy of a simulation is won or lost. If you want to go deeper into the CFD side, including how turbulence models depend on the near-wall mesh, the book Memahami Turbulensi: Konsep, Persamaan, dan Praktik CFD dengan Perangkat Lunak Gratis (in Indonesian) covers the concepts, equations and practice with free software. And if you found this tutorial useful, follow the Bugo Center blog for more practical engineering guides with open-source tools.