Skip to content

LS-DYNA state database (d3plot) ​

The binary result database LS-DYNA writes for post-processing, and what LS-PrePost, lasso-python and most public crash datasets start from. A run writes the family d3plot, d3plot01, d3plot02...: the geometry and then one state per output time. meshio++ reads the family as a sequence, d3plot → .vtu / VTKHDF / any meshio++ format, with every state as a step. The input deck (.k) is a different format: see LS-DYNA keyword decks.

Format namelsdyna_d3plot
Extensionsnone: found by its file name, d3plot (any case), or by its control block
Read / Write✓ / ✓ (conformance)
Extra dependenciesnone

Reading ​

python
import meshioplusplus

mesh = meshioplusplus.read("run/d3plot")                  # the first state
last = meshioplusplus.read("run/d3plot", time_step=-1)    # the last one

last.point_data["displacement"]             # (n, 3), relative to the initial geometry
last.cell_data["effective_plastic_strain"]  # per block: (cells, points)
last.cell_data["stress"]                    # per block: (cells, points * 6)
last.field_data["lsdyna_d3plot:layout:stress"]   # [points, 6]
last.cell_data["lsdyna:alive"]              # int8: 1 active, 0 deleted

meshioplusplus.lsdyna_d3plot.time_values("run/d3plot")   # the time of every state
bash
meshioplusplus convert run/d3plot 'state_{step}.vtu'    # one .vtu per state
meshioplusplus convert run/d3plot run.pvd               # or a ParaView collection

Both engines read the whole format: the C++ core, and a Python reference reader the core falls back to. Both write it too (Writing).

Malformed one-based indices, including the minimum signed 64-bit value, raise ReadError before conversion; airbag particle ranges must fit the particle table. These checks apply to the native reader and Python reference alike.

Which files ​

  • The family. Open the base file, d3plot. The files named after it with a number (d3plot01, d3plot02... d3plot100) are found beside it and taken in numeric order. Together they are one stream of states: a state never straddles two files, and zero padding at the end of a file is skipped. As in lasso-python, a file holds as many whole states as fit before its last non-zero word (lasso-python counts bytes, which loses a state of a big-endian file), and that word is the end mark (-999999) LS-DYNA writes after the last state. A numbered member opened on its own is refused with a message naming the base file, and a sequence glob such as run/d3plot* keeps the base file only.
  • Precision and byte order. Single (4-byte words) and double precision (8-byte words) runs, in either byte order, are told apart by the control block: its file-type word and its dimension word must be plausible in exactly one reading.
  • File types. d3plot, d3eigv (the eigenvector database, whose states are the modes) and, since v16.12.0, d3part (the parts a *DATABASE_BINARY_D3PART selects, in the d3plot's layout; found by its name d3part too) are read. Interface-force files, femzip-compressed files (d3plot.fz, or the femzip mark in the header), two-dimensional databases (NDIM = 2), CFD and multi-solver data and adaptive-remeshing runs (NADAPT != 0, whose geometry changes between states) are refused with a ReadError that names them.

The model ​

  • Points are the initial coordinates, in the file's node order. The user node ids are point_data["lsdyna:nid"] (the internal numbers 1..n when the file has no numbering section).
  • Cells. One cell block per element family and cell type, in the order solids, thick shells, beams, shells, and within a family in order of first appearance:
FamilyCell types
8-node solidshexahedron, and wedge, pyramid, tetra from repeated nodes (the keyword reader's collapse rules, detail/degenerate_solid.hpp)
10-node solids (NEL8 < 0)tetra10: the 8-node connectivity plus the two extra nodes, in LS-DYNA's order, which is meshio++'s
20-node solids (NEL20, since v16.12.0)hexahedron20: the 8 corners plus the 12 edge nodes; LS-DYNA numbers them 1-2, 2-3, 3-4, 4-1, 5-6 ... 8-5, 1-5 ... 4-8 (keyword manual, *ELEMENT_SOLID, figure 17-30), which is VTK's order
27-node solids (NEL27, since v16.12.0)hexahedron27: the corners plus nodes 9-27 (edges as above, then the faces 1-4-8-5, 2-3-7-6, 1-2-6-5, 4-3-7-8, 1-2-3-4, 5-6-7-8 and the centre, figure 17-31: VTK's order); the rows list the 19 extra nodes, or all 27 when QUADR > 0
SPH particles (NMSPH, since v16.12.0)vertex on the particle's node; lsdyna:eid is the node's id, lsdyna:part the material
airbag particles (NPEFG, since v16.12.0)vertex on points after the nodes, placed where each state puts them (NaN without a state); lsdyna:part is the airbag's id
rigid road segments (NDIM = 6, 7, 9, since v16.12.0)quad on the road's own nodes, points after the airbag particles; lsdyna:part is the road's id
thick shellsas the 8-node solids
beamsline (the orientation node is dropped)
shellsquad, triangle when n4 = n3, quad8 for the 8-node shells (NEL48)
  • Cell data. lsdyna:eid (the user element id) and lsdyna:part (the user part id), integer, per block.
  • Regions. One cell region per part with elements, tagged with the user part id and named by the part title (the 90001 titles section), or Part <id> without one; Airbag <id> and Rigid road <id> for the airbag particles and road segments.
  • Rigid bodies. field_data["lsdyna:rigid_body_part"] lists each rigid body's part (user id).
  • Part ids. With a numbering section that lists them, the element's part index maps through its first part-id array, as lasso-python reads it; otherwise through the titles section's ids when it lists every part; otherwise the part number itself.

States and data ​

Every state is a step of the sequence engine: time_step picks one (0 is the first, negative counts from the end), read_metadata(...).time_values lists the times, and read_sequence walks them. Only the chosen state is read.

Field dataMeaning
meshio:timethe state's time
lsdyna:stateits index in the family
global_kinetic_energy, global_internal_energy, global_total_energy, global_velocitythe global variables
part_internal_energy, part_kinetic_energy, part_velocity, part_mass, part_hourglass_energyone value (or vector) per part, when the file has them
lsdyna:rigid_body_coordinates, _rotation (9, row-major), _velocity, _rotational_velocity, _acceleration, _rotational_accelerationper rigid body (NDIM = 8, 9; the last four not in the reduced NDIM = 9 layout), since v16.12.0
lsdyna:road_displacement, lsdyna:road_velocityper rigid road, since v16.12.0
lsdyna:airbag:<name>per airbag, its state variables by their names in the file (Act Gas becomes act_gas, Bag Vol bag_vol), since v16.12.0

Point data, whichever the header flags: displacement (the current coordinates minus the initial ones; the points themselves stay where the geometry put them, as in FEBio plot files), velocity, acceleration, temperature (3 components with temperature layers), heat_flux, mass_scaling, temperature_gradient, residual_forces, residual_moments.

Cell data. A variable written per integration point (solids: 1 or 8) or through-thickness layer (shells and thick shells: MAXINT; beams: their integration points) is an array of shape (cells, points * components), point-major, with field_data["lsdyna_d3plot:layout:<name>"] = [points, components]: the same convention as the Abaqus .fil reader. A family with fewer points than the widest one, or without the variable, is NaN-padded. Point k means integration point k for a solid and layer k for a shell (for three layers: mid, inner, outer).

Cell dataFamilies
stress (6: xx, yy, zz, xy, yz, zx), effective_plastic_strainsolids, thick shells, shells; beams' plastic strain per integration point too
history_variablessolids (NEIPH), shells and thick shells (NEIPS), beams (NEIPB)
strain (6, per integration point)solids (ISTRN)
strain_inner, strain_outer (6 each)shells and thick shells (ISTRN)
plastic_strain_tensor, thermal_strain_tensorsolids and shells, when IDTDT flags them
thickness, internal_energy, shell_bending_moment, shell_shear_force, shell_normal_force, shell_element_variablesshells
beam_axial_force, beam_shear_force, beam_bending_moment, beam_torsion_moment, beam_axial_stress, beam_shear_stress, beam_axial_strainbeams
thermal_variablessolids (NT3D)

Shells in rigid parts (material type 20, listed in the material-type section of NDIM = 5 and 7 files) have no state data: their values are NaN.

SPH particles (since v16.12.0) have, as their ISPHFG flags say, sph_radius, sph_pressure, stress, effective_plastic_strain, density, internal_energy, sph_neighbors, strain, strain_rate, mass and history_variables. Airbag particles have velocity and airbag_<name> for each of their other variables (airbag_gasc_id, airbag_mass, airbag_radius...), integers (type code 1) read as integers. Node arrays are NaN at the airbag particles and road nodes.

Shells' plastic strain tensor. When IDTDT flags it but the shell record holds it at fewer points than the shells have layers (a composite shell with 10 layers and 3 tensors), it is read at as many points as fit, with a warning, rather than past the record.

Deletion. The deletion flags are an int8 mask, lsdyna:alive: 1 for an active element or node, 0 for a deleted one. With MDLOPT = 2 (element deletion) it is cell data; with MDLOPT = 1 (node deletion) it is point data. An SPH particle is deleted when its material word is negative; the deletion flags come before the SPH data in a state (before v16.12.0 a file with both read its flags from the wrong words). Families without flags (airbag particles, roads) are 1. Deleted cells stay in the mesh, so every state has the same cells; a consumer that wants them gone filters on the mask.

Writing ​

python
meshioplusplus.write("run/d3plot", mesh)                              # one state
meshioplusplus.write_sequence("run/d3plot", steps)                    # a state per step
with meshioplusplus.lsdyna_d3plot.SeriesWriter("run/d3plot", max_file_bytes=1 << 28) as w:
    for time, mesh in steps:
        w.write(time, mesh)
meshioplusplus.lsdyna_d3plot.write("run/d3plot", mesh, double=True)   # 8-byte words

The control block, geometry, user ids, part titles and states are the layout the reader (and lasso-python) reads, in single-precision little-endian words, or 8-byte words with double=True. Both engines write the same bytes. The states are split over d3plot, d3plot01, ... once a file would pass max_file_bytes (default 1 GiB; the base file also holds the geometry; a file always holds at least one state). Files named <path>NN already beside the base file are replaced, so the family never mixes runs.

  • Cells. vertex cells are SPH particles (on the nodes, lsdyna:part the material), hexahedron, wedge, pyramid and tetra are 8-node solids (the degenerate ones as bricks with repeated nodes), tetra10, hexahedron20 and hexahedron27 higher-order solids, line a beam and triangle, quad and quad8 shells. Other cell types are not written (a warning counts them); the blocks come back grouped by family, as the reader orders them.
  • Ids and parts. point_data["lsdyna:nid"] and cell_data["lsdyna:eid"] give the user ids (1, 2, ... without them). A cell's part is cell_data["lsdyna:part"], else the first cell region holding it (its tag, or a free number for tag 0; cells in no region share one part), and the region's name is the part title. A cell belongs to one part.
  • Point data, by the reader's names: displacement (current coordinates are the points plus it), velocity, acceleration, temperature (one value, or three through the thickness) with heat_flux, mass_scaling, temperature_gradient, residual_forces and residual_moments, and lsdyna:alive (a node deletion mask). A mesh with none of them still gets the coordinates, so its displacement reads back as zero.
  • Cell data, as the reader returns it, (cells, points * components) with field_data["lsdyna_d3plot:layout:<name>"] (solids hold 1 or 8 points, shells and beams any number of layers; a block with fewer points than the file's is padded with NaN, and a variable a block lacks is NaN): solids and shells stress, effective_plastic_strain, plastic_strain_tensor, thermal_strain_tensor, history_variables and strain (shells: strain_inner/strain_outer), solids thermal_variables, shells shell_bending_moment, shell_shear_force, shell_normal_force, thickness, shell_element_variables and internal_energy, beams beam_axial_force, beam_shear_force, beam_bending_moment, beam_torsion_moment, beam_axial_stress, beam_shear_stress, effective_plastic_strain, beam_axial_strain and history_variables, SPH particles sph_radius, sph_pressure, stress, effective_plastic_strain, density, internal_energy, sph_neighbors, strain, strain_rate, mass and history_variables, and the element deletion mask lsdyna:alive (for an SPH particle the sign of its material).
  • Field data. The rigid bodies' lsdyna:rigid_body_part (user part ids) and lsdyna:rigid_body_coordinates, _rotation, _velocity, _rotational_velocity, _acceleration and _rotational_acceleration (their node lists are not written); global_kinetic_energy, global_internal_energy, global_total_energy, global_velocity and, with all five present, the per-part part_* arrays; meshio:time is the state's time.
  • Series. The geometry is the first step's and every step must have the same points and cells; the header takes the union of the variables of all steps, so the family is written when the writer closes.
  • Thick shells. The reader marks them: when the file has any, cell_data["lsdyna:thick_shell"] is 1 on the bricks that were thick shells and 0 elsewhere. A block of hexahedron, wedge, pyramid or tetra cells all marked is written as the thick-shell family (NELT, NUMMATT, NV3DT, nine words per element and its own id and part): stress, effective_plastic_strain and history_variables per layer and, with the shells' strain flag, strain_inner and strain_outer. The layer count and the history width are the shells' (the file holds one of each), so a thick-shell array with more layers than the shells widens both; a block that mixes marked and unmarked cells is written as solids. lasso-python reads the thick shells written (element_tshell_*).
  • Airbag particles and rigid roads. The cells of the regions the reader names Airbag <id> (a vertex block) and Rigid road <id> (a quad block) are written into their own sections, not as nodes, SPH particles or shells: the points that only they use are left out of the nodes (the region name and its tag, or the number in the name, give the id). A bag's particles are grouped under it in the order the ids first appear; each particle holds its position (the mesh's current point), a Vel x/y/z triple from cell_data["velocity"] and one variable per cell_data["airbag_<name>"] array, named in the file after it in capitals (airbag_bag_vol is Bag Vol, cut to the eight characters the file holds; two names that collide are refused), all as reals; each bag holds the field_data["lsdyna:airbag:<name>"] arrays that have one value per bag. A road holds lsdyna:road_displacement and lsdyna:road_velocity ((roads, 3)). Rigid roads set NDIM to 6, or 9 with rigid bodies, whose state then holds the coordinates and rotation only (the velocities and accelerations are not written). The variable names of a real file are not in the manuals available, so an airbag array that came from a name with other capitals reads back under the same snake_case name but is written under the title-case one; the layout is checked against the reader and the hand-built family of the tests, not against LS-DYNA.
  • d3part. The name decides: a file called d3part is written with file type 5 and the layout of a d3plot, which is how the reader takes it; d3part01, ... belong to it like d3plot01, ... to a d3plot. No real d3part was available to check the layout against.

Not written: a check in LS-PrePost, which joins the licensed-run list.

What is not read ​

  • Rigid-wall forces, which follow the per-part values in the globals.
  • The sibling files d3thdt (a time-history database) and interface-force files; binout is its own format, lsdyna_binout.
  • Cubic and bubble solids (NEL21P, NEL15T, NEL20T, NEL40P, NEL64): refused by name; their node order is not in the manuals available.
  • Adaptive remeshing (NADAPT != 0), whose geometry changes between states: refused.

Verification ​

The word offsets and the state layout follow the LS-DYNA database manual, as lasso-python (BSD 3-Clause) reads it. The test suite reads lasso-python's five real test families (a shell model with deletion flags, a shell and solid model with 22 states, a beam with integration points, a thermal model and a family split over d3plot ... d3plot100) and three shell + solid + beam families written by lasso-python's own D3plot.write_d3plot (in single and double precision, in one file and in one file per state), whose shells and a solid are deleted state by state. On every state of every family the displacement, the solid and shell stress, the plastic strain and the deletion flags equal lasso-python 2.0.4's reading, and the two engines agree exactly on every array.

Two deviations from lasso-python, both toward the manual: the two extra nodes of ten-node solids are read once, after the 8-node connectivity (lasso-python reads a second copy, which its writer also writes and which is skipped here when present), and the displacement is computed in double precision. Since v16.12.0 two LS-DYNA families from Ansys' example data (MIT) are in the suite, trimmed: a bird strike (701 SPH particles on 2304 composite shells, its first state), whose SPH variables and deletion flags equal lasso-python's, and a projectile penetrating a plate (5664 bricks, element erosion: 18 elements deleted at the first kept state, 614 at the last). A family lasso-python writes (with the database manual's layout patched in where its writer fails) checks 20- and 27-node hexahedra and rigid-body motion. No file with airbag particles or rigid roads was available, and lasso-python cannot write them: those follow the manual (and lasso-python's reader, except for the road segments, whose count lasso-python reads one word late) and are checked on a file built in the tests. No d3part from a run has been read; the tests read a d3plot relabelled as one.

Released under the MIT License.