Nonlinear Member Hinge#
|
Modelling a connection that transfers moment in one direction only, to check the impact on internal forces:
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."
)