Skip to content

visualdynamics.io.sdynpy_shapes

sdynpy_shapes

Importer for sdynpy mode shape files (.npy).

A structured numpy array with one record per mode and fields 'frequency' (Hz), 'damping' (fraction of critical), 'coordinate' ((node, direction) pairs, one per DOF), 'shape_matrix' (a coefficient per DOF), 'modal_mass' and 'comment1'..'comment5'.

The file does not record which mass unit the shapes were normalized against, so they import unit-less unless mass_unit is declared.

Functions:

Name Description
save

Write mode shapes in sdynpy's own .npy layout.

Classes

Functions:

save

save(
    shapes: ShapeSet,
    path: str | PathLike,
    unit_system: UnitSystem | None = None,
) -> None

Write mode shapes in sdynpy's own .npy layout.

Values go out in unit_system's mass unit, or as stored without one. The format records neither the unit nor the normalization.

Source code in src/visualdynamics/io/sdynpy_shapes.py
def save(shapes: ShapeSet, path: str | os.PathLike, unit_system: UnitSystem | None = None) -> None:
    """Write mode shapes in sdynpy's own .npy layout.

    Values go out in `unit_system`'s mass unit, or as stored without one.
    The format records neither the unit nor the normalization.
    """
    from .exporters import shape_values
    dofs = shapes.num_dofs
    dtype = [('frequency', '<f8'), ('damping', '<f8'),
             ('coordinate', [('node', '<u8'), ('direction', 'i1')], (dofs,)),
             ('shape_matrix', '<f8', (dofs,)), ('modal_mass', '<f8'),
             *[(f'comment{i}', '<U80') for i in range(1, 6)]]
    records = np.zeros(shapes.num_shapes, dtype=dtype)
    records['frequency'] = shapes.frequency
    records['damping'] = shapes.damping
    records['modal_mass'] = shapes.modal_mass
    records['shape_matrix'] = np.real(shape_values(shapes, unit_system))

    pairs = np.zeros(dofs, dtype=[('node', '<u8'), ('direction', 'i1')])
    for i, dof in enumerate(shapes.coordinate):
        node, direction = parse_dof(dof)
        pairs[i] = (node or 0, direction_code(direction) if direction else 0)
    records['coordinate'] = np.tile(pairs, (shapes.num_shapes, 1))
    # the format has one comment field where visualdynamics has two: the comment the
    # file arrived with, and the description typed in visualdynamics. What the user
    # wrote wins, since the other came from a file in the first place.
    for i in range(shapes.num_shapes):
        described = str(shapes.description[i]).strip()
        records['comment1'][i] = (described or str(shapes.comment[i]))[:80]
    np.save(path, records, allow_pickle=True)