Skip to content

Architecture ​

meshio++ is one C++ core with six language surfaces on top of it and a set of tools built on those surfaces. This page is the map; every box below links to the page that owns it.

meshio++ architecture: one C++ core holding the format registry, the operations layer and one of three mesh backends; the Python, C, Fortran, Julia, R, WebAssembly and C++ surfaces over it; and the CLIs, viewers, MCP server, Blender add-on, ParaView plugin and PhysicsNeMo adapter built on those

One C++ core ​

Everything compiles into a single object library, meshioplusplus_core_obj (C++20), and every optional dependency compiles out when it is absent, so the core is dependency-free by default. Four things live in it.

  • The format registry — 79 readable and 72 writable formats (80 distinct core formats; five more are Python-only), each a reader and a writer over the uniform mesh API, registered in one dispatch table (registry.cpp: format name to reader and writer, file extension to default format). The Python binding keeps one function per format and dispatches in Python; every flat binding (C, Fortran, Julia, R, WebAssembly, the native CLI) dispatches through this table, which is why a new C++ format must be registered there to be reachable from them.
  • The operations layer — 45 mesh operations and 6 data operations, described below.
  • The detail/ kernels — the shared machinery the formats and operations are built from: the face and edge tables (cell_faces, cell_edges), the single owners of cell indexing and region remapping (cell_index, region_remap), the marching-tetrahedra cutter, the spatial hash, the polyhedron kernel, the refinement templates and the provenance scope.
  • The mesh backend — exactly one per build, selected by MESHIOPLUSPLUS_MESH_BACKEND: the meshio-mirroring MESHIO layout the Python wheel uses, the canonical NATIVE layout the WebAssembly build uses, or the Kratos-style KRATOS ModelPart. Formats, operations and bindings never touch a backend's own members; they go through the uniform mesh API, which is what lets one implementation compile under all three.

The in-memory model those pieces share is the Mesh data model: points, a list of homogeneous cell blocks, data aligned to points or to blocks, and named regions. Ragged cells (polygons and polyhedra) have their own representation and cross every flat binding as CSR arrays.

The operations layer ​

An operation is a computation on a mesh rather than a file format: it is written against the uniform mesh API only, parallelises its hot loop with parallel_for, and is exposed on every binding surface plus a CLI verb and an MCP tool. They group naturally.

GroupOperations
Inspection and topologyquality, stats, diff, extract_surface, extract_skin, reorder, transform, clean, merge, crop, split, partition, convert_cells
Refinement and coarseningrefine, undo_green, subdivide, agglomerate, decimate, decimate_volume
Remeshing and smoothingremesh, remesh_volume, optimize_volume, smooth
Fieldsinterpolate, conservative_interpolate, gradient, hessian, estimate_error, data_integrate
Cutting, grids and distanceslice, isosurface, grid and voxelize, parametric primitives, signed distance
Geometry helpers (physics-ML)curvature, normals, repair, shrinkwrap, sobolev_deform, tessellation
Data operationsmanage, average, calc, condition, info, tensor_invariants — the six that never touch geometry

Two things tie the operations together. Chains of them are described declaratively by the settings pipeline, whose typed layer (run_pipeline_steps) is the single owner of the step dispatch on every surface, and the sequence driver runs such a chain once per step of a transient dataset with at most one mesh alive at a time. Every write can carry a provenance record of where the mesh came from and what was done to it.

The language surfaces ​

  • Python — the meshioplusplus._core pybind11 extension with zero-copy numpy at the I/O boundary, wrapped by one shim per format that falls back to the pure-Python reference implementation when the core declines a file, logging why (see when the native path declines). This is the surface the Python CLI, the MCP server and the integrations are built on.
  • C — libmeshioplusplus, a pure C99 header with SOVERSION 0 and append-only option structs, compiled under every mesh backend. Fortran, Julia and R ride on it, each in its own idiom for handles, ownership and indexing.
  • WebAssembly — the @meshioplusplus/wasm npm package (embind over the NATIVE backend), in a sequential and a threaded build, working on a MEMFS virtual filesystem.
  • C++ — the installable C++ API, one real library per backend exported into the same CMake package as the C API, under a deliberate ABI contract; or the single-header amalgamation for a build with no CMake at all.

Threads and the GIL ​

The Python extension releases the GIL while the core reads, writes or computes, so other Python threads keep running during a long call, and files can be converted in a concurrent.futures.ThreadPoolExecutor. On the Linux wheels, whose core is sequential, threads are then the only way to run several conversions at once. The binding converts its arguments and builds its result with the GIL held, and releases it only around the C++ call. An array passed in stays alive for the whole call, but writing to it from another thread while the call runs is a data race.

The formats that reach HDF5 (xdmf, cgns, h5m, hmf, vtkhdf, nastran_h5, med, and gid's HDF5 flavour), netCDF (exodus), ADIOS2 (vtx), TecIO (szplt) or gidpost (gid), and the OpenCASCADE backend of fit_cad (its STEP writer keeps global state), use libraries that are not thread-safe. They serialise themselves on one process-wide lock in the core (detail/library_lock.hpp), taken at every entry point that can reach one of them, and the series writers take it in every method and in their destructor. It is one lock rather than one per library because netCDF-4, CGNS and MED sit on the same HDF5. So concurrent HDF5-family reads and writes queue rather than run in parallel, while ASCII and binary formats, and every operation, still run concurrently. The lock lives in the core, so the C, Fortran, Julia, R and WASM bindings get it too, and a pipeline or sequence takes it only around the reads and writes that need it. A Python binding releases the GIL before it takes the lock and nothing waits for the lock while holding the GIL, so the two cannot deadlock; the one path back into Python from under the lock, gid_write_series' callback, takes the GIL itself.

The lock cannot protect a library that someone else also calls: an h5py linked against the same libhdf5 does not take it. The PyPI wheels are built without HDF5, so they never hold a second copy; in a source or conda build that shares libhdf5 with h5py, do not read or write HDF5 from both at once.

Each OpenMP or TBB region inside an operation uses the whole machine. N Python threads that each run a parallel operation therefore oversubscribe the cores N times. When running a thread pool on a parallel build, cap the inner threads, for example with OMP_NUM_THREADS=1.

The tools built on them ​

The Python CLI and the Python-free native CLI mirror each other verb for verb. The MCP server exposes the whole Python surface to AI agents as file-path-based tools, with a read cache between calls. The browser viewer and dataset manager consume the published WebAssembly package, while the Polyscope viewer is a Python extra. The Blender add-on and ParaView plugin bring meshio++'s formats into those applications, and the interoperability, GPU, machine-learning, dataset and PhysicsNeMo layers hand meshes to the wider Python ecosystem without a file round-trip.

Two patterns that recur ​

A pure layer under gated wrappers. The interoperability, GPU, MCP, Blender and viewer modules all split the same way: the bulk of the logic is a pure payload layer that imports no optional library, mutates nothing and is tested in the default CI matrix with none of the targets installed, and the public functions are thin wrappers that import their target inside the function and raise a named install hint when it is missing. A feature that needs a heavy or version-pinned dependency never makes the core, or its tests, depend on it.

A single owner for anything two places could compute. The block-major cell index (detail/cell_index.hpp), the remapping of regions through an operation (detail/region_remap.hpp), the refinement templates, the marching cutter, the provenance credit line and the pipeline step dispatch each live in exactly one place and are reused by every consumer, including the numpy twins on the Python side, which are pinned byte-for-byte against the C++ path by tests. Where a second implementation would have to reproduce a discrete branch on a sign (a winding repair, a flip acceptance, a warp), the operation is C++-core only and says so by name.

Load on first use. import meshioplusplus imports no format subpackage and only the operations it must: formats register themselves when imported, so read and write find the subpackage a path or format name needs in a generated table (_format_table.py, written by tools/gen_format_table.py) and import just that one. Operations and submodules load through a module __getattr__. Reading a registry (reader_map, extension_to_filetypes, formats()) or listing every writer in an error message imports the rest first, so those still show every format, and from meshioplusplus import * still loads everything.

What keeps it honest ​

Four checks run over the whole of it rather than one feature. The C++ suite runs under ASan and UBSan on every pull request, and a libFuzzer campaign over every native reader runs weekly, its findings replayed on every pull request as regression inputs (fuzzing and sanitizers). Every writable format round-trips one canonical mesh against a declaration of what it keeps (format conformance). Property-based tests hold the operations to the invariants their pages state — volume and mass conservation, conformity, determinism, map composition (tests/python/test_properties.py). A benchmark harness records every format's read and write and the main operations per parallel backend, without gating (benchmarks).

Where to go next ​

Start with the quickstart, then the format table and the CLI reference. The roadmap lists what is not built yet.

Released under the MIT License.