Skip to content

visualdynamics.demo.barc

barc

The BARC, built from planes, and again from bricks.

The BARC — Box Assembly with Removable Component — is a small bolted aluminum structure the structural dynamics community shares as a common test article: a square box tube with its top wall slotted in two, and on it the "Bench", two channels standing on the two halves and a flat bar bolted across their tops. Its solid model, test data and finite element models are shared on the SEM Dynamic Substructuring Focus Group wiki, https://wiki.sem.org/wiki/BARC.

Here it is built the way a person would build a simple plate model with this package (Brandon, 2026-09-26): each part as the planes it is made of, meshed at mid-thickness (visualdynamics.mesh), each part an element group given its material and thickness, and the bolts as rigid, massless links. Every dimension below comes from the shared solid model (inches):

from visualdynamics.demo import barc

model = barc.build()
shapes = model.eigensolution(maximum_frequency=2000)

The joints are the model. The parts meet through bolts, and their mid-surfaces do not touch — a channel's foot sits 0.1875 in above the box wall's mid-surface — so what joins them is a modeling choice, and it decides the Bench's modes. The choices were checked against the finite element models shared on the wiki while this was built: a single rigid link at each bolt came out soft, and softer at every mesh refinement (a point tie on a plate is a local singularity, not a joint), and each foot tied over its whole area came out stiff. What is built ties each bolt over the elements its washer covers (washer_patch), to the part below with mesh.tie — what the app's Tie does with the same elements picked (2026-09-26) — so the model a person builds in the app by the documentation's steps is this one, node for node and link for link.

And from bricks (2026-10-07): the same structure as solid eight-node bricks (mesh.block), the higher-fidelity model, and the removable component on its own — the two channels and the beam, the part the BARC is named for (solid_geometry, solid_project). The joints follow the finite element model shared on the wiki: the feet are joined to the box over their whole footprint (the bricks share nodes there), the beam rests 0.001 in over the channels so it joins them only where it is bolted, tied over each washer with mesh.tie, and the bolts are point masses, an element group of point elements given a mass (fem.GroupProperties(mass=...)). Without the bolt masses the assembly reads 1 to 8 % stiffer. Checked against the wiki's models and test: the assembly's first ten elastic modes 1 to 4 % above the measured ones, its shapes matching the shared model's in order.

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

python3 -m visualdynamics.demo.barc

Functions:

Name Description
geometry

The BARC as a geometry of plates and rigid links, every element group

washer_patch

The elements a bolt's washer covers, and the element group it bolts them to:

build

The BARC's finite element model (geometry built).

project

A project to open in the app: the BARC's geometry, its element groups

solid_geometry

The BARC, or its removable component, as a geometry of bricks,

solid_build

The BARC's, or its removable component's, model of bricks

solid_project

A project to open in the app: the BARC and its removable

describe

Print the models and their first elastic modes.

Classes

Functions:

geometry

geometry(size: float = SIZE) -> Any

The BARC as a geometry of plates and rigid links, every element group given what it is made of — ready for fem.Model.from_geometry, or for the app's Solve Modes.

Parameters:

Name Type Description Default
size float

The element size aimed at, in inches.

SIZE

Returns:

Type Description
Geometry

Element groups 'box', 'right channel', 'left channel', 'beam' (6061-T6 plates) and 'bolts' (rigid links over the elements each washer covers, washer_patch), opening on VIEW.

Source code in src/visualdynamics/demo/barc.py
def geometry(size: float = SIZE) -> Any:
    """The BARC as a geometry of plates and rigid links, every element group
    given what it is made of — ready for `fem.Model.from_geometry`, or
    for the app's Solve Modes.

    Parameters
    ----------
    size : float
        The element size aimed at, in inches.

    Returns
    -------
    Geometry
        Element groups 'box', 'right channel', 'left channel', 'beam' (6061-T6
        plates) and 'bolts' (rigid links over the elements each washer
        covers, `washer_patch`), opening on `VIEW`.
    """
    whole = mesh.assemble(*_planes(size))
    whole.view = VIEW
    whole.group_properties = {
        int(group): fem.GroupProperties(
            MATERIAL, THICKNESS[whole.group_name[i]] * INCH)
        for i, group in enumerate(whole.group_id)}
    for bolt in BOLTS:
        patch, below = washer_patch(whole, bolt)
        mesh.tie(whole, patch, below, group='bolts')
    return whole

washer_patch

washer_patch(geometry: Any, bolt: tuple) -> tuple[list[int], str]

The elements a bolt's washer covers, and the element group it bolts them to: the elements of the part on top whose centers lie within the washer's radius of the bolt along both edges — at SIZE, the element the bolt passes through and the eight around it under a foot, the 4 × 4 around the node it passes through on the beam. A square rather than the washer's circle because it is what a person picks: whole rows of elements, countable in the view.

Parameters:

Name Type Description Default
geometry Geometry

The BARC's planes, assembled.

required
bolt tuple

One of BOLTS.

required

Returns:

Type Description
(list of int, str)

The element ids, and the name of the element group below.

Source code in src/visualdynamics/demo/barc.py
def washer_patch(geometry: Any, bolt: tuple) -> tuple[list[int], str]:
    """The elements a bolt's washer covers, and the element group it bolts them to:
    the elements of the part on top whose centers lie within the washer's
    radius of the bolt along both edges — at `SIZE`, the element the bolt
    passes through and the eight around it under a foot, the 4 × 4 around
    the node it passes through on the beam. A square rather than the
    washer's circle because it is what a person picks: whole rows of
    elements, countable in the view.

    Parameters
    ----------
    geometry : Geometry
        The BARC's planes, assembled.
    bolt : tuple
        One of `BOLTS`.

    Returns
    -------
    (list of int, str)
        The element ids, and the name of the element group below.
    """
    (bx, bz), _lower, upper, radius = bolt
    side = 'left channel' if bx < 0 else 'right channel'
    top, below = ('beam', side) if upper == BEAM_Y else (side, 'box')
    xyz = geometry.node_xyz / INCH
    patch = []
    for element in geometry.elements_in(top):
        row = int(np.flatnonzero(geometry.elem_id == element)[0])
        center = xyz[geometry.node_index(geometry.elem_conn[row])].mean(axis=0)
        if (abs(center[1] - upper) < 1e-6 and abs(center[0] - bx) <= radius
                and abs(center[2] - bz) <= radius):
            patch.append(element)
    return patch, below

build

build(size: float = SIZE) -> Model

The BARC's finite element model (geometry built).

Parameters:

Name Type Description Default
size float

The element size aimed at, in inches.

SIZE

Returns:

Type Description
Model
Source code in src/visualdynamics/demo/barc.py
def build(size: float = SIZE) -> fem.Model:
    """The BARC's finite element model (`geometry` built).

    Parameters
    ----------
    size : float
        The element size aimed at, in inches.

    Returns
    -------
    fem.Model
    """
    return fem.Model.from_geometry(geometry(size), name='BARC')

project

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

A project to open in the app: the BARC's geometry, its element groups given their properties — Solve Modes on it gives its modes.

barc.project().save('barc.vdyn')

With solved, the modes are solved to SOLVE_TO already: the example project the downloads page offers.

Parameters:

Name Type Description Default
size float

The element size aimed at, in inches.

SIZE
solved bool

Solve the modes as well.

False

Returns:

Type Description
Project
Source code in src/visualdynamics/demo/barc.py
def project(size: float = SIZE, solved: bool = False) -> Any:
    """A project to open in the app: the BARC's geometry, its element groups
    given their properties — Solve Modes on it gives its modes.

        barc.project().save('barc.vdyn')

    With `solved`, the modes are solved to `SOLVE_TO` already: the
    example project the downloads page offers.

    Parameters
    ----------
    size : float
        The element size aimed at, in inches.
    solved : bool, default False
        Solve the modes as well.

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

    out = Project('BARC')
    out.add('BARC', geometry(size))
    if solved:
        out.solve_modes('BARC', maximum_frequency=SOLVE_TO)
    return out

solid_geometry

solid_geometry(part: str = 'BARC', size: float = SIZE) -> Any

The BARC, or its removable component, as a geometry of bricks, rigid ties and point masses, every element group given what it is made of — ready for fem.Model.from_geometry, or for the app's Solve Modes.

Parameters:

Name Type Description Default
part str

One of PARTS: 'BARC', the whole assembly, or 'removable component', the two channels and the beam.

'BARC'
size float

The element size aimed at, in inches.

SIZE

Returns:

Type Description
Geometry

Element groups 'box' (the assembly only), 'right channel', 'left channel', 'beam' (6061-T6 bricks), 'bolt ties' (rigid links from the beam over each washer to its channel) and the bolts' masses, 'foot bolts' (the assembly only) and 'top bolts', opening on VIEW.

Source code in src/visualdynamics/demo/barc.py
def solid_geometry(part: str = 'BARC', size: float = SIZE) -> Any:
    """The BARC, or its removable component, as a geometry of bricks,
    rigid ties and point masses, every element group given what it is made of —
    ready for `fem.Model.from_geometry`, or for the app's Solve Modes.

    Parameters
    ----------
    part : str
        One of `PARTS`: 'BARC', the whole assembly, or 'removable
        component', the two channels and the beam.
    size : float
        The element size aimed at, in inches.

    Returns
    -------
    Geometry
        Element groups 'box' (the assembly only), 'right channel', 'left
        channel', 'beam' (6061-T6 bricks), 'bolt ties' (rigid links
        from the beam over each washer to its channel) and the bolts'
        masses, 'foot bolts' (the assembly only) and 'top bolts',
        opening on `VIEW`.
    """
    if part not in PARTS:
        raise ValueError(f'{part!r} is not one of {PARTS}')
    whole = mesh.assemble(*_bricks(size, part))
    whole.view = VIEW
    whole.group_properties = {int(group): fem.GroupProperties(MATERIAL)
                              for group in whole.group_id}
    xyz = whole.node_xyz / INCH
    for (bx, bz), _face, kind in SOLID_BOLTS:
        if kind != 'top':
            continue
        patch = []
        for element in whole.elements_in('beam'):
            row = int(np.flatnonzero(whole.elem_id == element)[0])
            center = xyz[whole.node_index(whole.elem_conn[row])].mean(axis=0)
            if (abs(center[0] - bx) <= TIE_RADIUS
                    and abs(center[2] - bz) <= TIE_RADIUS):
                patch.append(element)
        mesh.tie(whole, patch, 'left channel' if bx < 0 else 'right channel',
                 group='bolt ties')
    # each bolt's mass at the node of its head face nearest its axis
    xyz = whole.node_xyz / INCH
    for kind in ('foot', 'top'):
        heads = [(axis, face) for axis, face, k in SOLID_BOLTS if k == kind]
        if kind == 'foot' and part != 'BARC':
            continue
        nodes = []
        for (bx, bz), face in heads:
            on_face = np.flatnonzero(np.abs(xyz[:, 1] - face) < 1e-6)
            nearest = on_face[np.argmin(np.hypot(xyz[on_face, 0] - bx,
                                                 xyz[on_face, 2] - bz))]
            nodes.append([int(whole.node_id[nearest])])
        group = whole.add_group(f'{kind} bolts')
        whole.add_elements(nodes, [161] * len(nodes), [group] * len(nodes))
        whole.group_properties[group] = fem.GroupProperties(
            mass=BOLT_MASS[kind] * POUND)
    return whole

solid_build

solid_build(part: str = 'BARC', size: float = SIZE) -> Model

The BARC's, or its removable component's, model of bricks (solid_geometry built).

Parameters:

Name Type Description Default
part str

One of PARTS.

'BARC'
size float

The element size aimed at, in inches.

SIZE

Returns:

Type Description
Model
Source code in src/visualdynamics/demo/barc.py
def solid_build(part: str = 'BARC', size: float = SIZE) -> fem.Model:
    """The BARC's, or its removable component's, model of bricks
    (`solid_geometry` built).

    Parameters
    ----------
    part : str
        One of `PARTS`.
    size : float
        The element size aimed at, in inches.

    Returns
    -------
    fem.Model
    """
    return fem.Model.from_geometry(solid_geometry(part, size),
                                   name=PART_NAMES[part])

solid_project

solid_project(size: float = SIZE, solved: bool = False) -> Any

A project to open in the app: the BARC and its removable component, both of bricks — Solve Modes on either gives its modes.

barc.solid_project().save('barc-bricks.vdyn')

With solved, the BARC's modes are solved to SOLVE_TO and the removable component's to PART_SOLVE_TO: the example project the downloads page offers.

Parameters:

Name Type Description Default
size float

The element size aimed at, in inches.

SIZE
solved bool

Solve the modes as well.

False

Returns:

Type Description
Project
Source code in src/visualdynamics/demo/barc.py
def solid_project(size: float = SIZE, solved: bool = False) -> Any:
    """A project to open in the app: the BARC and its removable
    component, both of bricks — Solve Modes on either gives its modes.

        barc.solid_project().save('barc-bricks.vdyn')

    With `solved`, the BARC's modes are solved to `SOLVE_TO` and the
    removable component's to `PART_SOLVE_TO`: the example project the
    downloads page offers.

    Parameters
    ----------
    size : float
        The element size aimed at, in inches.
    solved : bool, default False
        Solve the modes as well.

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

    out = Project('BARC in Bricks')
    for part, top in zip(PARTS, (SOLVE_TO, PART_SOLVE_TO)):
        out.add(PART_NAMES[part], solid_geometry(part, size))
        if solved:
            out.solve_modes(PART_NAMES[part], maximum_frequency=top)
    return out

describe

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

Print the models and their first elastic modes.

Source code in src/visualdynamics/demo/barc.py
def describe(size: float = SIZE, modes: int = 10) -> None:
    """Print the models and their first elastic modes."""
    model = build(size)
    shapes = model.eigensolution(maximum_frequency=SOLVE_TO)
    print(f'BARC from planes: {model.num_nodes} nodes, {len(model.plates)} '
          f'plates, {len(model.rigid_links)} rigid links, '
          f'{model.structural_mass:.3f} kg')
    elastic = [f for f in shapes.frequency if f > 1.0][:modes]
    print('  elastic modes (Hz): ' + ', '.join(f'{f:.1f}' for f in elastic))
    for part, top in zip(PARTS, (SOLVE_TO, PART_SOLVE_TO)):
        model = solid_build(part, size)
        shapes = model.eigensolution(maximum_frequency=top)
        print(f'{PART_NAMES[part]} from bricks: {model.num_nodes} nodes, '
              f'{len(model.solids)} bricks, {len(model.rigid_links)} rigid '
              f'links, {model.total_mass:.3f} kg with the bolts')
        elastic = [f for f in shapes.frequency if f > 1.0][:modes]
        print('  elastic modes (Hz): '
              + ', '.join(f'{f:.1f}' for f in elastic))