Meshes¶
Every region of a case has its own mesh in constant/<region>/polyMesh (a single-region case,
such as a meshing step, has constant/polyMesh). umcfGui reads the files OpenFOAM writes there:
| File | Holds |
|---|---|
points |
point coordinates |
faces |
the points of each face (faceList, or faceCompactList in binary meshes) |
owner, neighbour |
the cells on either side of each face |
boundary |
the patches: name, type, faces, groups and parameters (neighbourRegion, samplePatch, …) |
cellZones |
named groups of cells (materials and cell zones of the solid regions) |
ASCII and binary (any arch), gzip-compressed or not, from OpenFOAM-8 and OpenFOAM-12. ASCII
points (and, for the 3D view, faces) with a FoamFile header are parsed as they are read,
compressed or not, so a big mesh's file is never held in memory whole, nor the text of its points
or internal faces. One the stream doesn't take (no header, a comment among the internal faces, a
uniform list) or a malformed one is read again whole, and the error names its line.
Checked before use¶
A mesh is checked as it is read; a mesh that fails is reported, not shown half-read:
- every face has at least three points, and every point it uses exists,
- there is one owner per face, and no more neighbours than faces,
- the number of cells matches
nCellsin theownerheader, - the patches cover the boundary faces exactly: each starts where the previous one ends, the first right after the internal faces, the last at the final face,
- every cell of a cell zone exists.
Mesh quality¶
umcf-cli mesh <case> (Command line) runs the geometric
checks OpenFOAM's checkMesh runs by default, on each region's mesh, with checkMesh's
thresholds. The computation is ported from OpenFOAM-12 (face areas and centres, cell centres
and volumes, and each check). checkMesh stays the authority: this is a quick look at meshes
nobody checked, and it says which faces and cells are involved, so they can be shown.
| Code | Level | What it means (checkMesh's threshold) |
|---|---|---|
mesh.non-finite |
Error | Points with a coordinate that is not a number or infinite, or so far apart that face areas or cell volumes overflow: no other number can be trusted |
mesh.unreadable |
Error | The region's mesh could not be read (the message says why) |
mesh.negative-volume |
Error | Cells with zero or negative volume |
mesh.face-pyramids |
Error | Faces wrongly oriented or twisted: the pyramid from the face to a cell centre has a negative volume (counted per pyramid, as checkMesh does: an internal face can have two) |
mesh.zero-area |
Error | Faces with zero area |
mesh.open-cells |
Error | Cells whose face area vectors don't add up to zero (openness above 1e-6) |
mesh.non-orthogonality-error |
Error | Internal faces 90° or more non-orthogonal: the two cell centres are on the wrong sides |
mesh.non-orthogonal |
Warning | Internal faces more than 70° non-orthogonal |
mesh.skewness |
Warning | Faces with skewness above 4 |
mesh.aspect-ratio |
Warning | Cells with an aspect ratio above 1000 (only the directions the mesh solves in count: the normal of empty patches doesn't) |
mesh.open-boundary |
Warning | The boundary's face area vectors don't add up to zero (above 1e-6, OpenFOAM-8's threshold; OpenFOAM-12's checkMesh passes 1 here, so it never fails this check) |
In a vegetation region every problem is a warning, except a mesh that can't be read or has
coordinates that are not numbers. The solver solves no flow equations on that mesh (it
carries the leaf temperature and the radiation exchange), and cases with a few wrongly oriented
faces there run: the large 3×3 cases have 6 to 12, which checkMesh reports too.
With -allGeometry¶
umcf-cli mesh --all-geometry (mesh.check with all_geometry: true) adds the checks of
checkMesh -allGeometry, ported from OpenFOAM-12 with its thresholds. They are slower (33 s
for 5.4 million cells, against 6 minutes for checkMesh) and all warnings: they hurt accuracy
or convergence, not the run.
| Code | Level | What it means (checkMesh's threshold) |
|---|---|---|
mesh.face-tets |
Warning | Faces whose decomposition into tets has a tet of low quality or negative volume (counted per failed test, up to three per face, as checkMesh does). Particle tracking and cell-point interpolation use the tets; the flow solution doesn't |
mesh.short-edges |
Warning | Points on edges shorter than a millionth of the mesh's extent |
mesh.near-points |
Warning | Pairs of points closer than a millionth of the mesh's extent. Like checkMesh, this counts only pairs about equally far from the origin, so it finds near-duplicate points |
mesh.concave-faces |
Warning | Faces with an angle between consecutive edges concave by more than 10° |
mesh.warped-faces |
Warning | Faces whose area is less than 0.8 of the sum of their triangles' areas |
mesh.cell-determinant |
Warning | Cells whose internal faces leave a direction poorly determined (determinant below 0.001), which gradients need. Cells of thin solid layers, with internal faces in one direction only, have it. The normals of empty and wedge patches are left out, as checkMesh does |
mesh.concave-cells |
Warning | Cells with a face centre on the outer side of another face's plane |
mesh.face-weights |
Warning | Faces with an interpolation weight below 0.05 (the face much nearer one cell centre) |
mesh.volume-ratio |
Warning | Faces between cells whose volumes differ by more than 100 times |
mesh.unchecked |
Info | Faces, cells or points left out of the pairwise checks (tet base points, concave cells, near points): over 1000 points or faces each, or past a work budget proportional to the mesh's size. Only a hostile or broken mesh reaches it |
If a coordinate is so large that a millionth of the extent overflows, short edges and near
points are not looked for (the mesh has mesh.non-finite or nonsense coordinates anyway).
Face-tet failures in a checkMesh log are warnings since M0.16 (they were errors): the
finite-volume solvers don't use the tet decomposition.
checkMesh counts high aspect ratios and skewness as failed checks. They are warnings here
because a case usually runs with them, if more slowly or less accurately.
Faces of cyclic and processor patches are treated as boundary faces: the cell on the other
side is not followed. So their non-orthogonality is not computed, and their skewness uses the
boundary formula. Wedge directions are not left out of the aspect ratio.
checkMesh's own results¶
checkMesh is the authority, so what it wrote is read too, for each region:
- Its log:
log.checkMesh*in the case directory or itslog/, matched to the region by its-regionargument. - A run over several times (
-time 3,8) checked one mesh per time; each gives its own result, labelled with its time. - A result is used only for a mesh with as many cells, faces and points as it checked: a log of the meshes in time directories, or of an older mesh, gets a note saying why it was left out. A log for no region of the case, or too large to read (over 16 MB), gets a note too.
- Each failed check (
***) becomesmesh.checkmesh. It is an error, ascheckMeshmeans it (wrong orientation, zero or negative volumes or areas, open cells, non-orthogonality errors, invalid labels, boundary, zone and wedge errors). The quality findings are warnings: aspect ratio, skewness, face tets, small weights and volume ratios, small determinants, concave cells, non-aligned edges, boundary openness. - Each remark (
*) becomes a note. - A log that ends in a fatal error is an error; one that stops before its verdict is a warning.
- A finding the native check also makes (its default checks: aspect ratio, skewness,
pyramids, volumes, areas, open cells, non-orthogonality, boundary openness) is a note when
the mesh was checked natively too, so nothing counts twice. So is an
-allGeometryfinding (face tets, weights, volume ratios, determinants, concave cells) when the native check ran with--all-geometry. The others (topology errors) count ascheckMeshsays. - Its sets: the face, cell and point sets
checkMeshwrites (OpenFOAM-8 on every run, OpenFOAM-12 with-writeSets) inconstant/<region>/polyMesh/sets, such asskewFaces,wrongOrientedFacesandnonClosedCells. Their labels are listed lowest first, for the 3D view. Sets with other names (topoSet'strees, …) are left alone. Sets written next to a mesh in a time directory, or inprocessor*by a parallel run, are not read.
A set that names faces, cells or points the mesh doesn't have was written for another mesh, and
is reported as mesh.stale-set.
| Code | Level | What it means |
|---|---|---|
mesh.checkmesh |
Error / Warning / Info | What a checkMesh log in the case reported for the region |
mesh.stale-set |
Warning | A checkMesh set names more faces, cells or points than the mesh has: it was written for another mesh |
mesh.unreadable-set |
Warning | A checkMesh set could not be read |
mesh.checkmesh-incomplete |
Error | A checkMesh log longer than is read has failures that are not listed: times past the first 1000 that failed a check or stopped (summarized by the size of mesh they checked, and matched to the mesh as other results are), or failed checks past 200 findings of one time. A warning in a vegetation region, as the findings themselves would be |
mesh.checkmesh-unmatched |
Warning | Such failed times whose mesh size isn't known (past 16 sizes, or without mesh stats, which checkMesh prints only when the topology changes: usually times with moved points): they may be of another mesh |
Tested against checkMesh:
- The five regions of the 3×3 test case give the same cell counts, volumes (min, max, total),
face areas (min, max), aspect ratios, non-orthogonality (max and average) and skewness as
OpenFOAM-12's checkMesh, to the six digits it prints (the opt-in test
matches_checkmesh_on_the_3x3_case). The number of high-aspect cells is the same too (2880 in
buildings, 1632 in street).
- With -allGeometry, the five regions give the same edge lengths, concave faces and maximum
angle, flatness, determinants and the cells below 0.001, concave cells, interpolation weights
and volume ratios (minimum and average) as OpenFOAM-12's checkMesh -allGeometry (the opt-in
test matches_checkmesh_all_geometry_on_the_3x3_case).
- On a copy of the vegetation mesh of the partly ported 3×3 case (5.4 million cells), OpenFOAM-12's checkMesh -writeSets finds 1440 incorrectly oriented faces. Its wrongOrientedFaces set holds the same faces as mesh.face-pyramids. With -allGeometry it finds 2880 face-tet errors, 96 concave faces (max 25.62°) and 44,396 concave cells, as --all-geometry does.
- The Hong Kong project's air mesh (16 million cells) gives what OpenFOAM-8's checkMesh
printed for it: non-orthogonality max 80.6° and average 11.5°, 41 faces above 70°, and
skewness max 5.79 with 8 faces above 4.
Checking that mesh takes about 40 seconds and 4 GB on one core: 13 seconds to read it, the rest to check it. It can't be cancelled once it has started.
Layers¶
When snappyHexMesh added layers, its log says how many each patch got: at the end of the
layer phase it prints, for each patch that asked for layers, the layers grown (on average over
the patch's faces) and the thickness reached as a percentage of the one wanted, after a table
of the layers wanted. mesh.check reads that summary from log.snappyHexMesh* in the case
directory or its log/, for each region:
- The region is the log's
-region, or the end of its name when that is a region of the case (log.snappyHexMesh.air, as cases that swapsnappyHexMeshDicts name their logs). A log of the default mesh in a case of regions (meshed, then moved:mv constant/polyMesh constant/air) is the region whose mesh has its size. - A log is used only for a mesh with as many cells, faces and points as the one it wrote: a region meshed again, or changed since (extruded, subset), gets a note saying why it was left out. A log whose run added no layers is not reported. In a log of several runs appended, the last one counts.
- A log in
log/with the name of one in the case directory is left out (the case scripts move older logs intolog/). A log over 16 MB is not read.
| Code | Level | What it means |
|---|---|---|
mesh.layers |
Warning | A patch got less than 90% of the layers it wanted (on average over its faces), or none; addLayers is on but no patch asked for layers (No layers to generate: the names under layers match no patch); or the log stops before the layer summary |
mesh.layers |
Info | The patches that got 90% of their layers or more (one note) |
mesh.snappyhexmesh |
Warning | snappyHexMesh stopped with a fatal error (in the layer phase or before), so the mesh it wrote may be unfinished |
mesh.snappyhexmesh |
Info | A snappyHexMesh log that could not be read, or is not used (another region's, another mesh's, or a copy in log/ of one in the case directory) |
mesh.layers |
Info | Patches that grew layers when the log doesn't say how many they wanted (one note) |
OpenFOAM-8 and 12 print the same tables; the iglooWithFridges tutorial meshed by either gives
igloo its 1 layer, and the fridges 1.96 and 2.09 of their 3 (65% and 70%: warnings).
Editing patches¶
patch.set changes patches of a region's mesh in constant/<region>/polyMesh/boundary: the
type (patch, wall, mappedWall, symmetry, empty, …), the groups (inGroups), other
entries (neighbourRegion, neighbourPatch, method, …), or the name.
umcf-cli run --case my-case \
--cmd 'patch.set={"type": "wall", "groups": ["wall"], "patches": [{"region": "air", "patch": "ground"}]}' \
--cmd 'patch.set={"name": "terrain", "patches": [{"region": "air", "patch": "ground"}]}' \
--cmd 'case.save={}'
- Only those entries change, byte for byte: the other patches, the comments, the layout. A
gzipped
boundary.gzis compressed again. - A new name follows the patch:
- into its own
boundaryFieldentry in each field file of the region at the start time and in0.orig(which preparing the case copies to0); - into its entry in the region's
viewFactorsDict(system/<region>andconstant/<region>, compressed or not); - into the patches that name it as their partner (
neighbourPatch,samplePatch,originalPatch): a mapped patch of another region, the other half of acyclicin the same region. - The command says what it didn't change:
- a field file too big to be edited here, over 256 MB decompressed (
patch.unread); - one whose pattern entry or included text may name the old name (
patch.pattern); - a file of
0.origor aviewFactorsDictthat wasn't changed (patch.unrenamed): it has the new name already, the old one twice, or can't be read or written back here; or a0.origdirectory that can't be listed; - the other time directories (
patch.times), which aren't read: a run started from one of them needs the name changed there; - a directory with the old name (
patch.mention), such as the data atimeVaryingMappedFixedValuecondition finds by the patch's name inconstant/<region>/boundaryData/<patch>; - each file and line under
system,constantand0.origthat still has the old name as a word (patch.mention): function objects incontrolDict, the meshing dictionaries (createPatchDict,changeDictionaryDict,topoSetDict,snappyHexMeshDict, …), a pattern in a field. A region or a field with the same name is listed too; the first 200 are named. Compressed files, files over 4 MB, and the contents ofpolyMesh,triSurface,geometryandboundaryDatadirectories are not searched. A file that can't be read, or a directory that couldn't be listed when the case was opened, is named (patch.unsearched); so is a0.origdirectory that can't be listed (patch.unrenamed). - A name already used is refused: another patch's, a patch group's, or an entry of a field file (a group or pattern entry would take the renamed patch's entry over). So is a number.
- Entries are written as given:
"entries": {"offset": [0, 0, 1], "distance": 0.5}isoffset (0 0 1); distance 0.5;. A type umcfGui doesn't know is set, with a note (patch.type). - The type is set as given: the case check then says what the fields' conditions
need (a constraint type such as
emptyorsymmetrywants the same condition type). - A decomposed case keeps the old patches in its
processor*directories until it is decomposed again; the command warns (patch.decomposed). - The meshing dictionaries are not changed: meshing again makes the patches as they say.
- One step in the undo history, written by
case.saveafter a backup.
New patches¶
patch.create prepares a new patch from boundary faces of a region's mesh, for OpenFOAM's
createPatch to make. The faces are the ones selected (picked in the view, select.pick or
select.rect with faces: true, or select.set) when nothing else is given (all of one region; they leave the
selection once prepared, and an undo doesn't bring them back: the selection isn't part of the
undo history), or are given as labels (faces), as the ones whose centre is in a
box (box), or as the ones whose centre is within a distance of an STL surface of the case, or
of one solid of it (surface). A face's centre is OpenFOAM's: the area-weighted centre of its
triangles. A box or a surface takes faces from the patches named in from, else from the
selected patches of the region, else from every patch of the region that is neither coupled nor
of a constraint type (empty, symmetry, symmetryPlane, wedge).
umcf-cli run --case my-case \
--cmd 'patch.create={"region": "air", "name": "skyWest", "box": {"min": [-1, -1, -1], "max": [295, 1e9, 1e9]}, "from": ["sky"]}' \
--cmd 'patch.create={"region": "air", "name": "roofs", "type": "wall", "surface": {"file": "buildings.stl", "solid": "roof", "distance": 0.5}}' \
--cmd 'case.save={}'
createPatch -region air -dict system/air/createPatchDict.umcf -overwrite # OpenFOAM-12
createPatch -region air -dict system/createPatchDict.umcf -overwrite # OpenFOAM-8
- The faces go into a face set,
constant/<region>/polyMesh/sets/<name>, and the patch intosystem/<region>/createPatchDict.umcf(system/createPatchDict.umcffor a single-region case) as{ name …; patchInfo { type …; inGroups …; … } constructFrom set; set …; }, after the patches prepared there before. That dictionary holds the patches prepared on the mesh as it is: a face set holds face labels, which hold for this mesh only. The case's owncreatePatchDict, which its scripts may run after meshing, isn't touched. A face set that is a symbolic link is not written over. - The mesh doesn't change yet: the patches are made when
createPatch -region <region> -dict … -overwriteruns, once, on this mesh; delete the dictionary then. The command says the line for the case's OpenFOAM version (patch.run): OpenFOAM-12 takes the path from the case (-dict system/<region>/createPatchDict.umcf), OpenFOAM-8 belowsystem/<region>/already (-dict system/createPatchDict.umcf). Adding to the dictionary is refused when it can't be for this mesh: it notes the number of faces of the mesh it was prepared on (a dictionary without that line is refused), and the mesh must have that many and none of the patches it prepares. A mesh made again with as many faces, before the dictionary has run, isn't caught. Until then, the patch isn't in the mesh, the view or the browser. When the case's scripts make the region's mesh again, the mesh they make doesn't have it (patch.remesh). - The fields follow when they can. When all the faces come from one patch of the same kind,
each field file of the region at the start time gets a copy of that patch's own entry for the
new patch: OpenFOAM reads an entry for a patch the mesh doesn't have yet without a word. The
kinds are the wall types (
wall,mappedWall, …),patchwith the mapped patch types, and each other type on its own: a wall function doesn't go to apatch, nortype emptyto anything else. A patch of a constraint type (empty,symmetry,symmetryPlane,wedge) gets none: its condition is its type's, and OpenFOAM gives anemptypatch its own. The command warns about the fields that still need one (patch.unfilled), and notes the ones whose pattern entry or included text may give one (patch.maybe, as#includeEtc "caseDicts/setConstraintTypes"does for the constraint types). An entry for one of the new patch's groups gives it one: the groups given,wallfor a wall type, the type for a constraint type. It also warns about field files with values per face (nonuniform) on a patch that gives faces (patch.resized), whose size is wrong oncecreatePatchhas run, about0.orig(patch.orig), a patch that gives all its faces (patch.emptied:createPatchremoves it, except OpenFOAM-12's, which keeps a constraint type without faces), and theprocessor*directories (patch.decomposed). Faces taken from a constraint patch (empty,symmetry, …) for another type are pointed out (patch.constraint): they are no longer treated as such, which is seldom meant (the front and back of a 2D mesh, picked in the view). - Refused: a name already used (a patch, a group, a field entry, a prepared patch, a face
set), a number, a face that isn't a boundary face, a face prepared for another patch already (from a face set, or from whole patches by a
constructFrom patchesentry of the dictionary, which may name patches and groups but no patterns), faces of a coupled patch (processor,cyclic,cyclicAMI, the non-conformal ones), whose facescreatePatchkeeps paired, and a new coupled patch, whose two halves are made together withcreatePatch's own dictionary. So is adding to a dictionary whose face sets are gone. - One step in the undo history, written by
case.saveafter a backup. An undo after the save doesn't take the new files away again (the dictionary, the face set): delete them by hand. - A box or a surface reads the region's points and boundary faces, not its cells: about 3 s for the 16-million-cell air region of the HK project, during which the desktop app shows its busy card.
- Checked on copies of an OpenFOAM-12 case of the solver (1458 of the 2916
skyfaces ofair) and an OpenFOAM-8 single-region case:createPatchread the set and made the patch,checkMeshpassed, and the fields read with the new mesh.
Tested on¶
Every region of six projects, from 25,000 to 16 million cells, reads and passes these checks:
the test cases, the partly ported cases, the meshing steps and the Hong Kong project (32 meshes
in all). Copies converted to binary by OpenFOAM-8's and OpenFOAM-12's foamFormatConvert read
back identical to their ASCII originals.
The largest, 16 million cells with 51 million faces and 19 million points, takes about 13 seconds: the four big files are read in parallel. Mesh indices are stored as 32-bit integers, so the mesh, once read, takes about 1.7 GB of memory. Reading it needs more: the files' text is held while they are parsed, and the measured peak was 3.7 GB. Plan memory for the peak.