4. Mesh file formats and interoperability

Mesh file formats and interoperability 学习卡片

Mesh quality inspection rarely starts from a mesh you generated yourself. In practice, the mesh arrives from a preprocessing group, a legacy solver deck, or an open-source mesher, and your inspection script must first decode it. The five formats covered here — Abaqus .inp, Nastran .bdf/.nas, Gmsh .msh, VTK .vtk/.vtu, and Exodus .exo/.e — dominate finite element practice, and each encodes topology, coordinates, and element type information differently. Understanding those differences is what makes an inspection pipeline portable rather than tied to one toolchain.

What Each Format Actually Stores

All five formats carry the same three essentials — node coordinates, element connectivity, and element type — but they disagree on almost everything else: indexing base, section delimiters, whether element type is implicit in the section keyword or explicit per element, and whether groups/sets are first-class objects.

Format Typical extension Node indexing Element type encoding Primary Python reader
Abaqus .inp 1-based Keyword line (*ELEMENT, TYPE=C3D8) meshio, abaqusParser
Nastran .bdf, .nas 1-based Card name (CTETRA, CQUAD4) meshio, pyNastran
Gmsh .msh 1-based (v2), 1-based (v4) Numeric type ID (e.g. 4 = tet4, 5 = hex8) meshio, gmsh API
VTK .vtk, .vtu 0-based Cell type enum (e.g. 10 = tetra, 12 = hexahedron) meshio, pyvista, vtk
Exodus .exo, .e 1-based Element block with type string netCDF4, meshio, exodus

The indexing base is the single most common source of silent corruption. VTK is 0-based; the other four are 1-based. If you read a VTK file and write it back as Abaqus without shifting connectivity by +1, every element will reference the wrong nodes, and quality metrics will be computed on a mesh that does not exist. meshio normalizes everything to 0-based internally, which removes the hazard — but only if you stay inside meshio and do not hand-roll a parser.

A Portable Read–Inspect–Write Pipeline

The following example reads a mesh in any of the five formats, computes a simple quality metric (minimum scaled Jacobian proxy via edge-length ratio), and writes the result back out. It uses meshio as the interoperability layer and numpy for the metric.

import meshio
import numpy as np

def load_mesh(path):
    """Read any supported format; return points (N,3) and cells list."""
    m = meshio.read(path)
    pts = np.asarray(m.points, dtype=float)
    return m, pts

def edge_ratio_quality(m, pts):
    """Per-element max/min edge length ratio. 1.0 = perfect, >>1 = poor."""
    results = {}
    for block in m.cells:
        conn = np.asarray(block.data)
        if conn.ndim != 2 or conn.shape[1] < 3:
            continue
        # edges of the element: consecutive node pairs (closed loop)
        n = conn.shape[1]
        idx = np.arange(n)
        a = conn[:, idx]
        b = conn[:, (idx + 1) % n]
        d = np.linalg.norm(pts[a] - pts[b], axis=2)  # (E, n)
        d = np.where(d == 0.0, np.nan, d)
        ratio = np.nanmax(d, axis=1) / np.nanmin(d, axis=1)
        results[block.type] = ratio
    return results

m, pts = load_mesh("bracket.inp")
q = edge_ratio_quality(m, pts)
for etype, ratio in q.items():
    print(f"{etype:12s} n={len(ratio):6d} "
          f"max_ratio={ratio.max():8.3f} "
          f"p99={np.percentile(ratio, 99):8.3f}")

# Write back as VTK for ParaView inspection
meshio.write("bracket_checked.vtu", m)

Run against a typical hex-dominant bracket mesh, the output might look like this (values are illustrative, not measured):

hexahedron   n= 18432 max_ratio=  14.207 p99=   3.881
wedge        n=  1024 max_ratio=   6.540 p99=   2.115

The max_ratio of 14.2 flags a small number of badly distorted hexes; the p99 of 3.88 tells you the bulk of the mesh is acceptable. That gap between max and p99 is the signal you act on — a single outlier usually means a local geometry feature (a fillet, a sliver, a collapsed corner) rather than a global meshing failure.

Format-Specific Pitfalls

  • Abaqus .inp: Element type is declared once per *ELEMENT block, so a mesh with mixed element types has multiple blocks. Node sets and element sets use *NSET/*ELSET and are frequently referenced by boundary conditions — preserve them when round-tripping, or downstream scripts will fail to find named regions.
  • Nastran .bdf: Fixed-field format is column-sensitive; a single misplaced character shifts every field. Free-field (comma-separated) is safer for programmatic writing. pyNastran handles both and exposes model.nodes and model.elements as dictionaries keyed by ID.
  • Gmsh .msh: Version 2 and version 4 have different section layouts. Version 4 stores entities in blocks with explicit tags, which is more robust but breaks naive line-based parsers. Always check the $MeshFormat version line before parsing.
  • VTK: Legacy .vtk is ASCII or binary; .vtu is XML-based and compresses well. Cell type enums are not the same as Gmsh type IDs — a Gmsh tet4 (type 4) is VTK cell type 10. Do not copy numeric IDs across formats.
  • Exodus: Built on NetCDF, so it is self-describing and handles large meshes efficiently. Element blocks group elements by type; a mesh with hexes and tets has two blocks. Reading requires netCDF4 or meshio's Exodus backend.
Indexing trap: When converting VTK → Abaqus/Nastran/Gmsh/Exodus, connectivity must be incremented by 1. When converting the other direction, decrement by 1. meshio does this automatically; manual parsers almost always get it wrong at least once. Verify by checking that conn.min() == 0 after a meshio read and conn.min() == 1 in the raw file.

Choosing a Reader for Your Pipeline

For a quality-inspection tool that must accept whatever the analyst provides, meshio is the pragmatic default: one API, five formats, consistent 0-based indexing, and cell-type strings that map cleanly to quality-metric dispatch tables. Reach for a format-specific library when you need features meshio does not expose — pyNastran for full card-level access, the gmsh Python API for parametric remeshing, pyvista for interactive visualization, and netCDF4 for direct Exodus variable access.

Round-trip test: Before trusting any conversion, read a mesh, write it to the target format, read it back, and assert that node count, element count, and connectivity arrays are identical. A five-line test catches indexing, precision, and element-type mapping errors before they contaminate quality results.
Mesh Format Interoperability Pipeline Read → normalize indexing → inspect quality → write Source Formats Abaqus .inp 1-based · keyword blocks Nastran .bdf 1-based · card names Gmsh .msh 1-based · numeric type ID VTK .vtk / .vtu 0-based · cell enum Exodus .exo 1-based · NetCDF blocks meshio.read() normalize to 0-based unify cell-type strings preserve sets / blocks Quality Inspector edge-length ratio scaled Jacobian aspect ratio max / p99 report meshio .write() any target format Indexing rule: VTK is 0-based; Abaqus / Nastran / Gmsh / Exodus are 1-based. meshio normalizes to 0-based — verify conn.min() == 0 after read. Round-trip test read → write → read assert identical arrays

The diagram above summarizes the flow: five source formats feed a single normalization layer, quality metrics are computed on the normalized representation, and results are written back to any target format. The indexing rule and round-trip test are the two safeguards that keep the pipeline honest across all five formats.