OpenFOAM 14 Case 03: Importing a Gmsh Mesh with gmshToFoam

by giux CFD and OpenFOAM 7 min read

Third case of the Learning OpenFOAM 14 path: complete reproducible procedure, mesh import and analytical validation. I rebuild the laminar channel from Case 01 (also used in the Case 02 mesh convergence study) with an external Gmsh mesh instead of blockMesh.

1. Purpose

This handout reproduces the laminar channel benchmark using an external Gmsh mesh instead of blockMesh. The physical model, boundary conditions, solver settings and analytical reference are intentionally kept identical to CFD Training Case 01. The only controlled change is the mesh-generation route.

The workflow is:

Gmsh geometry → Gmsh mesh → gmshToFoam → OpenFOAM → analytical validation.

The exercise demonstrates how to separate geometry/meshing from the CFD solver while preserving a verifiable benchmark.

2. Target result

QuantityResult
Mesh generatorGmsh
Import utilitygmshToFoam
Mesh formatMSH 2.2 ASCII
Cells8000
Cell type8000 hexahedra
checkMeshMesh OK
CFD sampled maximum Ux0.0149625470 m/s
Theoretical maximum Ux0.0150000000 m/s
Normalized RMSE0.150343 %
Comparison with Case 01numerically indistinguishable

3. Prerequisites

Check the required tools:

gmsh --version
command -v gmshToFoam
command -v changeDictionary
command -v checkMesh
command -v foamRun

If Gmsh is not installed on Debian:

su -
apt update
apt install gmsh
exit

The OpenFOAM 14 environment must already be loaded.

4. Create the case directory

The new case reuses the validated physics from Case 01 so that only the mesh-generation method changes.

cd $FOAM_RUN
mkdir -p 03_gmsh_channel/{0,constant,system,geometry}

Copy the validated physical and numerical dictionaries from Case 01:

cp 01_laminar_channel/0/U \
   01_laminar_channel/0/p \
   03_gmsh_channel/0/

cp 01_laminar_channel/constant/physicalProperties \
   01_laminar_channel/constant/momentumTransport \
   03_gmsh_channel/constant/

cp 01_laminar_channel/system/controlDict \
   01_laminar_channel/system/fvSchemes \
   01_laminar_channel/system/fvSolution \
   03_gmsh_channel/system/

cd $FOAM_RUN/03_gmsh_channel

No blockMeshDict is used in this case.

5. Gmsh geometry and structured mesh

Create geometry/channel.geo with the following complete content:

// Laminar channel benchmark
// Geometry: 0.5 x 0.01 x 0.001 m
// Structured mesh: 200 x 40 x 1 cells

Mesh.MshFileVersion = 2.2;
Mesh.Binary = 0;

// Points
Point(1) = {0.0, -0.005, 0.0};
Point(2) = {0.5, -0.005, 0.0};
Point(3) = {0.5,  0.005, 0.0};
Point(4) = {0.0,  0.005, 0.0};

Point(5) = {0.0, -0.005, 0.001};
Point(6) = {0.5, -0.005, 0.001};
Point(7) = {0.5,  0.005, 0.001};
Point(8) = {0.0,  0.005, 0.001};

// Edges
Line(1) = {1,2};
Line(2) = {2,3};
Line(3) = {3,4};
Line(4) = {4,1};

Line(5) = {5,6};
Line(6) = {6,7};
Line(7) = {7,8};
Line(8) = {8,5};

Line(9)  = {1,5};
Line(10) = {2,6};
Line(11) = {3,7};
Line(12) = {4,8};

// Surfaces
Curve Loop(1) = {1,2,3,4};
Plane Surface(1) = {1};

Curve Loop(2) = {5,6,7,8};
Plane Surface(2) = {2};

Curve Loop(3) = {1,10,-5,-9};
Plane Surface(3) = {3};

Curve Loop(4) = {2,11,-6,-10};
Plane Surface(4) = {4};

Curve Loop(5) = {3,12,-7,-11};
Plane Surface(5) = {5};

Curve Loop(6) = {4,9,-8,-12};
Plane Surface(6) = {6};

// Volume
Surface Loop(1) = {-1,2,3,4,5,6};
Volume(1) = {1};

// Structured resolution
Transfinite Curve {1,3,5,7} = 201;
Transfinite Curve {2,4,6,8} = 41;
Transfinite Curve {9,10,11,12} = 2;

Transfinite Surface {1,2,3,4,5,6};
Recombine Surface {1,2,3,4,5,6};
Transfinite Volume {1} = {1,2,3,4,5,6,7,8};

// OpenFOAM boundary names
Physical Surface("frontAndBack") = {1,2};
Physical Surface("walls")        = {3,5};
Physical Surface("outlet")       = {4};
Physical Surface("inlet")        = {6};

Physical Volume("fluid") = {1};

The Transfinite definitions specify node counts. Therefore:

  • 201 nodes in x → 200 cells
  • 41 nodes in y → 40 cells
  • 2 nodes in z → 1 cell

Total:

200 × 40 × 1 = 8000 cells.

6. Generate the Gmsh mesh

Run:

gmsh -3 geometry/channel.geo \
     -format msh2 \
     -o geometry/channel.msh

Check that the file exists:

ls -lh geometry/channel.msh

The msh2 format is used deliberately because it is straightforward for gmshToFoam and preserves the physical names required for patch mapping.

7. Import the mesh into OpenFOAM

Run:

gmshToFoam geometry/channel.msh | tee log.gmshToFoam

The validated import reported:

Vertices to be read: 16482
Cells to be read: 24480

Cells:
    total:8000
    hex :8000
    prism:0
    pyr :0
    tet :0

Patch 0 gets name frontAndBack
Patch 1 gets name walls
Patch 2 gets name outlet
Patch 3 gets name inlet

Writing zone 0 to cellZone fluid and cellSet
End

The Gmsh Physical Surface names are therefore transferred to OpenFOAM patch names.

8. Correct the OpenFOAM patch types

After gmshToFoam, imported surface groups are normally generic patches. The solver requires:

  • walls → wall
  • frontAndBack → empty

Create system/changeDictionaryDict:

FoamFile
{
    format      ascii;
    class       dictionary;
    object      changeDictionaryDict;
}

boundary
{
    walls
    {
        type wall;
    }

    frontAndBack
    {
        type empty;
    }
}

Apply the changes:

changeDictionary

9. Verify the imported mesh

Run:

checkMesh | tee log.checkMesh

Extract the key values:

grep -E \
"points:|faces:|cells:|hexahedra:|max aspect ratio|Mesh OK" \
log.checkMesh

Validated result:

points:               16482
faces:                32240
cells:                8000
hexahedra:            8000
Mesh OK.

Verify the boundary file:

grep -A6 -E \
"inlet|outlet|walls|frontAndBack" \
constant/polyMesh/boundary

Expected operational patch types:

frontAndBack   type empty
walls          type wall
outlet         type patch
inlet          type patch

10. Physical and numerical files

The following files are intentionally identical to Case 01:

  • 0/U
  • 0/p
  • constant/physicalProperties
  • constant/momentumTransport
  • system/controlDict
  • system/fvSchemes
  • system/fvSolution

This is essential for the comparison: the mesh generator is the only intended change.

Key physical values remain:

Uinlet = (0.01 0 0) m/s
nu     = 1e-6 m^2/s
simulationType = laminar

The steady run uses solver incompressibleFluid; and endTime 2000; exactly as in Case 01.

11. Execute the CFD calculation

Run:

foamRun | tee log.foamRun

Inspect the end of the log:

tail -40 log.foamRun

The calculation should terminate normally with:

End

12. Recreate the velocity-profile sampling

Create system/graphUniform:

FoamFile
{
    format      ascii;
    class       dictionary;
    object      graphUniform;
}

start       (0.45 -0.004875 0.0005);
end         (0.45  0.004875 0.0005);
nPoints     40;

fields      (U);

axis        y;

#includeEtc "caseDicts/postProcessing/graphs/graphUniform.cfg"

Run:

foamPostProcess \
    -solver incompressibleFluid \
    -func graphUniform \
    -latestTime

Locate the output:

find postProcessing/graphUniform -name line.xy

Expected file:

postProcessing/graphUniform/2000/line.xy

13. Quantitative analytical validation

Run the same comparison used for the blockMesh benchmark:

f=$(find postProcessing/graphUniform -name line.xy | sort -V | tail -1)

awk '
BEGIN {
    H=0.01
    Umean=0.01
    UmaxTheory=1.5*Umean
    umax=-1
}
$1 !~ /^#/ && NF >= 2 {
    y=$1
    u=$2

    uth=UmaxTheory*(1-(2*y/H)^2)

    e=u-uth
    ae=(e<0 ? -e : e)

    sumsq += e*e
    n++

    if (ae > maxerr)
        maxerr=ae

    if (u > umax)
        umax=u
}
END {
    rmse=sqrt(sumsq/n)

    printf "Points                 : %d\n",n
    printf "CFD maximum Ux         : %.10f m/s\n",umax
    printf "Theoretical Umax       : %.10f m/s\n",UmaxTheory
    printf "RMSE                   : %.10e m/s\n",rmse
    printf "Normalized RMSE        : %.6f %%\n",100*rmse/UmaxTheory
    printf "Maximum abs error      : %.10e m/s\n",maxerr
    printf "Max error / Umax       : %.6f %%\n",100*maxerr/UmaxTheory
}' "$f"

Validated Gmsh result:

Points              : 40
CFD maximum Ux      : 0.0149625470 m/s
Theoretical Umax    : 0.0150000000 m/s
RMSE                : 2.2551524240e-05 m/s
Normalized RMSE     : 0.150343 %
Maximum abs error   : 2.8078000000e-05 m/s
Max error / Umax    : 0.187187 %

14. Direct comparison with the blockMesh benchmark

Meshing routeCellsUmax [m/s]NRMSE [%]
blockMesh – Case 0180000.01496254700.150343
Gmsh -> gmshToFoam – Case 0380000.01496254700.150343

The RMSE values differ by only approximately

5.31 × 10−12 m/s,

which is negligible for this benchmark. The two CFD solutions are numerically indistinguishable.

This confirms that the solution is not tied to blockMesh; an equivalent structured mesh imported from Gmsh produces the same physical result.

15. ParaView visualization

Create a marker file:

cd $FOAM_RUN/03_gmsh_channel
touch gmsh_channel.foam

If pvserver is already running and the client is connected, no reconnection is necessary. In ParaView:

File -> Open

and open:

$FOAM_RUN/03_gmsh_channel/gmsh_channel.foam

Select internalMesh, enable U and p, then press Apply.

Recommended display:

Representation = Surface With Edges
Coloring       = U -> Magnitude

The image below shows the validated velocity field on the imported Gmsh mesh.

Velocity magnitude on the Gmsh-imported mesh shown in ParaView
Figure 1: Velocity magnitude on the imported Gmsh mesh in ParaView.

16. Streamline check

The same Stream Tracer settings as Case 01 can be used:

PropertyValue
VectorsU
Integration DirectionFORWARD
Seed TypeLine
Point1(0.001, -0.0045, 0.0005)
Point2(0.001, +0.0045, 0.0005)
Resolution20
Maximum Streamline Length0.5 m

The streamlines should remain straight and parallel to the channel axis, as in the blockMesh solution.

17. Generic troubleshooting

SymptomCheck / correction
Gmsh physical names do not appear as OpenFOAM patchesensure the surfaces are declared as Physical Surface groups
import behavior is inconsistentgenerate an ASCII MSH 2.2 file with -format msh2
walls remains a generic patchrun changeDictionary using the supplied dictionary
frontAndBack remains a generic patchset its type to empty before the CFD run
checkMesh cell count is not 8000verify the transfinite node counts: 201, 41 and 2
ParaView does not show the case filecreate it with touch gmsh_channel.foam
no post-processing file appearsuse foamPostProcess -solver incompressibleFluid -func graphUniform -latestTime

18. Acceptance criteria

Acceptance criterionStatus
Gmsh generates the structured volume meshPASS
gmshToFoam imports all named patchesPASS
imported mesh contains 8000 hexahedraPASS
checkMesh reports Mesh OKPASS
walls is wall and frontAndBack is emptyPASS
OpenFOAM run converges normallyPASS
velocity profile agrees with Poiseuille theoryPASS
NRMSE = 0.150343%PASS
Gmsh result matches the blockMesh benchmarkPASS
ParaView visualization is correctPASS

Final status: CASE 03 COMPLETE AND VALIDATED.

19. Reproducibility checklist

  • Verify Gmsh and OpenFOAM utilities.
  • Create 03_gmsh_channel.
  • Copy the validated physical/numerical files from Case 01.
  • Create the complete geometry/channel.geo file.
  • Generate geometry/channel.msh in MSH 2.2 format.
  • Import with gmshToFoam.
  • Create changeDictionaryDict and run changeDictionary.
  • Run checkMesh and verify 8000 hexahedra plus Mesh OK.
  • Run foamRun.
  • Create graphUniform and sample the final velocity profile.
  • Compare the sampled profile with the analytical Poiseuille solution.
  • Create gmsh_channel.foam.
  • Open the case through the existing ParaView client-server connection.
  • Verify U, p, mesh edges and streamlines.

The case demonstrates a complete external-meshing workflow while retaining the same validated CFD physics as the baseline case.

Previous case: Case 02 – mesh convergence study. Next case: Case 04 – parametric mesh with Gmsh and Python, where the same geometry is generated by a parametric Python script.

#CFD #Gmsh #mesh #OpenFOAM #ParaView

Leave a Reply

Your email address will not be published. Required fields are marked *