Nonlinear Member Hinge#

../../../../_images/nonlinear_member_hinge.png

Modelling a connection that transfers moment in one direction only, to check the impact on internal forces:

  • Create a single-span beam fixed at one end and pinned at the other

  • Assign a member hinge with the nonlinearity fixed if negative M_y at the fixed end

  • Set all remaining release constants explicitly, since an omitted constant releases that component

  • Calculate one gravity and one uplift load case in a single run

  • Compare the connection moment, the midspan moment, and the deflection of both load cases

Keywords:
member hinge hinge nonlinearity moment connection one-way connection propped cantilever internal forces
from math import inf

from dlubal.api import common, rfem

# -------------------------------------------------------
# Beam with a one-way moment connection.
#
# A seated beam-to-column connection can bear against its
# seat and transfer moment in one direction, but opens as
# soon as the moment reverses. This is modelled with the
# member hinge nonlinearity "fixed if negative M_y": the
# hinge releases phi_y, except while M_y is negative, where
# it acts as a rigid moment connection.
#
# One model is calculated for two load cases, and the hinge
# decides its own behaviour for each of them:
#   1) gravity - the connection closes and the beam acts as
#      a propped cantilever
#   2) uplift  - the connection opens and the beam acts as
#      a simply supported beam
#
# Note that the enum is named "fixed if", not "failure if".
# On a hinge the degree of freedom becomes rigid, it does
# not fail. The support objects keep the "failure if" names.
#
# Assumptions:
#   - single-span 2D beam in the global XZ plane
#   - Z-up coordinate system
#   - fixed support at the connection, pinned at the far end
#   - self-weight ignored, so results match hand calculation
# -------------------------------------------------------

# Editable parameters (SI units)
MODEL_NAME = "nonlinear_member_hinge"

SPAN = 6.0
MATERIAL = "S235"
CROSS_SECTION = "IPE 300"

LOAD = 20_000.0  # N/m, applied downwards and upwards


def define_structure() -> list:
    """Define the beam geometry, material, cross-section, and supports."""

    return [
        rfem.structure_core.Material(
            no=1,
            name=MATERIAL,
        ),
        rfem.structure_core.CrossSection(
            no=1,
            name=CROSS_SECTION,
            material=1,
        ),
        # Nodes
        rfem.structure_core.Node(no=1, coordinate_1=0.0, coordinate_2=0.0, coordinate_3=0.0),
        rfem.structure_core.Node(no=2, coordinate_1=SPAN, coordinate_2=0.0, coordinate_3=0.0),
        # Line
        rfem.structure_core.Line(no=1, definition_nodes=[1, 2]),
        # Connection end, rigidly supported so the hinge alone governs the restraint
        rfem.types_for_nodes.NodalSupport(
            no=1,
            nodes=[1],
            spring=common.Vector3d(x=inf, y=inf, z=inf),
            rotational_restraint=common.Vector3d(x=inf, y=inf, z=inf),
        ),
        # Far end, free to slide along the beam axis and to rotate in plane
        rfem.types_for_nodes.NodalSupport(
            no=2,
            nodes=[2],
            spring=common.Vector3d(x=0.0, y=inf, z=inf),
            rotational_restraint=common.Vector3d(x=inf, y=0.0, z=inf),
        ),
    ]


def define_hinge() -> list:
    """Define the one-way moment hinge and the member it is assigned to."""

    return [
        # Every release constant has to be given explicitly. An omitted spring
        # constant is zero, which releases that component and would disconnect
        # the member. Only phi_y is released here, the rest stays rigid.
        rfem.types_for_members.MemberHinge(
            no=1,
            user_defined_name_enabled=True,
            name="Seated connection, moment in one direction",
            axial_release_n=inf,
            axial_release_vy=inf,
            axial_release_vz=inf,
            moment_release_mt=inf,
            moment_release_my=0.0,
            moment_release_mz=inf,
            moment_release_my_nonlinearity=(
                rfem.types_for_members.MemberHinge.MOMENT_RELEASE_MY_NONLINEARITY_FIXED_IF_NEGATIVE
            ),
        ),
        # The hinge is attached through the member, not through the hinge itself.
        # Its own "members" attribute is a read-only back reference.
        rfem.structure_core.Member(
            no=1,
            line=1,
            cross_section_start=1,
            member_hinge_start=1,
        ),
    ]


def define_loading() -> list:
    """Define the analysis settings and the gravity and uplift load cases."""

    return [
        rfem.loading.StaticAnalysisSettings(
            no=1,
            analysis_type=rfem.loading.StaticAnalysisSettings.ANALYSIS_TYPE_GEOMETRICALLY_LINEAR,
        ),
        rfem.loading.LoadCase(
            no=1,
            name="Gravity",
            action_category=rfem.loading.LoadCase.ACTION_CATEGORY_PERMANENT_G,
            static_analysis_settings=1,
            self_weight_active=False,
        ),
        rfem.loading.LoadCase(
            no=2,
            name="Uplift",
            action_category=rfem.loading.LoadCase.ACTION_CATEGORY_PERMANENT_G,
            static_analysis_settings=1,
            self_weight_active=False,
        ),
        rfem.loads.MemberLoad(
            no=1,
            load_case=1,
            members=[1],
            load_type=rfem.loads.MemberLoad.LOAD_TYPE_FORCE,
            load_distribution=rfem.loads.MemberLoad.LOAD_DISTRIBUTION_UNIFORM,
            load_direction=rfem.loads.MemberLoad.LOAD_DIRECTION_GLOBAL_Z_OR_USER_DEFINED_W_TRUE_LENGTH,
            magnitude=-LOAD,
        ),
        rfem.loads.MemberLoad(
            no=2,
            load_case=2,
            members=[1],
            load_type=rfem.loads.MemberLoad.LOAD_TYPE_FORCE,
            load_distribution=rfem.loads.MemberLoad.LOAD_DISTRIBUTION_UNIFORM,
            load_direction=rfem.loads.MemberLoad.LOAD_DIRECTION_GLOBAL_Z_OR_USER_DEFINED_W_TRUE_LENGTH,
            magnitude=+LOAD,
        ),
    ]


def evaluate_load_case(rfem_app, load_case_no: int) -> dict:
    """Collect the connection moment, the span moments, and the deflection of one load case."""

    loading = f"LC{load_case_no}"

    internal_forces = rfem_app.get_results(
        results_type=rfem.results.STATIC_ANALYSIS_MEMBERS_INTERNAL_FORCES,
        filters=[rfem.results.ResultsFilter(column_id="loading", filter_expression=loading)],
    ).data.sort_values("location_x")

    deformations = rfem_app.get_results(
        results_type=rfem.results.STATIC_ANALYSIS_MEMBERS_GLOBAL_DEFORMATIONS,
        filters=[rfem.results.ResultsFilter(column_id="loading", filter_expression=loading)],
    ).data

    connection_moment = float(internal_forces["m_y"].iloc[0])
    midspan = (internal_forces["location_x"] - SPAN / 2).abs().idxmin()

    return {
        "connection_moment": connection_moment,
        "closed": abs(connection_moment) > 1.0,
        "midspan_moment": float(internal_forces["m_y"].loc[midspan]),
        "deflection": max(deformations["u_z"].max(), deformations["u_z"].min(), key=abs),
    }


def print_load_case(name: str, result: dict) -> None:
    """Print one load case as a single comparison row."""

    print(
        f"{name:<12}"
        f"{'closed' if result['closed'] else 'open':>10}"
        f"{result['connection_moment'] / 1000:>16.2f}"
        f"{result['midspan_moment'] / 1000:>16.2f}"
        f"{result['deflection'] * 1000:>14.2f}"
    )


with rfem.Application() as rfem_app:

    print(f"\nCreating model: {MODEL_NAME}")
    rfem_app.close_all_models(save_changes=False)
    rfem_app.create_model(name=MODEL_NAME)

    base_data = rfem_app.get_base_data()
    base_data.main.surfaces_active = False
    base_data.general_settings.global_axes_orientation = (
        rfem.BaseData.GeneralSettings.GLOBAL_AXES_ORIENTATION_ZUP
    )
    rfem_app.set_base_data(base_data=base_data)

    rfem_app.delete_all_objects()
    rfem_app.create_object_list(
        define_structure()
        + define_hinge()
        + define_loading()
    )

    calculation_info = rfem_app.calculate_all(skip_warnings=True)
    print(f"\nCalculation Succeeded:\n{calculation_info.succeeded}")

    print(
        f"\n{'Load case':<12}{'Hinge':>10}{'M_y,connection':>16}"
        f"{'M_y,midspan':>16}{'u_z,max':>14}"
    )
    print(f"{'':<12}{'':>10}{'[kNm]':>16}{'[kNm]':>16}{'[mm]':>14}")
    print("-" * 68)
    print_load_case("Gravity", evaluate_load_case(rfem_app, 1))
    print_load_case("Uplift", evaluate_load_case(rfem_app, 2))

    print(
        "\nUnder gravity the connection moment is negative, so the hinge stays "
        "rigid and the beam acts as a propped cantilever. Under uplift the "
        "moment reverses, the hinge releases, and the beam acts as a simply "
        "supported beam with no moment at the connection."
    )