OpenFOAM polyMesh (read-only)
A reader for OpenFOAM's native polyMesh representation: an unstructured, face-based mesh described by 4-5 sibling files (points, faces, owner, neighbour, boundary) rather than a single file. Supports both ASCII and binary (little-endian, label=32/64, scalar=32/64) encodings, and reconstructs general polyhedra as well as tetra/pyramid/wedge/hexahedron.
| Format name | openfoam |
| Extensions | .foam |
| Read / Write | ✓ / — |
| Extra dependencies | — |
Reading & writing
import meshioplusplus
mesh = meshioplusplus.read("case.foam") # a <case>/case.foam marker file
mesh = meshioplusplus.openfoam.read("/path/to/case") # or the case directory directly
mesh = meshioplusplus.openfoam.read("/path/to/constant/polyMesh") # or polyMesh directlyThere is no writer — register_format("openfoam", [".foam"], read, {}) is called with an empty writer map. read(filename) takes no keyword arguments; the polyMesh directory is located from whichever of the three input forms above is given (_resolve_polymesh): a .foam suffix looks for <parent>/constant/polyMesh; a directory literally named polyMesh is used as-is; any other directory is checked for constant/polyMesh then polyMesh as subdirectories. A FileNotFoundError is raised if none match.
File structure
Each polyMesh file (points, faces, owner, neighbour, boundary) is an OpenFOAM "FoamFile": a header block declaring format (ascii/binary) and (for binary) an arch string encoding label=32|64/scalar=32|64, followed by a count and a parenthesized list.
FoamFile
{
...
format binary;
arch "LSB;label=32;scalar=64;";
}
// ...
<N>
(
<binary or ascii data>
)- Header detection (
_detect_format) scans line-by-line for aformat ...;line and anarch "...";line, extractinglabel=/scalar=byte widths; defaults to ascii/8-byte if absent. - Binary
points(vectorField):N (followed directly byN*3*scalar_bytesraw floats, no per-row framing — read via a singlenp.frombuffer. - Binary
owner/neighbour(labelList):N (followed byN*label_bytesraw ints, same direct-buffer read. - Binary
faces(faceList, non-contiguous): each face is its ownlabelList—<count> ( <count*label_bytes bytes> )repeatedNtimes. Read in two passes: a sequential ASCII scan locating each face's byte offset and node count (cheap, sincefind(b"(")only ever scans the short ASCII gap between faces, never binary data that might coincidentally equal'('), then a single vectorized gather of every face's binary blob via a cumulative byte mask — bounded peak memory even for tens of millions of faces (see_RaggedArray, a CSR-style(conn, offsets)pair standing in for alist[list[int]]). - ASCII variants use simple regex/line-based parsing (
_parse_points_ascii,_parse_faces_ascii,_parse_int_list_ascii) after comment-stripping (/* */and//) and header-skipping. boundary: a dict ofpatch_name → {type, nFaces, startFace}, parsed via a brace-matching regex over the whole (header/comment-stripped) text.- Cell reconstruction: cell↔face topology is built once as a CSR
_RaggedArray(_cell_faces_csr, vectorized viaargsort+bincount) fromowner/neighbour; per cell, each face is oriented outward (reversed if the cell is that face's neighbour, since the stored normal points owner→neighbour) and classified by(n_faces, n_points):(4,4)→tetra,(5,5)→pyramid,(5,6)→wedge,(6,8)→hexahedron, anything else → a generalpolyhedron(kept as outward-oriented face lists). Each of the 4 named types has a dedicated orientation-fixing builder (_build_tetra/_build_pyramid/_build_wedge/_build_hexahedron) that computes a scalar triple product and flips the node order if it comes out negative, guaranteeing positive-volume connectivity regardless of the source mesh's face-normal convention.
Cell types
Volume cells: tetra, pyramid, wedge, hexahedron, and general polyhedron<N> (grouped by unique node count N, ragged per-cell face lists stored as an object-dtype array — one CellBlock per distinct N).
Boundary (patch) faces: triangle, quad, and polygon<N> for N > 4 (grouped by vertex count N, one CellBlock per size — via _build_boundary_polygons).
Data mapping
cell_data["cell_tags"]— per-cell-block tag array:0for every volume cell block, and a distinct negative "MED-style family id"-(patch_index+1)per boundary patch's face blocks (so a triangle patch and a quad patch on the same physical boundary would currently get different tag values — see Quirks).mesh.cell_tags— mesh-level attribute (notcell_data),{family_id: [patch_name]}, letting a MED write bridge these patch names through the same mechanism used for Gmsh physical groups (seemed.md).mesh.point_tags— always set to{}(present for interface symmetry with the MED-derived tag convention; OpenFOAM has no point-tag concept).- No point_data or field_data (OpenFOAM field files like
U,p,Tin the case's time directories are not read by this module — only the mesh topology underconstant/polyMesh).
Quirks & limitations
- Read-only. There is no
writefunction at all. - Degenerate volume cells that match a named type's
(n_faces, n_points)signature but whose topology doesn't resolve cleanly (_match_topfinds more or less than one vertical neighbour per base node) are silently skipped and logged as a warning count, rather than falling back to a general polyhedron. - Boundary patches are tagged by patch index, not patch identity across face-size groups — if one named patch contributes both triangles and quads, its triangle
CellBlockand quadCellBlockget the samecell_tagsid (assigned once per patch, reused across whichever size-buckets that patch's faces fall into), but two different named patches always get distinct ids. - All binary reads assume little-endian (
LSB) — the format's ownarchstring is trusted for label/scalar width but not for byte order. - Read goes through the C++ core (
meshioplusplus._core.openfoam_read, usingstd::filesystemfor the polyMesh directory), with the Python reference as an automatic fallback. General polyhedra cross the C++↔Python boundary via the raggedpolyhedron<N>cell representation (a copied list of face arrays); boundary patch names travel through anOpenFoamInfoside-channel struct asmesh.cell_tags.
Notes
- No
tests/meshes/reference fixture (no case directory is checked in);tests/test_openfoam.pybuilds small ASCII/binarypolyMeshfile sets inline undertmp_path, covering ASCII and binary variants, all 4 named volume cell types, general polyhedra, boundary polygon grouping, and the.foam/case-dir/polyMesh-dir path-resolution forms. Most tests import the internal Python functions directly (keeping the Python reference exercised); the public-API test drives the C++ path. - Ported from Simvia's meshlane fork (see
CHANGELOG.md) — this format did not exist upstream before that.