snappyHexMesh is the most powerful mesher in OpenFOAM, but it is also notoriously tricky to configure. Small errors in the snappyHexMeshDict, a bad STL file, or wrong location settings can produce a mesh that fails checkMesh, loses layers, or crashes entirely. This guide covers the full dict structure, the most common error messages, and how to fix each one.
snappyHexMesh runs in up to three phases, each controlled by a switch in the dict:
// system/snappyHexMeshDict — top-level phase switches
castellatedMesh true; // phase 1: refine and cut cells
snap true; // phase 2: move surface vertices onto geometry
addLayers true; // phase 3: grow prism layers on walls
When debugging, set addLayers false first and get a clean castellated+snapped mesh before worrying about layers. Layers are the most failure-prone phase.
A typical snappyHexMesh workflow is: 1) create the background hex mesh with blockMesh, 2) run surfaceFeatureExtract to extract .eMesh from STL, 3) run snappyHexMesh, 4) verify with checkMesh -latestTime, 5) rename or copy the final time directory to constant/polyMesh. Always place your STL files in constant/triSurface/.
geometry
{
myGeometry.stl
{
type triSurfaceMesh;
name body; // used as patch name in the mesh
regions
{
inlet_surface { name inlet; }
outlet_surface { name outlet; }
}
}
refinementBox
{
type searchableBox;
min (-1 -1 -1);
max (2 1 1);
}
}
castellatedMeshControls
{
maxLocalCells 1000000;
maxGlobalCells 2000000;
minRefinementCells 10;
maxLoadUnbalance 0.10;
nCellsBetweenLevels 3; // buffer cells between refinement levels
features
(
{
file "myGeometry.eMesh";
level 2;
}
);
refinementSurfaces
{
body
{
level (2 3); // (min max) refinement levels
patchInfo { type wall; }
}
}
refinementRegions
{
refinementBox
{
mode inside;
levels ((1E15 1)); // level 1 everywhere inside box
}
}
locationInMesh (0 0 0.5); // CRITICAL: must be inside the fluid domain
allowFreeStandingZoneFaces true;
}
snapControls
{
nSmoothPatch 3;
tolerance 2.0;
nSolveIter 30;
nRelaxIter 5;
nFeatureSnapIter 10;
implicitFeatureSnap false;
explicitFeatureSnap true; // requires .eMesh file
multiRegionFeatureSnap false;
}
addLayersControls
{
relativeSizes true; // layer thickness relative to local cell size
layers
{
body
{
nSurfaceLayers 5;
}
}
expansionRatio 1.2;
finalLayerThickness 0.3;
minThickness 0.1;
nGrow 0;
featureAngle 130; // surfaces with angle > this are treated as features
nRelaxIter 3;
nSmoothSurfaceNormals 1;
nSmoothNormals 3;
nSmoothThickness 10;
maxFaceThicknessRatio 0.5;
maxThicknessToMedialRatio 0.3;
minMedianAxisAngle 90;
nBufferCellsNoExtrude 0;
nLayerIter 50;
}
For explicitFeatureSnap true, you must pre-extract the edge features from your STL file using surfaceFeatureExtract (older versions) or surfaceFeatures (v2012+). This creates a .eMesh file referenced in the features block.
// system/surfaceFeatureExtractDict (pre-v2012)
myGeometry.stl
{
extractionMethod extractFromSurface;
extractFromSurfaceCoeffs
{
includedAngle 150; // extract edges sharper than 30° (180-150)
}
writeObj yes;
}
Run it with: surfaceFeatureExtract. This produces constant/triSurface/myGeometry.eMesh.
In OpenFOAM v2012+, the equivalent utility is called surfaceFeatures and uses system/surfaceFeaturesDict. The output is the same .eMesh format. Always verify the extracted features by loading the .obj file in ParaView — you should see edge lines on the expected sharp features only.
For large meshes (above ~5 million cells), running snappyHexMesh in parallel is essential. The workflow is: decompose the background mesh with decomposePar, then run snappy in parallel, then reconstruct.
// Run snappyHexMesh in parallel (8 cores)
blockMesh
decomposePar
mpirun -np 8 snappyHexMesh -overwrite -parallel
reconstructParMesh -constant
Use -overwrite to write directly to the constant/polyMesh directory instead of creating numbered time directories. Note that reconstructParMesh is needed (not reconstructPar) for mesh reconstruction.
For very large cases, disable writeInterval intermediate meshes by setting runTimeModifiable false and checking that the memory per core is adequate. Each snappyHexMesh phase (castellated, snap, addLayers) writes a separate time directory in parallel mode.
If snappyHexMesh prints Cannot find patch 'inlet' in mesh or similar, the patch name in snappyHexMeshDict does not match any named region in your STL file. STL regions are case-sensitive. Open your STL file and verify the solid name matches exactly what you reference in the regions block of the geometry section. Alternatively, export your geometry as a single STL per patch and list each file separately in the geometry block.
This happens when snappyHexMesh cannot grow prismatic layers in a region because the cells are too compressed, the surface has high curvature, or two surfaces are too close together. Strategies to fix it:
nSurfaceLayers to 3 or even 2 and check if layers formfinalLayerThickness to 0.1 or 0.2minThickness (e.g. 0.01) — this allows snappy to give up on thin patches and still produce the restmaxThicknessToMedialRatio to 0.5 or 0.6 for tight geometriesThe locationInMesh point tells snappyHexMesh which side of the geometry is the fluid domain. If this point falls inside a solid body, snappyHexMesh will remove the fluid cells and keep the solid region — producing an empty or inverted mesh. Always verify that your locationInMesh coordinate is clearly inside the fluid domain, not touching any surface.
Always run checkMesh after snappyHexMesh completes. Look for:
nCellsBetweenLevels)// Run checkMesh on the snappy output time directory
checkMesh -latestTime
Use searchableBox, searchableSphere, or searchableCylinder geometry objects in the refinementRegions block to add volumetric refinement in specific areas (near the body wake, around the inlet jet, etc.) without refining the entire mesh.
refinementRegions
{
wakeRegion // name defined in geometry{} block
{
mode inside;
levels ((1E15 2)); // level 2 refinement everywhere inside
}
}
The background hex mesh from blockMesh determines the base cell size from which snappyHexMesh refines. Sizing it correctly is critical: too coarse and you need too many refinement levels (expensive), too fine and the background mesh itself is expensive before refinement.
A good rule: the base cell size should be roughly the size of the domain divided by 20–50 in each direction. Then use refinement levels to achieve the target near-wall cell size. Each refinement level halves the cell size. For example, if your base cell is 0.1 m and you need 0.00625 m at the wall, you need 4 levels of refinement (0.1 / 2^4 = 0.00625).
// system/blockMeshDict — simple background box
vertices
(
(-2 -1 -1)
(10 -1 -1)
(10 3 -1)
(-2 3 -1)
(-2 -1 1)
(10 -1 1)
(10 3 1)
(-2 3 1)
);
blocks
(
hex (0 1 2 3 4 5 6 7) (120 40 20) simpleGrading (1 1 1)
// 120 cells in x (12m / 0.1m), 40 in y (4m / 0.1m), 20 in z (2m / 0.1m)
);
Keep the background mesh cells as close to cubic as possible (equal number of cells per unit length in each direction). Highly anisotropic background cells produce poor snapped meshes.
snappyHexMesh is sensitive to STL geometry quality. Common STL problems that cause snappy failures:
surfaceCheck to detect them.// Check STL quality before running snappyHexMesh
surfaceCheck constant/triSurface/myGeometry.stl
If surfaceCheck reports open edges or self-intersections, fix the geometry before proceeding. Tools like Blender, FreeCAD, or Meshmixer can repair STL files.
Upload your case and CFDpilot reads your snappyHexMeshDict and checkMesh output to flag refinement and layer problems.
Diagnose my mesh →The most common cause is a wrong locationInMesh point. This point must be clearly inside the fluid domain. If it falls inside a solid body, snappyHexMesh keeps the solid region and removes the fluid. Verify the coordinate by confirming it is in open fluid space, not touching any surface.
The patch name in snappyHexMeshDict (under the regions block of the geometry section) does not match the solid name in your STL file. STL region names are case-sensitive. Open the STL file in a text editor and check the solid name. Alternatively, export each patch as a separate STL file and reference each file individually in the geometry block.
First disable addLayers and confirm the castellated+snapped mesh passes checkMesh. Then re-enable layers with reduced nSurfaceLayers (try 3 instead of 5), reduce finalLayerThickness to 0.1, and increase minThickness to 0.01 to allow snappy to skip problem areas where geometry is too tight for layers.
The includedAngle setting extracts edges sharper than (180 - includedAngle) degrees. A value of 150 extracts edges sharper than 30 degrees, capturing most engineering features. Use 120 for smoother transitions. Using 180 extracts all edges — this can create excessive feature lines and degrade mesh quality.
Compute the required first cell height from the formula: y = y+ * nu / u_tau, where u_tau is the friction velocity (estimated from wall shear stress). Then set relativeSizes false and firstLayerThickness to the computed value. Set nSurfaceLayers and expansionRatio (typically 1.2) to grow the layer stack outward.
nCellsBetweenLevels sets the number of buffer cells inserted between refinement levels. A higher value (3 or 4 instead of the default 1) creates a smoother transition between coarse and refined regions, reducing non-orthogonality at refinement boundaries and improving checkMesh quality metrics.