4. Data exchange formats for multi-solver workflows
In a multi-solver CAE pipeline, the weakest link is rarely the physics — it is the handoff between tools. A geometry kernel, a mesher, a CFD solver, a structural solver, and a post-processor each have their own native data model, and the file format you choose at each boundary determines what survives the trip. This chapter maps the six formats that dominate Python-driven multi-solver workflows — STEP, IGES, STL, CGNS, HDF5, and JSON — to the three payload classes they carry: geometry, mesh, and results.
Geometry Interchange: STEP and IGES
STEP (ISO 10303, typically AP203/AP214/AP242) and IGES (ASME Y14.26M) are the two B-rep interchange standards you will encounter when moving CAD into a meshing or solver environment. STEP is the modern default: it carries analytic surfaces, NURBS, assembly structure, colors, and — in AP242 — PMI (Product Manufacturing Information). IGES is older, stores geometry as trimmed surfaces with weaker topology, and frequently produces gaps or duplicate edges on export. Prefer STEP unless a legacy tool forces IGES.
In Python, the practical entry point is pythonocc-core (OCCT bindings) or cadquery. A minimal STEP read-and-inspect looks like this:
from OCC.Core.STEPControl import STEPControl_Reader
from OCC.Core.IFSelect import IFSelect_RetDone
reader = STEPControl_Reader()
status = reader.ReadFile("bracket.step")
if status != IFSelect_RetDone:
raise RuntimeError("STEP read failed")
reader.TransferRoots()
shape = reader.OneShape()
print("solids:", shape.NbChildren())
Two failure modes dominate. First, unit mismatch: STEP files declare units in the header, but some exporters write millimetres while the receiving tool assumes metres, producing a 1000× scale error. Always verify the bounding box after import. Second, healing requirements: IGES surfaces often need ShapeFix_Shape before meshing. A quick sanity check is to compute the shape's volume and compare it against the CAD-reported value within 0.1%.
Mesh Exchange: STL and CGNS
STL is a triangle soup — no topology, no units, no metadata. It is adequate for surface tessellation and 3D printing, but for volumetric CFD or FEA it is a dead end: you cannot recover element connectivity or boundary patches from an STL. Use STL only as an intermediate for surface remeshing or visualization.
CGNS (CFD General Notation System, now an ISO standard) is the correct choice for mesh plus solution data in CFD. It stores a structured tree: Base → Zone → GridCoordinates and FlowSolution nodes, with boundary conditions attached as BC_t nodes. The Python binding is CGNS (pip install cgns) or h5py when the file is written in ADF/HDF5-backed mode (CGNS 4.x defaults to HDF5).
import CGNS.MAP as CGM
import CGNS.PAT.cgnslib as CGL
tree, links, paths = CGM.load("wing.cgns")
base = tree[2]
zone = base[2][2]
coords = zone[2][1] # GridCoordinates node
print("nodes:", coords[1][1].shape)
CGNS is verbose but unambiguous. The critical discipline is naming consistency: if the mesher writes a boundary patch as wall and the solver expects WALL, the BC will silently default to a farfield. Validate patch names against the solver's expected list before submitting a run.
Results and Metadata: HDF5 and JSON
HDF5 is the container of choice for large result arrays — displacement fields, pressure histories, transient time series. It supports chunking, compression, and partial reads, which matters when a single field is 4 GB. The h5py API is the standard Python interface:
import h5py, numpy as np
with h5py.File("results.h5", "w") as f:
grp = f.create_group("case_01/step_0001")
grp.create_dataset("displacement",
data=np.zeros((120000, 3), dtype="f8"),
compression="gzip", compression_opts=4)
grp.attrs["units"] = "m"
grp.attrs["solver"] = "CalculiX 2.21"
JSON handles the small, human-readable payloads: solver configuration, material cards, run manifests, and cross-tool metadata. It is not suitable for bulk numeric arrays — a 100k-node mesh serialized as JSON is both slow and bloated. The idiomatic split is JSON for control, HDF5 for data, with a JSON manifest referencing HDF5 dataset paths.
Choosing the Right Format at Each Boundary
| Boundary | Recommended format | Why |
|---|---|---|
| CAD → mesher | STEP AP242 | Preserves B-rep topology, units, and PMI |
| Legacy CAD → mesher | IGES | Only option; expect healing work |
| Surface → remesher / viewer | STL (binary) | Compact, universally readable |
| Mesher → CFD solver | CGNS 4.x | Carries zones, BCs, and solution fields |
| Solver → post-processor | HDF5 | Chunked, compressed, partial reads |
| Orchestration / config | JSON | Human-readable, diff-friendly |
The diagram below shows how these formats connect across a typical three-solver workflow, with the payload class carried on each edge.
Validation and Failure Symptoms
Format conversion is where silent corruption enters a workflow. Three checks catch most problems before they reach a solver:
- Bounding-box check. After any geometry import, compare the axis-aligned bounding box against the source CAD. A 1000× discrepancy means a unit mismatch; a 2× discrepancy usually means a mirrored or duplicated body.
- Patch-name audit. After CGNS import, list all
BC_tnode names and diff them against the solver's expected boundary list. Missing patches default to wall or farfield and produce plausible-but-wrong results. - Array shape and dtype check. Before reading an HDF5 field, assert
dataset.shape == (n_nodes, 3)anddataset.dtype == "f8". A transposed array or a float32/float64 mismatch will propagate through the solver without error.
A Minimal Manifest Template
The following JSON manifest is a practical starting point for any multi-solver run. It is intentionally small — extend it with solver-specific fields as needed, but keep the core keys stable so downstream tools can rely on them.
{
"case_id": "wing_aoa_04",
"created_utc": "2025-01-15T09:30:00Z",
"coordinate_system": "right-handed, +Z up, metres",
"artifacts": [
{"role": "geometry", "format": "STEP", "path": "geom/wing.step", "units": "m"},
{"role": "mesh", "format": "CGNS", "path": "mesh/wing.cgns", "zones": ["fluid"]},
{"role": "result", "format": "HDF5", "path": "results/wing.h5",
"datasets": ["case_01/step_0001/pressure"]}
],
"solver": {"name": "SU2", "version": "7.5.1", "turbulence": "SST"}
}
With this manifest in place, a Python orchestrator can resolve every artifact by role, verify units and coordinate system before each handoff, and fail fast when a required dataset is absent. The format choices — STEP for geometry, CGNS for mesh, HDF5 for results, JSON for control — are not arbitrary; each one is the smallest container that preserves the information the next tool actually needs.