Skip to content

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 namenastran
Extensions.bdf, .fem, .nas
Read / Write✓ / ✓
Extra dependencies

Reading & writing

python
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 for GRID and 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

Nastranmeshio++Nastranmeshio++
CBEAM, CBUSH, CBUSH1D, CROD, CGAP, CBARlineCTETRAtetra
CTRIAR, CTRIA3triangleCTETRA_*tetra10
CTRAX6, CTRIAX6, CTRIA6triangle6CPYRAM, CPYRApyramid
CQUADR, CSHEAR, CQUAD4quadCPYRA_*pyramid13
CQUAD8quad8CPENTAwedge
CQUAD9quad9CPENTA_*wedge15
CELAS1vertexCHEXAhexahedron
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):

typepermutation
triangle6/CTRAX6/CTRIAX6to-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 uses np.format_float_scientific(precision=11) then swaps e→E; the C++ writer instead searches increasing precision (0 through 11) for the shortest string that round-trips exactly via strtod, 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 .fem fixtures — 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-small point/cell format combination; the shim only attempts the C++ path for exactly that combination and only when no nastran:ref data is present.
  • Points are force-promoted to 3D on write if given 2D, with a warning.

Notes

  • tests/meshes/nastran/cylinder.fem and cylinder_cells_first.fem — HyperMesh/Optistruct-generated meshes matching tests/meshes/med/cylinder.med geometrically; used for a point-sum and per-type connectivity-sum checksum ({line:241, triangle:171, quad:721, pyramid:1180, tetra:5309}).

Released under the MIT License.