4. Reading LS-DYNA d3plot files with Python
The d3plot file is LS-DYNA's native binary results database. Every explicit run writes one or more of these files, and almost every post-processing task — contour plotting, nodal history extraction, energy checks, or automated report generation — begins by reading them. Unlike ASCII outputs such as nodout or glstat, the d3plot format is a compact, state-based binary layout: geometry (nodal coordinates and element connectivity) is written once at the beginning, followed by a sequence of state blocks, each holding displacement, velocity, acceleration, stress, strain, and other element/nodal fields for a single time step. Understanding this two-part structure is the key to reading it efficiently in Python.
File Structure and the State Model
A d3plot file is organized as a header, a geometry section, and then repeated state data. The header stores control information such as the number of nodes, number of solid/shell/beam elements, and the number of states. The geometry section contains the initial nodal coordinates and the connectivity arrays that define which nodes belong to each element. Each subsequent state block contains the per-node and per-element results at one output time.
This means a reader must distinguish between static data (coordinates, connectivity — read once) and transient data (displacement, stress — read per state). Confusing the two is the most common source of bugs: for example, plotting stress against the initial coordinates instead of the deformed coordinates produces a visually plausible but physically wrong contour.
Reading with lasso-python
lasso-python is the Python interface to the LS-DYNA LS-PrePost reader library. It exposes a D3plot reader class that handles the binary parsing for you and returns NumPy arrays, which makes downstream processing fast and vectorized.
from lasso.dyna import D3plot, ArrayType
# Open the d3plot (and any d3plot01, d3plot02 ... family files)
d3plot = D3plot("run01/d3plot")
# --- Static geometry ---
node_ids = d3plot.arrays[ArrayType.node_ids] # (n_nodes,)
node_coords = d3plot.arrays[ArrayType.node_coordinates] # (n_nodes, 3)
solid_conn = d3plot.arrays[ArrayType.element_solid] # (n_solids, 8)
shell_conn = d3plot.arrays[ArrayType.element_shell] # (n_shells, 4)
# --- Transient state data ---
disp = d3plot.arrays[ArrayType.node_displacement] # (n_states, n_nodes, 3)
vel = d3plot.arrays[ArrayType.node_velocity] # (n_states, n_nodes, 3)
sig = d3plot.arrays[ArrayType.element_solid_stress]# (n_states, n_solids, 6)
print("nodes:", node_coords.shape[0],
"states:", disp.shape[0])
The array keys are the important part. ArrayType.node_coordinates gives the reference geometry; ArrayType.node_displacement gives the per-state displacement. To obtain deformed coordinates at state i, add them:
deformed = node_coords + disp[i] # (n_nodes, 3)
Note the sign and coordinate convention: LS-DYNA uses a right-handed Cartesian system, and displacement is reported as the total displacement from the initial position, not an increment. If you need incremental displacement between two states, subtract them explicitly.
Extracting Connectivity and Building a Mesh
Connectivity arrays are zero-based in lasso-python but reference node indices, not node IDs. If your model uses non-sequential node IDs (common after merging or renumbering), map them through node_ids before indexing coordinates:
import numpy as np
# Map node ID -> array index
id_to_index = {nid: i for i, nid in enumerate(node_ids)}
# Coordinates of the first solid element's nodes
elem0 = solid_conn[0]
xyz0 = node_coords[[id_to_index[nid] for nid in elem0]]
For shell elements, the connectivity is typically 4 nodes (quad) or 3 nodes (tri, padded). Always check the array shape rather than assuming a fixed width, because mixed meshes produce ragged connectivity that the reader pads or splits.
Alternative Parsers
When lasso-python is unavailable or you need finer control, several alternatives exist:
- LS-PrePost batch mode — run
lsprepost -nographics -batch script.cfileto export selected data to CSV, then read the CSV in Python. Robust but slower and requires LS-PrePost installed. - pyD3plot / d3plot-reader — lightweight community parsers that read the header and state blocks directly. Useful for scripting on headless clusters, but they support fewer element types.
- Manual binary parsing with
structornumpy.fromfile— read the control words, then the geometry and state blocks. This is the most portable approach and worth understanding even if you normally use lasso-python.
import numpy as np
with open("d3plot", "rb") as f:
# First control word: file size / format flag
(file_flag,) = np.fromfile(f, dtype=np.int32, count=1)
# ... read header words, then geometry, then states
Manual parsing is error-prone because the format has version-dependent word sizes and optional sections. Use it for targeted extraction (for example, pulling only nodal coordinates) rather than full-database reads.
Practical Workflow and Verification
A reliable extraction routine follows a fixed order: open the file, read geometry, confirm node and element counts against the LS-DYNA output, then loop over states. The count check is your first verification — if node_coords.shape[0] does not match the "number of nodes" reported in the LS-DYNA message file, the reader has mis-parsed the header or you are reading the wrong d3plot family member.
Typical failure symptoms and their causes:
| Symptom | Likely cause | Fix |
|---|---|---|
| Displacement array is empty | Only geometry read; no state blocks parsed | Confirm the file is not truncated and the reader supports the format version |
| Coordinates look scaled or shifted | Unit system mismatch (mm vs m) | Check the LS-DYNA input units; d3plot stores raw values |
| Connectivity indices out of range | Using node IDs instead of array indices | Map through node_ids |
| Stress contour looks mirrored | Sign convention or element ordering | Verify against LS-PrePost for one element |
Once geometry and state arrays are loaded, the same data feeds contour plotting, nodal history curves, and automated pass/fail checks. The next step is to convert these raw arrays into physically meaningful quantities — which is where the coordinate and sign conventions you established here must remain consistent throughout the analysis.