visualdynamics¶
visualdynamics
¶
visualdynamics: units-aware structural dynamics analysis toolset.
Classes:
| Name | Description |
|---|---|
Issue |
Why one object does not fit the active geometry. |
Report |
Compatibility of every object in a test. |
ChannelTable |
Per-channel metadata, one fixed column set, each column typed. |
DataArray |
Base class; use a concrete subclass (TimeHistory, Spectrum, Frf, Psd). |
Frf |
Frequency response function: response per unit reference. |
Geometry |
Nodes, coordinate systems, elements and element groups. |
Psd |
Power spectral density: the declared unit is the engineering unit |
ShapeSet |
Mode shapes over a shared set of DOFs. |
ShockSpecification |
What a shock test was controlled to: an SRS, and its band. |
Specification |
What a random vibration test was controlled to: a PSD, and its band. |
Spectrum |
A linear spectrum: amplitude and phase at each frequency line. |
Srs |
A shock response spectrum: the peak an oscillator reached. |
TimeHistory |
A measurement against time: the record as it was acquired. |
TransientSpecification |
What a transient test was controlled to: a target time history. |
View |
The way a geometry opens in 3-D: where the eye is, seen from the |
Project |
Every object in one test, by name, plus the structure around them. |
UnitsRequired |
Raised when an operation needs units that have not been defined. |
UnitSystem |
A named mapping of dimension -> display unit. |
Functions:
| Name | Description |
|---|---|
check_compatibility |
Check every object in a test against the geometry it answers to. |
frequency_axis |
Read or set how frequency axes are drawn: 'log', 'linear', or |
export_file |
Write |
from_sep005 |
SEP 005 timeseries into |
import_file |
Import a foreign file, returning the visualdynamics object it contains. |
importers |
Every format visualdynamics can read, in the order they are tried. |
load |
Load a .vdyn file: the object it contains, or a whole test. |
register_importer |
Teach visualdynamics a format. Registered ones are tried in order, so a |
save |
Save a visualdynamics object to a .vdyn (HDF5) file. |
mixed_run |
A Rattlesnake random run with a sine sweep under it, worked up |
random_vibration_report |
A Rattlesnake random vibration run in, an HTML report out. |
random_vibration_run |
A Rattlesnake random vibration run, worked up into a project. |
report_kind |
Which one-call reports a Rattlesnake run gets: 'random', 'mixed', |
run_report |
A Rattlesnake run in, the report its type calls for out. |
sine_report |
A Rattlesnake sine sweep run in, an HTML report out. |
sine_run |
A Rattlesnake sine sweep run, worked up into a project. |
system_id_report |
A Rattlesnake system identification in, an HTML report out. |
system_id_run |
A Rattlesnake system identification, worked up into a project. |
export_html |
One figure, interactive, as a single self-contained HTML file. |
convert |
|
si_factor |
Multiplier converting values in |
launch_gui |
Launch the app, optionally importing files on the way in. |
Classes¶
Issue
dataclass
¶
Why one object does not fit the active geometry.
Report
dataclass
¶
Compatibility of every object in a test.
Methods:
| Name | Description |
|---|---|
is_compatible |
Whether one object fits the geometry it is linked to. |
issue_for |
What is wrong with one object, or None if nothing is. |
sub_item_flagged |
Whether one record within an object is incompatible. |
Attributes:
| Name | Type | Description |
|---|---|---|
incompatible_names |
list[str]
|
The objects that do not fit the geometry, by name — what |
Attributes¶
incompatible_names
property
¶
The objects that do not fit the geometry, by name — what the tree marks in red and a link refuses over.
Methods:¶
is_compatible
¶
Whether one object fits the geometry it is linked to.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The object to ask about. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
Whether it fits the geometry it is linked to. |
Source code in src/visualdynamics/compatibility.py
issue_for
¶
issue_for(name: str) -> Issue | None
What is wrong with one object, or None if nothing is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The object to ask about. |
required |
Returns:
| Type | Description |
|---|---|
Issue or None
|
What is wrong with it, or None if nothing is. |
Source code in src/visualdynamics/compatibility.py
sub_item_flagged
¶
Whether one record within an object is incompatible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The object to ask about. |
required |
index
|
int
|
Which record within it. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
Whether that record is one of the incompatible ones. |
Source code in src/visualdynamics/compatibility.py
ChannelTable
¶
Per-channel metadata, one fixed column set, each column typed.
Attributes:
frame: The table, one row per channel, columns in SCHEMA
order. Every cell is text except channel and node,
which are integers — a serial number that happens to be all
digits is not a number, and a column read back as int64
would lose its leading zero and come out of a round trip a
different string.
Methods:
| Name | Description |
|---|---|
set_cell |
Write one cell; a value the column cannot hold is refused. |
units_for |
The units this channel could be in, given its declared type. |
delete_channels |
Remove the given rows in place; the last one is refused. |
orientation |
One channel's measured direction against a geometry. |
derived_cells |
The derived columns' cells for one row, as text: three |
dof_strings |
Each channel's degree of freedom, as '101Z+' strings. |
rename_dof |
Give the channel at coordinate |
roles |
Each channel's declared role, '' where undeclared. |
controls |
Which channels are control channels, as booleans. |
types |
What each channel measures, '' where undeclared. |
sensitivities |
mV per engineering unit, NaN where undeclared. |
ranges |
The instrumentation voltage limit, per channel. |
save |
Write the table to a file of its own. |
Attributes:
| Name | Type | Description |
|---|---|---|
num_channels |
int
|
How many channels the table describes — one per row. |
column_names |
list[str]
|
The table's column headings, in order. |
Source code in src/visualdynamics/core/channel_table.py
Attributes¶
Methods:¶
set_cell
¶
Write one cell; a value the column cannot hold is refused.
Strict where _typed is lenient: a person typing gets the
reason, where a file gets the benefit of the doubt.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The column to write. |
required |
row
|
int
|
Which channel. |
required |
value
|
object
|
The value, refused if the column cannot hold it. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/channel_table.py
units_for
¶
The units this channel could be in, given its declared type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
int
|
Which channel. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
The units this channel could be in, given its declared type. |
Source code in src/visualdynamics/core/channel_table.py
delete_channels
¶
Remove the given rows in place; the last one is refused.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
indices
|
sequence of int
|
Which rows to remove. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/channel_table.py
orientation
¶
One channel's measured direction against a geometry.
The DOF the row names — node and direction — read through the
frame that node is measured in (Geometry.dof_direction), as
a unit vector in the geometry's global system; the global axis
it is nearest, signed ('X+', 'Z-'); and the angle between the
two in degrees. What the derived columns show.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
int
|
The channel's row. |
required |
geometry
|
Geometry
|
The geometry the channel is measured on. |
required |
Returns:
| Type | Description |
|---|---|
tuple
|
(vector, axis, angle), each None when the geometry cannot place the channel — a node it lacks, no direction, or a node measured in a frame that turns with position. |
Source code in src/visualdynamics/core/channel_table.py
derived_cells
¶
The derived columns' cells for one row, as text: three components to three decimals, the nearest axis, the angle to a tenth of a degree; empty where the geometry cannot place the channel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
int
|
The channel's row. |
required |
geometry
|
Geometry
|
The geometry the channel is measured on. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
One cell per |
Source code in src/visualdynamics/core/channel_table.py
dof_strings
¶
Each channel's degree of freedom, as '101Z+' strings.
rename_dof
¶
Give the channel at coordinate old the coordinate new,
in place — its node and direction cells rewritten, since a DOF
string is those two concatenated. The same correction a data
array's rename_dof makes, for the table that names the
channels (Brandon, 2026-09-06), and the same rule: the channel
moves, not every channel at the point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
old
|
str
|
The coordinate as the table has it, '101Z+'. |
required |
new
|
str
|
The coordinate to give it: a node number then a direction, normalized the way every DOF is, and refused when it is not one — a table row may lack a node or a direction, but a correction typed by a person is whole. |
required |
quantity
|
str
|
Which channel at |
None
|
Returns:
| Type | Description |
|---|---|
int
|
How many channels changed. |
Source code in src/visualdynamics/core/channel_table.py
roles
¶
controls
¶
types
¶
sensitivities
¶
mV per engineering unit, NaN where undeclared.
ranges
¶
save
¶
Write the table to a file of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or PathLike
|
Where to write it. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/channel_table.py
DataArray
¶
DataArray(abscissa: ArrayLike, ordinate: ArrayLike, response_dof: str | Sequence[str], reference_dof: str | Sequence[str] | None = None, ordinate_dim: str | Sequence[str] | None = None, comment: str | Sequence[str] | None = None, ordinate_unit: str | Sequence[str | None] | None = None, reference_unit: str | Sequence[str | None] | None = None, dimension_hint: str | Sequence[str | None] | None = None, block: str | Sequence[str] | None = None)
Base class; use a concrete subclass (TimeHistory, Spectrum, Frf, Psd).
One object holds many records — 36 accelerometer channels, or the
2 592 FRFs of a 36-by-72 matrix — sharing one abscissa. Everything
that varies between records is a list of that length, so record i
is ordinate[i] measured at response_dof[i], and there is no
per-record object to go stale.
Values are stored in SI once their units are known. A record
whose units were never declared keeps the file's raw numbers and
reports ordinate_dim == 'unknown'; what the file said it was,
without saying its scale, is kept beside it in dimension_hint.
Attributes:
abscissa: The x axis, shared by every record — seconds for a time
history, hertz for anything in the frequency domain. Stored
as it arrived: uneven spacing and out-of-order samples are
both allowed, because both are real and refusing them at
the door would refuse real data. What needs an even step —
anything with an FFT under it — asks for one at the point of
use and says so when it cannot have it.
ordinate: (records, len(abscissa)). Complex where the subclass
says so (complex_ordinate), real otherwise.
response_dof: What each record was measured at, as a DOF string
('101X+'). One per record.
reference_dof: What each record was measured against, for the
types that need one — the shaker on an FRF, the other channel
of a cross spectrum. None where the type has no reference.
block: Which repeat of the same measurement each record is: an
average, a run, a shock. A short label, not a time.
ordinate_dim: The quantity each record measures
('acceleration', 'force'), or 'unknown' while its units
are undeclared.
ordinate_unit: The unit its values are in — always the SI one
while the dimension is known, since that is how they are
stored. None means undeclared.
reference_unit: The same for the reference of a ratio, so an FRF
record knows both halves of m/s²/N.
dimension_hint: What the file claimed a record measures without
saying at what scale. Nothing is ever scaled by a hint: it
narrows the units offered, labels an axis, and survives
export.
comment: Free text per record, as the source file carried it.
Methods:
| Name | Description |
|---|---|
known_dim |
What quantity record |
rename_dof |
Give a channel's coordinate a new name, in place. |
delete_records |
Remove records in place — by index, by DOF, or by capture. |
define_units |
Declare what the ordinate values are in, converting them to SI. |
undefine_units |
Take a declaration back, restoring the file's raw values. |
column_keys |
What tells one record from another besides its response. |
record_label |
A short label for one record, for a legend or an axis. |
record_pair |
The (response, reference) record |
log_scaled |
Whether this object's magnitude reads on a log axis. |
display_abscissa |
The abscissa converted into a unit system's own units. |
display_ordinate |
Ordinate in display units; undefined records pass through as-is. |
display_blocks |
|
save |
Write this object to a |
plot |
Draw every record on one set of axes. |
save_plot |
Draw the records and write the figure to |
plot_waterfall |
The records spread along a depth axis, colored by level — |
Attributes:
| Name | Type | Description |
|---|---|---|
num_records |
int
|
How many records this object holds. One measurement per |
units_defined |
bool
|
Whether every record knows what it measures. False while |
undefined_records |
list[int]
|
Which records still have no declared dimension — the ones |
Source code in src/visualdynamics/core/data.py
Attributes¶
num_records
property
¶
How many records this object holds. One measurement per record, all sharing the object's single abscissa.
units_defined
property
¶
Whether every record knows what it measures. False while any record is still in the file's own unconverted numbers.
undefined_records
property
¶
Which records still have no declared dimension — the ones
holding the file's raw numbers, awaiting define_units.
Methods:¶
known_dim
¶
What quantity record i holds, whether or not its unit is known.
The dimension when the units are defined, otherwise the source's claim, otherwise 'unknown'. Never use this to scale anything — a hinted record's values are raw, and the scale is exactly what is missing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
i
|
int
|
Which record. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The quantity the record holds, falling back to its dimension hint when the unit is undeclared. |
Source code in src/visualdynamics/core/data.py
rename_dof
¶
Give a channel's coordinate a new name, in place.
A channel is a coordinate and a quantity — a drive point carries a load cell and an accelerometer at one DOF — and the rename is the channel's: a force labeled at the wrong node moves without taking the accelerometer at that node with it, and the other is changed explicitly if it should be (Brandon, 2026-09-06). The channel moves wherever a record wears it, as a response and as a reference alike: a CPSD's accelerometer is on both sides of its cross terms and is one sensor. A rename that would give two records one identity — two accelerometers at one point — is refused, where it would have made two rows of the grid into one and hidden a record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
old
|
str
|
The coordinate as it is, '101Z+'. |
required |
new
|
str
|
The coordinate to give it; normalized the way every DOF is ('101Z' is '101Z+'), and refused when it is not one. |
required |
quantity
|
str
|
Which channel at |
None
|
Returns:
| Type | Description |
|---|---|
int
|
How many records changed, counting a response and a reference on one record separately. |
Source code in src/visualdynamics/core/data.py
delete_records
¶
delete_records(indices: Sequence[int] | None = None, *, dof: str | Sequence[str] | None = None, dim: str | Sequence[str] | None = None, reference: str | Sequence[str] | None = None, capture: int | Sequence[int] | None = None) -> None
Remove records in place — by index, by DOF, or by capture.
Everything a record owns goes with it: its row of the ordinate, its DOFs, units, comment, hint, block — and, on a specification, its limit curves, which would otherwise silently belong to the wrong channels. Removing the last record is refused: an empty data array is not a state anything else here can show.
dof and capture are the selectors a person means — "drop
channel 101Z+", "drop the third run" — where indices are the
machine's (Brandon, 2026-08-30, reading thirteen indices in the
journal where one capture number would have said it). They
combine as an intersection, and either combines with explicit
indices as a union.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
indices
|
sequence of int
|
Which records to remove, by position. |
None
|
dof
|
str or sequence of str
|
Remove every record at these response DOFs. A drive point
carries two records at one DOF — a force and an
acceleration — and the DOF alone takes both; |
None
|
dim
|
str or sequence of str
|
Restrict to these quantities ('force', 'acceleration', …) — the other half of a channel's identity. |
None
|
reference
|
str or sequence of str
|
Remove every record at these reference DOFs — a column of
an FRF matrix, where |
None
|
capture
|
int or sequence of int
|
Remove these captures — each channel's n-th playing, the
numbering |
None
|
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/data.py
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 | |
define_units
¶
define_units(units: str | Sequence[str | None], reference_units: str | Sequence[str | None] | None = None) -> DataArray
Declare what the ordinate values are in, converting them to SI.
units is a single unit applied to every record, a sequence with one
entry per record, or a {record index: unit} mapping to set only some.
Entries of None leave a record's units undefined. Records that already
have units are reinterpreted, not re-scaled twice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
units
|
str or sequence of str
|
The unit each record's values are in; one string applies to every record. |
required |
reference_units
|
str or sequence of str
|
The denominator unit, for records that have one. |
None
|
Returns:
| Type | Description |
|---|---|
DataArray
|
Self, converted to SI in place. |
Source code in src/visualdynamics/core/data.py
undefine_units
¶
undefine_units(records: Sequence[int] | None = None) -> DataArray
Take a declaration back, restoring the file's raw values.
The inverse of define_units. A wrong guess should be correctable
without reimporting, and that means being able to withdraw one, not
only to replace it — there is no unit string meaning 'I no longer
know'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
records
|
sequence of int
|
Which records to revert. All of them when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
DataArray
|
Self, with the file's raw values restored. |
Source code in src/visualdynamics/core/data.py
column_keys
¶
What tells one record from another besides its response.
The reference DOF when records are a matrix of measurements, the block when they are the same measurement repeated, None when the response alone is the whole identity. This is what decides whether an object expands into a grid.
Source code in src/visualdynamics/core/data.py
record_label
¶
A short label for one record, for a legend or an axis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
i
|
int
|
Which record. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The record's DOF, plus whatever tells it from its neighbors. |
Source code in src/visualdynamics/core/data.py
record_pair
¶
The (response, reference) record i is between.
A record with no reference is an autospectrum — a channel against itself — so it pairs with the diagonal, which is what a specification bounds. The one reading of that rule: the plot, the table beside it and the report all ask here, so their labels cannot disagree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
i
|
int
|
The record. |
required |
Returns:
| Type | Description |
|---|---|
tuple of str
|
Response DOF, reference DOF. |
Source code in src/visualdynamics/core/data.py
log_scaled
¶
Whether this object's magnitude reads on a log axis.
Logarithmic for frequency-domain data unless the class pins it
(log_ordinate — a coherence is a 0..1 ratio and says nothing on
a log axis). The object answers so the 2-D plot and the 3-D
waterfall read one rule and cannot disagree about its axis.
Source code in src/visualdynamics/core/data.py
display_abscissa
¶
display_abscissa(unit_system: UnitSystem) -> ndarray
The abscissa converted into a unit system's own units.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_system
|
UnitSystem
|
The units to present in. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The abscissa in display units. |
Source code in src/visualdynamics/core/data.py
display_ordinate
¶
display_ordinate(unit_system: UnitSystem, records: Iterable[int] | None = None) -> ndarray
Ordinate in display units; undefined records pass through as-is.
records limits the work to the ones asked for. A 1356-record FRF
has one or two distinct dimensions in it, so the conversion is
gathered per dimension and applied to a whole block at once —
converting row by row meant a unit lookup per record, which is how
drawing a single curve came to cost 2713 trips through pint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_system
|
UnitSystem
|
The units to present the values in. |
required |
records
|
iterable of int
|
Which records to convert. All of them when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The values in display units. Records with undefined units pass through untouched. |
Source code in src/visualdynamics/core/data.py
display_blocks
¶
display_blocks(unit_system: UnitSystem, records: Iterable[int] | None = None, block_bytes: int | None = None)
display_ordinate a block of records at a time.
Yields (start, stop, values): positions within records and
the converted rows for them, each block at most block_bytes
(DISPLAY_BLOCK_BYTES by default) — so a reading that thins
every record before it draws it, the 3-D stage above all,
never holds a converted copy of the whole object. A record
larger than the block is still one block: a row is not split.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_system
|
UnitSystem
|
The units to present the values in. |
required |
records
|
iterable of int
|
Which records to convert. All of them when omitted. |
None
|
block_bytes
|
int
|
The most a block may hold, in bytes. |
None
|
Yields:
| Type | Description |
|---|---|
tuple of (int, int, numpy.ndarray)
|
The block's positions among |
Source code in src/visualdynamics/core/data.py
save
¶
Write this object to a .vdyn file of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or PathLike
|
Where to write it. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/data.py
plot
¶
plot(unit_system: UnitSystem | None = None, **kwargs: Any) -> Any
Draw every record on one set of axes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_system
|
UnitSystem
|
Units to draw in. |
None
|
**kwargs
|
Any
|
Passed through to the plotting layer. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget or plotter. |
Source code in src/visualdynamics/core/data.py
save_plot
¶
save_plot(path: str | PathLike, unit_system: UnitSystem | None = None, **kwargs: Any) -> Any
Draw the records and write the figure to path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or PathLike
|
Where to write the image. |
required |
unit_system
|
UnitSystem
|
Units to draw in. |
None
|
**kwargs
|
Any
|
Passed through to the plotting layer. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget or plotter. |
Source code in src/visualdynamics/core/data.py
plot_waterfall
¶
The records spread along a depth axis, colored by level —
the plot bar's 3-D reading, scripted. screenshot= renders
headless to a file; without it a window of the app's own 3-D
pane opens.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
records
|
sequence of int
|
Which records to stage. All of them when omitted. |
None
|
**kwargs
|
Any
|
Passed through to the scene. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget or plotter. |
Source code in src/visualdynamics/core/data.py
Frf
¶
Frf(abscissa: ArrayLike, ordinate: ArrayLike, response_dof: str | Sequence[str], reference_dof: str | Sequence[str] | None = None, ordinate_dim: str | Sequence[str] | None = None, comment: str | Sequence[str] | None = None, ordinate_unit: str | Sequence[str | None] | None = None, reference_unit: str | Sequence[str | None] | None = None, dimension_hint: str | Sequence[str | None] | None = None, block: str | Sequence[str] | None = None)
Bases: DataArray
Frequency response function: response per unit reference.
Methods:
| Name | Description |
|---|---|
plot_cmif |
The CMIF the fitting screen draws; with |
animate |
The operating deflection shape at one frequency line, moving |
Source code in src/visualdynamics/core/data.py
Methods:¶
plot_cmif
¶
The CMIF the fitting screen draws; with shapes the modal
model's synthesis is drawn dashed over the measurement.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shapes
|
ShapeSet
|
A modal fit, drawn as the synthesized CMIF over the measured one. |
None
|
**kwargs
|
Any
|
Passed through to the plotting layer. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget or plotter. |
Source code in src/visualdynamics/core/data.py
animate
¶
The operating deflection shape at one frequency line, moving
on a geometry as the GUI animates it. Defaults to the strongest
line; frequency picks another.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
Geometry
|
The geometry to move. |
required |
frequency
|
float
|
Which frequency line. The strongest when omitted. |
None
|
**kwargs
|
Any
|
Passed through to the scene. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget or plotter. |
Source code in src/visualdynamics/core/data.py
Geometry
¶
Geometry(node_id: Ids, node_xyz: ArrayLike, node_def_cs: ArrayLike | None = None, node_disp_cs: ArrayLike | None = None, node_color: ArrayLike | None = None, cs_id: Ids | None = None, cs_name: Sequence[str] | None = None, cs_type: ArrayLike | None = None, cs_matrix: ArrayLike | None = None, elem_id: Ids | None = None, elem_type: ArrayLike | None = None, elem_color: ArrayLike | None = None, elem_conn: Sequence[ArrayLike] | None = None, elem_group: Ids | None = None, group_id: Ids | None = None, group_name: Sequence[str] | None = None, length_unit: str | None = None, group_properties: Mapping[int, Any] | None = None)
Nodes, coordinate systems, elements and element groups.
Parameters are array-likes; connectivity lists contain one integer array of node ids per element. All coordinates in SI meters.
The arrays below are the storage; nodes, coordinate_systems,
elements and groups are views onto them, which
is how the tree lists a geometry and how a script should usually
reach one. A view is not a copy — writing through a row writes here.
Attributes:
node_id: Every node's id. Unique, because connectivity and
placement refer to nodes by id.
node_xyz: (nodes, 3) coordinates, in meters once
length_unit is declared and the file's raw numbers before
that.
node_def_cs: The coordinate system each node is placed in.
node_disp_cs: The system each node is measured in — the frame a
shape's values at that node are expressed in.
node_color: Palette index per node.
cs_id: Coordinate system ids. Unique, for the same reason as
nodes.
cs_name: A name per system, often empty.
cs_type: 0 cartesian, 1 cylindrical, 2 spherical (CS_TYPES).
cs_matrix: (systems, 4, 3) — three direction rows then the
origin, so cs_matrix[i, 3] is where system i sits.
(There are no tracelines (2026-09-30). A line drawn through
nodes is an element group of two-node line elements with no properties —
add_beams makes one, drawn_lines reads them back for the
formats that keep tracelines apart — and the same element group becomes
structure the moment its element group carries a section. One storage
for one drawn idea; PLAN.md "Geometry by element family".)
elem_id: Element ids. Labels — nothing refers to them.
elem_type: UFF dataset 2412 descriptor code per element
(ELEMENT_TYPES names them and says how each is drawn).
elem_color: Palette index per element.
elem_conn: One array of node ids per element.
elem_group: Which element group each element belongs to, by element group id.
group_id: The declared element groups. Unique, since elements name them.
group_name: A name per element group — 'wing', 'arm front left'. This
is where a mesh records that a region is a different part
from its neighbor, and fem.Model.from_geometry reads a
member's section from it.
length_unit: What the coordinates are in, or None while that
has not been declared — in which case they are the file's own
numbers and nothing has been scaled.
Methods:
| Name | Description |
|---|---|
validate |
Check the geometry hangs together — every element's nodes |
node_index |
Positions of the given node ids in the node arrays. |
contains_nodes |
Boolean array: which of |
missing_dofs |
The DOF strings whose node this geometry does not define. |
suggest_mass_properties |
The centroid of the nodes as the reference point, and no |
define_units |
Declare what the coordinates are in, converting them to SI. |
undefine_units |
Take the declaration back, restoring the file's raw coordinates. |
dof_direction |
A DOF's direction in global coordinates, through the frame |
add_node |
Append a node at |
add_nodes |
Append nodes at |
add_coordinate_system |
Append a coordinate system. Returns its id. |
add_beams |
A chain of two-node line elements through the given nodes, in |
mixed_groups |
{element group id: families} for every element group holding more than one |
split_groups_by_family |
One element group, one family: an element group holding elements of more than |
attach_drawn_lines |
Drawn lines from a format that keeps them apart from |
is_drawn_line |
Whether an element group is a drawn line: every element in it a |
drawn_lines |
The drawn lines, as the formats that keep tracelines apart |
group_of |
The name of the element group an element belongs to, or ''. |
elements_in |
The ids of the elements in an element group, named or numbered. |
add_group |
Declare an element group. Returns its id. |
add_element |
Append an element. Type defaults to whatever fits the node count: |
add_elements |
Append elements — |
renumber_node |
Give a node a new id, carrying its elements over. |
renumber_group |
Give an element group a new id, carrying its elements over. |
renumber_coordinate_system |
Give a coordinate system a new id, repointing the nodes using it. |
delete_nodes |
Remove nodes, and anything that referenced them. |
coincident_nodes |
{node: the node it coincides with}: every node within |
duplicate_elements |
{element id: the element it is} for every solid element on |
merge_duplicate_elements |
Make elements that are one cell one element ( |
merge_coincident_nodes |
Make nodes that are one point one node: every element and |
delete_coordinate_systems |
Remove coordinate systems, reassigning any node that used them. |
delete_groups |
Remove element groups with what they hold: their elements, and the |
merge_refusal |
Why these element groups cannot be one, or None when they can: they |
merge_groups |
One element group from several (Merge Element Groups, Brandon 2026-09-27): the |
delete_elements |
Remove elements by id. Ids that are not there are ignored. |
save |
Write the geometry to a file of its own. |
plot |
Draw the geometry: nodes and elements. |
plot_dofs |
This geometry with labeled arrows at every DOF |
Attributes:
| Name | Type | Description |
|---|---|---|
opening_view |
View
|
The view it opens on in 3-D: its own |
num_nodes |
int
|
How many nodes the geometry defines. |
nodes |
EntityView
|
Every node: |
coordinate_systems |
EntityView
|
Every coordinate system: |
elements |
EntityView
|
Every element: |
groups |
EntityView
|
Every element group: |
extent |
tuple[ndarray, ndarray]
|
(min_xyz, max_xyz), in meters once units are defined. |
units_defined |
bool
|
Whether the geometry knows what its coordinates mean. |
Source code in src/visualdynamics/core/geometry.py
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 | |
Attributes¶
opening_view
property
¶
opening_view: View
The view it opens on in 3-D: its own view, else the default
isometric.
coordinate_systems
property
¶
coordinate_systems: EntityView
Every coordinate system: ids, names, types, matrices.
groups
property
¶
groups: EntityView
Every element group: ids and names.
An element group groups elements rather than holding them — which elements
are in one is read off elem_group (elements_in), so moving an
element between element groups is an edit to the element.
extent
property
¶
(min_xyz, max_xyz), in meters once units are defined.
units_defined
property
¶
Whether the geometry knows what its coordinates mean.
False until define_units names the length unit.
Methods:¶
validate
¶
Check the geometry hangs together — every element's nodes present, every identifier unique — and report what does not.
Source code in src/visualdynamics/core/geometry.py
node_index
¶
Positions of the given node ids in the node arrays.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_ids
|
int or sequence of int
|
The identifiers, one or many. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Each identifier's row in the node arrays. |
Source code in src/visualdynamics/core/geometry.py
contains_nodes
¶
Boolean array: which of node_ids this geometry defines.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_ids
|
int or sequence of int
|
The identifiers, one or many. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
A boolean per identifier: whether the geometry has it. |
Source code in src/visualdynamics/core/geometry.py
missing_dofs
¶
The DOF strings whose node this geometry does not define.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dofs
|
sequence of str
|
The degrees of freedom to check, such as '101Z+'. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
Those the geometry has no node for — what makes a data object incompatible with it. |
Source code in src/visualdynamics/core/geometry.py
suggest_mass_properties
¶
suggest_mass_properties() -> MassProperties
The centroid of the nodes as the reference point, and no mass — unit rigid-body shapes about the middle of the model.
The seed the rigid-body pane opens with and what
generate_rigid_body_modes adopts when nothing was set: unlike
a whole-record truncation, this is a real answer, and the one
the virtual-point transformation wants most often.
Returns:
| Type | Description |
|---|---|
MassProperties
|
The centroid, unscaled. |
Source code in src/visualdynamics/core/geometry.py
define_units
¶
define_units(length_unit: str) -> Geometry
Declare what the coordinates are in, converting them to SI.
Re-declaring reinterprets the original file values rather than scaling twice, so a wrong guess can simply be corrected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length_unit
|
str
|
The unit the coordinates are in, such as 'm' or 'in'. |
required |
Returns:
| Type | Description |
|---|---|
Geometry
|
Self, converted to SI in place. |
Source code in src/visualdynamics/core/geometry.py
undefine_units
¶
undefine_units() -> Geometry
Take the declaration back, restoring the file's raw coordinates.
Source code in src/visualdynamics/core/geometry.py
dof_direction
¶
A DOF's direction in global coordinates, through the frame its node is measured in.
'101X+' at a node whose node_disp_cs is rotated is not global
X. The report's grid of control channels reads which global
axis a channel is nearest and how far off it sits (Brandon,
2026-09-19), and this is where that is answered. A rotational
DOF points along its axis, as the DOF arrows draw it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dof
|
str
|
The DOF, such as '101X+' or '1313RZ-'. |
required |
Returns:
| Type | Description |
|---|---|
ndarray or None
|
A unit vector, or None when the DOF names no node this geometry has, no axis (a node number alone), or a node measured in a cylindrical or spherical frame — whose local axes turn with the node's position, a reading this does not attempt rather than half-answer. |
Source code in src/visualdynamics/core/geometry.py
add_node
¶
add_node(xyz: ArrayLike, node_id: int | None = None, color: int = 1, def_cs: int | None = None, disp_cs: int | None = None) -> int
Append a node at xyz (SI). Returns its id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
xyz
|
array_like
|
The node's coordinates. |
required |
node_id
|
int
|
Its identifier. The next free one when omitted. |
None
|
color
|
int
|
Its display color index. |
1
|
def_cs
|
int
|
The coordinate system the position is given in. |
None
|
disp_cs
|
int
|
The coordinate system displacements are measured in. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The node's identifier. |
Source code in src/visualdynamics/core/geometry.py
add_nodes
¶
Append nodes at xyz (SI), numbered after the highest id —
add_node for many at once, one copy of each array rather than
one per node, which is what keeps a meshed plane of thousands of
nodes from costing seconds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
xyz
|
array_like
|
(n, 3) coordinates. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The new nodes' identifiers, in order. |
Source code in src/visualdynamics/core/geometry.py
add_coordinate_system
¶
add_coordinate_system(origin: ArrayLike = (0.0, 0.0, 0.0), rotation: ArrayLike | None = None, cs_id: int | None = None, name: str = '', cs_type: int = 0) -> int
Append a coordinate system. Returns its id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
origin
|
array_like
|
The system's origin. |
(0, 0, 0)
|
rotation
|
array_like
|
A 3x3 rotation matrix. Identity when omitted. |
None
|
cs_id
|
int
|
Its identifier. The next free one when omitted. |
None
|
name
|
str
|
What to call it. |
''
|
cs_type
|
int
|
0 cartesian, 1 cylindrical, 2 spherical. |
0
|
Returns:
| Type | Description |
|---|---|
int
|
The coordinate system's identifier. |
Source code in src/visualdynamics/core/geometry.py
add_beams
¶
A chain of two-node line elements through the given nodes, in order — what a traceline was (2026-09-30), and a run of beams once the element group carries a section. Returns the element group's id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_ids
|
sequence of int
|
The nodes the line runs through, in order; two at least. |
required |
group
|
int
|
The element group the segments join. A new, unnamed element group when omitted — a drawn line is its own element group, named by what the line was called. |
None
|
color
|
int
|
The display color index, on every segment. |
1
|
elem_type
|
int
|
The two-node line code ( |
21
|
Returns:
| Type | Description |
|---|---|
int
|
The element group the segments are in. |
Source code in src/visualdynamics/core/geometry.py
mixed_groups
¶
{element group id: families} for every element group holding more than one element family — none, in a geometry that keeps the rule.
Returns:
| Type | Description |
|---|---|
dict of int to list of str
|
|
Source code in src/visualdynamics/core/geometry.py
split_groups_by_family
¶
One element group, one family: an element group holding elements of more than one family keeps its first family and each other family moves into a new element group named for it beside the old name. What a source without element groups — UNV 2412, the sdynpy layout — needs on the way in (Brandon, 2026-09-30: split on import rather than show one element group under two families and edit it twice).
Returns:
| Type | Description |
|---|---|
dict of int to list of int
|
{old element group: [the new element groups made from it]}, empty when nothing was mixed. |
Source code in src/visualdynamics/core/geometry.py
attach_drawn_lines
¶
Drawn lines from a format that keeps them apart from
elements, each as its own element group of two-node line elements with
no properties — the reader's half of drawn_lines.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lines
|
sequence of (name, color, chains)
|
One entry per line: what it was called, its color index, and its runs, each a sequence of node ids in drawing order (a UNV line that lifted the pen has several). |
required |
Returns:
| Type | Description |
|---|---|
list of int
|
The element groups made, one per line. |
Source code in src/visualdynamics/core/geometry.py
is_drawn_line
¶
Whether an element group is a drawn line: every element in it a two-node line element, and no properties on the element group — the one discriminator, on screen and in every file (PLAN.md "Geometry by element family"). An empty element group is not one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
int
|
The element group's identifier. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in src/visualdynamics/core/geometry.py
drawn_lines
¶
The drawn lines, as the formats that keep tracelines apart
from elements write them: per drawn-line element group, in element group order,
{'group', 'name', 'color', 'chains'} — the chains the element group's
segments make when walked in element order, each a list of
node ids, a new chain wherever a segment does not start where
the last one ended. An element group read from one polyline gives that
polyline back; one from a UNV line that lifted the pen gives
its runs back under the one element group.
Returns:
| Type | Description |
|---|---|
list of dict
|
|
Source code in src/visualdynamics/core/geometry.py
group_of
¶
The name of the element group an element belongs to, or ''.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elem_id
|
int
|
Which element. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The name of the element group it belongs to. |
Source code in src/visualdynamics/core/geometry.py
elements_in
¶
The ids of the elements in an element group, named or numbered.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
int or str
|
An element group, by identifier or by name. |
required |
Returns:
| Type | Description |
|---|---|
list of int
|
The identifiers of the elements it holds. |
Source code in src/visualdynamics/core/geometry.py
add_group
¶
Declare an element group. Returns its id.
An element group with nothing in it is legitimate — exodus files carry empty ones, and an element group has to exist before an element can be put in it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
What to call the element group. |
''
|
group_id
|
int
|
Its identifier. The next free one when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The element group's identifier. |
Source code in src/visualdynamics/core/geometry.py
add_element
¶
add_element(node_ids: Ids, elem_type: int | None = None, color: int = 1, group: int | None = None) -> int
Append an element. Type defaults to whatever fits the node count: 2 nodes a beam, 3 a triangle, 4 a quadrilateral. Returns its index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_ids
|
int or sequence of int
|
The nodes the element connects, in order. |
required |
elem_type
|
int
|
The element type code. Inferred from the node count when omitted. |
None
|
color
|
int
|
Its display color index. |
1
|
group
|
int
|
Which element group it belongs to. Left out, the first element group that holds this element's family, else a new one: an element group holds one family (2026-09-30), so a beam added to a mesh of plates goes in an element group of its own rather than theirs. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The element's identifier. |
Source code in src/visualdynamics/core/geometry.py
1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 | |
add_elements
¶
Append elements — add_element for many at once, the nodes
checked once for all of them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connectivity
|
sequence of sequence of int
|
Each element's nodes, in order. |
required |
elem_types
|
sequence of int
|
Each element's type code. |
required |
groups
|
sequence of int
|
The element group each belongs to; an element group not declared yet is. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/geometry.py
renumber_node
¶
Give a node a new id, carrying its elements over.
Connectivity names nodes by id, so a rename that left it alone would orphan every line and face touching the node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
int
|
Which node, by row. |
required |
node_id
|
int
|
Its new identifier. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/geometry.py
renumber_group
¶
Give an element group a new id, carrying its elements over.
An element names its element group by id, so a renumber that left them
alone would put every element of the element group in an element group that is no
longer there — which validate refuses, after the damage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
int
|
Which element group, by row. |
required |
group_id
|
int
|
Its new identifier. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/geometry.py
renumber_coordinate_system
¶
Give a coordinate system a new id, repointing the nodes using it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
int
|
Which system, by row. |
required |
cs_id
|
int
|
Its new identifier. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/geometry.py
delete_nodes
¶
Remove nodes, and anything that referenced them.
An element naming a deleted node cannot survive, so it goes too. Returns what was removed, for reporting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_ids
|
int or sequence of int
|
The identifiers, one or many. |
required |
Returns:
| Type | Description |
|---|---|
dict of str to int
|
How many of each kind were removed, including the dependents that went with them. |
Source code in src/visualdynamics/core/geometry.py
coincident_nodes
¶
{node: the node it coincides with}: every node within
tolerance of another, mapped to the lowest id among those it is
joined to — the one a merge keeps. Chains join: a within tolerance
of b and b of c are one point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tolerance
|
float
|
How close two nodes must be to be one point, as the coordinates are held: meters once units are defined. |
required |
Returns:
| Type | Description |
|---|---|
dict of int to int
|
Only the nodes that would go, each to the node it becomes. |
Source code in src/visualdynamics/core/geometry.py
duplicate_elements
¶
{element id: the element it is} for every solid element on exactly the nodes of an earlier one, of the same type — two bricks filling one cell. The lowest id is the one that stays.
Solids only: two beams on one line can be two members, and a plate on a plate's nodes is how a doubler or a layer is modeled, but no two solids fill one cell on purpose.
Two element groups that overlap and share their nodes there — the bars of an X cross-section — put two elements in every cell of the overlap: the region counted twice, its stiffness and mass doubled, and every face of each pair drawn as interior, so the middle of the X vanished from the view (Brandon, 2026-10-02).
Source code in src/visualdynamics/core/geometry.py
merge_duplicate_elements
¶
Make elements that are one cell one element (duplicate_elements):
the later of each pair is removed, the earlier — and its element group —
stays. Returns how many were removed.
Source code in src/visualdynamics/core/geometry.py
merge_coincident_nodes
¶
Make nodes that are one point one node: every element and
element naming a node within tolerance of another is renamed
to the lowest id among them, and the rest are removed. Plates
connect only where they share nodes, so this is what ties a model
built from planes together at its corners.
Refused, with the element named, when the tolerance would fold an
element onto itself — two of its own corners within it — since
that is a tolerance larger than the mesh, not a coincidence.
Elements the merge leaves on exactly the same nodes are one cell
filled twice, and are made one (merge_duplicate_elements).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tolerance
|
float
|
How close two nodes must be to be one, as the coordinates are held: meters once units are defined. |
required |
Returns:
| Type | Description |
|---|---|
dict of str to int
|
'merged', the nodes removed, 'into', the nodes they became, and 'duplicates', the elements removed for filling a cell another already filled. |
Source code in src/visualdynamics/core/geometry.py
delete_coordinate_systems
¶
Remove coordinate systems, reassigning any node that used them.
The last coordinate system is never removed — nodes must reference something.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cs_ids
|
int or sequence of int
|
The identifiers, one or many. |
required |
Returns:
| Type | Description |
|---|---|
dict of str to int
|
How many of each kind were removed, including the dependents that went with them. |
Source code in src/visualdynamics/core/geometry.py
delete_groups
¶
Remove element groups with what they hold: their elements, and the nodes no element outside them uses.
Deleting a part deletes the part (Brandon, 2026-09-27). It used to
move a deleted element group's elements into the first element group left, which
made a delete a merge; merging is its own act now
(merge_groups). A node an element of another element group also uses
stays, so a neighboring part is not cut into along the line the
two share.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group_ids
|
int or sequence of int
|
The identifiers, one or many. |
required |
Returns:
| Type | Description |
|---|---|
dict of str to int
|
How many element groups, elements and nodes went. |
Source code in src/visualdynamics/core/geometry.py
merge_refusal
¶
Why these element groups cannot be one, or None when they can: they must hold the same element types and carry the same properties. A merged element group is given one material and one thickness or section, so merging different ones would change the model without saying so.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group_ids
|
sequence of int
|
The element groups, by identifier. |
required |
Returns:
| Type | Description |
|---|---|
str or None
|
|
Source code in src/visualdynamics/core/geometry.py
merge_groups
¶
One element group from several (Merge Element Groups, Brandon 2026-09-27): the
elements of the rest moved into the first, the rest removed —
refused, with the reason, unless merge_refusal allows it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group_ids
|
sequence of int
|
The element groups; the first keeps its id, name and properties. |
required |
Returns:
| Type | Description |
|---|---|
dict of str to int
|
'into', the element group kept; 'groups', how many were merged into it; 'elements', how many elements moved. |
Source code in src/visualdynamics/core/geometry.py
delete_elements
¶
Remove elements by id. Ids that are not there are ignored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elem_ids
|
int or sequence of int
|
The identifiers, one or many. |
required |
Returns:
| Type | Description |
|---|---|
dict of str to int
|
How many of each kind were removed, including the dependents that went with them. |
Source code in src/visualdynamics/core/geometry.py
save
¶
Write the geometry to a file of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or PathLike
|
Where to write it. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/geometry.py
plot
¶
plot(unit_system: UnitSystem | None = None, **kwargs: Any) -> Any
Draw the geometry: nodes and elements.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_system
|
UnitSystem
|
Units to draw in. |
None
|
**kwargs
|
Any
|
Passed through to the scene. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget or plotter. |
Source code in src/visualdynamics/core/geometry.py
plot_dofs
¶
plot_dofs(source: Any, quantity: str, unit_system: UnitSystem | None = None, **kwargs: Any) -> Any
This geometry with labeled arrows at every DOF source
measures as quantity — the GUI's DOF arrows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
DataArray
|
The object whose degrees of freedom are drawn. |
required |
quantity
|
str
|
Which quantity's DOFs to show, such as 'acceleration'. |
required |
unit_system
|
UnitSystem
|
Units to draw in. |
None
|
**kwargs
|
Any
|
Passed through to the scene. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plotter the scene is in. |
Source code in src/visualdynamics/core/geometry.py
Psd
¶
Bases: _Bands, DataArray
Power spectral density: the declared unit is the engineering unit whose square-per-Hz the values are in (declare 'g' for g^2/Hz).
Held complex because a cross spectrum is complex — the phase between two channels is most of what a CPSD is for. An autospectrum is not: a channel against itself is a magnitude squared, real by construction. So the type allows complex and the object stores what it actually has, which for a specification or a set of ASDs is a real array of half the size.
Methods:
| Name | Description |
|---|---|
principal_shapes |
The dominant shape of the cross-spectral matrix at each line, |
animate |
This set on a geometry, as the GUI shows it. |
area |
The area under one record, over a band or over all of it. |
areas |
|
written |
Which lines say something: finite, and for a requirement |
extent |
(low, high) this spectrum speaks for, in hertz. |
to_octave |
This spectrum integrated onto proportional bands. |
coherence |
The ordinary coherence of every cross term this CPSD holds: |
Source code in src/visualdynamics/core/data.py
Methods:¶
principal_shapes
¶
The dominant shape of the cross-spectral matrix at each line, as (dofs, shapes (channels x lines), quantity).
The channels of one quantity form a square Hermitian matrix per
line; its largest eigenvalue's eigenvector, scaled by the square
root of that eigenvalue, is the principal operating deflection
shape — the direction of the output spectra's own CMIF, with
each channel's phase relative to the others and no reference to
choose. quantity picks which channels (the commonest when not
told). Refuses a set with no cross records — an autospectrum set
has no phase and its reading is the envelope — and an incomplete
block, whose eigenvectors would be shapes of holes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quantity
|
str
|
Which quantity, for a mixed object. |
None
|
Returns:
| Type | Description |
|---|---|
tuple of (list of str, numpy.ndarray, str)
|
The DOF labels, the dominant shape at each line, and the quantity they are in. |
Source code in src/visualdynamics/core/data.py
2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 | |
animate
¶
animate(geometry: Any, frequency: float | None = None, quantity: str | None = None, **kwargs: Any) -> Any
This set on a geometry, as the GUI shows it.
A CPSD — cross records present — animates its principal
operating deflection shape: the dominant eigenvector of the
cross-spectral matrix per line, each channel's phase relative
to the others. An autospectrum set has no phase, so it shows
the envelope instead: two copies deflected ±sqrt(PSD), color
reading dB below the loudest node at any line. Defaults to the
strongest line; frequency picks another, quantity which
measurement deflects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
Geometry
|
The geometry to move. |
required |
frequency
|
float
|
Which frequency line. |
None
|
quantity
|
str
|
Which quantity, for a mixed object. |
None
|
**kwargs
|
Any
|
Passed through to the scene. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget or plotter. |
Source code in src/visualdynamics/core/data.py
area
¶
The area under one record, over a band or over all of it.
The one integral. Whichever way this spectrum is read, it is read the same way here as it is drawn — that is what the field above is for, and why nothing outside this method chooses.
Units are the ordinate's times frequency, so the square root of it is an RMS for a PSD.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
int
|
Which record. |
0
|
low
|
float
|
The band to integrate over. |
None
|
high
|
float
|
The band to integrate over. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The area beneath the curve — the mean square, whose root is the RMS. |
Source code in src/visualdynamics/core/data.py
areas
¶
area over several bands at once: the comparison is
judged cell by cell, and a thousand cells must not cost a
thousand passes over the lines.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
int
|
Which record. |
required |
lows
|
array - like
|
The bands' edges, paired. |
required |
highs
|
array - like
|
The bands' edges, paired. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
One area per band; NaN where nothing is written. |
Source code in src/visualdynamics/core/data.py
written
¶
Which lines say something: finite, and for a requirement positive — a controller writes zero or NaN on every line it did not control, and those lines are not a requirement of nothing. Over one record, or any record when none is named.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
int
|
The record; every record when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
A boolean per line. |
Source code in src/visualdynamics/core/data.py
extent
¶
(low, high) this spectrum speaks for, in hertz.
For a density per bin, the outer edges of the written bins — a line stands for its whole bin. For a curve between breakpoints, the first and last written points. What octave banding clips its end bands to, and what a comparison is judged over (Brandon, 2026-09-19): a controller's target is stored on every FFT line to Nyquist and speaks only where it is written.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
int
|
The record; the union over every record when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
tuple of float, or None
|
None when nothing is written above zero hertz. |
Source code in src/visualdynamics/core/data.py
to_octave
¶
to_octave(per_octave: int | None = None, low: float | None = None, high: float | None = None) -> Psd
This spectrum integrated onto proportional bands.
An integration, not a resampling: each band takes the mean-square content that falls in it, divided by its own width, so the area under the spectrum — and therefore the RMS it carries — is unchanged. Reading the narrowband curve at each band center would throw away everything between the centers.
The band grid is absolute (see visualdynamics.core.octave), so this
needs no specification to be told about: the frequency range
only chooses which bands of the one fixed grid come back, and
two runs banded the same way land on the same bands whatever
their ranges were.
Cross terms come through complex, which is what makes this work for a CPSD as well as a PSD.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
per_octave
|
int
|
Bands per octave. |
None
|
low
|
float
|
The band to cover. |
None
|
high
|
float
|
The band to cover. |
None
|
Returns:
| Type | Description |
|---|---|
Psd
|
The same power, arranged on proportional bands. |
Source code in src/visualdynamics/core/data.py
coherence
¶
coherence() -> Coherence
The ordinary coherence of every cross term this CPSD holds: |S_pq|² / (S_pp S_qq), from the cross spectrum and the two autospectra beside it.
Banded or not — a coherence of an octave-banded CPSD is the banded quantity, each band's averaged cross spectrum against its averaged autospectra, and keeps the bands so it draws flat across them the way the CPSD does (2026-09-26, for the band-average paper). It is not the average of a narrowband coherence over the band, which would weight a band's lines equally whatever their power.
Returns:
| Type | Description |
|---|---|
Coherence
|
One record per cross term, response against reference. |
Source code in src/visualdynamics/core/data.py
ShapeSet
¶
ShapeSet(frequency: ArrayLike, damping: ArrayLike, coordinate: Sequence[str], shape_matrix: ArrayLike, modal_mass: ArrayLike | None = None, comment: str | Sequence[str] | None = None, mass_unit: str | None = None, description: Sequence[str] | None = None, unscaled: bool = False, modal_damping: ArrayLike | None = None)
Mode shapes over a shared set of DOFs.
shape_matrix is (modes, dofs). coordinate lists the DOF strings the
columns correspond to ('101X+').
A fitted set is also the record of the fit: reopening one in the app (Edit Fit) reconstructs the session that produced it, which is why the description and the scaling flag ride along with the numbers.
Attributes:
frequency: Hz per mode. A rigid-body mode is exactly 0.0 — the
FRF synthesis cancels its 0/0 by testing for that, so 'very
small' is not the same thing.
damping: Fraction of critical per mode, so 2% is 0.02.
coordinate: The DOF string of each column of shape_matrix.
shape_matrix: (modes, dofs). Complex for a complex mode; the
overlay and MAC machinery handles either.
modal_mass: Per mode. 1.0 throughout for a mass-normalized set,
which is what an eigensolution here produces. For complex
shapes it is modal A, the first-order scaling, which is what
synthesize_frf reads it as. Complex when an imported source
carried it complex — kept as measured, never squeezed real.
modal_damping: Complex modal damping per mode where a source
carried one (I-DEAS ADFs do), or None. Distinct from
damping, the viscous fraction of critical: this is the
complex-mode estimate as the identifying tool reported it.
mass_unit: What modal_mass is in, or None when undeclared.
description: Free text per mode — what the shape is, filled in
while reading the table ('first torsion').
comment: One line about the set as a whole.
unscaled: True when the fit had no drive point to pin the
mass-normalized scale. Shapes and MACs are unaffected;
modal masses are then a convention rather than physics, and
comparisons refuse to read a scale factor out of them.
Methods:
| Name | Description |
|---|---|
auto_mac |
MAC of every mode against every other; the diagonal is 1. |
covers |
Does the shape set have a coefficient at this DOF? |
synthesize_frf |
FRFs from the modal model, one row per DOF pair. |
delete_modes |
Remove the given modes in place; the last one is refused. |
define_units |
Declare the mass unit the shapes were normalized against. |
undefine_units |
Take the declaration back, restoring the file's raw coefficients. |
display_shapes |
Coefficients in the display system's 1/sqrt(mass); undefined pass |
unit_label |
'1/√kg' for the stored unit, or the display system's. |
mode_label |
'Mode 3 — 12.4 Hz, 2.0% damping'. |
save |
Write the shape set to a file of its own. |
plot_mac |
The MAC grid: this set against itself, or against |
animate |
This mode moving on a geometry, as the GUI animates it. |
plot |
The set's own reading: its auto-MAC, or one mode animated |
Attributes:
| Name | Type | Description |
|---|---|---|
num_shapes |
int
|
How many mode shapes the set holds. |
num_dofs |
int
|
How many degrees of freedom each shape covers. |
is_complex |
bool
|
Whether these are complex modes. Real normal modes move |
units_defined |
bool
|
Whether the shapes carry a mass unit, without which a |
Source code in src/visualdynamics/core/shapes.py
Attributes¶
is_complex
property
¶
Whether these are complex modes. Real normal modes move every DOF in phase; complex ones do not, which is what a damped or non-proportionally damped structure produces.
units_defined
property
¶
Whether the shapes carry a mass unit, without which a modal mass is a number with no scale behind it.
Methods:¶
auto_mac
¶
covers
¶
Does the shape set have a coefficient at this DOF?
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dof
|
str
|
A degree of freedom, such as '101Z+'. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
Whether the shapes include it. |
Source code in src/visualdynamics/core/shapes.py
synthesize_frf
¶
synthesize_frf(frequencies: ArrayLike, response_dof: Sequence[str], reference_dof: Sequence[str], modes: Sequence[int] | None = None, power: int = 0) -> ndarray
FRFs from the modal model, one row per DOF pair.
For real shapes, the second-order residue form
H_jk(f) = sum_r (iw)^power phi_jr phi_kr
/ (m_r (w_r^2 - w^2 + 2i z_r w_r w))
with w = 2pif — exact for mass-normalized shapes, with
modal_mass carrying any other scaling. For complex shapes, the
first-order form, a pole and its conjugate:
H_jk(f) = sum_r (iw)^power [ psi_jr psi_kr / (A_r (iw - l_r))
+ conj(psi_jr psi_kr / A_r) / (iw - conj(l_r)) ]
with the pole l_r = -z_r w_r + i w_r sqrt(1 - z_r^2) and
modal_mass read as modal A, the scaling a complex mode
carries (unity when a set has none). The two are the same FRF
for a real mode whose modal A is 2i w_d m_r, with w_d the damped
frequency, which is how they connect.
Which form is decided by the shapes alone, complex or real, as
sdynpy decides it. The second-order form cannot represent a
complex mode: however modal_mass is set, it gives the
conjugate pole the residue -R where the structure has conj(R),
and with modal A in modal_mass it is off by 2i w_d outright —
every imported complex set synthesized an answer rotated 90
degrees and 2 w_d times too small, until 2026-09-23. Modal A is
conjugated in the second term, where sdynpy's is not; the two
agree whenever modal A is real, and only the conjugate is right
when it is not — sdynpy is 2.5% of peak off the exact FRF on the
complex modal A of the test system (measured 2026-09-24).
power picks the response quantity: 0 displacement per force, 1
velocity, 2 acceleration. It applies inside the sum because a
real rigid-body mode's denominator is exactly -w^2: at w = 0 its
accelerance cancels to the finite residue, where an
after-the-fact multiply is 0/0 and a screenful of warnings. Its
displacement and velocity there are genuinely unbounded and
come back as nan.
Both forms are checked against the direct inverse of the dynamic
stiffness — proportionally damped for real shapes, and not
proportionally damped for complex ones — and against sdynpy's own
synthesis, all to rounding (tests/test_frf_synthesis_exact.py,
tests/test_modal_frf_oracle.py).
modes restricts the sum; a truncated synthesis beside the
measurement is what shows which modes the measurement actually
contains. Raises ValueError for a DOF the shapes do not cover;
covers says so in advance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frequencies
|
array_like
|
The lines to synthesize at, in Hz. |
required |
response_dof
|
sequence of str
|
The response degrees of freedom. |
required |
reference_dof
|
sequence of str
|
The drive degrees of freedom. |
required |
modes
|
sequence of int
|
Which modes to include. All of them when omitted. |
None
|
power
|
int
|
0 receptance, 1 mobility, 2 accelerance. |
0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The synthesized FRFs, one row per response and drive pair. |
References
- Ewins, D. J. (2000). Modal Testing: Theory, Practice and Application, 2nd ed. Research Studies Press. The residue form for real modes, and why non-proportional damping makes the modes complex.
- Maia, N. M. M., & Silva, J. M. M. (1997). Theoretical and Experimental Modal Analysis. Research Studies Press. The first-order (state-space) form, its conjugate pole pairs, and modal A as the scaling that goes with them.
Source code in src/visualdynamics/core/shapes.py
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 | |
delete_modes
¶
Remove the given modes in place; the last one is refused.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
indices
|
sequence of int
|
Which modes to remove. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/shapes.py
define_units
¶
define_units(mass_unit: str) -> ShapeSet
Declare the mass unit the shapes were normalized against.
Re-declaring reinterprets the file's values rather than scaling twice, so a wrong guess can be corrected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mass_unit
|
str
|
The unit modal mass is in. |
required |
Returns:
| Type | Description |
|---|---|
ShapeSet
|
Self, converted to SI in place. |
Source code in src/visualdynamics/core/shapes.py
undefine_units
¶
undefine_units() -> ShapeSet
Take the declaration back, restoring the file's raw coefficients.
Source code in src/visualdynamics/core/shapes.py
display_shapes
¶
display_shapes(unit_system: UnitSystem) -> ndarray
Coefficients in the display system's 1/sqrt(mass); undefined pass through unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_system
|
UnitSystem
|
The units to present in. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The shape matrix in display units. |
Source code in src/visualdynamics/core/shapes.py
unit_label
¶
unit_label(unit_system: UnitSystem | None = None) -> str
'1/√kg' for the stored unit, or the display system's.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_system
|
UnitSystem
|
Units to label in. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
How the shapes' own unit reads. |
Source code in src/visualdynamics/core/shapes.py
mode_label
¶
'Mode 3 — 12.4 Hz, 2.0% damping'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
i
|
int
|
Which mode. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A short label: its frequency, and its damping when known. |
Source code in src/visualdynamics/core/shapes.py
save
¶
Write the shape set to a file of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or PathLike
|
Where to write it. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/core/shapes.py
plot_mac
¶
plot_mac(other: ShapeSet | None = None, **kwargs: Any) -> Any
The MAC grid: this set against itself, or against other.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
ShapeSet
|
The set to compare against. This set against itself when omitted, which is how repeated modes show up. |
None
|
**kwargs
|
Any
|
Passed through to the plotting layer. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget. |
Source code in src/visualdynamics/core/shapes.py
animate
¶
animate(geometry: Geometry, mode: int = 0, **kwargs: Any) -> Any
This mode moving on a geometry, as the GUI animates it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
Geometry
|
The geometry to move. |
required |
mode
|
int
|
Which mode, by index. |
0
|
**kwargs
|
Any
|
Passed through to the scene. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget or plotter. |
Source code in src/visualdynamics/core/shapes.py
plot
¶
plot(geometry: Geometry | None = None, mode: int = 0, **kwargs: Any) -> Any
The set's own reading: its auto-MAC, or one mode animated when a geometry says where to put it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
Geometry
|
The geometry to draw on. |
None
|
mode
|
int
|
Which mode. |
0
|
**kwargs
|
Any
|
Passed through to the scene. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget or plotter. |
Source code in src/visualdynamics/core/shapes.py
ShockSpecification
¶
ShockSpecification(*args: Any, warning_lower: ArrayLike | None = None, warning_upper: ArrayLike | None = None, abort_lower: ArrayLike | None = None, abort_upper: ArrayLike | None = None, **kwargs: Any)
What a shock test was controlled to: an SRS, and its band.
The same relationship a Specification has to a Psd. A shock
target is written as a required SRS with tolerance either side of
it — conventionally +6 dB and -3 dB, which is a factor of 2 up and
0.707 down — and the limits are those curves.
Written at one Q, and read at that Q: a target quoted at Q = 10 says nothing about what the same shock does to a Q = 50 oscillator, so the amplification travels with the target the way it travels with a measurement.
Source code in src/visualdynamics/core/data.py
Specification
¶
Specification(*args: Any, warning_lower: ArrayLike | None = None, warning_upper: ArrayLike | None = None, abort_lower: ArrayLike | None = None, abort_upper: ArrayLike | None = None, **kwargs: Any)
What a random vibration test was controlled to: a PSD, and its band.
A specification is a PSD in every respect — same abscissa, same
quantity, same conversions — with the four limit curves Bounded
carries. So it inherits both, rather than reimplementing either.
Methods:
| Name | Description |
|---|---|
reading_of |
How a specification written at these frequencies reads: |
written |
Which lines the requirement is written on: finite and |
to_octave |
This specification integrated onto proportional bands, its |
Source code in src/visualdynamics/core/data.py
Methods:¶
reading_of
staticmethod
¶
How a specification written at these frequencies reads: 'bin', a density per line, for many lines on an even grid — a controller's target on its FFT lines — and 'log_log', breakpoints of a power law, for a few unevenly spaced points (Brandon, 2026-09-19: "breakpoint specifications have very few frequency lines and I would expect an uneven spacing of them"). Both octave-band and narrowband specifications step; only a breakpoint curve is drawn as the law between its points.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frequencies
|
array_like
|
The specification's frequency lines, ascending. |
required |
Returns:
| Type | Description |
|---|---|
str
|
'bin' or 'log_log'. |
Source code in src/visualdynamics/core/data.py
written
¶
Which lines the requirement is written on: finite and positive. A controller writes zero on the lines it did not control, and zero is not a requirement of silence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
int
|
The record; every record when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
A boolean per line. |
Source code in src/visualdynamics/core/data.py
to_octave
¶
to_octave(per_octave: int | None = None, low: float | None = None, high: float | None = None) -> Specification
This specification integrated onto proportional bands, its warning and abort limits with it.
The same area rule as Psd.to_octave, read the way the object
is: a specification computed on lines is a density and each
line is a bin; one written at breakpoints is a power law
between them, and each band takes the exact area under that law
(compliance.log_log_area) over the part of the band the
specification covers, divided by the band's width. The limits
are curves written the same way and go through the same rule,
so a band's warning line stands in the same relation to its
target as the breakpoints did. Cross terms of a breakpoint
specification are not power laws (a phase is not), so they are
read onto a fine log grid the way the authoring sheet reads
them — magnitude log–log, phase straight — and integrated there.
The result is a density per band (interpolation 'bin', the
bands' widths carried), which is what a banded measurement is
compared against band for band. Brandon, 2026-09-18: the
specification and the measurement it judges should be
convertible alike, so a compliance can be read on bands at both
ends — reversing the earlier reasoning (PLAN.md, "Octave
bands") that a written curve needed no banding because the
comparison integrates it exactly; it does, and a banded
specification is still the object a person asks to see and
hand on.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
per_octave
|
int
|
Bands per octave. |
None
|
low
|
float
|
The band to cover. |
None
|
high
|
float
|
The band to cover. |
None
|
Returns:
| Type | Description |
|---|---|
Specification
|
The same power and the same limits, arranged on proportional bands. |
Source code in src/visualdynamics/core/data.py
3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 | |
Spectrum
¶
Spectrum(abscissa: ArrayLike, ordinate: ArrayLike, response_dof: str | Sequence[str], reference_dof: str | Sequence[str] | None = None, ordinate_dim: str | Sequence[str] | None = None, comment: str | Sequence[str] | None = None, ordinate_unit: str | Sequence[str | None] | None = None, reference_unit: str | Sequence[str | None] | None = None, dimension_hint: str | Sequence[str | None] | None = None, block: str | Sequence[str] | None = None)
Bases: DataArray
A linear spectrum: amplitude and phase at each frequency line.
The complex average of a record's frames, not a power average — so
content whose phase is random frame to frame averages toward zero,
which is what makes this the wrong reading for burst random and the
right one for a deterministic signal. A Psd is the power average
and does not have that property.
Methods:
| Name | Description |
|---|---|
animate |
The operating deflection shape at one frequency line, moving |
Source code in src/visualdynamics/core/data.py
Methods:¶
animate
¶
The operating deflection shape at one frequency line, moving
on a geometry as the GUI animates it. Defaults to the strongest
line; frequency picks another.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
Geometry
|
The geometry to move. |
required |
frequency
|
float
|
Which frequency line. The strongest when omitted. |
None
|
**kwargs
|
Any
|
Passed through to the scene. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plot widget or plotter. |
Source code in src/visualdynamics/core/data.py
Srs
¶
Bases: DataArray
A shock response spectrum: the peak an oscillator reached.
Not a spectrum of the shock. Every point is the largest response a single-degree-of-freedom oscillator of that natural frequency ever reached while its base was shaken by the measured transient — one number out of one whole run of one filter. Two shocks with the same SRS can look nothing alike, and an SRS cannot be turned back into a time history, because the phase that produced each peak is gone.
The abscissa is the oscillator's natural frequency, not a frequency
in the shock — laid out in decades (log_abscissa): an SRS is
specified at octave-spaced natural frequencies, and drawn linear
the bottom five octaves crush into the left margin. The ordinate is in the quantity the base was measured
in — an acceleration transient gives an acceleration SRS.
q and kind change what the curve means, so they belong to the
object rather than to whoever happened to compute it: an SRS at
Q = 10 and the same shock at Q = 50 are different curves, and a
maximax reading is not a positive one. See visualdynamics.core.srs.
Attributes:
| Name | Type | Description |
|---|---|---|
damping |
float
|
The damping ratio the amplification factor means. |
Source code in src/visualdynamics/core/data.py
TimeHistory
¶
TimeHistory(abscissa: ArrayLike, ordinate: ArrayLike, response_dof: str | Sequence[str], reference_dof: str | Sequence[str] | None = None, ordinate_dim: str | Sequence[str] | None = None, comment: str | Sequence[str] | None = None, ordinate_unit: str | Sequence[str | None] | None = None, reference_unit: str | Sequence[str | None] | None = None, dimension_hint: str | Sequence[str | None] | None = None, block: str | Sequence[str] | None = None)
Bases: DataArray
A measurement against time: the record as it was acquired.
Everything else in a random or shock test is derived from one of
these, and the deriving is here — spectra, PSDs, the full CPSD
matrix, multiple coherence, shock response spectra. Two things ride
along that say how to read it: averaging, the frames a spectrum
is averaged over, and shocks, the events an SRS is computed from.
Both are the app's two views of a trace, and both are stored on the
history rather than passed at the call, so a PSD and the coherence
beside it cannot describe different measurements.
Methods:
| Name | Description |
|---|---|
compute_spectra |
The averaged spectrum of every channel — sdynpy's convention. |
channel_key |
What makes record |
suggest_averaging |
Averaging parameters worked out from the record itself. |
default_averaging |
The averaging a view of this record opens on. |
capture_indices |
Which playing each record is: 0 for a channel's first |
suggest_truncation |
The whole record — the only neutral span. |
truncate |
This record cut to a span ( |
suggest_sine_extraction |
The extraction setting a record starts from: automatic |
suggest_filtering |
A starting low-pass: a tenth of the sample rate, order 4. |
filter |
This record through its filter ( |
integrate |
One integration — acceleration to velocity, velocity to |
differentiate |
One differentiation — displacement to velocity, velocity to |
srs_windows |
The stretches an SRS of this record would read, settled. |
srs_band |
(low, high) in Hz: the band these windows can support. |
compute_srs |
A shock response spectrum for every channel of every shock. |
compute_psds |
One-sided auto-power spectral density per channel, averaged |
psd_type |
What a PSD of this history is. |
srs_type |
What an SRS of this history is — see |
compute_cpsds |
The full cross-spectral density matrix, averaged across the |
drive_dofs |
The DOFs this history looks like it was driven at. |
default_role |
The role a channel measuring |
channel_identities |
Every channel as (DOF, quantity), in first-record order — |
channel_roles |
Every channel's role, {(DOF, quantity): role}: the one |
reference_channels |
The channels an FRF or a coherence takes as references, as |
response_channels |
The channels an FRF or a coherence explains, as (DOF, |
compute_frfs |
The frequency response functions, one per response/drive pair. |
compute_multiple_coherence |
How much of each response the drives together account for. |
to_sep005 |
This record as SEP 005 timeseries — the sdypy ecosystem's |
Attributes:
| Name | Type | Description |
|---|---|---|
sample_rate |
float
|
Samples per second, from the abscissa — which must be even. |
records_per_channel |
dict[tuple[str, str, str | None], int]
|
{channel key: how many records carry it}. |
split_into_frames |
bool
|
Whether the records are already the averages. |
average_counts |
tuple[int, int]
|
(fewest, most) frames any one channel will be averaged over. |
Source code in src/visualdynamics/core/data.py
Attributes¶
sample_rate
property
¶
Samples per second, from the abscissa — which must be even.
records_per_channel
property
¶
{channel key: how many records carry it}.
Usually one. A capture a controller saved frame by frame holds one record per average.
split_into_frames
property
¶
Whether the records are already the averages.
A controller that saves its spectral captures writes each frame as its own record. There is then nothing to slice and nothing to overlap: the frame length and the count are settled by the file, and the only parameter left to choose is the window.
average_counts
property
¶
(fewest, most) frames any one channel will be averaged over.
The two agree for anything a controller wrote, which saves every channel the same number of times. They part only for a history assembled by hand out of unequal captures, and then the table has to say so rather than quote a number that is true of some channels and not others.
Methods:¶
compute_spectra
¶
compute_spectra() -> Spectrum
The averaged spectrum of every channel — sdynpy's convention.
A channel's frames — its records across the averages — are each
FFT'd single-sided with no amplitude scaling (numpy's rfft,
norm='backward': a sine of amplitude A on a bin reads AN/2),
rectangular window, and averaged as the complex mean*, exactly
what sdynpy's TimeHistoryArray.fft does with frames. Phase is
preserved; content whose phase is random frame to frame — a
burst random excitation's response — averages toward zero,
which is that convention's documented behavior. Returns a
Spectrum with one record per channel, carrying the channel's
own quantity and units.
Source code in src/visualdynamics/core/data.py
channel_key
¶
What makes record i the same channel as another.
The DOF and what is measured there, never the DOF alone: a drive point carries a force record and an acceleration record at the same DOF, and those two are not each other's averages. This is the key the framing groups on, so counting channels and averaging them cannot disagree about what a channel is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
i
|
int
|
Which record. |
required |
Returns:
| Type | Description |
|---|---|
tuple of (str, str, str or None)
|
The DOF, quantity and unit that identify the channel — records sharing this key are the same channel. |
Source code in src/visualdynamics/core/data.py
suggest_averaging
¶
suggest_averaging(**kwargs: Any) -> Averaging
Averaging parameters worked out from the record itself.
A run holds more than the test — the shaker coming up, a
reduced-level check, whatever was still recording afterwards —
and this finds the settled stretch worth averaging and as many
frames as it will carry. See visualdynamics.core.detect.
A record that already carries an averaging — a controller's own recipe from an import, or one set by hand — keeps its frame length, window, overlap and detrend: only the start and the count are worked out (Brandon, 2026-09-19: Detect must not override the frame length, window and overlap the controller used). A bare record gets the detector's own recipe.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Any
|
Overrides for individual parameters, such as |
{}
|
Returns:
| Type | Description |
|---|---|
Averaging
|
Parameters worked out from the record itself. |
Source code in src/visualdynamics/core/data.py
default_averaging
¶
default_averaging() -> Averaging
The averaging a view of this record opens on.
The record's own when its start was chosen — by a file's import
that detected it, a drag, a typed number. Otherwise the
detector's answer, keeping whatever recipe the record carries
(frame length, window, overlap), as suggest_averaging does —
unless that answer holds fewer frames than the record's own
asks, when the record's own stands: a burst record has no
settled stretch to find, and the detector once returned four of
the twenty averages a modal survey asked for (2026-09-19). A
bare record gets the detector's recipe; one that cannot be
framed at all, each record as one frame (Brandon, 2026-09-30:
the view should open where Detect would put it, not at t = 0).
Returns:
| Type | Description |
|---|---|
Averaging
|
|
Source code in src/visualdynamics/core/data.py
capture_indices
¶
Which playing each record is: 0 for a channel's first
record, 1 for its second, and so on — in exactly the order
_spectral_frame pools them, so the playing the averaging
view pages to is one of the playings the average adds up. A
channel is a channel_key group, the same grouping the
pooling uses.
Source code in src/visualdynamics/core/data.py
suggest_truncation
¶
The whole record — the only neutral span.
A starting point for the truncate view's handles, never a default the act adopts: keeping everything is not an act, so Truncate Data refuses until a real span is set.
Source code in src/visualdynamics/core/data.py
truncate
¶
truncate(truncation: Any = None) -> TimeHistory
This record cut to a span (core.truncate.truncate), using
the history's own truncation unless one is passed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
truncation
|
Truncation
|
The start and stop, in seconds on the record's own clock.
Defaults to the record's own |
None
|
Returns:
| Type | Description |
|---|---|
TimeHistory
|
The samples inside the span, every channel, the clock kept. |
Source code in src/visualdynamics/core/data.py
suggest_sine_extraction
¶
The extraction setting a record starts from: automatic smoothing — chosen from the recording against the specification when the levels are read — with the clock refinement on.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
specification
|
SineSweepSpecification
|
The sweep the levels would be read against. When given,
the smoothing the automatic would choose is worked out now
and rides the setting as |
None
|
Returns:
| Type | Description |
|---|---|
SineExtraction
|
|
Source code in src/visualdynamics/core/data.py
suggest_filtering
¶
A starting low-pass: a tenth of the sample rate, order 4.
A low-pass rather than any other kind, because cutting noise above the content is the reach-for-first case; the filter view offers high- and band-pass beside it.
A judgment, not a detection — nothing in the record says where its content stops being signal. A tenth of the rate is where the integrate/differentiate round trip was measured at ~2% RMS (core.filters), and it sits below the mounted-resonance range a shock accelerometer pollutes. The filter view exists precisely so this number gets looked at rather than trusted.
Source code in src/visualdynamics/core/data.py
filter
¶
filter(filtering: Any = None) -> TimeHistory
This record through its filter (core.filters.filtered) —
low-, high- or band-pass, whichever the filtering describes —
using the history's own filtering unless one is passed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filtering
|
Filtering
|
The pass-band edges and order. Defaults to the record's
own |
None
|
Returns:
| Type | Description |
|---|---|
TimeHistory
|
Every channel through the filter, zero phase. |
Source code in src/visualdynamics/core/data.py
integrate
¶
integrate(drift_corner: Any = ...) -> TimeHistory
One integration — acceleration to velocity, velocity to
displacement (core.filters.integrate). ... takes the
default drift corner; None integrates raw, drift and all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
drift_corner
|
float or None
|
High-pass corner in Hz applied after integrating, so a sensor
bias cannot become a ramp. |
...
|
Returns:
| Type | Description |
|---|---|
TimeHistory
|
Acceleration becomes velocity, velocity becomes displacement. Other quantities are left out. |
Source code in src/visualdynamics/core/data.py
differentiate
¶
differentiate() -> TimeHistory
One differentiation — displacement to velocity, velocity to
acceleration (core.filters.differentiate).
srs_windows
¶
The stretches an SRS of this record would read, settled.
The fallback chain compute_srs has always used, extracted so
the shock panel's derived rows and the spectrum itself cannot
disagree about it (one implementation): the shocks the record
carries; failing those, the averaging frames when the record
is being read as frames; failing everything, the whole record
as one window. Clipped to the record either way.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shocks
|
sequence of Shock
|
Override windows; the record's own when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
list of Shock
|
The settled windows, clipped to the record. |
Source code in src/visualdynamics/core/data.py
srs_band
¶
(low, high) in Hz: the band these windows can support.
From a frequency low enough that the shortest window still
holds a cycle of it — a curve is one grid across every event,
so the shortest is what the grid has to fit — up to a fifth
of the sample rate, above which the ramp-invariant filter is
being asked about frequencies the record cannot resolve.
What compute_srs uses when no band is given, and what the
shock panel states beside its settings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shocks
|
sequence of Shock
|
Override windows; the record's own when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
tuple of float
|
(low, high) in Hz. |
Source code in src/visualdynamics/core/data.py
compute_srs
¶
compute_srs(shocks: Sequence[int] | None = None, low: float | None = None, high: float | None = None, per_octave: int | None = None, q: float | None = None, kind: str = 'maximax') -> Srs
A shock response spectrum for every channel of every shock.
The parameters live on the history, the way averaging does: pass
shocks to override, or leave it and the windows already on the
object are used. With none anywhere, the whole record is one
window, which is what a history holding a single trimmed
transient is.
The windows are the shocks the record carries, or — when it is being read as frames rather than events — the frames. A specification carries neither and is one window: it is a single playing of a waveform, whole.
One curve per channel per event, never averaged across events.
A shock test is judged on the worst shock, and the mean of four
of them describes none of them. Which event a curve came from is
in block, exactly as which average a frame came from is.
The band defaults to what the windows can support: from a frequency low enough that the shortest window still holds a cycle of it — a curve is one grid across every event, so the shortest is what the grid has to fit — up to a fifth of the sample rate, above which the ramp-invariant filter is being asked about frequencies the record cannot resolve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shocks
|
sequence of int
|
Which shock windows to use. All of them when omitted. |
None
|
low
|
float
|
The natural-frequency band, in Hz. |
None
|
high
|
float
|
The natural-frequency band, in Hz. |
None
|
per_octave
|
int
|
Frequency lines per octave. |
None
|
q
|
float
|
The oscillator amplification. |
None
|
kind
|
str
|
Which peak to keep: 'maximax' (largest magnitude of either sign), 'positive' or 'negative'. |
'maximax'
|
Returns:
| Type | Description |
|---|---|
Srs
|
One curve per channel per shock. |
Source code in src/visualdynamics/core/data.py
1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 | |
compute_psds
¶
One-sided auto-power spectral density per channel, averaged across the frames.
Welch's method where each average is already its own frame: rectangular window, no overlap, Gxx = 2|X|²/(fs·N) except at DC and Nyquist, which have no mirror image to fold in and so are |X|²/(fs·N) — as scipy's welch has it, and what makes the spectrum's total the record's mean square. The frames' powers are averaged. Power is phase-insensitive, so burst random's random phase costs nothing here. Values land in (SI unit)²/Hz with the Psd dimension convention ('acceleration**2/frequency'); a channel with undefined units stays undefined, its hint squared along.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
averaging
|
Averaging
|
How to cut the record into frames. Defaults to the record's
own |
None
|
Returns:
| Type | Description |
|---|---|
Psd
|
One auto-power spectral density per channel. |
Source code in src/visualdynamics/core/data.py
psd_type
¶
psd_type() -> type[Psd]
What a PSD of this history is.
A plain record's spectra are plain spectra. A target's are
still a target: the PSD of a waveform the article was required
to see is the spectrum it was required to see, and losing that
on the way through an FFT would leave two objects of the same
class with nothing but a name to say which was the requirement.
Overridden in TransientSpecification rather than decided by
the caller, so a script and the app cannot disagree.
Source code in src/visualdynamics/core/data.py
compute_cpsds
¶
The full cross-spectral density matrix, averaged across the frames — every channel against every channel, not just each against itself.
Gxy = 2·conj(X)·Y/(fs·N), not doubled at DC and Nyquist, which is
compute_psds on the diagonal, where conj(X)·X is |X|². The
cross terms are how two channels move together, which is most of
what a CPSD is for, and they are what a PSD throws away.
Laid out as the importer lays an imported matrix out: one record
per (response, reference) pair, row by row, so an n-channel
history gives n² records that read as a grid. A cross term's
dimension is the product of the two, a*b/frequency, against
a**2/frequency down the diagonal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
averaging
|
Averaging
|
How to cut the record into frames. Defaults to the record's
own |
None
|
Returns:
| Type | Description |
|---|---|
Psd
|
The full cross-spectral matrix, every channel against every channel. |
Source code in src/visualdynamics/core/data.py
drive_dofs
¶
The DOFs this history looks like it was driven at.
A guess from the quantities alone, and it is only ever a default. What actually makes a channel a drive is that the controller had a feedback device on it, which the channel table records and a time history does not.
Source code in src/visualdynamics/core/data.py
default_role
classmethod
¶
The role a channel measuring quantity has when nothing
has said otherwise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quantity
|
str
|
What the channel measures, as |
required |
Returns:
| Type | Description |
|---|---|
str
|
'reference', 'response' or 'monitor'. |
Source code in src/visualdynamics/core/data.py
channel_identities
¶
Every channel as (DOF, quantity), in first-record order —
the identity roles names a channel by.
channel_roles
¶
Every channel's role, {(DOF, quantity): role}: the one
reading FRFs, multiple coherence and the grid's Role column
share. What roles says, and for a channel it does not name,
the default from the quantity (default_role): a force a
reference, a motion a response, anything else a monitor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stated
|
bool
|
False gives the guess alone, whatever |
True
|
Returns:
| Type | Description |
|---|---|
dict of (str, str) to str
|
Every channel's role, in first-record order. |
Source code in src/visualdynamics/core/data.py
reference_channels
¶
The channels an FRF or a coherence takes as references, as (DOF, quantity) pairs.
Omitted, the channels whose role is 'reference'
(channel_roles). A call may instead name them, as a script
does — as (DOF, quantity) pairs, or by bare DOF, where a DOF
picks the excitation channel at the point (the force at a drive
point, not the accelerometer beside it, which is what a
controller computes against) and only where there is none does
the DOF alone decide.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
references
|
sequence of str or (str, str)
|
References to use in place of the roles. |
None
|
Returns:
| Type | Description |
|---|---|
list of (str, str)
|
The reference channels, each once; a name that matches no channel is left out, and the caller says so. |
Source code in src/visualdynamics/core/data.py
response_channels
¶
The channels an FRF or a coherence explains, as (DOF,
quantity) pairs: every channel that is neither a reference nor a
monitor. With references named by the call, a channel whose
role is 'reference' but that the call left out is a response —
a script asking for one drive of two gets the other explained —
and a monitor stays out either way.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
references
|
sequence of str or (str, str)
|
As |
None
|
Returns:
| Type | Description |
|---|---|
list of (str, str)
|
|
Source code in src/visualdynamics/core/data.py
compute_frfs
¶
compute_frfs(references: Sequence[str] | None = None, averaging: Averaging | None = None, method: str = 'Hv') -> Frf
The frequency response functions, one per response/drive pair.
The three estimators differ in one assumption — where the noise is — and agree wherever there is little of it. They part company exactly where a measurement is worst, which is why the choice matters and why it is a choice.
H1 assumes the noise is on the response. It biases low at resonance, where the response is large and the force small, and it is what a controller computes and a modal fit expects.
Gfx = Gff H, so H = Gff^-1 Gfx
With one reference that is the textbook Gfx / Gff. With
several it is the MIMO estimate, and the matrix inverse is the
whole point: two shakers driving one article are correlated, and
dividing each response by each drive separately would credit
both with the same motion.
H2 assumes the noise is on the reference, and biases high
at anti-resonance for the mirror-image reason. With one
reference it is Gxx / Gxf, one response at a time. With
several references it needs as many equations as unknowns, and
there are exactly enough only when the system is square — as
many responses as references — where it becomes the classical
coupled form Gxx * Gfx^-1 (Rocklin, Crowley and Vold, 1985),
computed here exactly as sdynpy computes it (matched by
decision, Brandon 2026-08-28, and pinned against its numbers).
The coupling is worth knowing about: every response feeds one
matrix inverse, so a channel's H2 depends on which other
channels are in the set — measured at 0.2% on the oracle
signals, growing with noise — where H1 and Hv rows never do. A
non-square multi-reference set is refused; Hv answers the same
noise-on-both question per response, uncoupled.
Hv (the default) assumes noise on both and asks for neither: it is the total-least-squares fit, the null direction of
[[Gff, Gfx], [Gxf, Gxx]]
taken as the eigenvector of its smallest eigenvalue, per response and per line. It falls between H1 and H2 — strictly between, wherever the coherence is under one — and needs no claim about which instrument is the better one. It is the default because that claim is the one a test least often gets to make honestly: an accelerometer out on a structure and a force cell in the load path are both imperfect, in different places.
Note what a total-least-squares fit means with units in play: it weighs a unit of error on the force against a unit of error on the acceleration, and those are not the same thing. That is baked into the estimator and is the received formulation; it is why Hv is a middle reading and not a better one.
A pseudo-inverse rather than a solve for H1, because two shakers
can be very nearly the same drive and Gff is then close to
singular — where a solve raises or returns nonsense, a
pseudo-inverse gives the least-squares answer the estimate is
asking for anyway.
Built on the same frames compute_psds averages and the same
references compute_multiple_coherence uses, so the coherence
beside an FRF is that FRF's coherence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
references
|
sequence of str
|
The drive DOFs. Detected from the record when omitted. |
None
|
averaging
|
Averaging
|
How to cut the record into frames. Defaults to the record's
own |
None
|
method
|
str
|
The estimator: 'Hv', 'H1' or 'H2'. |
'Hv'
|
Returns:
| Type | Description |
|---|---|
Frf
|
One record per response and drive pair. |
References
The coupled multi-reference H2 form is Rocklin, Crowley and Vold's; the rest set out the three estimators and their errors.
- Rocklin, G. T., Crowley, J., & Vold, H. (1985). "A comparison of H1, H2 and Hv frequency response functions." Proceedings of the International Modal Analysis Conference (IMAC).
- Ewins, D. J. (2000). Modal Testing: Theory, Practice and Application, 2nd ed. Research Studies Press. Which estimator biases which way, and why the bias is worst exactly where the measurement matters.
- Bendat, J. S., & Piersol, A. G. (2010). Random Data: Analysis and Measurement Procedures, 4th ed. Wiley. The single- and multiple-input frequency response estimates and their errors.
- Van Huffel, S., & Vandewalle, J. (1991). The Total Least Squares Problem: Computational Aspects and Analysis. SIAM. The fit Hv performs, and what it means to weigh error in one measured quantity against error in another.
Source code in src/visualdynamics/core/data.py
1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 | |
compute_multiple_coherence
¶
compute_multiple_coherence(references: Sequence[str] | None = None, averaging: Averaging | None = None) -> MultipleCoherence
How much of each response the drives together account for.
Ordinary coherence asks what one reference explains. Multiple coherence asks what a whole set of them explains at once, which is the only useful question in a MIMO test: two shakers driving one article are correlated with each other, so a response can look poorly coherent with either one alone while being fully accounted for by the pair.
For a response x and references r,
gamma^2 = (Grx^H Grr^-1 Grx) / Gxx
— the power of the best linear prediction of x from all the references at once, over the power actually measured. With one reference it collapses to the ordinary coherence, which is the cheapest check that the algebra is right.
Built on the same frames compute_psds averages, so it covers
the stretch of record the averaging view has set and no other:
a coherence worked out over the whole file would describe a
different measurement from the PSD beside it.
references and the framing are _cross_spectral_frame's, the
same ones compute_frfs uses — so the coherence beside an FRF
is that FRF's coherence and not a differently-framed one.
A pseudo-inverse rather than a solve, because two shakers driving one article can be very nearly the same drive and the reference matrix is then close to singular — where a solve raises or returns nonsense, a pseudo-inverse gives the least-squares answer the estimate is asking for anyway.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
references
|
sequence of str
|
The drive DOFs. Detected from the record when omitted. |
None
|
averaging
|
Averaging
|
How to cut the record into frames. Defaults to the record's
own |
None
|
Returns:
| Type | Description |
|---|---|
MultipleCoherence
|
One curve per response channel. |
References
The multiple coherence function is standard; these are where it and the bias of an estimate of it are set out.
- Bendat, J. S., & Piersol, A. G. (2010). Random Data: Analysis and Measurement Procedures, 4th ed. Wiley. Multiple-input systems: the multiple coherence function, read as the fraction of a response's power the whole set of references accounts for.
- Carter, G. C., Knapp, C. H., & Nuttall, A. H. (1973). "Estimation of the magnitude-squared coherence function via overlapped fast Fourier transform processing." IEEE Transactions on Audio and Electroacoustics, 21(4), 337-344. doi:10.1109/TAU.1973.1162496 The bias of a coherence estimate against the number of averages, which is why a coherence from too few frames reads high.
Source code in src/visualdynamics/core/data.py
2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 | |
to_sep005
¶
This record as SEP 005 timeseries — the sdypy ecosystem's
interchange form (io.sep005 reads them back).
timeseries = history.to_sep005('run 4')
Returns the standard's list form: usually one dict, and one
per unit where channels mix. The sdypy validator holds a
series' unit_str to a single string, so accelerometers
beside a force gauge cannot be one compliant series — the list
of series is exactly what the standard provides for that, and a
split series wears the unit in its name so two of them stay
distinguishable.
Values go exactly as they are held: SI where units are defined,
with unit_str naming the SI unit, and the file's raw
numbers where they are not, with unit_str empty — the
standard allows an empty unit, and inventing one would claim a
scale nobody declared. fs says the sampling when it is
even; an uneven record sends its time vector, which the
standard equally accepts. quantity rides where the
standard has a letter for what a series measures.
name defaults to the comment when the record carries one,
because a SEP 005 series must be named and the comment is the
nearest thing to a name an object holds — the project knows
what it called this record, the record does not.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
A name for the series. |
None
|
Returns:
| Type | Description |
|---|---|
list of dict
|
One SEP 005 timeseries mapping per channel. |
Source code in src/visualdynamics/core/data.py
2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 | |
TransientSpecification
¶
TransientSpecification(abscissa: ArrayLike, ordinate: ArrayLike, response_dof: str | Sequence[str], reference_dof: str | Sequence[str] | None = None, ordinate_dim: str | Sequence[str] | None = None, comment: str | Sequence[str] | None = None, ordinate_unit: str | Sequence[str | None] | None = None, reference_unit: str | Sequence[str | None] | None = None, dimension_hint: str | Sequence[str | None] | None = None, block: str | Sequence[str] | None = None)
Bases: TimeHistory
What a transient test was controlled to: a target time history.
A different thing from a ShockSpecification, and the difference is
worth keeping straight because the two get called by each other's
names. A shock specification is an SRS: a required response
spectrum, and a controller meets it by producing some transient
whose spectrum lands inside the band. A transient specification
is a waveform: this acceleration, sample by sample, and the
controller inverts the structure's transfer function to reproduce
it. Rattlesnake can run the second today; the first it cannot.
So this is a time history that happens to be a target, and the comparison it invites is against another time history — what the article actually did — rather than against a band. It carries no limits for that reason: a tolerance on a waveform is not a settled idea the way a tolerance on a spectrum is, and inventing one here would be inventing a convention rather than reading one.
Its derived spectra stay targets. A PSD of this is a
Specification and an SRS of it is a ShockSpecification — both
without limits, for the reason above — because the spectrum of a
waveform the article was required to see is the spectrum it was
required to see. Left as plain objects they would be
indistinguishable from the response's own spectra but for a name,
and the comparison between them would have to be made by hand
instead of by type, which is the one thing this whole arrangement
exists to avoid.
Methods:
| Name | Description |
|---|---|
psd_type |
What a PSD of this record is: still a specification. |
srs_type |
What an SRS of this record is: a shock specification, for |
Source code in src/visualdynamics/core/data.py
Methods:¶
psd_type
¶
psd_type() -> type[Psd]
What a PSD of this record is: still a specification.
The spectrum of a required waveform is itself a requirement,
so it comes back as Specification rather than a plain Psd.
Source code in src/visualdynamics/core/data.py
View
¶
The way a geometry opens in 3-D: where the eye is, seen from the model's center, and which way is up on screen.
Only a direction: every view is fitted to what it shows, so there is no distance or zoom to keep. Set once on a geometry, it is how the app opens it and everything drawn on it, how Reset View returns, and how the report's scenes and exported figures are drawn (Brandon, 2026-09-27) — a model built Y-up opens upright without its nodes being turned.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
eye
|
sequence of 3 floats
|
The direction from the model toward the eye, in the global frame; any length. |
(1, 1, 1)
|
up
|
sequence of 3 floats
|
Which way is up on screen. Only its part across the line of sight
counts, so it need not be exactly perpendicular to |
(0, 0, 1)
|
Examples:
A model built with Y up, seen from the front, above and to the right:
Methods:
| Name | Description |
|---|---|
basis |
(right, up) on screen, as unit vectors in the global frame — |
Source code in src/visualdynamics/core/geometry.py
Project
¶
Project(name: str = 'Project', objects: dict[str, Any] | None = None, active_geometry: str | None = None, project_type: str | None = None, object_groups: Iterable[ObjectGroup] | None = None, provenance: dict[str, dict[str, Any]] | None = None)
Bases: dict
Every object in one test, by name, plus the structure around them.
A Project is the name-to-object mapping, so project['FRF'],
list(project) and project.items() read the way the tree reads.
It is also what the desktop app holds: the window's objects,
links, project_type and active_geometry are properties over one
of these, and its buttons call the verbs below. A project built by
clicking and one built by calling are the same object, and open in
each other.
Objects are usually reached by type rather than by name —
project.geometry, project.basis.frf, project.other.shapes. The
singular gives the one there is and says so when several qualify; the
plural is always a list.
Attributes:
links: The groups objects have been declared to belong to, as
{'members': [...], 'role': 'Basis' | None}.
Association is explicit here, never inferred from names.
project_type: What kind of test this is — 'Modal Test', 'Random
Vibration', 'Shock', 'Transient' — which decides the report
template and the skeleton of slots the tree shows.
active_geometry: The name of the geometry data is drawn on when
nothing says otherwise.
name: What the project is called, which is what a saved .vdyn
and a rendered report are titled.
Methods:
| Name | Description |
|---|---|
grouped_names |
[(group or None, [names])] in the order the tree shows them: |
ordered_names |
Every name, flat, in the order the tree shows them. |
add |
Add an object under a unique name; returns the name used. |
duplicate |
Copies of objects, added beside them (Copy, then Paste, in |
import_file |
Import a file into this project; returns the names added. |
remove |
Delete objects, pruning them out of every object group. |
rename |
Rename an object; every reference to it follows. |
rename_dof |
Correct a channel's coordinate on an object and on everything |
link |
Declare objects part of one group, merging any they are in. |
unlink |
Take objects out of their groups; a group of one dissolves. |
relink |
Move one object into the group holding |
name_object_group |
Name the object group an object belongs to, or unname it. |
object_group_of |
The members linked with |
role_of |
'Basis', or None for an unroled or unlinked object. |
placed |
{role: [members]} — which objects are in each named group. |
sides |
{side: [object names]} for the typed skeleton: the Basis by |
missing |
The typed skeleton's empty slots — what the tree shows gray. |
object_group_with_role |
The object group carrying a role, or None. |
place |
Put one object into the named group, making it if need be. |
set_channel_role |
Give one channel a role — reference, response or monitor — |
set_role |
Name what a group is. The Basis is unique: taking the role |
set_basis |
Declare the Basis of comparisons: the group whose DOFs |
geometry_for |
(name, geometry) the object answers to: its group's, else |
absorb_links |
Take on the object groups of a project being imported. |
verbs |
The processing verbs that apply to an object, each with its |
selection_verbs |
The processing verbs a selection can act on, each with its |
compute_spectra |
Spectra from a time history's averages (the averaging |
compute_psds |
PSDs from a time history's averages (Compute PSDs). |
compute_octave |
A spectrum integrated onto proportional bands (Compute |
compute_frfs |
Frequency response functions from a time history (Compute |
compute_multiple_coherence |
Multiple coherence from a time history (Compute Multiple |
compute_srs |
Shock response spectra from a time history's shocks (Compute |
detect_shocks |
Find the events in a time history and mark them on it (the |
filter_data |
A time history through its low-pass (the filter view's |
truncate_data |
A time history cut to its truncation's span (the |
integrate |
One integration of a time history (Integrate): acceleration |
differentiate |
One differentiation of a time history (Differentiate): |
compute_cpsds |
The full cross-spectral matrix from a time history's averages |
transform |
Physical responses through a shape set to modal responses |
expand |
Modal responses back through a shape set to physical |
author_specification |
A specification written from a sheet (the Specification |
generate_rigid_body_modes |
The six rigid-body mode shapes of a geometry (Generate Rigid |
merge_coincident_nodes |
Make a geometry's coincident nodes one node (Merge Coincident |
new_geometry |
An empty geometry, to build a model in (the project's +): |
set_view |
Set the view a geometry opens on in 3-D, in the app, the report |
add_plane |
Add a meshed rectangle of plates to a geometry (Add Plane): a |
tie_elements |
Tie a patch of a geometry's elements rigidly to the part under |
merge_groups |
Merge a geometry's element groups into one (Merge Element Groups): the first |
add_block |
Add a meshed box of solid bricks to a geometry (Add Block): |
solve_modes |
The normal modes of a geometry whose element groups carry their |
fit_modes |
Fit a modal model to an FRF set (the fitting screen). |
project_onto_basis |
A shape set sampled at the Basis set's DOFs (Project onto |
match_modes |
Commit matched mode pairs (the comparison screen's +). |
comparison_mac |
The MAC between two shape sets as the comparison screen |
plot_mac |
The MAC picture the comparison screen draws: |
merge |
Combine compatible objects into one (Merge). |
export |
Write an object to a foreign format, chosen by suffix — |
generate_report |
Build a report from a starter template, bound symbolically |
work_up |
Every missing object the project's type expects, computed |
export_report |
Write a report as one self-contained HTML file (Export). |
table |
(headers, rows) for an object that reads as a table. |
plot |
Plot an object the way the GUI plots it: data as curves, a |
animate |
A mode shape — or a complex spectrum's operating deflection — |
name_of |
The name an object goes by here; a name passes through. |
extract_sine |
Each specification tone's level, read out of a recording |
stale |
{derived name: why} for everything whose source's settings |
refresh |
Recompute a derived object in place, under its own name. |
refresh_stale |
Refresh everything stale, sources before their dependents, |
save |
Write the whole project to one file: |
open |
Read a project back, from |
journal_as |
Record a stretch of front-end work as one replaying line. |
record_setting |
A settings write, journaled the way a script would make it. |
record_call |
A method call on an object, journaled as a script makes it. |
session_script |
This sitting's acts as a runnable Python script. |
Attributes:
| Name | Type | Description |
|---|---|---|
basis |
Selection
|
The Basis group, reached by type: |
object_group_selections |
list[Selection]
|
Every object group, the Basis first, each reached by type. |
other |
Selection
|
The one object group that is not the Basis — the model side of |
names |
list[str]
|
Every object's name, in the order the tree shows them. |
Source code in src/visualdynamics/project.py
Attributes¶
basis
property
¶
basis: Selection
The Basis group, reached by type: project.basis.frf.
Empty (and falsy) when no group has been declared the Basis,
so if project.basis: still asks the question it reads as.
Its member names are project.basis.names.
object_group_selections
property
¶
object_group_selections: list[Selection]
Every object group, the Basis first, each reached by type.
other
property
¶
other: Selection
The one object group that is not the Basis — the model side of a correlation, usually. Says so when there are several.
Methods:¶
grouped_names
¶
[(group or None, [names])] in the order the tree shows them: the Basis group first, then the other object groups, then what is unlinked — each in the canonical type order, and objects of one type in the order they arrived.
Source code in src/visualdynamics/project.py
ordered_names
¶
add
¶
Add an object under a unique name; returns the name used.
A clash is numbered rather than refused or overwritten, exactly as importing twice does in the GUI. The first geometry added becomes the active one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
What to call it. A clash gets a numbered suffix. |
required |
obj
|
object
|
Any object the project can hold. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
duplicate
¶
Copies of objects, added beside them (Copy, then Paste, in the tree): each under its own name with ' copy', numbered when that is taken. Returns the names added.
Independent objects, not views: a copy's arrays are its own, so editing one leaves the other as it was. Links and provenance stay with the originals — a copy is a fresh object that happens to hold the same numbers, and what it is for is the user's to say.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*names
|
str or object
|
The objects to copy, by name or as the objects. |
()
|
Returns:
| Type | Description |
|---|---|
list of str
|
The names the copies were added under, in order. |
Source code in src/visualdynamics/project.py
import_file
¶
Import a file into this project; returns the names added.
Anything visualdynamics reads: a geometry, a Rattlesnake run, or a whole saved project. A project brings its structure with it — its object groups follow the objects even when a name clash renamed them — and, into an empty project, its name, type and active geometry too. Foreign readers' keys become readable names ('Modal_frf' is an FRF), the way the tree spells them.
A file that knows what kind of test it was says so: a controller's own save records which environment drove the run, and adopting it here settles the project type in a script the same way importing one settles it in the window.
options pass through to the format's reader — an exodus
file's steps='time' and nodes=[...], a geometry's
length_unit='m' — so a script can declare what the
window asks about in a dialog.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or PathLike
|
The file to read. The importer is chosen by content and extension. |
required |
**options
|
Any
|
Passed through to the importer. |
{}
|
Returns:
| Type | Description |
|---|---|
list of str
|
The names of every object added, in the order added. |
Source code in src/visualdynamics/project.py
remove
¶
Delete objects, pruning them out of every object group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*names
|
str
|
The objects to act on, by name. |
()
|
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/project.py
rename
¶
Rename an object; every reference to it follows.
Object groups, matched-modes sets and report block bindings all name their objects, and a rename that left any of them pointing at the old name would strand a figure or a bracket.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
old
|
str
|
The current name. |
required |
new
|
str
|
The name to give it. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The name actually used, which may carry a suffix. |
Source code in src/visualdynamics/project.py
rename_dof
¶
Correct a channel's coordinate on an object and on everything derived from it (double-click a row or reference column of the grid and type).
The channel, not the point: a force labeled at the wrong node moves without taking the accelerometer at that node with it (Brandon, 2026-09-06 — the other is changed explicitly if it should be). The spectra computed from a time history inherited its channels, so one found mislabeled is mislabeled in every one of them; the correction follows the derivation chain rather than leaving each derived object to be fixed by hand or recomputed. An object downstream that does not carry the channel is left alone.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object whose grid row or column was edited. |
required |
old
|
str
|
The coordinate as it is, '101Z+'. |
required |
new
|
str
|
The coordinate to give it, normalized the way every DOF is. |
required |
quantity
|
str
|
Which channel at |
None
|
Returns:
| Type | Description |
|---|---|
list of str
|
The names of the objects changed, the source first. |
Source code in src/visualdynamics/project.py
link
¶
Declare objects part of one group, merging any they are in.
A group holds at most one geometry — its members are read against it — and a member naming nodes that geometry lacks is refused, because the link would be a claim that is not true. Returns the group's members.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*names
|
str
|
The objects to act on, by name. |
()
|
role
|
str
|
The role to give the group — 'Basis', or None. |
None
|
name
|
str
|
What to call the group (Brandon, 2026-09-30: an activity in the Engineering Sciences Common Data Format has a name, and a named group exports as one that keeps its identity). Left out, a group being merged into keeps the name it had. |
None
|
Returns:
| Type | Description |
|---|---|
list of str
|
The group's members after linking. |
Source code in src/visualdynamics/project.py
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 | |
unlink
¶
Take objects out of their groups; a group of one dissolves.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*names
|
str
|
The objects to act on, by name. |
()
|
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/project.py
relink
¶
Move one object into the group holding target.
link merges the groups its arguments are in, which is right
for declaring two things related and wrong for moving one thing
between groups — linking a geometry to the other side would pull
its whole group across with it. This takes the object out first,
so only it moves; target of None just takes it out.
The group it lands in keeps its role, so dropping something into the Basis makes it part of the Basis rather than dissolving it. Returns the members of the group it ends up in, empty if none.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str or object
|
The object to move. |
required |
target
|
str or object
|
An object whose group it should join. None removes it from its current group. |
None
|
Returns:
| Type | Description |
|---|---|
list of str
|
The group's members afterwards. |
Source code in src/visualdynamics/project.py
name_object_group
¶
Name the object group an object belongs to, or unname it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
member
|
str
|
Any member of the group, by name. |
required |
name
|
str or None
|
The group's new name; None removes it. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
The group's members. |
Source code in src/visualdynamics/project.py
object_group_of
¶
The members linked with name, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str or object
|
The object to look up. |
required |
Returns:
| Type | Description |
|---|---|
list of str, or None
|
The names sharing its object group, or None when it is in no group. |
Source code in src/visualdynamics/project.py
role_of
¶
'Basis', or None for an unroled or unlinked object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str or object
|
The object to look up. |
required |
Returns:
| Type | Description |
|---|---|
str or None
|
Its object group's role, or None if it has none. |
Source code in src/visualdynamics/project.py
placed
¶
{role: [members]} — which objects are in each named group.
Nothing is guessed here. An object nobody has placed is in no named group, which is what makes the tree's gray slots mean anything: a slot is filled by an object in its own group, so a modal test whose only geometry is the model's still shows a slot for the measured one.
Source code in src/visualdynamics/project.py
sides
¶
{side: [object names]} for the typed skeleton: the Basis by
its role, and under OTHER_SIDE every member of every group
that is not the Basis — the other side has no name, so it is
read off the groups rather than declared. What the tree's gray
slots and missing are computed from.
Returns:
| Type | Description |
|---|---|
dict of str to list of str
|
|
Source code in src/visualdynamics/project.py
missing
¶
The typed skeleton's empty slots — what the tree shows gray.
Each is project_expectations' (label, class, icon key,
ordinal, optional, side). An untyped project expects nothing.
Until a Basis is declared every object counts for every slot:
the window places what arrives, a script may never, and a
script's project with everything in it and no groups is not
one with nothing in it.
Returns:
| Type | Description |
|---|---|
list of tuple
|
|
Source code in src/visualdynamics/project.py
object_group_with_role
¶
The object group carrying a role, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
role
|
str
|
Which named group to fetch — 'Basis' is the only name. |
required |
Returns:
| Type | Description |
|---|---|
ObjectGroup or None
|
That group, or None if unset. |
Source code in src/visualdynamics/project.py
place
¶
Put one object into the named group, making it if need be.
Unlike an ordinary link this takes a single object, because a named group is a declaration rather than an observed relation — and needing two members before the group can exist at all is what made it impossible to move a wrongly-sorted pair across one at a time. Returns the group's members.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str or object
|
The object being placed. |
required |
role
|
str or None
|
'Basis', or None for the other group — the one group that is not the Basis, made if there is none yet. With several groups besides the Basis there is no one other, and this refuses: relink onto a member of the one meant. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
The group's members. |
Source code in src/visualdynamics/project.py
1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 | |
set_channel_role
¶
Give one channel a role — reference, response or monitor — on a time history or on a channel table, and on whatever is linked with it.
The time history's role is the truth: it is what FRFs and multiple coherence read. A channel table linked with a history whose channels it describes shows the history's roles, so a change made on either lands on both (Brandon, 2026-09-25: the channel table is just a pointer to the linked time data's role). A channel table with no such history holds its own roles, as it always did.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The time history or channel table, by name or as itself. |
required |
dof
|
str
|
The channel's degree of freedom, '101Z+'. |
required |
quantity
|
str
|
What it measures, as a history's |
required |
role
|
str
|
'reference', 'response' or 'monitor'. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
The objects whose roles changed. |
Source code in src/visualdynamics/project.py
1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 | |
set_role
¶
Name what a group is. The Basis is unique: taking the role takes it from whatever group held it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The object whose group is being labeled. |
required |
role
|
str or None
|
The role, or None to clear it. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/project.py
set_basis
¶
Declare the Basis of comparisons: the group whose DOFs comparisons happen in, whose modes are the MAC rows and the frequency-error baseline. One name marks that object's group; several link them first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*names
|
str or object
|
The objects that form the basis set. |
()
|
Returns:
| Type | Description |
|---|---|
list of str
|
The basis group's members. |
Source code in src/visualdynamics/project.py
geometry_for
¶
geometry_for(name: Any) -> tuple[str, Geometry] | None
(name, geometry) the object answers to: its group's, else the active one. What it is drawn on, and checked against.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str or object
|
The object whose geometry is wanted. |
required |
Returns:
| Type | Description |
|---|---|
tuple of (str, Geometry), or None
|
The geometry's name and the geometry itself, or None when the object is not linked to one. |
Source code in src/visualdynamics/project.py
absorb_links
¶
Take on the object groups of a project being imported.
The arriving objects have already been placed in named
groups by the project type's rules — one at a time, as each arrived,
which is a guess made without the file's own structure to go
on. The file knows better, so it goes last and _prune_links
lets it win.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
groups
|
iterable of ObjectGroup
|
Object groups from another project, merged into this one's. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/project.py
verbs
¶
The processing verbs that apply to an object, each with its
one-line reading — how a script writer discovers what can be
done with what (Brandon, 2026-08-31: a flat method list says
nothing about what integrate is for).
The applicability table is the same one the window's bar reads, so the two surfaces cannot disagree; the summaries are the first paragraph of each verb's own docstring, so this and the API reference cannot disagree either.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to ask about — a name looks it up here, an object answers for itself whether or not it has been added (what applies to a result is knowable before it is kept). Omitted, every processing verb is listed. |
None
|
Returns:
| Type | Description |
|---|---|
list of (str, str)
|
|
Source code in src/visualdynamics/project.py
selection_verbs
¶
The processing verbs a selection can act on, each with its one-line reading — what the window's bar offers (Brandon, 2026-09-04: every act on the bar, none behind a menu).
One object: its own verbs, less the ones that need a partner
(transform needs a shape set beside the record). Several: the
partner verbs that apply to exactly that combination — a
record and a shape set transform or expand, two shape sets on
two geometries project, siblings of one type merge — and none
of the verbs that apply to one of them alone. The same table
verbs reads, so the bar and the API cannot disagree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*names
|
str or object
|
The selection, by name or as the objects themselves. |
()
|
Returns:
| Type | Description |
|---|---|
list of (str, str)
|
|
Source code in src/visualdynamics/project.py
compute_spectra
¶
Spectra from a time history's averages (the averaging view's Compute Spectra).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to read, by name or as the object itself;
|
required |
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
compute_psds
¶
PSDs from a time history's averages (Compute PSDs).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to read, by name or as the object itself;
|
required |
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
compute_octave
¶
A spectrum integrated onto proportional bands (Compute Octave Bands) — the same power, arranged the way it is read; a specification with its warning and abort limits banded the same way.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to read, by name or as the object itself;
|
required |
per_octave
|
int
|
Bands per octave. Defaults to |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
compute_frfs
¶
Frequency response functions from a time history (Compute FRFs) — one per response and drive, over the frames a PSD uses.
method is 'Hv', 'H1' or 'H2': where the noise is assumed to
be, which is the one thing the three estimators disagree about.
The name goes on the object, since two FRF sets from one history
differ in nothing else a reader can see.
The frames are detected if the history has none, the same way the coherence does it, so the two describe one measurement.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to read, by name or as the object itself;
|
required |
method
|
str
|
Which estimator: 'Hv', 'H1' or 'H2' — where the noise is assumed to be, which is the one thing they disagree about. |
'Hv'
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
compute_multiple_coherence
¶
Multiple coherence from a time history (Compute Multiple Coherence) — how much of each response the drives account for.
Averaged over frames, and the frames are detected if the history has none. Computed over the whole selection instead, the reference set fits every response exactly and the answer is 1.0 at every line — a number that says nothing, arrived at honestly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to read, by name or as the object itself;
|
required |
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
compute_srs
¶
compute_srs(source: Any, *, per_octave: int | None = None, q: float | None = None, kind: str = 'maximax') -> str
Shock response spectra from a time history's shocks (Compute SRS) — one curve per channel per event.
The events are the history's own — detected only when there is nothing else to say where they are, so this answers rather than asking the caller to go and find them.
Detection is the last resort and not the first. A record being read as frames already says where its events are: a transient run's playings are its averaging, and set loose on one the detector answered with thirty-one events where there were six. A target is one playing by definition and gets no detector at all — it was being cut into three.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to read, by name or as the object itself;
|
required |
per_octave
|
int
|
Natural-frequency lines per octave. Defaults to the module's convention (12). |
None
|
q
|
float
|
The oscillator amplification, Q = 1/(2ζ); 10 — 5% damping, the shock-test convention — when omitted. |
None
|
kind
|
str
|
Which peak each oscillator reports: 'maximax' (largest magnitude of either sign), 'positive' or 'negative'. |
'maximax'
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
detect_shocks
¶
Find the events in a time history and mark them on it (the shock view's Detect), returning how many.
The verb the API was missing (Brandon, 2026-08-25). compute_srs
detects as a side effect when a record carries no windows, which
served while the SRS came straight off the recording — but the
recommended shock workflow filters first, and then the detection
happened on the filtered record and the recording itself was
left unmarked. The events belong to the recording: mark them
there and every derivation carries them forward, because
core.filters copies the marks onto whatever it makes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to read, by name or as the object itself;
|
required |
Returns:
| Type | Description |
|---|---|
int
|
How many events were found and marked on the record. |
Source code in src/visualdynamics/project.py
filter_data
¶
A time history through its low-pass (the filter view's Apply Filter) — every channel, zero phase, so the peaks stay put.
The settings are the history's own filtering, set in the
filter view; with none set, the suggestion is adopted the way
compute_frfs adopts a suggested averaging, so the button
works before the view has been visited.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to read, by name or as the object itself;
|
required |
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
truncate_data
¶
A time history cut to its truncation's span (the truncate view's Apply Truncation) — every channel between start and stop, the clock kept.
The span is the history's own truncation, set in the
truncate view. Unlike Filter Data there is no suggestion to
adopt: the whole record is the only neutral span and keeping
all of it is not an act, so with none set this refuses and
says where to set one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to read, by name or as the object itself;
|
required |
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
integrate
¶
One integration of a time history (Integrate): acceleration channels become velocity, velocity becomes displacement.
Whole record, never the shock windows — the reasons live in
core.filters. The drift corner is a parameter of the act,
recorded in the recipe: ... takes the default, None
integrates raw.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to read, by name or as the object itself;
|
required |
drift_corner
|
float or None
|
High-pass corner in Hz applied after integration, to stop a
sensor bias becoming a ramp. |
...
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
differentiate
¶
One differentiation of a time history (Differentiate): displacement channels become velocity, velocity becomes acceleration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to read, by name or as the object itself;
|
required |
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
compute_cpsds
¶
The full cross-spectral matrix from a time history's averages (Compute CPSDs) — every channel against every channel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The object to read, by name or as the object itself;
|
required |
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
transform
¶
transform(source: Any, shapes: Any, *, records: Sequence[int] | None = None, name: str | None = None) -> str
Physical responses through a shape set to modal responses
(Transform to Modal Responses) — q = Φ⁺u for the motions,
Φᵀf for the forces, one record per mode and quantity at the
modal coordinates M1 … Mn.
Any set serves: the six rigid-body shapes of a geometry make
this the virtual point transformation. The result stands alone
in the tree — its DOFs are on no geometry — with its
provenance naming both the record and the set, and a
transform_report saying what was shared, dropped and left
unexplained.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The time history, by name or as the object itself;
|
required |
shapes
|
str or object
|
The shape set to transform through, by name or as itself. |
required |
records
|
sequence of int
|
Which of the record's channels to carry through — the ones picked in the tree. All of them when omitted. |
None
|
name
|
str
|
What to call the result. Defaults to the record's name followed by 'Modal Responses'. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
expand
¶
expand(source: Any, shapes: Any, *, records: Sequence[int] | None = None, name: str | None = None) -> str
Modal responses back through a shape set to physical
responses (Expand to Physical Responses) — u = Φq at every
DOF the set covers, linked into the set's group so the result
animates on the geometry. A pick of modes expands those modes'
contribution alone, and the name says which.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The modal time history, by name or as the object itself;
|
required |
shapes
|
str or object
|
The shape set it was transformed through, by name or as itself. |
required |
records
|
sequence of int
|
Which modal records to expand — the modes picked in the tree. All of them when omitted. |
None
|
name
|
str
|
What to call the result. Defaults to the record's name with 'Modal Responses' read as 'Physical Responses', and the modes carried in brackets when they are not all. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
author_specification
¶
author_specification(source: Any, draft: Any, *, name: str | None = None, replace: bool = False) -> str
A specification written from a sheet (the Specification
reading's Make Specification) — autospectra at breakpoints,
every cross term from a stated coherence and phase, bands in
decibels — beside the object the sheet was opened on: a shape
set (at its modal coordinates, ready to expand through it), a
channel table (at its control channels), or a specification.
With replace, the sheet is written into the specification
it was opened from under its own name, so links and report
slots hold — or into several at once, from a sheet that spans
them. A sheet holding every channel of the specification
rewrites it in the sheet's own form, its breakpoints or its
lines becoming the object's; a sheet holding a picked subset
merges back at the specification's own lines with the other
channels untouched. A pair the sheet leaves unstated is absent
from the result — nothing is assumed for it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str, object, or list of str
|
The object the sheet was opened on, by name or as itself; the specifications' names when the sheet spans several. |
required |
draft
|
SpecificationDraft
|
What the author stated ( |
required |
name
|
str
|
What to call the result. Defaults to the source's name followed by 'Specification'. |
None
|
replace
|
bool
|
Write the sheet into |
False
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix — or the source's own name when replaced (the first of them when several). |
Source code in src/visualdynamics/project.py
2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 | |
generate_rigid_body_modes
¶
The six rigid-body mode shapes of a geometry (Generate Rigid Body Mode Shapes) — three translations and three rotations about its reference point, as a shape set in the geometry's group.
The point, and the mass and inertia that mass-normalize the
set, are the geometry's own mass_properties, set in the
rigid-body view. With none set the centroid is adopted, unit
shapes about the middle of the model — a real answer, unlike
a whole-record truncation, and the one the virtual-point
transformation wants most often — and stored, so the
staleness fingerprint records what was actually used.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The geometry, by name or as the object itself; |
required |
name
|
str
|
What to call the result. Defaults to the geometry's name followed by 'Rigid Body Modes'. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
merge_coincident_nodes
¶
Make a geometry's coincident nodes one node (Merge Coincident Nodes): elements renamed to the lowest id at each point, the rest removed. Plates connect only where they share nodes, so this is what ties planes meshed apart at the lines where they meet.
Refused while an object linked with the geometry names a node the merge would remove — its data would point at a node that is gone; merge first, then measure or solve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The geometry, by name or as the object itself. |
required |
tolerance
|
float
|
How close two nodes must be to be one, in meters (as the geometry holds its coordinates). Defaults to a millionth of the geometry's size. |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
'merged', the nodes removed, and 'into', the nodes they became. |
Source code in src/visualdynamics/project.py
new_geometry
¶
An empty geometry, to build a model in (the project's +):
planes added with add_plane, links in the app's add mode.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
What to call it. |
'Geometry'
|
unit
|
str
|
Its length unit. Coordinates are held in SI either way; this is the unit it remembers it was built in. |
'm'
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
set_view
¶
set_view(source: Any, view: View | None = None) -> None
Set the view a geometry opens on in 3-D, in the app, the report and every exported figure.
The app's Set Default View captures the view on screen; from a script it is stated, a model built Y-up seen from the front and above, say. Everything drawn on the geometry — its shapes, an ODS, the DOF arrows — opens on it too, and Reset View returns to it. The nodes are not turned: only how the model is looked at (Brandon, 2026-09-27).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or Geometry
|
The geometry, by name or as the object itself. |
required |
view
|
View
|
Where the eye is, seen from the model's center, and which way is up; None goes back to the default isometric (from +X+Y+Z, Z up). |
None
|
Returns:
| Type | Description |
|---|---|
None
|
|
Examples:
>>> from visualdynamics import View
>>> project.set_view('BARC', View(eye=(1, 1, -1), up=(0, 1, 0)))
Source code in src/visualdynamics/project.py
add_plane
¶
add_plane(source: Any, corner: Any, edge_a: Any, edge_b: Any, size: float, group: str = '', *, unit: str = 'm', tolerance: float | None = None) -> dict
Add a meshed rectangle of plates to a geometry (Add Plane): a
corner, two perpendicular edges and an element size, each edge
divided evenly into the whole number of elements nearest that
size (mesh.plane). Its nodes that fall on nodes already there
become them, so planes meeting along a line are tied there, and
the nodes already there keep their ids (mesh.join).
Planes given the same element group name are one element group — the five walls of a box, each named 'box', are the box, given its material once in the Element Groups table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The geometry, by name or as the object itself. |
required |
corner
|
array_like
|
One corner, (x, y, z). |
required |
edge_a
|
array_like
|
The two edges from that corner, as vectors; perpendicular. |
required |
edge_b
|
array_like
|
The two edges from that corner, as vectors; perpendicular. |
required |
size
|
float
|
The element size aimed at. |
required |
group
|
str
|
The element group the plates go in, by name: an existing element group of that name, or a new one. |
''
|
unit
|
str
|
The unit the lengths above are in. A geometry whose units are not defined takes them as given. |
'm'
|
tolerance
|
float
|
How close a node must be to one already there to be it, in meters. Defaults to a millionth of the size of the two together. |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
'added', the nodes added; 'shared', the plane's nodes that fell on nodes already there; 'elements', the plates added; 'groups', the element group they went into. |
Source code in src/visualdynamics/project.py
tie_elements
¶
Tie a patch of a geometry's elements rigidly to the part under
it (Tie): each node of the patch linked by a rigid, massless link
to the nearest node of to — a bolted joint, from the elements
its washer covers (mesh.tie).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The geometry, by name or as the object itself. |
required |
elements
|
sequence of int
|
The patch, by element id. |
required |
to
|
str, int or sequence of int
|
An element group, by name or id, or a second patch, by element ids. |
required |
group
|
str
|
The element group the links go into, made rigid if new. Defaults to the geometry's first rigid element group, or a new one named 'ties'. |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
'links', how many were added; 'shared', patch nodes the target already holds; 'group', where the links went. |
Source code in src/visualdynamics/project.py
merge_groups
¶
Merge a geometry's element groups into one (Merge Element Groups): the first
keeps its id, name and properties and the others' elements move
into it — refused unless they hold the same element types and
carry the same material and thickness or section
(Geometry.merge_refusal).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The geometry, by name or as the object itself. |
required |
groups
|
sequence of int
|
The element groups, by id; the first is the one kept. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
'into', the element group kept; 'groups', how many merged into it; 'elements', how many elements moved. |
Source code in src/visualdynamics/project.py
add_block
¶
add_block(source: Any, corner: Any, edge_a: Any, edge_b: Any, edge_c: Any, size: float, group: str = '', *, unit: str = 'm', holes: Any = (), hole_group: str | None = None, tolerance: float | None = None) -> dict
Add a meshed box of solid bricks to a geometry (Add Block):
a corner, three perpendicular edges and an element size, each
edge divided evenly into the whole number of elements nearest
that size (mesh.block), with cylindrical holes cut the way a
structured mesh cuts them. Its nodes that fall on nodes already
there become them, so blocks meeting over a face are tied there
(mesh.join).
Blocks given the same element group name are one element group — the rails and uprights of a frame, each named 'frame', are the frame, given its material once in the Element Groups table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The geometry, by name or as the object itself. |
required |
corner
|
array_like
|
One corner, (x, y, z). |
required |
edge_a
|
array_like
|
The three edges from that corner, as vectors; perpendicular. |
required |
edge_b
|
array_like
|
The three edges from that corner, as vectors; perpendicular. |
required |
edge_c
|
array_like
|
The three edges from that corner, as vectors; perpendicular. |
required |
size
|
float
|
The element size aimed at. |
required |
group
|
str
|
The element group the bricks go in, by name: an existing group of that name, or a new one. |
''
|
unit
|
str
|
The unit the lengths above are in. A geometry whose units are not defined takes them as given. |
'm'
|
holes
|
sequence of tuple
|
Holes as |
()
|
hole_group
|
str
|
The element group the holes' bricks go to — an insert's — or None to leave them out. |
None
|
tolerance
|
float
|
How close a node must be to one already there to be it, in meters. Defaults to a millionth of the size of the two together. |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
'added', the nodes added; 'shared', the box's nodes that fell on nodes already there; 'elements', the bricks added; 'groups', the element groups they went into. |
Source code in src/visualdynamics/project.py
2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 | |
solve_modes
¶
solve_modes(source: Any, *, maximum_frequency: float | None = None, num_modes: int | None = None, damping: float = 0.0, name: str | None = None, progress: Any = None) -> str
The normal modes of a geometry whose element groups carry their properties (Solve Modes): the finite element model built from the element groups, solved, and the shapes added in the geometry's group.
The geometry is the model: each element group a material and a thickness
or a section (fem.GroupProperties, set in the Element Groups table or
on geometry.group_properties), every quad a plate, every
triangle a triangle, every two-node line a beam
(fem.Model.from_geometry). The solution is free-free unless the
geometry says otherwise later; the six rigid-body modes come
back at exactly 0 Hz with the elastic ones after them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The geometry, by name or as the object itself. |
required |
maximum_frequency
|
float
|
Solve for every mode up to this frequency, in Hz. |
None
|
num_modes
|
int
|
Or for this many modes, rigid ones included. |
None
|
damping
|
float
|
The fraction of critical damping every mode is given; a finite element model has none of its own. |
0.0
|
name
|
str
|
What to call the result. Defaults to the geometry's name with ' Modes' after it. |
None
|
progress
|
callable
|
Told |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The name the shape set was added under. |
Source code in src/visualdynamics/project.py
fit_modes
¶
fit_modes(source: str, *, bounds: tuple[float, float] | None = None, limit: int = 30, name: str | None = None, at: Sequence[tuple[float, float]] | None = None, refine: int = 0) -> str
Fit a modal model to an FRF set (the fitting screen).
The screen's loop, scripted: confirm the suggestion, take the
next, limit times. The session's own suggestion logic is the
whole judgment — a confirmed peak is spoken for unless the
shape standing there is somebody else's — so the loop adds no
second opinion. It used to: a proximity guard here vetoed any
suggestion within 1 Hz of a confirmed mode, which was the same
ridge-trap bandaid the session has since outgrown, and it
silently skipped the repeated pair's second tooth that
suggest had deliberately offered. On the screen the person
stops the loop; scripted, limit is that judgment, and the
plate demo's own cap is the worked example of choosing it.
On the screen the equivalent of bounds is the zoom — what is
on the plot is what gets searched. A script has no plot, so it
says so here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str
|
The FRF set to fit. |
required |
bounds
|
tuple of float
|
(low, high) frequency limits to fit within. Defaults to the whole band. |
None
|
limit
|
int
|
The most modes to accept. |
30
|
name
|
str
|
What to call the shape set. |
None
|
at
|
sequence of tuple
|
Explicit (frequency, damping) picks — each optionally
(frequency, damping, description) — confirmed in the
order given — the residual is peeled sequentially, so the
order is part of the fit. This is how an interactive
session replays: the fitting screen journals its confirms
as exactly this call. |
None
|
refine
|
int
|
Times to run the joint residue refinement after the confirms — the screen's Refine All, counted. |
0
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 | |
project_onto_basis
¶
project_onto_basis(source: str, *, onto: str | None = None, tolerance: float = 0.02, name: str | None = None) -> str
A shape set sampled at the Basis set's DOFs (Project onto
Basis DOFs): nearest node within tolerance of the basis
model's extent, the displacement there dotted with each basis
DOF's direction. Returns the new set's name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str
|
The shape set to project. |
required |
onto
|
str
|
The basis to project onto. Defaults to the project's basis. |
None
|
tolerance
|
float
|
The residual a fit may leave. |
0.02
|
name
|
str
|
What to call the result. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
match_modes
¶
match_modes(first: str, second: str, *, pairs: Iterable[tuple[int, int]] | None = None, macs: Iterable[float] | None = None, threshold: float = 0.7, name: str = 'Matched Modes') -> str
Commit matched mode pairs (the comparison screen's +).
With pairs those pairs exactly; otherwise each of first's
modes takes its best partner in second when the MAC clears
threshold. Comparing across geometries goes through the
projection first, as the screen does.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
first
|
str
|
One shape set, by name. |
required |
second
|
str
|
The other shape set, by name. |
required |
pairs
|
iterable of tuple of int
|
Explicit (first, second) index pairs, overriding the automatic matching. |
None
|
macs
|
iterable of float
|
MAC values for those pairs. |
None
|
threshold
|
float
|
The lowest MAC an automatic pairing may have. |
0.7
|
name
|
str
|
What to call the result. |
'Matched Modes'
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 | |
comparison_mac
¶
The MAC between two shape sets as the comparison screen shows it: across geometries the second set is projected onto the first's DOFs, because matching DOF names across geometries would trust them to mean the same directions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
first
|
str
|
One shape set, by name. |
required |
second
|
str
|
The other shape set, by name. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The MAC matrix, first's shapes down the rows and second's across the columns. |
Source code in src/visualdynamics/project.py
plot_mac
¶
The MAC picture the comparison screen draws: first
against itself, or against second — projected across
geometries exactly as comparison_mac does it, which a
shape set's own plot_mac cannot, since it compares by DOF
name and knows no geometry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
first
|
str or ShapeSet
|
One shape set, by name or as the object. |
required |
second
|
str or ShapeSet
|
The other. Absent, the auto-MAC. |
None
|
**kwargs
|
Any
|
Passed to the drawing: |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
Whatever the drawing returns — a window, an image. |
Source code in src/visualdynamics/project.py
merge
¶
Combine compatible objects into one (Merge).
Same concrete type only, and each kind has its own rule about what may join: geometries need disjoint node ids, shape sets the same DOF cover, data arrays an identical abscissa. The merged object replaces its parts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*names
|
str
|
The objects to act on, by name. |
()
|
name
|
str
|
What to call the merged object. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
export
¶
Write an object to a foreign format, chosen by suffix —
every registered writer, .unv, .exo, .npz, .bdf,
.afu/.ati/.ash, .xlsx, .3mf, .stl, .vdreport and
the rest of io.exporters() (Export).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The object to write. |
required |
path
|
str or PathLike
|
Where to write it. The format follows the extension. |
required |
unit_system
|
UnitSystem
|
Units to write in. Defaults to the project's own. |
None
|
**kwargs
|
Any
|
Passed through to the exporter. |
{}
|
Returns:
| Type | Description |
|---|---|
str
|
The path written. |
Source code in src/visualdynamics/project.py
generate_report
¶
generate_report(template: str = 'modal', name: str = 'Report', marking: str | None = None, marking_color: str | None = None) -> str
Build a report from a starter template, bound symbolically to this project's structure (Generate Report).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
template
|
str
|
Which starter to build: 'modal', 'random', 'shock',
'transient', 'sine', 'sysid' or 'empty' (a random-and-sine
project makes 'random' and 'sine', one report each) — or a saved
template, by the name it was saved under in the templates
folder or by the path of a |
'modal'
|
name
|
str
|
What to call the report object. |
'Report'
|
marking
|
str
|
The banner across the top and bottom of every page — 'UNCLASSIFIED' unless said. A batch stamps its own (Brandon, 2026-10-02). |
None
|
marking_color
|
str
|
'ink' or 'red'. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The name the result was added under, which is unique within the project — a clash gets a numbered suffix. |
Source code in src/visualdynamics/project.py
work_up
¶
Every missing object the project's type expects, computed from what is loaded, and the report last (the tree bar's Automatic; Brandon, 2026-09-30).
The skeleton's empty slots in their own order, each made from the objects already there the way the workflow guide makes it — PSDs from the time data, the octave bands from the PSDs and the specification, the coherence from the time data; the FRFs and a fitted shape set for a modal test; the levels for a sine sweep; the filtered record, its SRS and the motion chain for a shock; the two streams' PSDs on shared frames and the H1 plant for a system identification — and the typed report once they are in. A slot the loaded data cannot fill (a geometry, the photographs, a specification) is left gray; a computation the data refuses (no drive channel for a coherence) is skipped, and the rest still happen. Nothing already present is remade: pressed twice, the second press adds nothing.
One journal line, project.work_up(), the way merge is one
line: the verbs it calls are the ones the guide teaches, and a
script wanting them one at a time calls them.
Returns:
| Type | Description |
|---|---|
list of str
|
The names added, in the order made; empty when the skeleton was already full. |
Raises:
| Type | Description |
|---|---|
ValueError
|
When the project has no type — there is no skeleton to fill. |
Source code in src/visualdynamics/project.py
2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 | |
export_report
¶
Write a report as one self-contained HTML file (Export).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The report object to render. |
required |
path
|
str or PathLike
|
Where to write the self-contained HTML file. |
required |
unit_system
|
UnitSystem
|
Units to render in. Defaults to the project's own. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The path written. |
Source code in src/visualdynamics/project.py
table
¶
(headers, rows) for an object that reads as a table.
The instrumentation of a channel table, the identified parameters of a shape set — the same rows the report prints, so a script and a report cannot disagree about what is in one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str or object
|
The object to tabulate. |
required |
Returns:
| Type | Description |
|---|---|
tuple of (list of str, list of list of str)
|
The column headings and the rows, both as text. |
Source code in src/visualdynamics/project.py
plot
¶
Plot an object the way the GUI plots it: data as curves, a geometry as its scene, a shape set as its auto-MAC.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The object to draw. |
required |
**kwargs
|
Any
|
Passed through to the object's own plot method. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
Whatever the underlying plot call returns. |
Source code in src/visualdynamics/project.py
animate
¶
A mode shape — or a complex spectrum's operating deflection — moving on the geometry it answers to.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The shape set to animate. |
required |
mode
|
int
|
Which mode, by index. |
0
|
**kwargs
|
Any
|
Passed through to the scene. |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The plotter the animation is running in. |
Source code in src/visualdynamics/project.py
name_of
¶
The name an object goes by here; a name passes through.
Verbs take either, so project.compute_psds('Time History')
and project.compute_psds(project.basis.time_history) are the
same call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str or object
|
A name, or an object the project holds. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The name it is stored under. |
Source code in src/visualdynamics/project.py
extract_sine
¶
Each specification tone's level, read out of a recording (Extract Sine Levels) — one object per tone, because each tone sweeps its own frequencies on its own clock.
The specification is found in the project when not named — the one SineSweepSpecification there is — and each result is linked to the recording it was read from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or object
|
The sine level set or run to read. |
required |
specification
|
str or object
|
The sweep specification to extract against. Defaults to the project's own, when it holds exactly one. |
None
|
progress
|
callable
|
Told |
None
|
Returns:
| Type | Description |
|---|---|
list of str
|
The names of the levels added, one per tone. |
Source code in src/visualdynamics/project.py
stale
¶
{derived name: why} for everything whose source's settings have moved since it was computed.
A missing source, or a derivation this bookkeeping predates, answers nothing — absence of evidence is not staleness.
Source code in src/visualdynamics/project.py
refresh
¶
Recompute a derived object in place, under its own name.
The links, the report's bindings and the grids all key on the name, so replacing the value under it is what keeps every reference honest. Anything derived from this object goes stale by content, which is the cascade — refreshed one badge at a time, or all at once, but always by a person.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str or object
|
The derived object to recompute, in place and under its own name. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The name refreshed. |
Source code in src/visualdynamics/project.py
refresh_stale
¶
Refresh everything stale, sources before their dependents, until nothing is — the project row's one click.
Source code in src/visualdynamics/project.py
save
¶
Write the whole project to one file: .vdyn, .mat for
the same layout in MATLAB's container, or .h5 for the
Engineering Sciences Common Data Format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or PathLike
|
Where to write the file. A |
required |
**options
|
Any
|
Passed to a foreign writer: an ESCDF file's |
{}
|
Returns:
| Type | Description |
|---|---|
str
|
The path written. |
Source code in src/visualdynamics/project.py
open
classmethod
¶
open(path: str | PathLike) -> Project
Read a project back, from .vdyn, .mat or an ESCDF .h5
(.hdf5, .escdf).
Source code in src/visualdynamics/project.py
journal_as
¶
Record a stretch of front-end work as one replaying line.
The GUI imports a file by building the objects itself and
adding them one by one; journaled verb by verb, that stretch
is a pile of not-replayable comments — when the honest record
is the single import_file call a script would make. Inside
the stretch every verb stays quiet, exactly as verbs nested in
verbs do; the line lands only when the stretch succeeds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
line
|
str or None
|
The line that replays the stretch — None to record nothing at all. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/project.py
record_setting
¶
A settings write, journaled the way a script would make it.
The front ends' funnel: the GUI stores analysis settings by assignment — a dragged averaging span, a filter corner, the shock windows — and those writes are session acts as much as any verb. A repeated write to the same slot replaces its own last line, so a session of nudging settles to the one assignment that stands rather than a line per keystroke.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str or object
|
The object written to, by name or as itself. |
required |
attribute
|
str
|
Which settings attribute was stored. |
required |
value
|
Any
|
What was stored; its repr must rebuild it, which every settings dataclass here guarantees. |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/project.py
record_call
¶
A method call on an object, journaled as a script makes it.
The front ends' funnel for object verbs that are not Project verbs — a line added to a geometry, a photo renamed — each an act of the session the console must speak (Brandon, 2026-08-30: adding a line said nothing).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
str or object
|
The object acted on, by name or as itself. |
required |
method
|
str
|
The method a script would call. |
required |
*args
|
Any
|
The call's arguments; their reprs must rebuild them. |
()
|
**kwargs
|
Any
|
Keyword arguments, same rule. |
{}
|
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/project.py
session_script
¶
This sitting's acts as a runnable Python script.
The journal joined under its imports: every verb that ran and every setting stored — clicked in the GUI or called from a script — recorded as the line that reproduces it, so a session worked up by hand can be replayed, adapted, or kept. Reads and refusals are absent on purpose: the script is what happened to the project, and a verb that raised changed nothing.
Returns:
| Type | Description |
|---|---|
str
|
A Python script; running it rebuilds this session's project from the same inputs. |
Source code in src/visualdynamics/project.py
UnitsRequired
¶
Bases: UnitError
Raised when an operation needs units that have not been defined.
Source code in src/visualdynamics/units.py
UnitSystem
dataclass
¶
UnitSystem(name: str, units: dict = dict(), base: UnitSystem | None = None)
A named mapping of dimension -> display unit.
base is the coherent system this one came from — the same object for
a coherent system, and the parent for one carrying display-only
overrides such as accelerations in g. Exports use it, because no
foreign format can record "g" as a unit.
Methods:
| Name | Description |
|---|---|
unit |
Display unit for a dimension or dimension expression. |
transform |
(scale, offset) converting a display value to SI. |
factor |
SI-per-display-unit scale (offset-free dimensions only). |
from_si |
Convert SI values to this system's display unit for |
to_si |
Convert values from this system's units into SI. |
label |
Display unit text; empty when the dimension is undefined. |
label_text |
Display unit for plain-text output, with real exponents: in/s². |
label_ascii |
Display unit in plain ASCII, exponents as carets: in/s^2. |
label_html |
Display unit as HTML — a stacked fraction when it has one. |
with_units |
A copy of this system with per-dimension unit overrides. |
Attributes:
| Name | Type | Description |
|---|---|---|
coherent |
UnitSystem
|
This system with display-only overrides stripped. |
Attributes¶
Methods:¶
unit
¶
Display unit for a dimension or dimension expression.
Expressions compose from the base units: with in-lbf-s, 'acceleration/force' -> '(in/s**2)/lbf'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dimension
|
str
|
A dimension tag, such as 'acceleration' or 'force'. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The unit string for this dimension. |
Source code in src/visualdynamics/units.py
transform
¶
(scale, offset) converting a display value to SI.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dimension
|
str
|
A dimension tag, such as 'acceleration' or 'force'. |
required |
Returns:
| Type | Description |
|---|---|
tuple of (float, float)
|
The scale and offset taking SI to display — an offset matters for temperature and nothing else. |
Source code in src/visualdynamics/units.py
factor
¶
SI-per-display-unit scale (offset-free dimensions only).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dimension
|
str
|
A dimension tag, such as 'acceleration' or 'force'. |
required |
Returns:
| Type | Description |
|---|---|
float
|
What an SI value is multiplied by to display it. |
Source code in src/visualdynamics/units.py
from_si
¶
Convert SI values to this system's display unit for dimension.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
array_like
|
Values in SI. |
required |
dimension
|
str
|
A dimension tag, such as 'acceleration' or 'force'. |
required |
Returns:
| Type | Description |
|---|---|
array_like
|
The same values in this system's units. |
Source code in src/visualdynamics/units.py
to_si
¶
Convert values from this system's units into SI.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
array_like
|
Values in this system's units. |
required |
dimension
|
str
|
A dimension tag, such as 'acceleration'. |
required |
Returns:
| Type | Description |
|---|---|
array_like
|
The same values in SI. |
Source code in src/visualdynamics/units.py
label
¶
Display unit text; empty when the dimension is undefined.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dimension
|
str
|
A dimension tag, such as 'acceleration' or 'force'. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The unit's name in this system. |
Source code in src/visualdynamics/units.py
label_text
¶
Display unit for plain-text output, with real exponents: in/s².
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dimension
|
str
|
A dimension tag, such as 'acceleration' or 'force'. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The label as plain text. |
Source code in src/visualdynamics/units.py
label_ascii
¶
Display unit in plain ASCII, exponents as carets: in/s^2.
For renderers that quietly drop what they cannot draw: VTK's
3-D axis titles lose unicode superscripts in every text mode,
so label_text's (in/s²)²/Hz read (in/s)/Hz off the waterfall
— wrong by two squarings, with nothing saying so.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dimension
|
str
|
A dimension tag, such as 'acceleration' or 'force'. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The label with no unicode, for renderers that drop it. |
Source code in src/visualdynamics/units.py
label_html
¶
Display unit as HTML — a stacked fraction when it has one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dimension
|
str
|
A dimension tag, such as 'acceleration' or 'force'. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The label with HTML superscripts. |
Source code in src/visualdynamics/units.py
with_units
¶
with_units(name: str | None = None, **overrides) -> UnitSystem
A copy of this system with per-dimension unit overrides.
Example: IN_LBF_S.with_units(acceleration='g') displays acceleration in g while everything else stays inch-pound-second.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
What to call the derived system. |
None
|
**overrides
|
Dimension/unit pairs to change. |
{}
|
Returns:
| Type | Description |
|---|---|
UnitSystem
|
A copy with those units replaced. |
Source code in src/visualdynamics/units.py
Functions:¶
check_compatibility
¶
check_compatibility(objects: Mapping[str, Any], geometry_name: str | None = None, object_groups: Sequence[Mapping[str, Any]] | None = None) -> Report
Check every object in a test against the geometry it answers to.
objects maps name -> object. An object linked into a group that
holds a geometry is judged against that geometry — a FEM shape
set beside its own FEM mesh is consistent, whichever geometry is
active. Everything else is judged against geometry_name (the
active geometry; without it the first geometry found). Returns a
Report.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
objects
|
Mapping[str, Any]
|
The test's objects, by name. |
required |
geometry_name
|
str
|
The geometry everything not linked to one is judged against; the first geometry found when omitted. |
None
|
object_groups
|
sequence of Mapping
|
The project's object groups, which say which geometry each object answers to. |
None
|
Returns:
| Type | Description |
|---|---|
Report
|
What is consistent and what is not, object by object. |
Source code in src/visualdynamics/compatibility.py
frequency_axis
¶
Read or set how frequency axes are drawn: 'log', 'linear', or 'default' (each kind of data by its own convention).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode
|
('log', 'linear', 'default')
|
The choice to make. Omitted, the current one is read back. True and False stand for 'log' and 'linear'; None for 'default'. |
'log'
|
Returns:
| Type | Description |
|---|---|
str
|
The choice in force after the call. |
Source code in src/visualdynamics/core/data.py
export_file
¶
export_file(obj: Any, path: str | PathLike, format: str | None = None, unit_system: UnitSystem | None = None, **kwargs: Any) -> None
Write obj to a foreign format, chosen by name or by suffix.
unit_system is the system to write in; without one the stored values
go out as they are. Raises ValueError naming what the object can be
written as, since "cannot export" is nearly always a question of which
format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
object
|
What to write: a geometry, data, shapes, a project, and so on. |
required |
path
|
str or PathLike
|
Where; the suffix picks the format when |
required |
format
|
str
|
An exporter by name ( |
None
|
unit_system
|
UnitSystem
|
The units to write in; the stored values as they are when omitted. |
None
|
**kwargs
|
Any
|
Passed to the format's writer, e.g. an ESCDF file's |
{}
|
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/io/exporters.py
from_sep005
¶
SEP 005 timeseries into TimeHistory objects.
history = visualdynamics.from_sep005({'data': y, 'fs': 256.0,
'name': 'run 4',
'unit_str': 'm/s²'})
One dict returns one TimeHistory; a list — the standard's form
for several series — returns {name: TimeHistory}, numbering a
repeated name the way the project tree would.
unit_str entries that parse are declared on the object
(values converted to SI, exactly as define_units would), because
the producer stated them; one that does not parse leaves that
channel's values raw with the claim kept in dimension_hint, where
quantity also lands when there is no unit at all. Nothing is
ever scaled by a guess.
Refused, with the reason: a series with no data, with neither
fs nor time, a time vector of the wrong length, or a
channel_name list that does not match the channel count.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeseries
|
dict or list of dict
|
One SEP 005 series, or a list of them. |
required |
Returns:
| Type | Description |
|---|---|
TimeHistory or dict of str to TimeHistory
|
One for one dict; for a list, every series by name. |
Source code in src/visualdynamics/io/sep005.py
import_file
¶
import_file(path: str | PathLike, format: str | None = None, progress: Any | None = None, **kwargs: Any) -> Any
Import a foreign file, returning the visualdynamics object it contains.
Units may be declared here (e.g. length_unit='m') for sources that do not
carry them; without a declaration the object imports unit-less, holding
the file's raw values until define_units() is called.
format forces a specific importer by name. progress is a
(done, total) callable, honored where the reader can count — a
project file's objects, a Rattlesnake run's samples (2026-10-02) —
and quietly unused where it cannot: a UFF file is one parse, and
nothing inside it reports fractions worth relaying.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or PathLike
|
The file to read. |
required |
format
|
str
|
An importer by name, rather than the one the file is recognized as. |
None
|
progress
|
callable
|
Called as |
None
|
**kwargs
|
Any
|
Passed to the importer: declared units ( |
{}
|
Returns:
| Type | Description |
|---|---|
object
|
The object the file holds; a dict of them by name for a file that
holds several, and a |
Source code in src/visualdynamics/io/__init__.py
load
¶
Load a .vdyn file: the object it contains, or a whole test.
progress is called as (objects loaded, objects in the file) —
once up front with 0 and once per object — because a project file
is minutes of someone's day and the reader is the only thing that
knows how far along it is. A single-object file reports nothing:
one object is one step, and a bar with one step is a light bulb.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or PathLike
|
The |
required |
progress
|
callable
|
Called as |
None
|
Returns:
| Type | Description |
|---|---|
object
|
The object the file holds, or a |
Source code in src/visualdynamics/io/native.py
register_importer
¶
register_importer(name: str, description: str, sniff: Callable, load: Callable, project_type: Callable | None = None) -> None
Teach visualdynamics a format. Registered ones are tried in order, so a reader added later is asked last.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The importer's name, which |
required |
description
|
str
|
What the format is, as a file dialog lists it. |
required |
sniff
|
callable
|
|
required |
load
|
callable
|
|
required |
project_type
|
callable
|
|
None
|
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/io/__init__.py
save
¶
Save a visualdynamics object to a .vdyn (HDF5) file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
object
|
The object to save. |
required |
path
|
str or PathLike
|
Where; |
required |
Returns:
| Type | Description |
|---|---|
None
|
|
Source code in src/visualdynamics/io/native.py
mixed_run
¶
mixed_run(run: str | PathLike, per_octave: int | None = None, *, last: float | None = None, geometry: str | PathLike | None = None, length_unit: str | None = None, photos: Any = None) -> Project
A Rattlesnake random run with a sine sweep under it, worked up into a project: both halves.
project = visualdynamics.mixed_run('run.nc4')
Everything random_vibration_run does — the run imported, the PSDs
averaged and banded, the specification banded, the coherence
measured, the geometry and photographs brought in — and then the
sine half: each tone's level extracted from the same recording
against the sweep specification the file carried. The run says it
is both, so the project comes back declared Random and Sine. A run
with no sine specification is refused by name rather than worked
up as half of what was asked for; random_vibration_run is the
call for it. The keywords are random_vibration_run's.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run
|
str or PathLike
|
The controller's |
required |
per_octave
|
int
|
Bands per octave for the banded PSD and specification; the project's default (a sixth) when omitted. |
None
|
last
|
float
|
Import only the last |
None
|
geometry
|
str or PathLike
|
A geometry file to import and link to the run. |
None
|
length_unit
|
str
|
The geometry's length unit, for a file that does not say. |
None
|
photos
|
str, os.PathLike or sequence of them
|
A folder of photographs, one photograph, or several in order. |
None
|
Returns:
| Type | Description |
|---|---|
Project
|
The worked-up project, declared Random and Sine. |
Source code in src/visualdynamics/project.py
random_vibration_report
¶
random_vibration_report(run: Any = ASK, path: str | PathLike | None = None, *, last: float | None = None, geometry: Any = None, photos: Any = None, per_octave: int | None = None, unit_system: Any = None, marking: str | None = None) -> Any
A Rattlesnake random vibration run in, an HTML report out.
visualdynamics.random_vibration_report('run.nc4', 'report.html')
visualdynamics.random_vibration_report(
'run.nc4', 'report.html', last=100.0,
geometry='article.stp', photos='setup_photos/')
visualdynamics.random_vibration_report() # ask for both
visualdynamics.random_vibration_report('run.nc4', 'reports/')
Left out, it asks. Called with no run, it opens a file dialog
and takes as many runs as are chosen, writing one report each and
returning the list; a run chosen that way is asked about its
geometry too, where Cancel means none. visualdynamics.ASK in
either place forces the question, so a script with a run in hand
can still be asked for the geometry. A run given without a
geometry keyword means no geometry, as it always has, and
nothing opens.
A folder is a folder. path may be the file to write, or a
folder the report lands in under the run's own name with an
.html extension. Without a path it lands beside the run. Several
runs need a folder or no path, never one file name.
The whole workflow in one call, with nothing to click (Brandon,
2026-09-19): import the run — or only its last last seconds, a
shorter run taken whole — detect the averaging, compute the PSDs,
band them and the specification onto octave bands, compute the
multiple coherence, bring in the geometry and the photographs when
they are given, generate the Random Vibration report and write it
as one self-contained HTML file. Returns the path written, or the
list of them when several runs were chosen. The keywords are
random_vibration_run's, plus unit_system for the units the
report is written in.
Everything it does is random_vibration_run followed by
generate_report and export_report; reach for those instead when
the project is wanted afterwards — to write the test summary, or to
save it as .vdyn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run
|
str, os.PathLike, sequence of them, or `visualdynamics.ASK`
|
The controller's |
ASK
|
path
|
(str, PathLike or None)
|
The file to write, or a folder (one that exists, or a name ending in a separator) the report lands in under the run's own name. Beside the run when omitted. Several runs need a folder or None. |
None
|
last
|
float
|
Import only the last |
None
|
geometry
|
str, os.PathLike, `visualdynamics.ASK` or None
|
A geometry file to import and link to the run. Asked for once for
the whole batch when the run was asked for, Cancel meaning none;
|
None
|
photos
|
str, os.PathLike or sequence of them
|
A folder of photographs, one photograph, or several in order. |
None
|
per_octave
|
int
|
Bands per octave for the banded PSD and specification; the project's default (a sixth) when omitted. |
None
|
unit_system
|
UnitSystem
|
The units the report is written in; the package default, 'in-slinch-lbf-s (g)', when omitted. |
None
|
marking
|
str
|
The banner across the top and bottom of every page; 'UNCLASSIFIED' when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
str or list of str
|
The path written; a list of them when several runs were chosen or a list of runs was given. |
Source code in src/visualdynamics/project.py
5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 | |
random_vibration_run
¶
random_vibration_run(run: str | PathLike, per_octave: int | None = None, *, last: float | None = None, geometry: str | PathLike | None = None, length_unit: str | None = None, photos: Any = None) -> Project
A Rattlesnake random vibration run, worked up into a project.
project = visualdynamics.random_vibration_run('run.nc4')
project = visualdynamics.random_vibration_run(
'run.nc4', last=100.0, geometry='article.stp', length_unit='mm',
photos='setup_photos/')
Every step the window would take on the way from a controller file to a finished project, in the order it takes them: import the run, average PSDs from the control time histories, band those onto proportional bands — and the specification onto the same bands, limits and all, since the report reads the banded measurement against the banded requirement (Brandon, 2026-09-18) — and measure how much of each response the drives account for. The run says it is a random vibration test, so the project comes back declared as one.
The averaging is the Detect answer: the controller's own frame length, window and overlap from the file, with the start and the count worked out from the record — where the run is at level and how many frames that stretch holds — so a script, an import and a click on Detect reach the same numbers (Brandon, 2026-09-19).
The rest is what the window's tree asks for after the run is in,
given here so a script never has to open it (Brandon,
2026-09-19): the article's geometry, with its length unit
declared when the file does not carry one; the setup photographs,
as a folder of png or jpeg files, one file, or a list of files in
the order they should appear; and, for a run too long to hold,
last — the seconds before the end to import, the same window the
import dialog's Last field sets, and a run shorter than that is
taken whole. The geometry and the photographs are linked into the
run's own group, which is what lets the report read them against
the channel table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run
|
str or PathLike
|
The controller's |
required |
per_octave
|
int
|
Bands per octave for the banded PSD and specification; the project's default (a sixth) when omitted. |
None
|
last
|
float
|
Import only the last |
None
|
geometry
|
str or PathLike
|
A geometry file to import and link to the run. |
None
|
length_unit
|
str
|
The geometry's length unit, for a file that does not say. |
None
|
photos
|
str, os.PathLike or sequence of them
|
A folder of photographs, one photograph, or several. |
None
|
Returns:
| Type | Description |
|---|---|
Project
|
The worked-up project, declared Random Vibration. |
Source code in src/visualdynamics/project.py
4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 | |
report_kind
¶
Which one-call reports a Rattlesnake run gets: 'random', 'mixed',
'sine' or 'sysid', from the project type the file declares — 'mixed'
being a random run with a sweep under it, which gets a random report
and a sine report (run_report) — and
'sysid' for a streamed save with a system ID's shape, two streams,
a quiet one then a loud one, where the window asks and this
decides (Brandon, 2026-10-02: a batch defaults to the likeliest
reading). A run of a type with no one-call report, a modal or a
transient run, is refused by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run
|
str or PathLike
|
The controller's |
required |
Returns:
| Type | Description |
|---|---|
str
|
'random', 'mixed', 'sine' or 'sysid'. |
Source code in src/visualdynamics/project.py
run_report
¶
run_report(run: Any = ASK, path: str | PathLike | None = None, *, last: Any = None, kinds: Any = None, geometry: Any = None, photos: Any = None, per_octave: int | None = None, unit_system: Any = None, marking: str | None = None, progress: Any = None) -> Any
A Rattlesnake run in, the report its type calls for out.
visualdynamics.run_report('run.nc4', 'report.html')
visualdynamics.run_report() # ask for the runs and the geometry
visualdynamics.run_report('run.nc4', 'reports/')
visualdynamics.run_report(['a.nc4', 'b.nc4'], 'reports/',
geometry='article.stp')
The one-call report that reads the run's own type (report_kind)
and writes that report: a random run gets random_vibration_report's,
a sweep sine_report's, a system identification
system_id_report's, and a random-and-sine run both a random and a
sine report, as <name>_random.html and <name>_sine.html — the
random reading only the last last seconds when they are given, the
sine always the whole run, since a sweep cut short loses tones (the
combined report retired 2026-10-05). A streamed save
that looks like a system ID, two streams quiet then loud, is taken
as one rather than asked about. Named for what it takes, since
visualdynamics.report is the rendering package. The asking, the batch and the
folder are the same rule the typed functions share, and a batch
may mix kinds. The keywords are the union of theirs; per_octave
reaches the random halves alone.
Every run's kind is read before any report is written, so a batch
holding a run with no one-call report is refused whole rather than
stopping partway through. kinds overrides the reading run by run —
a restarted random run the system-ID guess took for one, a file
that declares no type (Reports from Runs' per-run choice,
2026-10-06) — and last may be given run by run too. progress(done, total) is told after
each run, the hook the window's bar and Cancel ride on (Reports
from Runs, 2026-10-04); a run is the smallest step, so a cancel
lands between runs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run
|
str, os.PathLike, sequence of them, or `visualdynamics.ASK`
|
The controller's |
ASK
|
path
|
(str, PathLike or None)
|
The file to write, or a folder (one that exists, or a name ending in a separator) the report lands in under the run's own name. Beside the run when omitted. Several runs need a folder or None. |
None
|
last
|
float or Mapping
|
Import only the last |
None
|
kinds
|
Mapping
|
The report each run gets, by path: 'random', 'mixed' (a random
and a sine report), 'sine' or 'sysid'. A run it leaves out gets
the report its file declares ( |
None
|
geometry
|
str, os.PathLike, `visualdynamics.ASK` or None
|
A geometry file to import and link to the run. Asked for once for
the whole batch when the run was asked for, Cancel meaning none;
|
None
|
photos
|
str, os.PathLike or sequence of them
|
A folder of photographs, one photograph, or several in order. |
None
|
per_octave
|
int
|
Bands per octave for the banded PSD and specification; the project's default (a sixth) when omitted. The random halves alone read it. |
None
|
unit_system
|
UnitSystem
|
The units the report is written in; the package default, 'in-slinch-lbf-s (g)', when omitted. |
None
|
marking
|
str
|
The banner across the top and bottom of every page; 'UNCLASSIFIED' when omitted. |
None
|
progress
|
callable
|
Called as |
None
|
Returns:
| Type | Description |
|---|---|
str or list of str
|
The path written; a list of them when several runs were chosen, a list of runs was given, or a run was random and sine (two reports). |
Source code in src/visualdynamics/project.py
4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 | |
sine_report
¶
sine_report(run: Any = ASK, path: str | PathLike | None = None, *, last: float | None = None, geometry: Any = None, photos: Any = None, unit_system: Any = None, marking: str | None = None) -> Any
A Rattlesnake sine sweep run in, an HTML report out.
visualdynamics.sine_report('sweep.nc4', 'report.html')
visualdynamics.sine_report() # ask for both
visualdynamics.sine_report('sweep.nc4', 'reports/')
random_vibration_report's twin for a sweep: the same asking when
the run is left out, the same batch and folder rules, the workup
of sine_run, and the Sine Sweep report written as one
self-contained HTML file. Returns the path written, or the list of
them when several runs were chosen (Brandon, 2026-10-02: the batch
tool stopped at a run that was sine alone). marking is the banner
across every page of the report, 'UNCLASSIFIED' unless said — on
every one-call report since 2026-10-02, when a batch wanted its own
heading.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run
|
str, os.PathLike, sequence of them, or `visualdynamics.ASK`
|
The controller's |
ASK
|
path
|
(str, PathLike or None)
|
The file to write, or a folder (one that exists, or a name ending in a separator) the report lands in under the run's own name. Beside the run when omitted. Several runs need a folder or None. |
None
|
last
|
float
|
Import only the last |
None
|
geometry
|
str, os.PathLike, `visualdynamics.ASK` or None
|
A geometry file to import and link to the run. Asked for once for
the whole batch when the run was asked for, Cancel meaning none;
|
None
|
photos
|
str, os.PathLike or sequence of them
|
A folder of photographs, one photograph, or several in order. |
None
|
unit_system
|
UnitSystem
|
The units the report is written in; the package default, 'in-slinch-lbf-s (g)', when omitted. |
None
|
marking
|
str
|
The banner across the top and bottom of every page; 'UNCLASSIFIED' when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
str or list of str
|
The path written; a list of them when several runs were chosen or a list of runs was given. |
Source code in src/visualdynamics/project.py
sine_run
¶
sine_run(run: str | PathLike, *, last: float | None = None, geometry: str | PathLike | None = None, length_unit: str | None = None, photos: Any = None) -> Project
A Rattlesnake sine sweep run, worked up into a project.
project = visualdynamics.sine_run('sweep.nc4')
The run imported, each tone's level extracted from the control
channels against the sweep specification the file carried, and the
geometry and photographs brought in and linked, as
random_vibration_run brings them. The run says it is a sine
sweep, so the project comes back declared as one. A run with no
sine sweep specification is refused by name; random_vibration_run
or mixed_run is the call for it. The keywords are
random_vibration_run's, less per_octave, which a sweep has no
use for.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run
|
str or PathLike
|
The controller's |
required |
last
|
float
|
Import only the last |
None
|
geometry
|
str or PathLike
|
A geometry file to import and link to the run. |
None
|
length_unit
|
str
|
The geometry's length unit, for a file that does not say. |
None
|
photos
|
str, os.PathLike or sequence of them
|
A folder of photographs, one photograph, or several in order. |
None
|
Returns:
| Type | Description |
|---|---|
Project
|
The worked-up project, declared Sine Sweep. |
Source code in src/visualdynamics/project.py
4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 | |
system_id_report
¶
system_id_report(run: Any = ASK, path: str | PathLike | None = None, *, last: float | None = None, geometry: Any = None, photos: Any = None, unit_system: Any = None, marking: str | None = None) -> Any
A Rattlesnake system identification in, an HTML report out.
visualdynamics.system_id_report('sysid.nc4', 'sysid.html')
visualdynamics.system_id_report() # ask for both
system_id_run followed by the System ID report: the measured
plant's FRFs, the coherence map, and the signal-to-noise of the
measurement, line by line and per channel — where it falls to
zero decibels, the plant is the room.
Everything about being asked, writing a batch and taking a folder
is random_vibration_report's, and means the same things: left
out, the run is asked for and as many may be chosen as there are
reports wanted; the geometry is asked for once for the whole
batch when the run was asked for, Cancel meaning none;
visualdynamics.ASK forces either question; and path may be the
file to write or the folder to write into.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run
|
str, os.PathLike, sequence of them, or `visualdynamics.ASK`
|
The controller's |
ASK
|
path
|
(str, PathLike or None)
|
The file to write, or a folder (one that exists, or a name ending in a separator) the report lands in under the run's own name. Beside the run when omitted. Several runs need a folder or None. |
None
|
last
|
float
|
Import only the last |
None
|
geometry
|
str, os.PathLike, `visualdynamics.ASK` or None
|
A geometry file to import and link to the run. Asked for once for
the whole batch when the run was asked for, Cancel meaning none;
|
None
|
photos
|
str, os.PathLike or sequence of them
|
A folder of photographs, one photograph, or several in order. |
None
|
unit_system
|
UnitSystem
|
The units the report is written in; the package default, 'in-slinch-lbf-s (g)', when omitted. |
None
|
marking
|
str
|
The banner across the top and bottom of every page; 'UNCLASSIFIED' when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
str or list of str
|
The path written; a list of them when several runs were chosen or a list of runs was given. |
Source code in src/visualdynamics/project.py
4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 | |
system_id_run
¶
system_id_run(run: str | PathLike, *, last: float | None = None, geometry: str | PathLike | None = None, length_unit: str | None = None, photos: Any = None) -> Project
A Rattlesnake system identification, worked up into a project.
project = visualdynamics.system_id_run('sysid.nc4')
The steps the window would take, in its order: import the recording, name the two streams for what they are, measure the plant from the driven one by H1 — the controller's own estimator, the excitation being known and the noise on the response — take the multiple coherence, and average a density from each stream on the same frames, since the signal-to-noise is a ratio of densities and a ratio only exists on shared lines. The project comes back declared a System ID.
last, geometry, length_unit and photos are
random_vibration_run's, and mean the same things.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run
|
str or PathLike
|
The controller's |
required |
last
|
float
|
Import only the last |
None
|
geometry
|
str or PathLike
|
A geometry file to import and link to the run. |
None
|
length_unit
|
str
|
The geometry's length unit, for a file that does not say. |
None
|
photos
|
str, os.PathLike or sequence of them
|
A folder of photographs, one photograph, or several in order. |
None
|
Returns:
| Type | Description |
|---|---|
Project
|
The worked-up project, declared System ID. |
Source code in src/visualdynamics/project.py
export_html
¶
export_html(path: str | PathLike, data: Any = None, *, specification: Any = None, channel: str | None = None, mode: str = 'curves', geometry: Any = None, shapes: Any = None, dofs: tuple[str, Any] | None = None, name: str | None = None, caption: str = '', theme: str | None = None, unit_system: UnitSystem | None = None, fill: bool = True) -> str
One figure, interactive, as a single self-contained HTML file.
visualdynamics.export_html('spec.html', psd, specification=spec,
channel='101Z+', theme='light')
visualdynamics.export_html('modes.html', geometry=g, shapes=modes)
The figure the report draws — zoom, pan and the readout on a plot,
turning and animating on a scene — with nothing else: no title and
no marking, and with fill the figure takes whatever frame holds
it, an iframe or a box on a slide, redrawing when the frame changes
size (Brandon, 2026-09-27, for an interactive slide deck from the
scripts that draw a paper's printed figures). Everything the page
needs is in the file, so it opens offline.
A plot of data — against specification and its zones when one
is given, one channel of it when named, in mode ('curves',
'stage', 'cmif', 'mac' or 'map'); or, given a geometry, a scene of
it, its shapes animating or the dofs one object measures as a
quantity, ('acceleration', frf), drawn as labeled arrows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or path - like
|
Where the .html goes. |
required |
data
|
DataArray or ShapeSet
|
What a plot draws. |
None
|
specification
|
Specification
|
What |
None
|
channel
|
str
|
The one control channel of a comparison to draw. |
None
|
mode
|
str
|
The plot's reading. |
'curves'
|
geometry
|
Geometry
|
Makes the figure a scene. |
None
|
shapes
|
ShapeSet
|
What the scene animates. |
None
|
dofs
|
(str, object)
|
A quantity and an object: its DOFs of that quantity, as arrows. |
None
|
name
|
str
|
What the legend calls |
None
|
caption
|
str
|
A line under the figure. |
''
|
theme
|
('light', 'dark')
|
Fixes the figure's theme; unset, it follows the reader's. |
'light'
|
unit_system
|
UnitSystem
|
The units it is drawn in. |
None
|
fill
|
bool
|
Fill the frame; False lays it out as the report page does. |
True
|
Returns:
| Type | Description |
|---|---|
str
|
The path written. |
Source code in src/visualdynamics/report/__init__.py
3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 | |
convert
¶
values from one unit to another, through SI.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
array - like
|
The values, in |
required |
from_unit
|
str
|
The unit they are in, e.g. 'in/s**2'. |
required |
to_unit
|
str
|
The unit wanted, of the same dimension. |
required |
Returns:
| Type | Description |
|---|---|
ndarray or float
|
The values in |
Source code in src/visualdynamics/units.py
si_factor
¶
Multiplier converting values in unit to SI.
Raises for affine units (degC, degF), which need to_si/from_si.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit
|
str
|
The unit, e.g. 'mm' or 'lbf'. |
required |
dimension
|
str
|
The dimension the unit is meant as, where a spelling is ambiguous. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The factor: a value in |
Source code in src/visualdynamics/units.py
launch_gui
¶
Launch the app, optionally importing files on the way in.
visualdynamics.launch_gui('test.vdyn', 'frfs.unv')
Normally this runs the window in this interpreter and blocks
until it closes, and returns nothing — the window is gone, there is
nothing to hand back. If pyqtgraph has already been bound to another
Qt — import sdynpy does that, with PyQt5, and the choice cannot be
undone in a running process — the app is started in a fresh process
instead, and the Popen handle comes back straight away because
that one is still running. So a session that has been using sdynpy
still gets a window, and sdynpy's own plotting in that session is
left alone.
in_process overrides the decision either way.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*paths
|
str
|
Files to import on the way in. |
()
|
in_process
|
bool
|
True to run the window in this interpreter, False to start a fresh process; decided from the Qt already bound when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
Popen or None
|
The process handle when the window was started apart; None when it ran here and has closed. |