Importing and exporting¶
Visual Dynamics can write what it reads as test data, so the data can
go on to whoever needs it next. The formats marked read-only below are
the exceptions: solver results (a Nastran punch file, a Femap neutral
file) and controller runs (a Rattlesnake .nc4) are produced by other
programs, and a file written here would claim to come from a program
that never made it.
The native format is .vdyn (HDF5), and it is the only one that keeps
everything a project holds, units included. Saving and opening a
project use it. While Visual Dynamics is an alpha its format may still
change (the .vdyn page says what that means for a
file you have). It is standard HDF5 with a documented layout, given on
that same page, so any HDF5 tool reads one without this package. The
other formats are for exchanging data with other tools, and each one
loses whatever it has no way to record.
One row per format; R/W means both read and written:
| format | ext | Geometry | ShapeSet | function data | ChannelTable |
|---|---|---|---|---|---|
| Universal file ⁴ | .unv (read also as .uff and .uf) |
R/W | R/W | R/W | — |
| I-DEAS ADF ² | .afu/.ati/.ash |
— | R/W | R/W | — |
| Exodus ¹ | .exo (read also as .e, .exo2, .g and .gen) |
R/W | R/W | R/W | — |
| Nastran bulk ³ | .bdf/.dat/.nas (written as .bdf) |
R/W | — | — | — |
| Nastran punch ³ | .pch |
— | R | — | — |
| Femap Neutral ³ | .neu |
R | R | — | — |
| sdynpy | .npz (geometry, data), .npy (shapes) |
R/W | R/W | R/W | — |
| Rattlesnake run (below) | .nc4 (read also as .nc) |
— | — | R | R |
| Rattlesnake random specification | .npz |
— | — | R/W | — |
| Rattlesnake sine specification | .npz |
— | — | R | — |
| Excel | .xlsx (read also as .xlsm) |
— | — | — | R/W |
| 3MF CAD mesh ⁵ | .3mf |
R/W | — | — | — |
| STL mesh ⁵ | .stl |
R/W | — | — | — |
| STEP / IGES ⁶ | .step/.stp/.iges/.igs |
R | — | — | — |
| MATLAB ⁷ | .mat |
R/W | R/W | R/W | R/W |
| Engineering Sciences Common Data Format ⁸ | .h5 |
R/W | R/W | R/W | R/W |
| Delimited text ⁹ | .csv (read also as .tsv, .txt, .tab, .asc and .prn) |
— | — | R/W (time histories) | — |
Three more go in and out whole rather than as one object. The first is
the project file itself: .vdyn, with everything a project holds;
.mat, the same layout as MATLAB structs (note ⁷); or .h5, the
standard's types with each object whole beside them (note ⁸). The
second is a report template (.vdreport), a report on its own to load
into another project (see reports). The third is
photographs, a folder of images (.png/.jpg/.heic), through the
project's Photos.
"Function data" uses the dataset 58 vocabulary throughout: time
histories, FRFs, PSDs, spectra, coherence, multiple coherence and SRS.
Not every format carries every kind. Exodus takes time histories and
spectra on a mesh, and ADF splits functions and time data between
.afu and .ati.
A channel table is the one object that is a spreadsheet, so it gets a spreadsheet format: every column in order, channel numbers as integers, the rest as text. Its units are metadata, not values, so nothing is converted.
Notes on the formats¶
¹ Mode shapes go to exodus on a geometry. The file is a mesh with
results, one time step per mode with the frequency as the step's time,
which is what ParaView animates (its reader assembles DispX/Y/Z into a
Disp vector for Warp By Vector). Shapes without a geometry are
refused, because a modal exodus file with no mesh is a file nothing
can draw. In the app, Save As… puts the shapes on the geometry linked
to them, otherwise on the active geometry (set with Set as Active
Geometry), otherwise on the project's only geometry. The status line
names the geometry used, and a geometry holding none of the shape DOFs
is refused. From a script, pass geometry= to export_file or
exodus.save.
Damping and modal mass go out as two global variables, Damping and
ModalMass, one value per step. A complex shape goes out as its real
part under the plain name, so ParaView still assembles its Disp
vector, with the imaginary part beside it as DispX_IM and so on, and
the reader puts the two back together. DOFs at nodes the mesh does not
have are dropped.
The geometry's coordinate systems go out as the file's coordinate
frames (an id, three points and a rectangular, cylindrical or
spherical tag) and come back as systems, with their definitions but no
names. Nothing in exodus lets a node or a variable refer to a frame,
so shape values at a node with a local displacement system are rotated
to global on the way out. Which node is placed or measured in which
system is written as named node sets (def_cs_7, disp_cs_7), a
standard feature that other exodus tools show as ordinary node sets
and that this reader restores the assignments from. A modal model
makes the round trip through Visual Dynamics without loss; another
tool reads the mesh, the modes and the frames, and sees the node sets
as node sets.
Time histories and spectra go on a geometry the same way. The abscissa
becomes the file's step axis, and each (quantity, direction) becomes a
nodal variable named so that it reads back as itself (AccX,
Pressure). A complex spectrum is written as _RE/_IM pairs, and a
record with no DOF becomes a global variable named by its block label
or its comment. A (node, variable) with no record is NaN, never zero,
because a missing reading is not a reading of zero. Values in a node's
local displacement frame are rotated to the global frame, as shapes
are. A stacked object (averages) is refused with the collision named,
since one variable per node cannot hold twenty readings of one
channel; select records first.
To read such a file back you say what its steps are, with
load(steps='time') or load(steps='frequency'), or in the app's
Reading the results question, because the file cannot say what its
own step axis means. The default reading is one mode per step. Exodus
records no units in either direction: values go out in the display
unit system and come back raw, with the variable's stem kept as a
quantity hint. A mode whose step time is negative (an eigensolver's
rigid-body mode, whose eigenvalue comes out slightly either side of
zero) is read as 0 Hz, the file's own number is kept in the mode's
description, and the import says so.
² ADF is the I-DEAS Associated Data File format, which Siemens NX also reads. Files written here were checked by reading them back with the reference tooling, value for value, including complex shapes and complex modal mass and damping. Two properties belong to the format and no writer can change them. ADF stores SI whatever the display system (the one exception to the display-units rule below; the format defines it as an SI-writing dataset 164 in binary), and ADF stores float32 only, so double-precision data is narrowed on the way out. A shape file fills each node out to whole X/Y/Z (or 6-DOF) vectors, with zeros at directions the set does not carry, and the importer's zero-column rule drops those again on the way back in, so the round trip is stable. The layout the readers and writers follow was derived from sample files and is held to them by tests.
The same three extensions import like any other file. Since an ADF is SI by definition, its data arrives with units declared, not hinted. The exception is a record typed Unknown or General: it was never converted to SI, its values are raw, and its ordinate units label (free text, and often wrong in real files) arrives as a hint only, naming the axis and narrowing the shortlist in the Imported Units pane. You declare the units yourself, because nothing is scaled on a guess.
³ These formats carry a finite element model and its modes, so that a test can be set beside a model:
- Nastran bulk data is read and written as a mesh. The reader
takes grids (all three field formats, bare-exponent floats
included), CORD2R/C/S coordinate systems with chained references
resolved, elements, concentrated masses (CONM2), and PLOTELs as
drawn lines. PSOLID, PSHELL and MAT1 cards become the element
groups' materials and thicknesses. A deck has no units, so its
values are kept as written until its units are declared, in the
geometry's Units table or with
length_unit=andmass_unit=on import. A deck's units are a length and a mass, with seconds, and force, stress and density follow from them: millimeters with tonnes (newtons and MPa), inches with lbf·s²/in (pounds and psi), meters with kilograms. The mass defaults to that usual one for the length and can be stated otherwise. Declared, the coordinates and every property value (thickness, modulus, density, point masses, springs) are converted to SI together. A material that comes out with a modulus or density no structural material has is reported, since that is what a wrong unit system looks like. CELAS1 and CELAS2 springs are read with their stiffness (a CELAS1's from its PELAS), one element group per stiffness and direction, and SPC and SPC1 cards become ground points held in their components. A deck with more than one SPC set, an SPC that enforces a displacement, a spring joining two different components, a CORD1 system, or a connection card the reader does not know is refused by name. Rigid elements (RBE2, RBE3, RBAR, RROD), MPCs, loads and control cards are skipped. The written deck is for interchange, not a runnable model: grids go out as large-field GRID* cards at full precision, elements carry PID 1 with no property or material cards (Nastran would need PSHELL/PSOLID cards added before it runs), springs go out as CELAS2 with their stiffness, point masses as CONM2, ground as SPC1 and drawn lines as PLOTEL chains, and the header line says Visual Dynamics wrote it. Coordinates are written in SI, as the geometry stores them, whatever the display system. - A Nastran punch file brings the modes: SOL 103 eigenvectors read into a ShapeSet, with the frequencies from the eigenvalues. Complex output is refused by name.
- A Femap neutral file reads both halves from one file, the geometry (blocks 403 and 404) and normal-modes output (450 with 451 or 1051), against layouts taken from real files. A version whose record layout differs is refused, naming the version, rather than misread. Rigid elements and nodes defined in local coordinate systems are refused by name; static and stress vectors are skipped.
Modes imported from a punch or a neutral file arrive unscaled. Neither
format records its normalization, so shapes and MACs are exact, and
nothing downstream reads a physical scale out of modal masses that
were never in the file. The docstrings of io/nastran.py,
io/punch.py and io/femap.py list every rule each reader follows.
OP2 is planned and waits on a real file to test against. Abaqus ODB is
not read: it is a proprietary binary whose only practical reader is
Abaqus's own API. The BK Connect and Vibrata formats are not read
either, since they are proprietary, with no public specification and
no sample file to work from. Any of the three would need a real file
and a real need.
⁴ The universal file has two encodings, and both are read. An ordinary
dataset 58 writes its values as 13.5E text; dataset 58b keeps the
same eleven ASCII header records and then writes the values as raw
IEEE 754 floats. The binary form is roughly a third smaller and, unlike
the text form, exact: five decimal places do not survive a round trip,
and raw doubles do. LMS Test.Lab and I-DEAS both write it.
Reading takes either form, in either byte order, at either precision,
and tells the two apart by the b on the dataset's own marker line.
Only IEEE 754 floats are decoded. Dataset 58b can also declare DEC VMS
or IBM 5/370 floating point, which are different representations, not
different byte orders, and reading one as IEEE would return plausible
wrong numbers instead of failing, so the reader names the format it
found and stops. The specification is mirrored at
docs/uff_spec/58b.asc.
Writing uses the text form by default, because every reader takes it.
The binary form, written as little-endian doubles, is
visualdynamics.io.unv.save(obj, path, binary=True), or the
unv_binary exporter (export_file(obj, path, format='unv_binary')),
which is also its own entry in the app's Save As… dialog. Only the
functions change form; geometry, mode shapes and the units dataset have
no binary variant.
⁵ 3MF and STL are for CAD that is already tessellated: export either
one from SolidWorks or any CAD tool. 3MF keeps each element group as a
named part and declares its unit (it is written in meters). STL has no
units and, in its usual binary form, no part names, so a binary STL
arrives as one element group named for the file; the rare ASCII form
gives an element group per solid. Both hold faces only, and a
geometry without face elements is refused. On mesh density: this
geometry is context drawn behind the sensor points, so coarse export
settings in the CAD program are enough, and a fine tessellation costs
drawing time without adding anything.
⁶ STEP and IGES are B-rep CAD, tessellated on import by the bundled OpenCASCADE kernel (LGPL-2.1 with exception, one more replaceable library; see NOTICE.md). Named parts become named element groups, and coordinates are converted to meters from whatever the file declares. These formats are import only: Visual Dynamics holds meshes, not surfaces, so a B-rep written from one would be made up.
⁷ MATLAB .mat is the project file in MATLAB's container, not a
foreign format. A .mat written here holds what a .vdyn holds, under
the same names (the .vdyn layout), so every kind of
object, the project itself included, goes out and comes back.
load('modal.mat') gives objects, a cell array of structs, each with
its name and kind, alongside the project's test_name,
project_type, links and provenance. A single object saved alone
is one struct named for its kind (data, geometry, shapes, …). The
changes MATLAB needs are mechanical: ragged connectivity is a cell
array of row vectors, one-dimensional datasets are column vectors,
strings are char arrays and lists of them cellstr, and '' still means
undeclared.
Values are in the display units, and every unit field names the unit
its values are in. With in-slinch-lbf-s (g) chosen, an acceleration is
in g with an ordinate_unit of 'g', an FRF in in/s² per lbf with
'in/s**2' and 'lbf', and a geometry's coordinates in inches with a
length_unit of 'in'. A unit_system variable names the system,
and reading the file back takes every value to SI from its own unit,
so a record comes back declared in the unit it was written in.
Material and section properties stay in SI, as the project file holds
them, and an abscissa is in seconds or hertz in every system. Saving
the project as .mat (Save Project As…, or project.save in a
script), or exporting from a script with no unit_system, writes the
project file exactly: SI values, each record beside the unit it was
declared in. A file with no unit_system variable is read as SI.
The reader takes back the files it writes, and one other thing: a
single struct built by hand to the documented layout of one object,
without the schema stamp. Anything else in a .mat is refused by name
rather than guessed at. Only version 5 files are read and written
(MATLAB's -v7), which hold at most 4 GB per variable. A larger record
is refused before anything is written, and the .vdyn, which MATLAB's
h5read opens directly, is the file to hand over instead.
⁸ The Engineering Sciences Common Data Format (ESCDF) is a public
standard built on HDF5. A geometry, a channel table, curves and modes
are each a type defined by a plain-text specification file, and
activities hold results and link the metadata that defines them. A
project written here is one file. Every geometry and channel table is
a metadata group, and every curve set and shape set is a result in the
activity of its object group (a named group is an activity of that
name). Each object is also stored whole, in its own .vdyn layout, as
an attachment of its group, so the file reads back here exactly, and
any other reader of the format gets the standard fields with nothing
of ours in the way. A report is attached twice, rendered as a complete
HTML page and as its definition, so it reopens editable here, and
photographs are attached as the images they are.
A file another program wrote is read by its standard fields: nodes,
lines, elements and per-node axes (a node with its own axes gets a
coordinate system), records with their units, frequencies and shapes,
and activities as named object groups. A type the specifications do
not define is left out with a note. A data object here shares one
abscissa across its records, so a dataset whose rows each carry their
own and do not agree is read as one object per abscissa, named Run,
Run (2) and so on, with a note. A shorter row padded out with
non-finite values is read to its own length. The reader and writer were checked
against the format's reference implementation in both directions, and
the specification files that define the types ship inside the package.
A file is written as .h5, the container's suffix, which is how the
format's files are named where it is used; a file named .hdf5 or
.escdf keeps its name, and reading looks at the contents, never the
suffix.
⁹ Delimited text is the column export of controllers, recorders and
spreadsheets: one column per channel, with or without a time column,
a header and a preamble. The reader finds the delimiter, the decimal
point, the header, the units, the time column and a stated sample
rate, takes any of them as stated instead, and refuses with the line
and column what it cannot read; Importing a time history from
delimited text gives the rules. A time history
is written back as a .csv with a header of names and units that the
reader takes back in, in the display system itself, g included.
SEP 005, the sdypy project's standard for time series, is exchanged in
the API rather than as a file, since it is an in-memory dict format.
visualdynamics.from_sep005(...) reads the dicts a sdypy package hands
over, TimeHistory.to_sep005(...) produces them, and the exported form
passes sdypy's own compliance validator.
export_file(obj, path) picks the exporter by suffix, and format=
names one outright. Where two exporters share a suffix the first one
listed is taken: .unv gives the text form, and .npz gives sdynpy
data, so a specification written for Rattlesnake needs
format='rattlesnake_specification'. In the app, Save As… on an
object offers .vdyn first and then only the formats that object can
be written as, so a mode shape is never offered a format that cannot
hold one. Exodus is offered for shapes and data, and the save is
refused in the status line when the project has no geometry for them
to go on (note ¹).
Units¶
Exports are written in the display unit system. What is on screen is
what goes in the file: with in-slinch-lbf-s selected, a model one inch
across is written as 1.0, not as 0.0254. A display system that
shows acceleration in g (such as in-slinch-lbf-s (g)) writes the
coherent system it is built on, in/s² in that case, because MATLAB's
.mat is the only one of these formats that labels a value as g; the
status line names the system written. The exceptions are ADF, whose
definition requires SI on disk (every ADF reader converts on its own
side, so the numbers still arrive right); Nastran bulk data, written in
SI; 3MF, written in meters and declared as meters; ESCDF, where each
value is written in the unit it was declared in and labeled with that
unit (a geometry in inches as inches, a record in g as g); and MATLAB,
written in the display units including g, with every unit field
labeled (note ⁷).
UNV says which units it used, in a dataset 164 declaring the system and its conversion factors, so the file comes back to exactly the same SI values whatever system it was written in. 3MF, MATLAB and ESCDF also carry their units. sdynpy, exodus, Nastran, STL and the Rattlesnake specification record none, so they carry the numbers you were looking at, and the status line after the save names the units, since it is the only place they are stated.
An object with no units declared is written exactly as it stands. There is no factor to convert it by, and multiplying by one would claim it was SI. No dataset 164 is written, and it re-imports unit-less, which leaves the next reader in the same position.
A dataset 58 can name a quantity without giving its size. A record
that knows it holds accelerations, because the file it came from said
so (kept in dimension_hint), goes out as data type 12 with no 164 to
scale it, and reads back knowing the same thing. Only a record that
knows nothing is written as data type 0.
In a script, export_file without a unit_system writes the stored
values, which are SI once units are declared.
What each format drops¶
sdynpy keeps geometry, including coordinate systems, drawn lines
(its tracelines) and elements; data, including reference DOFs; and
shapes, including frequency, damping, modal mass and the DOF set. Where
Visual Dynamics has both a comment (from the file an object came
from) and a description (typed in Visual Dynamics), the format has
one field, and the description is written.
Mode shapes go to UNV as one dataset 55 per mode, the standard
nodal mode-shape record. Real modes are written as normal modes
(analysis type 2), with the frequency, modal mass and damping ratio
and the vector at each node. Complex modes are written as complex
eigenvalues (analysis type 3), with the eigenvalue, modal A and modal
B, which is where curve fitters put them and where readers look for
them. Both types are read back. A complex set's modal A is what
ShapeSet.synthesize_frf scales it by. Exodus stores modes as it
stores any nodal result: one time step per mode, with the mode's
frequency as the step's time, and nodal variables DispX/DispY/DispZ
(plus RotX/RotY/RotZ when the shape carries rotations). Damping
and modal mass are the file's global variables Damping and
ModalMass, one value per step, so a modal file comes back with
everything it went out with.
Dataset 55 stores a whole vector per node, so a shape covering only some directions is padded with zeros and comes back covering all of them.
UNV keeps local frames; exodus keeps them beside the values¶
The two formats can say different things, so they are treated differently.
Dataset 55 takes a node's values in
that node's own displacement system, so the values are written as they
stand, and a dataset 2420 is written alongside defining every
coordinate system: label, type, name and its 4x3 transform. The node
references (node_def_cs, node_disp_cs) come from dataset 2411. A
file exported this way makes the round trip with its frames intact.
Caveat: the published specification for dataset 2420 could not be
found. The SDRL index lists it, but its file is not on the site,
although the datasets mirrored under docs/uff_spec/ are. The layout
used here makes the round trip through Visual Dynamics but has not been
checked against the standard or against a file from another tool. If
you have the specification, add it to docs/uff_spec/ so it can be
checked.
Exodus results are written in the global frame¶
Exodus can hold coordinate frames, and the geometry's systems go out
as them, but nothing in the format lets a node or a variable refer to
one. A shape value left in a node's own displacement frame would be
read back as though it were global, and be wrong without any warning.
So values are rotated into global on the way out, translations as one
triple and rotations as another, with the same convention the animator
uses. That needs the geometry, since the shapes alone do not say what
frame they are in: pass geometry= to exodus.save, which also writes
the mesh alongside. Without it the values go out as they stand. Which
node was placed or measured in which frame is not lost: it is written
as the def_cs_<id> / disp_cs_<id> node sets described in note ¹,
and the reader restores the assignments from them, so the systems, the
assignments and the global values together reproduce the model
exactly.
Element groups survive the round trip¶
An exodus mesh is written as named element blocks, and the block is
where a part's identity is kept: wing, tail, nacelle front left.
A geometry carries them as element groups (elem_group per element,
with group_id/group_name declaring them), so a file read and
written back keeps its groups.
They matter beyond the round trip. fem.Model.from_geometry reads the
section of a member from the element group of the element it came
from, so a saved geometry and sections={'prop': BLADE} are a complete
description of a structure, and a geometry whose element groups carry
their own properties builds with nothing passed. Asking a node instead
would not work: a node on a seam between two element groups belongs to
both and can answer for only one.
Formats that do not record element groups import as a single element group with no name, which is what they said. Nothing invents a division.
One case cannot make the round trip. Exodus holds a single element type
per block, so an element group with quads and triangles in it (a
canopy group, for example) goes out as two blocks and comes back as
two element groups. The two blocks cannot share the id or the name the
group declared: the first keeps both, and the next takes the lowest
free id and its element type in its name (canopy TRI3). Blocks
sharing an id make a file that ParaView's IOSS reader cannot open (it
reports zero cells without saying why), and Visual Dynamics refuses
duplicate block ids on import.
Exodus has no traceline and needs none: a drawn line is an element group of two-node beam elements with no properties, and it goes out as that block and comes back as one. Coordinate systems survive as frames, with their assignments in node sets (above), but their names do not, since a frame is only an id, three points and a tag.
Visual Dynamics keeps no separate
tracelines: a line drawn through nodes is an element group of two-node
line elements with no properties, and that is how it is recognized on
the way out. UNV, sdynpy, ESCDF and Nastran keep lines separate from
elements, so such a group is written as the format's line (dataset 82,
the traceline array, line_connection, PLOTEL chains) and not also as
elements. A group that carries a section is structure, and is written
as elements. On the way in, a line arrives as its own element group,
named for the line and colored by it; a UNV line that lifts the pen
arrives as one group of several runs. An element group holds one
element family, so a source that mixes families in a group is split on
arrival, with the new groups named for their family beside the old
name. (UNV and sdynpy carry no groups at all, and a Nastran PSHELL
covers quads and triangles alike.)
UNV writes geometry as datasets 2411, 82, 2412 and 2420, and data as one dataset 58 per record. The two coherence types keep their own function codes, 6 for ordinary and 26 for multiple, because writing multiple coherence as 6 would promise a reference DOF per record that is not there. When the object's units are defined, a dataset 164 is written declaring the unit system the values are in, and a file that has one reads back with its units. When they are not defined, no 164 is written.
A dataset 58 names its dimension from a fixed vocabulary, and a PSD's
acceleration**2/frequency is not in it. Rather than write half a
dimension, the record says nothing and the data comes back unit-less.
Values, DOFs and function type all survive.
The dimension a record names is whatever the record knows: its own when its units are defined, otherwise its hint. So a UNV that arrived with no 164 goes back out still saying what its channels are.
Rattlesnake runs¶
A Rattlesnake .nc4 is the recording of a controller run, with its
hardware settings, environments and everything else the controller
kept, none of which Visual Dynamics holds. A file written from Visual
Dynamics would be a controller run that never happened, so the format
is import only. The controller's random specification file (.npz) is
different: a specification edited here can be written back out as one
(see the table). The controller's own specification files name no
channels, since it matches them to its channel table by position, so
their channels are read as '1', '2' and so on in that order, the same
as a sine specification's.
The one .nc4 that is written is the run itself, shorter: a truncated
copy. Save Truncated Copy… in the long-run import window below, or
File → Truncate Rattlesnake File… for a run of any size, writes the
run cut to the stretch chosen. Every group, attribute, dimension and
variable is copied as the file has it, with the same storage,
compression and chunking: the hardware settings, all of the channel
fields, and each environment's control settings and specification. Only
the time data changes, each stream cut to the samples an import of the
same window reads. All channels are kept, since leaving one out would
mean rewriting the channel table and each environment's channel
indices. The file has no clock of its own, so the copy's time starts at
zero again, and the name it is offered under says where the stretch
came from (random_30-60s.nc4 for seconds 30 to 60 of random.nc4).
Settings that describe the run as a whole, such as its ramp time, still
describe the original run. From a script:
visualdynamics.io.rattlesnake.save_window(path, target, start=30,
stop=60), which is also the call the journal records.
Rattlesnake writes three kinds of file for a run, and all three are read: the streamed recording, the spectral saves, and the saved system-ID package (Save System ID), which holds the measured plant FRF with its multiple coherence, plus the driven and ambient autospectra as a pair of PSDs. The signal-to-noise is a reading of that pair, not a third object. Select the two PSDs together and the bar offers them Overlaid, as Signal to noise (dB) line by line, or as RMS signal to noise per channel. A line where the ambient is zero is left as a gap (NaN) rather than an infinite ratio: on real hardware zero means below resolution, and in a simulation it means the ambient was exactly silent. The package records hardware channel numbers but no node names or units, so its DOFs arrive as those numbers and its drives as 9000-series labels (9001, 9002, …), which are plainly not real channels.
A run that controlled a virtual response (a response transformation matrix over the control channels, as a MIMO test on a rigid fixture often uses) imports as both halves of what the controller did. The streamed time data stays in hardware channels, and after them, in the same time history, come the transformation's rows over those channels, computed as the controller computed them. Because they are one object, one pass of averaging gives the PSDs of the raw channels and of the virtual ones together, and the virtual ones are what the specification was judged against. The specification, the FRF, the coherence and the control CPSD are over those rows too, because that is what the controller saved. The file does not name the rows, so they import as node-only coordinates numbered from 1; rename them in the tree if the rows mean something (a virtual point's X, Y and Z, say). A row's unit is the control channels' unit when they all share one, and undeclared when they do not.
A long run need not be imported whole. A stream that would take a
quarter of the machine's memory or more opens a dialog before it is
read. It shows one channel's envelope over the whole run (read in slabs
and thinned to 4,096 points, so the preview of a 22 GB run is a few
hundred kilobytes), the truncate reading's span over it to choose the
stretch, the channels to keep, and what the choice costs in samples and
gigabytes against the machine's memory. Import reads exactly that
window as a slice of the file, inclusive at both ends on the run's own
clock, which the records keep, and every row's comment names the window
and the file. A script says the same thing with
import_file(path, start=60, stop=120, channels=['101Z+', '104Z+']),
and the journal replays the import as it was made. A stream the window
misses entirely is left out, a windowed stream is not split into a
spectral save's frames, and an environment's virtual responses come
along only when every control channel they are computed from is among
the channels kept. Below the threshold nothing is asked, and the API
imports the whole run unless given a window. Once the stretch is less
than the whole run, the dialog also offers Save Truncated Copy…,
which writes the run cut to that stretch as a .nc4 of its own
(above).
A system ID saved as time data is a different case: the file cannot be
told apart from a run of its environment, since two streams are two
streams. When an import has exactly that shape (two streams, a quiet
one then a loud one), the app asks whether it is a system ID run
instead of guessing. If you answer yes, the project type becomes System
ID and the two recordings take their phase names, Noise Time History
and Excitation Time History, told apart by level. If you answer no,
everything keeps its generic name. Scripted imports never ask; set
project.project_type yourself.