Node ordering
meshio++ stores every cell in the VTK node order. A format that numbers the nodes of an element differently needs a permutation on the way in and its inverse on the way out. Since v16.0.0 those permutations live in one registry, keyed by (format, cell type):
- C++:
detail/node_order.hpp,node_order(format, cell_type); - Python:
meshioplusplus._node_order,node_order(fmt, cell_type),to_meshio(...)andfrom_meshio(...).
A type with no entry uses the identity. Every entry holds both directions as gather tables:
to_meshio[k]is the file slot meshio++ nodekcomes from on read:meshio[k] = file[to_meshio[k]];from_meshio[j]is the meshio++ node written to file slotj:file[j] = meshio[from_meshio[j]].
Most tables are their own inverse, but not all. MED's hexahedron27 is not, and a writer that reused its read table would scramble the face centres, so a format never has to know.
What is in the registry
| Format | Cell types with a permutation | Pinned against |
|---|---|---|
med | tetra, pyramid, wedge, hexahedron and their quadratic forms up to wedge18/hexahedron27 | MEDCoupling's CellModel.cxx edge and face tables (orientation and mid-edges); hexahedron27/wedge18 (v16.0.0) against a file written by MED-fichier itself and Code_Aster's MED reader |
code_aster | wedge15, wedge18, hexahedron20, hexahedron27 | Code_Aster's gmsh reader (inigms.F90) exactly, and its MED reader (lrmtyp.F90) up to a symmetry of the reference cell; see Code_Aster |
elmer | hexahedron20, hexahedron27 | ElmerSolver's elements.def reference coordinates and its own VTU writer's Elmer2VtkIndexes permutation; gmsh meshes converted by ElmerGrid read back node for node as meshio++'s gmsh reader reads them (v16.2.0); see Elmer |
febio (.feb and .xplt) | hexahedron27 | the shape functions of FEHex27 in FEBio's FECore/FESolidElementShape.cpp (the mid-height face centres run y−, x+, y+, x−); FEBio's own parsers and plot files, read against FEBio 4.12 (v16.2.0); see FEBio |
flux | tetra, tetra10, pyramid, wedge, wedge15, hexahedron, hexahedron20 | FEconv's FLUX samples: every solid is VTK's element mirrored (base face clockwise); read through these tables all have positive Jacobians and mid-edge nodes at edge midpoints, and equal their I-DEAS UNV twins row for row. wedge15 has no sample and follows the same rule |
frd | hexahedron20, wedge15, line3 | ccx 2.23 output for the same .inp |
patran | hexahedron20, wedge15 | the Patran Reference Manual's Element Library (the vertical mid-edges come before the top ring), with fixtures written from its edge lists (v16.5.0); see Patran |
libmesh | hexahedron20, hexahedron27, wedge15, wedge18 | libMesh's own VTK connectivity (cell_hex20.C, cell_hex27.C, cell_prism15.C, cell_prism18.C); every element type of libMesh's tests/meshes/xdrio_elements samples reads with positive volumes and its higher-order nodes where meshio++'s tables put them (v16.7.0); see libMesh |
radioss | hexahedron20 | gmsh's getVertexRAD for /BRIC20 (bottom ring, vertical mid-edges, top ring), confirmed by OpenRadioss users (discussion #2809) (v16.7.0); see Radioss |
z88 | hexahedron, hexahedron20, tetra10 | Z88's hexahedra list the face its manual draws on top first (read as-is, every hexahedron of the Z88OS examples has a negative volume), and its tet10 mid-edges run 2-4, 3-4, 1-4 (the mid-edge positions of Z88OS example b11); Z88R solves the decks written through these tables (v16.7.0); see Z88 |
mphtxt (also mphbin) | quad, pyramid, hexahedron, triangle6, quad9, tetra10, pyramid14, wedge18, hexahedron27 | COMSOL's "Mesh Element Numbering Conventions" (corners in tensor order, then the quadratic lattice in lexicographic order), real COMSOL files (deal.II, FEconv, Wolfram FEMAddOns), and AWS Palace's COMSOL-to-gmsh tables composed with the gmsh ones (v16.1.0) |
unv | line3, triangle6, quad8, quad9, tetra10, pyramid13, wedge15, hexahedron20 | gmsh's .unv/.msh twins and Salome's SMESH driver |
The gmsh, CGNS, GiD, Exodus and Kratos tables still live in their own readers. They move here when those formats are next touched (roadmap §1.8).
MSC Marc (v16.8.0) has no entry because it needs none: every Marc element type meshio++ reads, quadratic ones included, numbers its nodes in meshio++'s order (checked on real Marc Mentat decks: every cell positively oriented, every mid-edge node at its edge's midpoint). See Marc.
Self-test
tests/cpp/test_node_order.cpp and tests/python/test_node_order.py check every entry the same way:
- it is a permutation of its cell type's node count, and its two directions are inverses;
- a reference element (mid-edge nodes on midpoints, face and body centres on centroids) written through
from_meshioand read back throughto_meshiois again a valid, positively oriented element; - the C++ and Python tables are identical;
- the
code_astertables equal Code_Aster's gmsh reader composed with the gmsh tables, and agree with its MED reader composed with themedtables.