Nastran OP2 results (.op2)
The binary result file MSC Nastran and Simcenter Nastran (NX Nastran) write with PARAM,POST,-1 (or -2), and that Femap, Patran and most post-processors read. MSC runs that write HDF5 are read by nastran_h5; OP2 is the route for Simcenter/NX Nastran and for MSC decks without MDLPRM,HDF5. meshio++ reads it as a sequence, .op2 → .vtu / VTKHDF / any meshio++ format, with every subcase, mode, time or frequency as a step.
| Format name | nastran_op2 |
| Extensions | .op2 |
| Read / Write | ✓ / ✓ (the model, the nodal tables and the element results in the reader's layouts: stresses, strains, forces, heat fluxes, energies and grid point forces, real, complex and random) |
| Extra dependencies | none |
Reading
import meshioplusplus
mesh = meshioplusplus.read("job.op2") # the first step
mode3 = meshioplusplus.read("job.op2", time_step=2) # the third one
mesh.point_data["DISPLACEMENT"] # (n, 3) translations
mesh.point_data["DISPLACEMENT_ROT"] # (n, 3) rotations
mode3.point_data["EIGENVECTOR"] # a mode shape
mesh.cell_data["STRESS:VON_MISES"] # solids' centre von Mises, NaN elsewhere
mesh.cell_data["STRESS:VON_MISES1"] # shells' fibre-1 von Mises
mesh.cell_data["STRESS:X@corner"] # (cells, nodes): solid corner values
mesh.cell_data["GRID_FORCE:F1"] # (cells, nodes): grid point forces
mesh.field_data["nastran:subcase"], mesh.field_data["meshio:time"]
meshioplusplus.nastran_op2.time_values("job.op2")meshioplusplus convert job.op2 'mode_{step}.vtu'Both engines read the whole format: the C++ core, and a Python reference reader the core falls back to. The writer is below.
Which files
An OP2 is a Fortran unformatted file of named tables. meshio++ reads MSC and NX/Simcenter files, 32-bit (4-byte words) and 64-bit (8-byte words, still with 4-byte record markers), in either byte order. The table names of 64-bit NX files, written four characters per word, are read as the 32-bit ones. A formatted (text) OP2 is refused. A file is recognised by its .op2 extension, or by content: the PARAM,POST,-1 header (a date and the tape code record) is sniffed in any word size and byte order.
The model
The mesh is built as the nastran_h5 reader builds it, from the same card table (detail/nastran_model.hpp):
- Points are the
GEOM1GRIDs, in file order, in the basic coordinate system (since v16.10.0): a GRID withCP != 0is moved from its local system, which theGEOM1CORD1R/C/SandCORD2R/C/Srecords define, andpoint_data["nastran:cp"](["nastran:cd"]) records the system ids. Each system the file defines is returned asfield_datanastran:CORD2_<id>:TYPE,A,X,YandZ(its type, its origin and its axes, all in basic coordinates), which a write uses to put the GRIDs back in their systems. The rules are those ofnastran_h5. NX's 32-bit GRID records with double-precision coordinates are read as such. A mesh taken from a sibling deck keeps the deck reader's raw coordinates. - Cells. The
GEOM2element records with a cell type:CQUAD4,CQUADR,CSHEAR,CQUAD8,CQUAD,CTRIA3,CTRIAR,CTRIA6,CTETRA,CPYRAM,CPENTA,CHEXA(linear or quadratic by their mid-side nodes),CROD,CTUBE,CONROD,CBAR,CBEAM,CBUSH,CGAP,CVISC,PLOTELas lines,CONM1andCONM2as vertices, and (since v16.12.0) the springs, dampers and massesCELAS1,CELAS2,CDAMP1,CDAMP2,CMASS1andCMASS2as lines between their two GRIDs or vertices when grounded (one of them 0), in card-name order; NX 2019 and later'sCQUAD4/CTRIA3records (keys 15401 and 15301) are read as those cards. A card whose record length fits more than one layout (MSC and NXCQUAD4s differ by a word) takes the first whose entries validate: ids increasing, corner nodes that are GRIDs. Records with no cell type (CELAS3/4,CDAMP3/4andCMASS3/4, which join scalar points,CBUSH1DandCBEND, whose properties are nonlinear tables and curved-pipe sections, and the axisymmetric elements) are named in one warning, and a spring with a scalar point at either end is skipped. - Field data. The property and material values
nastran:<CARD>_<id>:<MEMBER>of thePSHELL,PROD,PTUBE,PSOLID,PBAR,PELAS,PDAMP,PVISC,PGAP,PSHEAR,PMASS,MAT1,MAT2,MAT4,MAT5,MAT8andMAT9records, and of the compositePCOMPrecords:nastran:PCOMP_<id>:Z0,NSM,SB,TREF,GE,FTandSYM(1 for a symmetric layup), andPLY, the plies as(n, 4)rows of MID, T, THETA and SOUT (see Writing; since v17.4.0). APBUSHgivesnastran:PBUSH_<id>:KandBas(6,)arrays,GEas one value (the 18-word NX record) or six (the 23-word MSC record), andSA,ST,EAandETas scalars. APBEAMgivesnastran:PBEAM_<id>:MID,NSEG(the segments; the stations areNSEG + 1) andCCFas scalars, and for its stationsSO,XXB,A,I1,I2,I12,JandNSMas(n,)arrays, with the outline pointsC1,C2,D1andD2as(n, 2)rows of x and y. The library sections givenastran:PBARL_<id>:MID,GROUPandTYPE(the last two asuint8text),DIM(the section's dimensions) andNSM, and aPBEAMLthe same withDIMas(stations, n)rows andSO,XXBandNSMper station; aPCOMPGgives thePCOMPscalars withLAMOPTandPLYas(n, 5)rows of GPLYID, MID, T, THETA and SOUT. The sections and global plies are read from 4-byte-word files only (a 64-bit file writes their texts four characters a word). APMASSgivesnastran:PMASS_<id>:M, aMAT4K,CP,RHO,H,MU,HGEN,REFENTH,TCH,TDELTAandQLAT, aMAT5K1...K6,CP,RHOandHGEN, and aMAT9the upper triangle of its stiffness matrixG11,G12...G66,RHO,A1...A6,TREFandGE. - Element identity. Every cell block carries
nastran:card, the GEOM2 record's element type number of its card (CROD30,CONROD16,CTUBE37,CONM215,CONM114,CMASS110,CMASS211,CELAS16,CELAS27,CDAMP12,CDAMP23,CBAR24,CBEAM54,CBUSH26,CVISC39,PLOTEL52,CSHEAR31; the shells and solids by theirs). Lines that share a cell type and have no property id, such asCONROD,CELAS2andCDAMP2, are told apart by it. A spring or damper also carries its components at each end,nastran:c1andnastran:c2(c2is 0 at a vertex, which is grounded at its second end; every other element has 0). ACBARcarries its orientation vectornastran:x(X1 to X3, zeros with a G0), its G0nastran:g0(0 with a vector), its pinsnastran:pin(PA and PB) and its end offsetsnastran:w(W1A to W3A and W1B to W3B); these four arrays are present only when the file holds CBARs. Since v17.4.0. ACMASS2carries its massnastran:m, and aCONM1its mass matrixnastran:conm1,(n, 21)rows of M11, M21, M22, M31...M66 (the record's order); aCONM1in a coordinate system other than the basic one is read as given, with a warning. - Point data.
nastran:nid, the GRID ids, when they are not 1 to n. A read withpoints_only=Truedrops it, like all point data, and anarrays=read keeps it only when"nastran:nid"is named (since v17.4.0). - Cell data.
nastran:eidandnastran:pid(-1 forCONM2,CONROD,PLOTEL). - Regions. One cell region per property id, named
<PTYPE>_<pid>after itsEPTrecord (PSHELL,PSOLID,PCOMP,PCOMPG,PBAR,PBARL,PBEAM,PBEAML,PROD,PSHEAR,PTUBE,PBUSH...),PID_<pid>when none defines it. NX writes eachPCOMPa second time as aPSHELLwith material ids from 100000000: that twin is ignored, and aPCOMPGwins over aPCOMPof the same id, as pyNastran reads them. - Without geometry. A file with no
GEOM1GRIDs (written without the model, or withPARAM,POST,-2defaults) takes its mesh from the input deck beside it:<stem>.bdf,.dat,.nasor.blk, read by the bulk Nastran reader, withnastran:eidadded. The bulk reader does not followINCLUDEcards. Without a deck (since v16.12.0) the points come from the basic grid point table:BGPDTSrows (output system and basic coordinates, in internal order) named by theEQEXINStable, or NX's newerBGPDT, whose rows carry the GRID id; scalar points are left out, and elements still come fromGEOM2when the file has it. Their output systems are unknown withoutGEOM1, so results in a non-basicCDstay in it, with a warning. With neither, theReadErrorlists the paths it looked for. A fluid (acoustic) GRID'sCD = -1reads as the basic system. A GRID repeated in a secondGEOM1table (a restart's) with the same definition is kept once; a different one is an error. PARAM,POST,-2files carry no header: the first table's name opens them. They read as aPOST,-1file does, and are recognised without the.op2extension by that name (a one-word 2, an eight-character upper-case name, a one-word -1).
Results and steps
A results table alternates a 146-word header (approach, table and element-type codes, subcase, mode or time, width) with the data it describes. Every distinct (subcase, analysis code, word 5 of the header: load set, mode number, time or frequency) of the tables below is a step, in the order the file first names it; grid point force and energy tables join a step the other tables made (their word 5 is 0 in a static subcase), and an energy table makes its own only when no table names its subcase and analysis. time_step picks one, read_metadata(...).time_values lists them and read_sequence walks them.
| Field data | Meaning |
|---|---|
meshio:time | the time (transient) or frequency, the eigenvalue of a mode (real modes: λ = ω²) or the post-buckling eigenvalue, 0 for a static subcase; as written, not converted |
nastran:subcase, nastran:analysis | the subcase and the approach's analysis code (1 statics, 2 modes, 5 frequency, 6 transient, 8 post-buckling...) |
nastran:mode | the mode number (analysis 2, 8 and 9), else 0 |
nastran:eigi | a complex mode's imaginary eigenvalue (analysis 9), with meshio:time its real part, both from the CLAMA table (since v16.12.0) |
Nodal results
Real SORT1 tables with 8 words per node (OUG*, BOUG*, OQG*, OQMG*, OPG*) become point data by their table code, with the nastran_h5 names: DISPLACEMENT, EIGENVECTOR, VELOCITY, ACCELERATION, SPC_FORCE, MPC_FORCE (the OQMG tables, which share the SPC forces' code) and APPLIED_LOAD, each (n, 3) with a _ROT twin; a thermal displacement table is TEMPERATURE (n,). A point with no row is NaN, and scalar-point rows are dropped. When two tables give the same point a value in the same step, the first one read keeps it. Vectors of a GRID with CD != 0 are rotated from that system to basic (since v16.10.0), except in the BOUG* tables, which Nastran already writes in basic.
Since v16.12.0:
- Complex tables (frequency response, complex modes; 14 words per node) give
<name>_realand<name>_imag(DISPLACEMENT_real,DISPLACEMENT_ROT_imag...), thenastran_h5names. Magnitude and phase (format 3) are converted, the phase in degrees. Both parts are rotated to basic. - Random tables (
OUGPSD,OUGRMS,OQGNO...: the table code's hundreds name the quantity) give<name>_PSD,_ATO,_RMS,_NOor_CRM. They are spectral quantities, not vectors, so they stay in the GRID's output system. - SORT2 tables (one GRID or element over every step, each row led by its time, frequency or mode) are pivoted: every row joins the step its first word names, so a SORT2 table and its SORT1 twin give the same arrays.
Element results
Stress (OES*) and strain (OSTR*) tables become cell data STRESS:<M> or STRAIN:<M>, the centre value per element (a beam's first station), NaN on cells without it:
| Element type (code) | Members |
|---|---|
CROD (1), CONROD (10) | A, MSA, T, MST |
CTUBE (3) | AS, MSA, TS, MST |
CSHEAR (4) | TMAX, TAVG, MS |
CBAR (34) | X1A...X4A, AX, MAXA, MINA, MST, X1B...X4B, MAXB, MINB, MSC |
CQUAD4 (33), CTRIA3 (74), and the centre of CQUAD8 (64), CTRIAR (70), CTRIA6 (75), CQUADR (82), corner CQUAD4 (144) | per fibre k = 1, 2: FD<k>, X<k>, Y<k>, TXY<k>, ANGLE<k>, MAJOR<k>, MINOR<k>, VON_MISES<k> (or MAX_SHEAR<k>) |
CTETRA (39), CHEXA (67), CPENTA (68), CPYRAM (255) | X, Y, Z, TXY, TYZ, TZX, PRINCIPAL_A, PRINCIPAL_B, PRINCIPAL_C (in the file's A, B, C order, not sorted), PRESSURE, VON_MISES (or OCT_SHEAR) |
CBEAM (2) | SD, XC, XD, XE, XF, MAX, MIN, MST, MSC |
CBAR stations (100) | SD, XC, XD, XE, XF, AX, MAX, MIN, MS |
composite CQUAD4 (95), CQUAD8 (96), CTRIA3 (97), CTRIA6 (98), NX CQUADR (232), CTRIAR (233) | per ply: X1, Y1, T1, L1, L2, ANGLE, MAJOR, MINOR, VON_MISES (or MAX_SHEAR) |
CELAS1...CELAS4 (11-14) | S |
CBUSH (102) | TX, TY, TZ, RX, RY, RZ |
NX CHEXA (300), CPENTA (301), CTETRA (302), CPYRAM (303) | corners only (no centre value): X, Y, Z, TXY, TYZ, TZX, VON_MISES as <M>@corner |
Since v16.12.0 the other element tables are read too:
- Complex stresses and strains (rods, springs,
CSHEAR,CBAR,CBEAM, centroidal and corner shells, solids, composites,CBUSH, and NX's von Mises variantsOESVM1/OSTRVM1) give<M>_realand<M>_imagfor each complex member; the real ones (fibre distances, von Mises of theVMtables) keep their names. - Random stresses and strains (
OESPSD,OESRMS...) have their own magnitude-only layouts: rodsA,T;CSHEARTMAX,TAVG;CBARX1A...AX...X4B;CBEAMstations; shellsFD<k>,X<k>,Y<k>,TXY<k>(andVON_MISES<k>); solidsX...TZX(andVON_MISES); composites per ply;CBUSH. The names carry the random suffix (STRESS:X1_RMS). No file with random element tables was available: these layouts follow pyNastran's reader and are not checked against one. - Element forces (
OEF*) becomeELEMENT_FORCE:<M>, real, complex (_real,_imag) or random (suffixed): rods,CTUBE,CONRODandCVISCAF,TRQ; springs and dampersF;CBARBM1A,BM2A,BM1B,BM2B,TS1,TS2,AF,TRQ;CBARstations (100)SD,BM1,BM2,TS1,TS2,AF,TRQper station;CSHEARF41...F14,KF1,S12...KF4,S41; shellsMX,MY,MXY,BMX,BMY,BMXY,TX,TY(centre, and@cornerfor corner output);CBUSHFX...MZ;CGAPFX,SFY,SFZ,U,V,W,SV,SW;CBEAMSD,BM1,BM2,TS1,TS2,AF,TTRQ,WTRQper station. The complexCSHEARlayout is the forces' real then imaginary parts, then the kick forces' and shear flows' (pyNastran pairs them otherwise). - Heat fluxes (an
OEFof a heat-transfer run) becomeHEAT_FLUX:<M>: line, shell and solid elementsXGRAD,YGRAD,ZGRAD,XFLUX,YFLUX,ZFLUX;CHBDYE/G/PFAPPLIED,FREECONV,FORCECONV,FRAD,FTOTAL;CONVFREECONV,FREECONVK.CHBDY*elements have no cell, so their fluxes are not kept. - Energies: strain energy (
ONRGY*) becomesENERGY:ENERGY,ENERGY:PCT,ENERGY:DEN, and kinetic energy (OEKE*)KINETIC_ENERGY:<M>likewise; complex energy hasENERGY_real/_imag. The total row of each set is no element and is dropped.
The members common to nastran_h5 (X1, TXY1, X, TZX, A, TMAX...) have its names, so an OP2 and an HDF5 file of the same run give the same arrays; the derived values the HDF5 schema does not store have their own names. Whether the last plate and solid column is von Mises or the maximum (octahedral) shear, and whether a table holds stress or strain, come from the header's stress code. Elements share results by id; CONM2 elements, which have none, are left out.
Since v16.10.0 the values beyond the centre are read too, with the arrays and nastran:layout:<name> field data of nastran_h5: <name>@corner for the corners of solids (39, 67, 68, 255) and corner-output shells (64, 70, 75, 82, 144), <name>@ply for the composite plies (column PLY - 1) and <name>@station for CBEAM's 11 stations and CBAR's station entries (stations not output stay NaN). The grid point force tables (OGPFB*) give GRID_FORCE:F1...M3 per element node and GRID_FORCE:<label>:<M> point data for the rows of no element (*TOTALS*, APP-LOAD, F-OF-SPC..., named without blanks and asterisks: GRID_FORCE:TOTALS:F1), rotated from each GRID's CD to basic. MSC writes a different header word 5 in OGPFB1 than in the other tables of the subcase, so grid point forces join the step the subcase's other tables made instead of starting their own.
Not read, and named in one warning: complex grid point forces, heat-transfer temperature rates and loads, nonlinear stress tables (OESNLX*), energy loss (OEDE), every other element type, and every other table (contact and bolt tables...).
Writing
import meshioplusplus
meshioplusplus.write("model.op2", mesh) # one step
meshioplusplus.write("model.bin", mesh, file_format="nastran_op2")
with meshioplusplus.nastran_op2.SeriesWriter("run.op2") as w: # one file, a block per step
for time, mesh in steps:
w.write(time, mesh)
meshioplusplus.write_sequence("run.op2", steps) # the samemeshioplusplus convert result.vtu model.op2The writer is the reader's inverse: an MSC-dialect file of 32-bit little-endian words and SORT1 tables, the one a Nastran post-processor reads with no flags. It writes this one dialect: 64-bit words, big-endian files and NX/Simcenter's interlaced table names and double-precision GRIDs are what Nastran builds and versions write for their own platforms, the reader accepts all of them and detects them from the content, and a mesh read from one is written back in the MSC dialect (whether NX/Simcenter post-processors accept that is a vendor check). The model is written the way the reader builds it:
- Points are the
GEOM1GRIDs, numbered from 1 in point order unlesspoint_data["nastran:nid"]gives their ids (positive and unique), in the basic system (CP = CD = 0); a 2-D point set getsz = 0. The elements, the nodal tables and a solid's corner GRIDs use those ids;GEOM1holds the GRIDs by id, as Nastran writes it, so a file read back has its points in id order. - Cells are the
GEOM2element cards, one record per card in card-name order and each card's elements by id:triangleisCTRIA3,quadCQUAD4(orCSHEARfor a cell whosenastran:cardis 31, so one quad block can hold both),tetraCTETRA,pyramidCPYRAM,wedgeCPENTA,hexahedronCHEXA,triangle6CTRIA6,quad8CQUAD8,quad9CQUAD, the quadratic solids with their mid-side nodes in Nastran's order (the inverse of the permutation the reader applies tohexahedron20andwedge15), avertexCONM2and alineCROD,CTUBE(when its property id belongs to a region namedPTUBE_<pid>) orCONROD(whennastran:pidis zero or negative). Withnastran:card(a mesh read from an OP2 has it) a line or vertex takes the card that number names:CROD,CONROD,CTUBE,CBAR,CONM2,CELAS1,CDAMP1,CELAS2,CDAMP2,CVISCPLOTEL(a plot element has two nodes and no property, so itsnastran:pidis not written),CMASS1,CMASS2or, on a vertex,CONM1, and a spring, damper or mass vertex (CELAS1,CDAMP1,CELAS2,CDAMP2,CMASS1,CMASS2) is grounded at its second end. ACMASS2takes its mass fromnastran:m, and aCONM1its mass matrix fromnastran:conm1((n, 21)rows in the record's order, in the basic system); both are required. ACELAS2takes its stiffness fromnastran:k, its damping coefficient fromnastran:geand its stress coefficient fromnastran:s(zero when absent), and aCDAMP2its damping fromnastran:b, which the write needs. ACQUADRis written as aCQUAD4and aCTRIARas aCTRIA3, which have the same nodes. Element ids arenastran:eid, else 1, 2, ... over the written blocks; they must be positive and unique. A cell block of another type (a polygon...) is not written, with a warning naming it. - Properties.
nastran:pidgives each cell's property id, else 1 for shells, 2 for solids and 3 for rods. OneEPTcard is written per id, by the cell family it belongs to:PSHELL,PSOLID,PROD,PTUBE,PBAR(for aCBAR),PELAS(for aCELAS1),PDAMP(for aCDAMP1),PSHEAR(for aCSHEAR, fromnastran:PSHEAR_<id>:MID,T,NSM,F1andF2: material 1, thickness 1 and zeros when absent) orPVISC(for aCVISC, fromnastran:PVISC_<id>:CEandCR, 1 and 0 when absent),PMASS(for aCMASS1, fromnastran:PMASS_<id>:M, 1 when absent); a shell whose id hasnastran:PCOMP_<id>:PLYis aPCOMPinstead of aPSHELL, and one withnastran:PCOMPG_<id>:PLY((n, 5)rows of GPLYID, MID, T, THETA and SOUT, with thePCOMPscalars andLAMOPT) aPCOMPG; aCBARwhose id hasnastran:PBARL_<id>:TYPEis on aPBARL(TYPEandGROUPas text,MID,DIM,NSM) and aCBEAMwithnastran:PBEAML_<id>:TYPEon aPBEAML(the same withDIMas(stations, n)rows andSO,XXB,NSMper station; the dimensions must match the section type's count, 22 types), in place of thePBARandPBEAM, and the materials of its plies areMAT1cards, so that the reader names the regionsPSHELL_<pid>and so on. A property id shared by two families is aWriteError. The shell, solid and rod cards are placeholders: a thickness, an area or an outer diameter of 1, and material 1, which one unitMAT1(E = 1,nu = 0.3) defines, so the file loads and cross-references; they are not the model's, and only the id and the family carry over unlessfield_datagives the values (see below). ACBEAMgoes out with thePBEAMof its id, from thenastran:PBEAM_<id>:*field data; aPBEAMwith no field data gets one unit segment of constant section. Its stations go in the slots the file uses: the first in slot 0, the last in slot 10, and any between them in the slots after the first. ACGAP(nastran:card19) goes out with thePGAPof its id, from thenastran:PGAP_<id>:*field data; its orientation isnastran:xornastran:g0, as for aCBUSH, and its coordinate system (CID) is written as 0 and not read. ACBUSHgoes out with thePBUSHof its id, from thenastran:PBUSH_<id>:*field data (K of 1, B of 0, GE of 0 and stress and strain coefficients of 1 by default), as the 23-word MSC record. Its orientation isnastran:xornastran:g0, as for aCBAR; a bush oriented by its element system (flag -1) is read with a zero X vector. Cards the writer holds no cell type for (the scalar-point elementsCELAS3/4,CDAMP3/4andCMASS3/4,CBUSH1DandCBEND, the axisymmetric elements, and aCSHEARnumber on a cell that is not a quad) are left out of the file, with one warning naming them, so a mesh read from an OP2 writes the cards above and nothing else of its model. ACBARneeds its orientation (nastran:x, ornastran:g0for a G0), aCGAPthe same, aCBEAMitsnastran:x, ornastran:g0for a G0 (flag 2, the grid at word 6; the reader gives a beam oriented by a local system a zero X vector, and pyNastran takes a beam's flag from word 10 and so reads a G0 only whenPAis 2), aCELAS2itsnastran:kand aCDAMP2itsnastran:b, else the write is refused. APBEAMneeds one to ten segments (NSEG). ACBUSHneeds its orientation (nastran:xornastran:g0). - Coordinate systems. A GRID with a non-zero
nastran:cpis written in its system's local coordinates. Each system a GRID names is written as itsCORD2R(type 1),CORD2C(type 2) orCORD2S(type 3) record, from thenastran:CORD2_<id>:TYPE,A,X,YandZfield data, with the pointsBon +z andCin the xz plane. A GRID whose system has no such field data is refused, and a system the file reads as CORD1 is written back as the equivalent CORD2 record. A GRID with a non-zeronastran:cdis written with thatCD(its system also needs thenastran:CORD2_<id>:*field data), and the nodal vectors (displacements and the other point tables, real and complex, with their rotations) and the grid point forces at it are rotated from basic into that system, point by point for cylindrical and spherical ones, which the reader rotates back.
The results are the arrays the reader returns, under the same names:
- Point data:
DISPLACEMENTandEIGENVECTOR(OUGV1, table codes 1 and 7),VELOCITYandACCELERATION(10, 11),TEMPERATURE(OUGV1, a thermal table),SPC_FORCE(OQG1),MPC_FORCE(OQMG1) andAPPLIED_LOAD(OPG1), each(n, 3)with a_ROTtwin for the rotations (zeros when absent) andTEMPERATURE(n,). A point whose components are all NaN is left out of the table; the reader gives it NaN back. - Cell data are the arrays the reader returns for the element tables, under the same names, written through the reader's own layout table inverted: for a card, the first of its element types whose arrays the mesh holds is written, with the layout (and word count) that holds the most of them.
STRESS:<M>(OES1X1) andSTRAIN:<M>(OSTR1X) are written for the rodsCROD,CONROD(A,MSA,T,MST) andCTUBE(AS,MSA,TS,MST), the linear shellsCQUAD4andCTRIA3(FD1...VON_MISES2, orMAX_SHEAR1andMAX_SHEAR2when the mesh has those and no von Mises), the solidsCTETRA,CHEXA,CPENTAandCPYRAM(X...TZX,PRINCIPAL_A...C,PRESSURE,VON_MISESorOCT_SHEAR; quadratic solids write the same table, for their corner nodes), the quadratic shellsCQUAD8andCTRIA6, the barsCBARandCBEAM, the bushesCBUSH, the shear panelsCSHEAR, and the springs, dampers and viscous dampersCELAS1,CELAS2,CDAMP1,CDAMP2andCVISC, whichever layout the reader decodes for that type.ELEMENT_FORCE:<M>(OEF1X, table code 4) is written for the same cards (AFandTRQfor a rod,MX...TYfor a plate,FX...MZfor a bush, the station forcesSD,BM1... of a bar or beam...).HEAT_FLUX:<M>(HOEF1, a heat-transfer table: header word 23 is 1) is written for the rods, shells and solids (XGRAD...ZFLUX), andENERGY:ENERGY,PCT,DEN(ONRGY1, table code 18, whose header word 3 holds the record's total energy as a single-precision float) andKINETIC_ENERGY:<M>(OEKE1, 36) for any element. The values beyond an element's centre are written from the location arrays:<M>@corner(a solid's corner nodes and the corner output ofCQUAD4type 144,CQUAD8,CTRIA6; the centre value is repeated for a corner the mesh leaves NaN, so a read of the file gains the@cornerarrays),<M>@ply(the plies of a composite shell,PCOMP) and<M>@station(the stations of aCBARandCBEAM); an element type whose location arrays are what make it that type (the composite shells, the bar with stations) is written only when the mesh has them. A member the mesh lacks, and a NaN inside a written row, are written as 0; a cell with every member NaN is left out. - Property and material values.
field_data["nastran:<CARD>_<id>:<MEMBER>"](one value each, float64, theMIDmembers int64) sets the values the writer put in itsPSHELL(MID1,T,NSM),PROD(MID,A,J,C,NSM),PTUBE(MID,OD,T,NSM),PSOLID(MID),PBAR(MID,A,I1,I2,J,NSM,C1,C2,D1,D2,E1,E2,F1,F2,K1,K2,I12),PELAS(K,GE,S),PDAMP(B),PVISC(CE,CR),PGAP(U0,F0,KA,KB,KT,MU1,MU2,TMAX,MAR,TRMIN; defaults 0, 0, 1e8, 1e-6, 0, 0, 0, 0, 100 and 0.001),PSHEAR(MID,T,NSM,F1,F2),PCOMP(Z0,NSM,SB,FT,TREF,GE,SYM, and the plies asPLY),PMASS(M),MAT1(E,G,NU,RHO,A,TREF,GE),MAT2(G11,G12,G13,G22,G23,G33,RHO,A1,A2,A3,TREF,GE,ST,SC,SS, and the integerMCSID)MAT4(K,CP,RHO,H,MU,HGEN,REFENTH,TCH,TDELTA,QLAT),MAT5(K1...K6,CP,RHO,HGEN),MAT8(E1,E2,NU12,G12,G1Z,G2Z,RHO,A1,A2,TREF,XT,XC,YT,YC,S,GE,F12,STRN) andMAT9(G11,G12...G66,RHO,A1...A6,TREF,GE) records, and the reader returns them under the same names (the first record of an id wins). A property with no value keeps the old placeholder (material 1, thickness, area and outer diameter 1, a spring's stiffness and a damper's coefficient 1); oneMAT1is written for every material a property names and everynastran:MAT1_<id>:*given, unit modulus and Poisson's ratio 0.3 unless set (since v17.4.0). A material id withnastran:MAT2_<id>:*ornastran:MAT8_<id>:*field data is written as that card instead of aMAT1, so a ply of aPCOMPcan be aMAT8and aPSHELLmaterial aMAT2; the members left out are zero, except aMAT2'sG11,G22andG33(1) and aMAT8'sE1,E2(1) andNU12(0.3). An id given as two of the cards is aWriteError. - Complex nodal tables.
<name>_realand<name>_imag(and<name>_ROT_real,<name>_ROT_imag, all(points, 3)) ofDISPLACEMENT,EIGENVECTOR,VELOCITY,ACCELERATION,SPC_FORCE,MPC_FORCEandAPPLIED_LOADare written as a complex table beside the real one: table code 1000 plus the real code, format code 2 (real and imaginary parts), 14 words a node. Setnastran:analysisto 5 (frequency) or 9 (complex modes) as the solution would. pyNastran reads them (since v17.4.0). - Complex element tables.
<M>_realand<M>_imagcell data (and@corner,@ply,@stationtwins) of any of the groups above are written as a complex table: table code 1000 plus the real one, format code 2, 14 words a node or the reader's complex layout of the element type; members that have no imaginary part (the fibre distances) are shared with the real table. Setnastran:analysisto 5 (frequency) or 9 (complex modes). pyNastran reads them where it supports the layout. - Random tables.
<M>_RMS,<M>_NO,<M>_PSD,<M>_CRMand<M>_ATOcell data of the stresses, strains and forces are written as a table of their own (OESRMS1,OESNO1,OESPSD1,OESCRM1,OESATO1, and theOSTRandOEFones) with MSC's table codes (random bit 4 in the sort code, 100 times 5 for CRM, 6 PSD, 7 ATO, 8 RMS, 9 NO added to the quantity's code:4805is an RMS stress). The tables are SORT1 and hold magnitudes only, as the reader reads them. MSC writes PSD, CRM and ATO as SORT2 (one record per element, a row per frequency): pyNastran reads RMS and NO SORT1 tables and not the SORT1 PSD, CRM and ATO ones, so those are read by meshio++ (both engines) and by readers that accept either sort; the writer does not pivot to SORT2, which holds the same numbers. - Grid point forces.
GRID_FORCE:F1...M3cell data ((cells, nodes)per block, a column per node of the connectivity, NaN where a node has no force) is written as oneOGPFB1row per node of each element (table code 19, 10 words: the GRID, the element, its card name and the six components), and eachGRID_FORCE:<label>:<M>point array (TOTALS,APP-LOAD,F-OF-SPC...) as a row of element 0 with the label as its text (*TOTALS*forTOTALS). The forces are in basic coordinates (every GRID hasCD0). A reader attaches grid point forces to a step another table of the subcase made, so write them beside a nodal or element table. - Not written, and named in one warning: every other array (SORT2 tables, complex grid point forces, the results of elements the writer holds no card for), field data other than
nastran:*andmeshio:time, regions, andnastran:cpandnastran:cd.
A step is nastran:subcase (default 1), nastran:analysis (default 1, or 6 when meshio:time is not 0) and nastran:mode (default 1) of the field data, and meshio:time (0 when absent). The reader's own rules are inverted: a statics, buckling or other subcase is told apart by its subcase alone, a mode (analyses 2, 8 and 9) by its mode number with the eigenvalue as its time, and a time or frequency step (5, 6, 10 and 12) by its time, stored in single precision. A series is the model once and a block per step in each result table, so the file is assembled when the writer closes. Every step must have the first step's points count, cell blocks and result arrays, and a subcase, analysis and mode or time of its own; a step that does not is a WriteError naming it, and {step} in the path writes one file per step instead. A step's time is the argument of write (or the pair's first item in write_sequence), not its meshio:time. One step is write's bytes.
Values are stored as float32 and ids as int32, so a read of a written file returns the values rounded to single precision. The bytes depend on the mesh only (the header's date is fixed): the C++ write_nastran_op2 and NastranOp2SeriesWriter and their Python twins write the same bytes, which the test suite compares, and a hand-framed oracle spells out. A repeated element id, a property id shared by shells, solids and rods, an array of the wrong size, a subcase or analysis that is not a positive code and a step that breaks the series rules are a WriteError.
The files were read back by pyNastran 1.3.4 (read_op2, and read_op2_geom with cross-referencing): the displacements and rotations of every step, the plate, solid and rod stress and strain, and the nodes, elements, properties and material, for the one-step, mode, transient and static files and for a mesh of every cell type. They were not opened in Femap or Patran.
Verification
The test suite reads eleven files from the pyNastran test models (BSD 3-Clause, credited in tests/python/meshes/nastran_op2/README.md): MSC and NX output, 32- and 64-bit, SOL 101 statics (among them static_solid_shell_bar, a solid, shell and bar model), modes, buckling, transient heat transfer, an NX 2412 file, and one with its geometry tables removed next to its deck. On every step of every file the nodal vectors, the centre stress and strain, and the corner, ply, station and grid point force values equal pyNastran 1.4.1's reading to float precision, and the two engines agree exactly. The nodal vectors and centre values also match on 40 further pyNastran models checked outside the suite (before the corner, ply, station and grid point force values were read). No file from an MSC or NX version newer than those samples, and no big-endian file Nastran wrote, has been read.
Since v16.12.0 three more pyNastran models are in the suite: freq_elements2 (frequency response, magnitude and phase, every element family's complex forces, stresses and strains, NX's von Mises variants), modes_complex_elements (complex modes) and test_vba (random: PSD, RMS and NO displacements, accelerations and SPC forces, and acoustic GRIDs). Their complex, random and SORT2 nodal values, and every step's element forces, heat fluxes and strain energies across the suite, equal pyNastran's to float precision (pyNastran keeps complex values in single precision). Left out of that comparison where pyNastran misreads: complex CSHEAR forces, CBAR station forces (it files them with CBAR 34's), complex CTRIA3 from OESVM1 and complex composite von Mises. Derived files check the rest: time_thermal_elements_sort2_only.op2 (the SORT2 tables alone read as their SORT1 twins), static_elements_bgpdt.op2 and sol401_tstep1_bgpdt.op2 (GEOM1 removed: the BGPDTS/EQEXINS and NX BGPDT points equal the GRIDs), and a header-less copy of static_solid_shell_bar.op2 for PARAM,POST,-2. Five more (cbush, cbush_2021, sdbush01, cc188b, rms_tri_oesrmx1) check CBUSH forces, stresses and strains (real and complex), CGAP forces, random element tables and complex strain energies the same way (pyNastran labels its four complex energy columns with three names: the reference names them itself); GRIDs whose output system is not basic are left out of the comparison, since pyNastran keeps their results in that system. No sample has complex SORT2 nodal tables or NX solids 300-303.
The coordinate systems are checked on static_solid_shell_bar_cord.op2, derived from static_solid_shell_bar.op2 by pointing every GRID's CP and CD at the file's own CORD2R, CORD2C and CORD2S systems (tools/gen_nastran_cord_reference.py): points match pyNastran's to 1e-12 and displacements to the file's single precision. They are compared with upstream pyNastran's rotation matrices, because 1.4.1 composes cylindrical and spherical ones the wrong way round.