Moving Load Wizard#

../../../../_images/load_wizard_moving.png

Driving a wheel load across a single-span slab bridge deck with the Moving Load Wizard:

  • Build a simply supported 12 x 8 m reinforced-concrete deck slab

  • Model the traffic lane as a line set and use it as the wizard’s trajectory

  • Activate the load-wizards add-on in base data

  • Define the vehicle as a Load Model carrying a 150 kN LM1 wheel

  • Set the lane’s moving step and end offsets through the wizard tree tables, addressed by row key

  • Generate one load case per 0.5 m step of the wheel along the lane

  • Calculate all positions and read the influence line of the deck deflection

Keywords:
moving load wizard load model tree table bridge deck influence line
"""
Moving Load Wizard via the RFEM gRPC API.

Builds a single-span reinforced-concrete slab bridge deck, drives one wheel load
along the traffic lane with the Moving Load Wizard, calculates every generated
position and prints the resulting influence line of the deck deflection.

The model
---------
    12.0 m span, 8.0 m wide, 0.60 m C30/37 slab, simply supported at both
    abutments. The traffic lane is the deck centreline (y = 4.0 m), modelled as
    a LineSet - that line set is the wizard's trajectory.

    A LoadModel (the "vehicle") carries one concentrated wheel force. The lane
    keeps the wheel 1.0 m clear of each abutment and steps it along in 0.5 m
    increments, so the wizard generates one load case per wheel position
    (21 of them, x = 1.0 ... 11.0 m).

    The wheel force is 150 kN - the EN 1991-2 Load Model 1 tandem axle Q1k =
    300 kN shared by two wheels - applied here as a single wheel print for
    simplicity.

Wizard tree tables
------------------
    The load components of a LoadModel and the lane definitions of a MovingLoad
    are tree tables. Their rows are addressed by `key` (independent of the RFEM
    language). A row is written through its typed field (e.g. `moving_step`)
    and read back through `value`, a string holding the number in SI units.
"""

from dlubal.api import rfem, common
from math import inf

SPAN = 12.0          # m, abutment to abutment
WIDTH = 8.0          # m, deck width
THICKNESS = 0.60     # m
WHEEL_LOAD = 150.0   # kN, EN 1991-2 LM1 tandem axle Q1k = 300 kN over two wheels
MOVING_STEP = 0.5    # m, distance between two generated wheel positions
END_CLEARANCE = 1.0  # m, wheel kept this far from each abutment


def dump_tree(rows, depth=0):
    """Print a wizard tree table: row keys and their values (SI units)."""
    for row in rows:
        print(f"{'  ' * depth + row.key:36s} {row.value}".rstrip())
        dump_tree(row.rows, depth + 1)


def tree_value(rows, path) -> float:
    """Value of the tree row addressed by its chain of keys, in SI units."""
    for key in path:
        row = next(row for row in rows if row.key == key)
        rows = row.rows
    return float(row.value)


def define_structure() -> list:
    """Simply supported slab deck plus the centreline lane used as trajectory."""
    return [
        rfem.structure_core.Material(no=1, name="C30/37"),
        rfem.structure_core.Thickness(no=1, material=1, uniform_thickness=THICKNESS),

        # Deck corners, then the two centreline nodes carrying the lane.
        rfem.structure_core.Node(no=1, coordinate_1=0.0, coordinate_2=0.0),
        rfem.structure_core.Node(no=2, coordinate_1=SPAN, coordinate_2=0.0),
        rfem.structure_core.Node(no=3, coordinate_1=SPAN, coordinate_2=WIDTH),
        rfem.structure_core.Node(no=4, coordinate_1=0.0, coordinate_2=WIDTH),
        rfem.structure_core.Node(no=5, coordinate_1=0.0, coordinate_2=WIDTH / 2),
        rfem.structure_core.Node(no=6, coordinate_1=SPAN, coordinate_2=WIDTH / 2),

        rfem.structure_core.Line(no=1, definition_nodes=[1, 2]),   # edge beam, y = 0
        rfem.structure_core.Line(no=2, definition_nodes=[2, 3]),   # abutment, x = SPAN
        rfem.structure_core.Line(no=3, definition_nodes=[3, 4]),   # edge beam, y = WIDTH
        rfem.structure_core.Line(no=4, definition_nodes=[4, 1]),   # abutment, x = 0
        rfem.structure_core.Line(no=5, definition_nodes=[5, 6]),   # traffic lane

        rfem.structure_core.Surface(no=1, boundary_lines=[1, 2, 3, 4], thickness=1),

        # The trajectory must be a LINE SET, not a plain line.
        rfem.structure_core.LineSet(no=1, lines=[5]),

        # Simply supported: one abutment held in all three directions, the other
        # free to slide along the span so the deck is not restrained axially.
        rfem.types_for_lines.LineSupport(
            no=1, user_defined_name_enabled=True, name="Fixed bearing",
            lines=[4],
            spring=common.Vector3d(x=inf, y=inf, z=inf),
            rotational_restraint=common.Vector3d(x=0.0, y=0.0, z=0.0),
        ),
        rfem.types_for_lines.LineSupport(
            no=2, user_defined_name_enabled=True, name="Sliding bearing",
            lines=[2],
            spring=common.Vector3d(x=0.0, y=inf, z=inf),
            rotational_restraint=common.Vector3d(x=0.0, y=0.0, z=0.0),
        ),

        rfem.loading.StaticAnalysisSettings(no=1),
    ]


def max_deck_deflection(rfem_app, load_case_no: int) -> tuple[float, float]:
    """Largest downward deck deflection for one load case: (u_z [mm], x [m]).

    Global Z points down, so the maximum u_z is the deepest sag. On a simply
    supported span it stays near midspan wherever the wheel stands; only its
    size changes.
    """
    results = rfem_app.get_results(
        results_type=rfem.results.STATIC_ANALYSIS_SURFACES_GLOBAL_DEFORMATIONS_MESH_NODES,
        filters=[rfem.results.ResultsFilter(column_id='loading',
                                            filter_expression=f'LC{load_case_no}')],
    ).data
    worst = results.loc[results['u_z'].idxmax()]
    return float(worst['u_z']) * 1000.0, float(worst['location_x'])


with rfem.Application() as rfem_app:

    rfem_app.close_all_models(save_changes=False)
    rfem_app.create_model(name='moving_load_wizard')
    rfem_app.delete_all_objects()

    # The wizard lives in the Load Wizards add-on. The combination wizard stays
    # off, so the generated load cases keep the wizard's default action
    # category - assigning another one requires the combination wizard.
    base_data: rfem.BaseData = rfem_app.get_base_data()
    base_data.addons.load_wizards_active = True
    base_data.addons.combination_wizard_and_classification_active = False
    rfem_app.set_base_data(base_data=base_data)

    rfem_app.create_object_list(define_structure())

    # --- The vehicle -----------------------------------------------------------
    # TYPE_SURFACES because the load travels over the deck slab. RFEM gives the
    # load model one component, a concentrated force acting in global Z; only
    # its magnitude (SI, in N) is set here, every other row keeps its default.
    load_model = rfem.load_wizards.LoadModel
    component = load_model.LoadComponentsTreeTableRow
    rfem_app.create_object(
        load_model(
            no=1,
            type=load_model.TYPE_SURFACES,
            user_defined_name_enabled=True,
            name="LM1 wheel",
            load_components=load_model.LoadComponentsTreeTable(rows=[
                component(key="load_component", rows=[
                    component(key="parameters", rows=[
                        component(key="load_magnitude_P", load_magnitude_P=WHEEL_LOAD * 1000.0),
                    ]),
                ]),
            ]),
            comment="Created via gRPC API",
        )
    )

    components = rfem_app.get_object(load_model(no=1)).load_components
    print("=" * 74)
    print("Load model components (key, value [SI]):")
    print("=" * 74)
    dump_tree(components.rows)
    wheel_load = tree_value(components.rows,
                            ["load_component", "parameters", "load_magnitude_P"]) / 1000.0

    # --- The moving load -------------------------------------------------------
    # The lane follows line set 1. Its moving step and its start / end offsets
    # are set in the lane-definitions tree; the lane length is taken from the
    # line set. Every movement row needs `lane_definition` - without it the
    # wizard generates nothing.
    moving_load = rfem.load_wizards.MovingLoad
    lane = moving_load.LaneDefinitionsTreeTableRow
    rfem_app.create_object(
        moving_load(
            no=1,
            user_defined_name_enabled=True,
            name="LM1 wheel crossing the deck",
            all_surfaces_used=True,
            trajectory_set_line=1,
            lane_definitions=moving_load.LaneDefinitionsTreeTable(rows=[
                lane(key="lane", rows=[
                    lane(key="parameters", rows=[
                        lane(key="moving_step", moving_step=MOVING_STEP),
                    ]),
                    lane(key="offset_and_bumper", rows=[
                        lane(key="start_offset", start_offset=END_CLEARANCE),
                        lane(key="end_offset", end_offset=END_CLEARANCE),
                    ]),
                ]),
            ]),
            movements=moving_load.MovementsTable(rows=[
                moving_load.MovementsRow(
                    no=1,
                    lane_definition=1,
                    load_model=1,
                    load_factor=1.0,
                ),
            ]),
            generate_load_cases=True,
            comment="Created via gRPC API",
        )
    )

    wizard = rfem_app.get_object(moving_load(no=1))

    print("=" * 74)
    print("Lane definitions (key, value [SI]):")
    print("=" * 74)
    dump_tree(wizard.lane_definitions.rows)

    lane_length = tree_value(wizard.lane_definitions.rows, ["lane", "parameters", "length"])
    moving_step = tree_value(wizard.lane_definitions.rows, ["lane", "parameters", "moving_step"])
    start_offset = tree_value(wizard.lane_definitions.rows,
                              ["lane", "offset_and_bumper", "start_offset"])

    generated = [row.load_case for row in wizard.load_cases.rows]
    print("-" * 74)
    print(f"lane length        : {lane_length:.1f} m   (= length of line set 1)")
    print(f"moving step        : {moving_step:.1f} m")
    print(f"start offset       : {start_offset:.1f} m")
    print(f"generated LCs      : {len(generated)} (LC{generated[0]} ... LC{generated[-1]})")
    print(f"wheel load         : {wheel_load:.0f} kN (load model, load factor 1.0)")

    # --- Calculate every position and read the influence line -------------------
    rfem_app.calculate_all(skip_warnings=True)

    print("=" * 74)
    print("Influence line - maximum deck deflection per wheel position")
    print("=" * 74)
    print(f"{'LC':>4}  {'wheel x [m]':>12}  {'max u_z [mm]':>13}  {'at x [m]':>9}")

    influence = []
    for index, load_case_no in enumerate(generated):
        position = start_offset + index * moving_step
        deflection, at_x = max_deck_deflection(rfem_app, load_case_no)
        influence.append((position, deflection))
        print(f"{load_case_no:>4}  {position:>12.1f}  {deflection:>13.2f}  {at_x:>9.2f}")

    worst_position, worst_deflection = max(influence, key=lambda item: item[1])
    print("-" * 74)
    print(f"governing wheel position : x = {worst_position:.1f} m "
          f"(span/2 = {SPAN / 2:.1f} m)")
    print(f"maximum deflection       : {worst_deflection:.2f} mm")