Nastran (.bdf, .fem, .nas)
The MSC/NX Nastran bulk-data format: fixed-width card entries (GRID, CTRIA3, CTETRA, CHEXA, …) in small-field, large-field, or free (comma-separated) layout.
| Format name | nastran |
| Extensions | .bdf, .fem, .nas |
| Read / Write | ✓ / ✓ |
| Extra dependencies | — |
Reading & writing
import meshioplusplus
mesh = meshioplusplus.read("model.bdf")
meshioplusplus.nastran.write("out.bdf", mesh,
point_format="fixed-large", # "fixed-small", "fixed-large", or "free"
cell_format="fixed-small",
)point_format/cell_format— the field layout forGRIDand element cards, respectively.
File structure
Parsing starts at a line beginning "BEGIN BULK" and stops at "ENDDATA". Three field layouts are auto-detected per line:
- Small field (fixed): 10 fields of exactly 8 characters each.
- Large field (fixed): 8 + 4×16 + 8 characters; the keyword carries a trailing
*(e.g.GRID*), and the record continues onto a second physical line starting with*— the two 16-char halves of a continued field are re-merged before further parsing. - Free field: comma-separated (detected by the presence of a
,on the line).
Continuation is signalled either by an explicit +/* marker as the first character of the next line, or implicitly when the last field of one line and the first field of the next are both blank.
GRID/GRID*: [id, ref(optional), x, y, z]. Coordinates may use Nastran's compressed-exponent float notation (e.g. 1.5+1, .7E1) — decoded by inserting an implicit e before a bare +/- not already following e/E.
Element cards: [element_id, ref(optional), node_ids...], except CBAR/CBEAM/CBUSH/CBUSH1D/CGAP, which only take the first 2 node ids from the card — a 3rd field present on those cards (an orientation vector or grid id) is discarded on read. Second-order solids (CTETRA, CPYRA, CPENTA, CHEXA) are auto-upgraded to their 10/13/15/20-node meshio++ counterparts whenever a card lists more node ids than the linear element's base count.
Cell types & node ordering
| Nastran | meshio++ | Nastran | meshio++ |
|---|---|---|---|
CBEAM, CBUSH, CBUSH1D, CROD, CGAP, CBAR | line | CTETRA | tetra |
CTRIAR, CTRIA3 | triangle | CTETRA_* | tetra10 |
CTRAX6, CTRIAX6, CTRIA6 | triangle6 | CPYRAM, CPYRA | pyramid |
CQUADR, CSHEAR, CQUAD4 | quad | CPYRA_* | pyramid13 |
CQUAD8 | quad8 | CPENTA | wedge |
CQUAD9 | quad9 | CPENTA_* | wedge15 |
CELAS1 | vertex | CHEXA | hexahedron |
CHEXA_* | hexahedron20 |
(* = "fictive" type names used internally to represent the auto-detected second-order upgrade of the base solid card.)
Node-order permutations (involutions — the same array is used in both directions unless noted):
| type | permutation |
|---|---|
triangle6/CTRAX6/CTRIAX6 | to-VTK [0,2,4,1,3,5], to-Nastran [0,3,1,4,2,5] |
hexahedron20 (CHEXA_) | [0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19,12,13,14,15] |
wedge15 (CPENTA_) | [0,1,2,3,4,5,6,7,8,12,13,14,9,10,11] |
Data mapping
point_data["nastran:ref"]— the GRID card's optional reference field.cell_data["nastran:ref"]— the element card's optional reference field, one array per block.mesh.points_id/mesh.cells_id— mesh-level attributes (not data-dict entries) holding the original GRID/element ids; set only by the Python reader.
Quirks & limitations
- The 16-character float encoding used for large-field
GRID*cards is the trickiest part of this format: the Python writer usesnp.format_float_scientific(precision=11)then swapse→E; the C++ writer instead searches increasing precision (0 through 11) for the shortest string that round-trips exactly viastrtod, then trims trailing mantissa zeros. Both target the same 16-character field limit but are not guaranteed to produce byte-identical text. CBAR/CBEAM/CBUSH/CBUSH1D/CGAP's 3rd node/orientation field is always dropped on read — a genuinely lossy round-trip for those 1D element types.- The 2nd-order-solid upgrade is a heuristic ("more node ids on the card than the linear element's count ⇒ treat as quadratic"), not a version flag — any card with unexpected extra trailing tokens could be misclassified.
- The C++ reader is sentinel-gated: it only parses files carrying the exact literal comment string the C++ writer itself emits (
"meshioplusplus-cpp-nastran") as the first$comment line. Any real-world Nastran file — including this project's own reference.femfixtures — lacks that sentinel and is therefore always parsed by the (more permissive, general-purpose) Python reader. This is the single most consequential interop rule for this format. - The C++ writer only emits the
fixed-large/fixed-smallpoint/cell format combination; the shim only attempts the C++ path for exactly that combination and only when nonastran:refdata is present. - Points are force-promoted to 3D on write if given 2D, with a warning.
Notes
tests/meshes/nastran/cylinder.femandcylinder_cells_first.fem— HyperMesh/Optistruct-generated meshes matchingtests/meshes/med/cylinder.medgeometrically; used for a point-sum and per-type connectivity-sum checksum ({line:241, triangle:171, quad:721, pyramid:1180, tetra:5309}).