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 |
gmsh | tetra10, hexahedron20, hexahedron27, wedge15, wedge18, pyramid13, pyramid14 | the gmsh reference manual's "Node ordering" (MHexahedron.h, MPrism.h, MPyramid.h): every mid-edge and face-centre slot at the midpoint or centroid of its corners. Moved in from the gmsh reader and writer (v16.17.0) |
cgns | wedge15, wedge18, hexahedron20, hexahedron27 | CGNS SIDS, "Unstructured Grid Element Numbering Conventions": the vertical mid-edges before the top ring, HEXA_27's face centres z−, y−, x+, y+, x−, z+. Moved in from both CGNS readers and the writer (v16.17.0) |
gid | wedge15, hexahedron27 | Kratos's geometry classes, which its GiD writer emits unchanged for these two types; GiD's hexahedron20 is meshio++'s order (the reorder Kratos applies before writing it), and pyramid13 needs none. Moved in from the GiD reader and writer (v16.17.0); see GiD |
mdpa | wedge15, hexahedron20, hexahedron27 | Kratos's own hexahedra_3d_20.h, hexahedra_3d_27.h and prism_3d_15.h (PointsLocalCoordinates, GenerateEdges and the shape functions agree): the vertical mid-edges before the top ring. Moved in from the MDPA reader and writer (v16.17.0), which until then swapped mid-edge slots 9/11, 13/15 and 17/19 of a hexahedron20 and left wedge15 unpermuted |
exodus | hexahedron20, hexahedron27, wedge15 | SEACAS Ioss's Hex20, Hex27 and Wedge15 topologies, the tables vtkExodusIIReader applies: the vertical mid-edges before the top ring, and HEX27's body centre first (node 21), then the face centres z−, z+, x−, x+, y−, y+. Until v16.17.0 Exodus files were read and written in VTK's order, so VTK and ParaView misplaced the mid-edge nodes of our HEX20/HEX27 |
Every format's table lives here since v16.17.0. Two neighbours stay in their readers because they are not node permutations: Exodus's side-set numbering (its side k of a hexahedron is meshio++'s facet (2,1,3,0,4,5)[k], a facet map) and the slot layouts of Ansys's degenerate shapes and Femap's 20-slot brick, which place a cell's nodes into a wider record.
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;
- for the gmsh, CGNS, GiD, Kratos and Exodus tables, an element built in the file's own order from that format's documentation (each file slot the midpoint or centroid of the corners the format says, v16.17.0) reads through
to_meshioas a valid, positively oriented meshio++ element (mid-edge nodes on midpoints, face and body centres on centroids). Until v16.17.0 this check mapped a reference element out and back through the same table, which cannot fail; the new form fails on both the old MDPA table and the missing Exodus one; - 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.