Skip to content

visualdynamics.demo.frame_wing

frame_wing

The Frame-Wing, a frame and its wings, built from blocks of bricks.

The Frame-Wing is a small aluminum frame and two wings the structural dynamics community shares as a common test article for substructuring: a 16 by 6 in ladder cut from half-inch plate, two rails and five uprights half an inch wide, with 41 threaded steel inserts for bolting things to, and two rectangular 22 by 4.4 in wings, thin (1/8 in) and thick (1/4 in), that screw across two of the frame's uprights. Its solid models, finite element models and test data are shared on the SEM Dynamic Substructuring Focus Group wiki, https://wiki.sem.org/wiki/Round_Robin_Frame_Structure.

Here it is built the way a person builds it in the app, every step a project verb the app records. Each part is made as solids: the frame a plate less its four windows (with rounded corners, which leave the fillets) and its 41 holes, the inserts a union of cylinders in the tops of the holes, and each wing a plate less its eighteen holes. The holes are copied in rows with Pattern, and the solids are meshed into bricks with Mesh Solids. A wing is bolted on the way the BARC's bricks are, with Mate Surfaces: the wing and the washers under it are meshed apart from the frame, and each washer's underside is tied to the frame and the screw beneath it, though the meshes do not match. The screws are steel, threaded into the four inserts they pass through (a steel cylinder over each, made last so it takes those bricks), with the rest of each screw's mass as a point mass on the wing's top. Every dimension below is read from the shared finite element models, in inches:

from visualdynamics.demo import frame_wing

model = frame_wing.build()                    # the frame alone
shapes = model.eigensolution(maximum_frequency=2000)
both = frame_wing.build('thick wing')         # the frame with a wing on it

The model is made of solid elements. Beams describe a half-inch bar spanning a window of about a hundred millimeters poorly, and plates cannot describe it at all, so this example is what the eight-node brick with incompatible modes was written for. The element was checked against the mesh the wiki shares: solved here node for node, its first ten elastic modes are within 0.1 % of the frequencies MSC Nastran gives for the same mesh. The model built here is coarser than the shared mesh, a brick a twelfth of an inch across and 45,087 nodes for the frame, and it is within a few percent of the measured frames, which differ from each other by as much.

The measured frequencies of the frames are on the wiki, and the wings' measured frequencies were fitted from the shared test FRFs with Project.fit_modes. The assemblies were checked against the finite element models of the frame with each wing shared on the wiki (A. Linderholt, Linnaeus University, 2024), tuned there to test: within about 4 % over the first nine modes. None of the wiki's files ship with this package.

Run it directly to print the models and their first modes:

python3 -m visualdynamics.demo.frame_wing

Functions:

Name Description
geometry

The frame, a wing, or the frame with a wing screwed on, as a

build

The finite element model, built from geometry.

project

A project to open in the app with five geometries: the frame, each

describe

Print each of the five models with its size, its mass and its

Classes

Functions:

geometry

geometry(wing: str | None = None, size: float = SIZE, wing_size: float = WING_SIZE) -> Any

The frame, a wing, or the frame with a wing screwed on, as a geometry of bricks and point masses built from solids.

Every element group is given its material or mass, so the geometry is ready for fem.Model.from_geometry or for the app's Solve Modes. The geometry keeps the solids it was meshed from, so it can be meshed again. Any other wing raises ValueError.

Parameters:

Name Type Description Default
wing (None, 'frame', 'thin wing', 'thick wing', 'thin wing alone', 'thick wing alone')

None or 'frame' for the frame alone; a wing's name for the frame with that wing; 'thin wing alone' or 'thick wing alone' for the wing by itself.

None
size float

The frame's target element size, in inches; SIZE, 1/12 in, by default.

SIZE
wing_size float

The wing's target element size, in inches; WING_SIZE, 1/8 in, by default.

WING_SIZE

Returns:

Type Description
Geometry

Element groups 'frame' (6061-T6), 'inserts' (the insert material), the wing (6061-T6), and on an assembly 'screws' and 'washers' (steel) and 'screw heads' (point masses), opening on VIEW.

Source code in src/visualdynamics/demo/frame_wing.py
def geometry(wing: str | None = None, size: float = SIZE,
             wing_size: float = WING_SIZE) -> Any:
    """The frame, a wing, or the frame with a wing screwed on, as a
    geometry of bricks and point masses built from solids.

    Every element group is given its material or mass, so the geometry
    is ready for `fem.Model.from_geometry` or for the app's Solve Modes.
    The geometry keeps the solids it was meshed from, so it can be meshed
    again. Any other `wing` raises ValueError.

    Parameters
    ----------
    wing : {None, 'frame', 'thin wing', 'thick wing', 'thin wing alone', 'thick wing alone'}, optional
        None or 'frame' for the frame alone; a wing's name for the
        frame with that wing; 'thin wing alone' or 'thick wing alone'
        for the wing by itself.
    size : float
        The frame's target element size, in inches; `SIZE`, 1/12 in, by
        default.
    wing_size : float
        The wing's target element size, in inches; `WING_SIZE`, 1/8 in,
        by default.

    Returns
    -------
    Geometry
        Element groups 'frame' (6061-T6), 'inserts' (the insert material),
        the wing (6061-T6), and on an assembly 'screws' and 'washers'
        (steel) and 'screw heads' (point masses), opening on `VIEW`.
    """
    from visualdynamics.project import Project

    choice = (wing or 'frame').strip()
    if choice.removesuffix(' alone') not in ('frame', *WINGS):
        raise ValueError(f'{wing!r}: the frame, {" or ".join(WINGS)}, on '
                         'the frame or alone')
    project = Project('Frame-Wing')
    _model(project, 'Part', choice, size, wing_size)
    return project['Part']

build

build(wing: str | None = None, size: float = SIZE, wing_size: float = WING_SIZE) -> Model

The finite element model, built from geometry.

Parameters:

Name Type Description Default
wing str | None

As for geometry.

None
size str | None

As for geometry.

None
wing_size str | None

As for geometry.

None

Returns:

Type Description
Model
Source code in src/visualdynamics/demo/frame_wing.py
def build(wing: str | None = None, size: float = SIZE,
          wing_size: float = WING_SIZE) -> fem.Model:
    """The finite element model, built from `geometry`.

    Parameters
    ----------
    wing, size, wing_size
        As for `geometry`.

    Returns
    -------
    fem.Model
    """
    name = (wing or 'frame').strip()
    return fem.Model.from_geometry(geometry(wing, size, wing_size),
                                   name=f'Frame-Wing: {name}')

project

project(solved: bool = False, size: float = SIZE, wing_size: float = WING_SIZE) -> Any

A project to open in the app with five geometries: the frame, each wing alone, and the frame with each wing (MODELS).

They are built by the verbs the app records, so the project's journal is the workflow, and their element groups are given their properties, so Solve Modes on any of them gives its modes.

frame_wing.project().save('frame-wing.vdyn')

With solved, every model's modes are already solved to SOLVE_TO (1500 Hz). That is the example project the downloads page offers.

Parameters:

Name Type Description Default
solved bool

Solve the modes as well.

False
size float

The target element sizes of the frame and the wings, in inches.

SIZE
wing_size float

The target element sizes of the frame and the wings, in inches.

SIZE

Returns:

Type Description
Project
Source code in src/visualdynamics/demo/frame_wing.py
def project(solved: bool = False, size: float = SIZE,
            wing_size: float = WING_SIZE) -> Any:
    """A project to open in the app with five geometries: the frame, each
    wing alone, and the frame with each wing (`MODELS`).

    They are built by the verbs the app records, so the project's
    journal is the workflow, and their element groups are given their
    properties, so Solve Modes on any of them gives its modes.

        frame_wing.project().save('frame-wing.vdyn')

    With `solved`, every model's modes are already solved to `SOLVE_TO`
    (1500 Hz). That is the example project the downloads page offers.

    Parameters
    ----------
    solved : bool, default False
        Solve the modes as well.
    size, wing_size : float
        The target element sizes of the frame and the wings, in inches.

    Returns
    -------
    Project
    """
    from visualdynamics.project import Project

    out = Project('Frame-Wing')
    for label, choice in MODELS.items():
        _model(out, label, choice, size, wing_size)
        if solved:
            out.solve_modes(label, maximum_frequency=SOLVE_TO)
    return out

describe

describe(size: float = SIZE, modes: int = 9) -> None

Print each of the five models with its size, its mass and its first modes elastic frequencies (those above 1 Hz).

Source code in src/visualdynamics/demo/frame_wing.py
def describe(size: float = SIZE, modes: int = 9) -> None:
    """Print each of the five models with its size, its mass and its
    first `modes` elastic frequencies (those above 1 Hz)."""
    for name in MODELS.values():
        model = build(name, size)
        shapes = model.eigensolution(maximum_frequency=SOLVE_TO)
        print(f'{name}: {model.num_nodes} nodes, {len(model.solids)} bricks, '
              f'{model.total_mass * 1000:.1f} g')
        elastic = [f for f in shapes.frequency if f > 1.0][:modes]
        print('  elastic modes (Hz): ' + ', '.join(f'{f:.1f}' for f in elastic))