3. Reading and parsing CAE result files

Reading and parsing CAE result files 学习卡片

Once a solver has finished, the physics lives in a binary or text result file rather than in the model. Extracting nodal and elemental results from those files is the first real data-engineering step of any automation pipeline, because every downstream chart, table, and dashboard depends on getting clean arrays out of a format that was designed for a solver, not for Python. This section covers the five formats named in the course outline — .odb, .rst, .op2, .h3d, and .f06 — and shows how to read each one with a Python library, how to align the extracted values with node and element IDs, and how to verify that what you read is what the solver wrote.

What each format actually is

Before writing code it helps to know what kind of container you are opening. The formats differ in origin, in whether they are binary or text, and in whether a Python library can read them directly or only through a vendor API.

FormatSolver / originEncodingTypical Python access
.odbAbaqus/Standard, Abaqus/ExplicitBinary, HDF5-basedodbAccess (ships with Abaqus), or h5py for the HDF5 variant
.rstANSYS Mechanical APDLBinarypyansys / ansys-mapdl-reader
.op2Nastran (MSC, NX)Binary, Fortran-record basedpyNastran
.h3dAltair HyperWorks / OptiStructBinarypyH3D or HyperWorks scripting APIs
.f06Nastran printed outputFixed-width ASCIIPlain Python text parsing, pandas.read_fwf

The practical consequence is that .f06 is the easiest to read and the least structured, while .odb and .op2 are highly structured but require a library that understands their internal tables. A robust pipeline usually reads the binary format for bulk data and uses the .f06 only for spot-checking scalar values such as total strain energy or a single grid-point stress.

Reading an Abaqus .odb

The odbAccess module is only importable from the Abaqus Python interpreter (abaqus python or abaqus cae -noGUI), so the extraction script is normally launched by the solver installation rather than by a system Python. The pattern is: open the database, select the step and frame, then request a field output by variable name.

from odbAccess import openOdb

odb = openOdb('bracket_static.odb', readOnly=True)
step = odb.steps['Step-1']
frame = step.frames[-1]                 # last converged increment

disp = frame.fieldOutputs['U']          # nodal displacement
stress = frame.fieldOutputs['S']        # element stress (tensor)

node_ids = [v.nodeLabel for v in disp.values]
u_mag = [v.magnitude for v in disp.values]

# Elemental data: average to element centroid
elem_ids = [v.elementLabel for v in stress.values]
mises = [v.mises for v in stress.values]

odb.close()

Two details matter here. First, fieldOutputs['S'] is stored at integration points by default; calling stress.getSubset(position=ELEMENT_NODAL) or position=CENTROID changes the sampling location, and the numbers will differ. Second, v.mises is a convenience accessor for the von Mises invariant of the tensor — if you need the raw six components, read v.data and map it to the solver's component order (11, 22, 33, 12, 13, 23).

Reading ANSYS .rst and Nastran .op2

For ANSYS, ansys-mapdl-reader exposes the result file as an object with named arrays. A typical extraction of nodal displacement and elemental stress looks like this:

from ansys.mapdl import reader as pymapdl_reader

rst = pymapdl_reader.read_binary('plate.rst')
disp = rst.nodal_displacement(0)        # result index 0
stress = rst.element_stress(0)          # list of (elem_id, array)

node_ids = rst.mesh.nodes_num

For Nastran, pyNastran reads the .op2 and gives you a results object keyed by subcase:

from pyNastran.op2.op2 import OP2

op2 = OP2()
op2.read_op2('wing.op2')
case = op2.case_control_deck
disp = op2.displacements[1]             # subcase 1
stress = op2.cbar_stress[1]             # CBAR elemental stress

Both libraries return objects whose attribute names encode the result type and whose data arrays are indexed by the solver's internal ordering. The node and element IDs are stored alongside the values, so always carry the ID array through the pipeline rather than assuming the array index equals the ID.

Reading .h3d and .f06

HyperWorks .h3d files are read through pyH3D or through the HyperWorks Python API. The access pattern is similar to the others: open the file, list the result pools, and pull a pool by name.

import pyH3D

h3d = pyH3D.H3DReader('crash.h3d')
pools = h3d.get_result_pools()
disp = h3d.get_nodal_result('Displacement', step=0)

The .f06 is a different animal: it is a printed report with fixed column widths. The reliable approach is to locate the block by its header string, then parse with pandas.read_fwf using explicit column spans taken from the Nastran documentation.

import pandas as pd

with open('model.f06') as f:
    lines = f.readlines()

start = next(i for i, l in enumerate(lines)
             if 'ELEMENT STRESSES' in l)
block = lines[start + 3:start + 40]

df = pd.read_fwf(block, colspecs=[(0,8),(8,16),(16,24),(24,32)],
                 names=['elem_id','axial','bending','shear'])

Because .f06 formatting varies between Nastran versions and between result types, the column spans must be verified against a real file before the parser is trusted. A quick sanity check is to confirm that the number of parsed rows equals the number of elements reported in the header.

Aligning results with the model

Extraction is only half the job. The values must be joined back to the mesh so that a node ID in the result file maps to the same node in the geometry. The safest pattern is to build a dictionary keyed by ID and then iterate the mesh in its own order:

disp_by_node = dict(zip(node_ids, u_mag))
mesh_nodes = mesh.node_ids
aligned = [disp_by_node.get(nid, float('nan')) for nid in mesh_nodes]

Using .get() with a NaN default makes missing nodes visible instead of silently shifting the array. If the count of NaN entries is non-zero, the result file and the mesh are out of sync — usually because the result was written for a different set, or because a node was renumbered during export.

Verification habit: after extraction, compare one scalar against the solver's own printed value. For example, read the maximum displacement from the .f06 and compare it with max(u_mag) from the .odb. A mismatch larger than rounding error means the wrong frame, subcase, or position was selected.

Common failure modes

  • Wrong frame or subcase: the last frame is not always the one of interest; check step.frames count and the frame's frameValue.
  • Integration-point vs. nodal values: reading S without specifying position returns integration-point data, which will not match a nodal contour plot.
  • Unit inconsistency: .f06 values follow the model's unit system, while some libraries return SI; document the unit system once and apply it consistently.
  • Truncated files: a solver killed mid-run leaves a partial result file; check the file size and the presence of the final frame before parsing.
Result File Extraction Pipeline .odb (Abaqus) .rst (ANSYS) .op2 (Nastran) .h3d (HyperWorks) .f06 (ASCII) odbAccess / h5py step → frame → fieldOutput ansys-mapdl-reader nodal_displacement(i) pyNastran OP2 displacements[subcase] pyH3D get_nodal_result(pool) pandas.read_fwf colspecs from header Extract node IDs + values element IDs + values step / frame / subcase position (nodal / IP) Align to mesh dict by ID NaN for missing verify vs .f06 unit consistency Clean arrays → DataFrame / dashboard node_id, u_mag, elem_id, mises, step, frame Key checks before trusting extracted data Frame/subcase index matches the intended result Position (nodal vs integration point) matches the contour being reproduced Row count equals reported node/element count One scalar cross-checked against the solver's printed output

The diagram above summarizes the flow from each solver-specific file through its reader into a common extraction step, then into ID-based alignment and finally into a clean tabular structure. The key engineering decision is not which library to import but which frame, subcase, and position to request — those three choices determine whether the numbers you extract correspond to the result you intend to report.