Some checks failed
CI / lint (push) Has been cancelled
CI / test (macos-latest, 3.10) (push) Has been cancelled
CI / test (macos-latest, 3.11) (push) Has been cancelled
CI / test (macos-latest, 3.12) (push) Has been cancelled
CI / test (ubuntu-latest, 3.10) (push) Has been cancelled
CI / test (ubuntu-latest, 3.11) (push) Has been cancelled
CI / test (ubuntu-latest, 3.12) (push) Has been cancelled
CI / test (windows-latest, 3.10) (push) Has been cancelled
CI / test (windows-latest, 3.11) (push) Has been cancelled
CI / test (windows-latest, 3.12) (push) Has been cancelled
- core/units: two dominant systems (Metric m/kN, Imperial ft/kip) with display conversion helpers, legacy 4-system migration in ProjectMeta - dialogs/docks: unit-aware material, section, case, load, grid and results labels; diagram renderer unit labels; render controls update - docs: add consistent_units.md; regen examples/*.osmodel artifacts - tests: update persistence/phase8/project/unit-labels for new systems
227 lines
7 KiB
Python
227 lines
7 KiB
Python
"""OpenSees Example 1b. Elastic Portal Frame.
|
|
|
|
OpenSees Wiki:
|
|
https://opensees.berkeley.edu/wiki/index.php?title=OpenSees_Example_1b._Elastic_Portal_Frame
|
|
|
|
This packages the original Example 1b portal frame into one OTKO
|
|
project with shared gravity preload and both lateral-load cases:
|
|
|
|
- static pushover
|
|
- base-excitation earthquake analysis with ``BM68elc.acc``
|
|
|
|
Run from the repository root:
|
|
|
|
python examples/ex1b_portal2d.py
|
|
|
|
Produces ``examples/ex1b_portal2d.osmodel``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
if __package__ is None or __package__ == "":
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
|
|
from otko.core import ( # noqa: E402
|
|
ElasticBeamColumn,
|
|
ElasticSection,
|
|
LinearTimeSeries,
|
|
NodalLoad,
|
|
Node,
|
|
PathTimeSeries,
|
|
PlainLoadPattern,
|
|
Project,
|
|
ProjectMeta,
|
|
PushoverCase,
|
|
StaticCase,
|
|
TransientCase,
|
|
UniformElementLoad,
|
|
UniformExcitationPattern,
|
|
UnitSystem,
|
|
)
|
|
from otko.services import load_project, save_project # noqa: E402
|
|
from otko.services.peer_record import parse_plain_values # noqa: E402
|
|
|
|
|
|
L_BEAM = 504.0
|
|
L_COL = 432.0
|
|
TOP_MASS = 5.18
|
|
|
|
A_COL = 3_600_000_000.0
|
|
IZ_COL = 1_080_000.0
|
|
A_BEAM = 5_760_000_000.0
|
|
IZ_BEAM = 4_423_680.0
|
|
E_MODULUS = 4227.0
|
|
|
|
GRAVITY_W = -7.94
|
|
LATERAL_NODE_LOAD = 2000.0
|
|
|
|
PUSH_STEP = 0.1
|
|
PUSH_TARGET = 10.0
|
|
|
|
GROUND_DT = 0.01
|
|
GROUND_FACTOR = 1.0
|
|
ANALYSIS_DT = 0.02
|
|
ANALYSIS_STEPS = 1000
|
|
DAMPING_RATIO = 0.02
|
|
|
|
_ROOT = Path(__file__).resolve().parent
|
|
GROUND_MOTION_FILE = _ROOT / "data" / "BM68elc.acc"
|
|
REFERENCE_PUSH_TCL = _ROOT / "data" / "Ex1b.Portal2D.Push.tcl.txt"
|
|
REFERENCE_EQ_TCL = _ROOT / "data" / "Ex1b.Portal2D.EQ.tcl.txt"
|
|
|
|
|
|
def _ground_motion_values() -> list[float]:
|
|
return parse_plain_values(GROUND_MOTION_FILE)
|
|
|
|
|
|
def build_ex1b_portal2d() -> Project:
|
|
values = _ground_motion_values()
|
|
|
|
return Project(
|
|
meta=ProjectMeta(
|
|
name="OpenSees Ex 1b - Elastic Portal Frame",
|
|
author="OpenSees Wiki / Silvia Mazzoni & Frank McKenna",
|
|
description=(
|
|
"Original Ex 1b elastic portal frame with shared gravity "
|
|
"preload, static pushover, and BM68elc earthquake cases."
|
|
),
|
|
units=UnitSystem.IMPERIAL,
|
|
),
|
|
ndm=2,
|
|
ndf=3,
|
|
nodes=[
|
|
Node(
|
|
id=1,
|
|
name="Base-L",
|
|
coords=(0.0, 0.0, 0.0),
|
|
restraint=(True, True, False, False, False, True),
|
|
),
|
|
Node(
|
|
id=2,
|
|
name="Base-R",
|
|
coords=(L_BEAM, 0.0, 0.0),
|
|
restraint=(True, True, False, False, False, True),
|
|
),
|
|
Node(id=3, name="Top-L", coords=(0.0, L_COL, 0.0), mass=(TOP_MASS, 0.0, 0.0, 0.0, 0.0, 0.0)),
|
|
Node(id=4, name="Top-R", coords=(L_BEAM, L_COL, 0.0), mass=(TOP_MASS, 0.0, 0.0, 0.0, 0.0, 0.0)),
|
|
],
|
|
sections=[
|
|
ElasticSection(id=1, name="Column", E=E_MODULUS, A=A_COL, Iz=IZ_COL, Iy=IZ_COL, G=1.0, J=1.0),
|
|
ElasticSection(id=2, name="Beam", E=E_MODULUS, A=A_BEAM, Iz=IZ_BEAM, Iy=IZ_BEAM, G=1.0, J=1.0),
|
|
],
|
|
elements=[
|
|
ElasticBeamColumn(id=1, name="Col-L", nodes=(1, 3), section_id=1, geom_transf="Linear"),
|
|
ElasticBeamColumn(id=2, name="Col-R", nodes=(2, 4), section_id=1, geom_transf="Linear"),
|
|
ElasticBeamColumn(id=3, name="Beam", nodes=(3, 4), section_id=2, geom_transf="Linear"),
|
|
],
|
|
time_series=[
|
|
LinearTimeSeries(id=1, name="Gravity"),
|
|
LinearTimeSeries(id=2, name="Lateral"),
|
|
PathTimeSeries(
|
|
id=3,
|
|
name="BM68elc",
|
|
dt=GROUND_DT,
|
|
factor=GROUND_FACTOR,
|
|
values=values,
|
|
file_path=str(GROUND_MOTION_FILE.name),
|
|
),
|
|
],
|
|
load_patterns=[
|
|
PlainLoadPattern(
|
|
id=1,
|
|
name="Gravity",
|
|
time_series_id=1,
|
|
element_loads=[UniformElementLoad(element_id=3, wy=GRAVITY_W)],
|
|
),
|
|
PlainLoadPattern(
|
|
id=2,
|
|
name="Pushover-X",
|
|
time_series_id=2,
|
|
nodal_loads=[
|
|
NodalLoad(node_id=3, forces=(LATERAL_NODE_LOAD, 0.0, 0.0, 0.0, 0.0, 0.0)),
|
|
NodalLoad(node_id=4, forces=(LATERAL_NODE_LOAD, 0.0, 0.0, 0.0, 0.0, 0.0)),
|
|
],
|
|
),
|
|
UniformExcitationPattern(
|
|
id=3,
|
|
name="GroundMotion-X",
|
|
direction=1,
|
|
accel_series_id=3,
|
|
),
|
|
],
|
|
analyses=[
|
|
StaticCase(
|
|
id=1,
|
|
name="Gravity",
|
|
pattern_ids=[1],
|
|
n_steps=10,
|
|
load_factor_increment=0.1,
|
|
system="BandGeneral",
|
|
constraints="Plain",
|
|
integrator="LoadControl",
|
|
algorithm="Newton",
|
|
test="NormDispIncr",
|
|
tolerance=1e-8,
|
|
max_iter=6,
|
|
),
|
|
PushoverCase(
|
|
id=2,
|
|
name="Push",
|
|
preload_case_ids=[1],
|
|
pattern_ids=[2],
|
|
control_node=3,
|
|
control_dof=1,
|
|
target_disp=PUSH_TARGET,
|
|
step_size=PUSH_STEP,
|
|
base_nodes=[1, 2],
|
|
system="BandGeneral",
|
|
constraints="Plain",
|
|
algorithm="Newton",
|
|
test="NormDispIncr",
|
|
tolerance=1e-8,
|
|
max_iter=6,
|
|
),
|
|
TransientCase(
|
|
id=3,
|
|
name="Earthquake",
|
|
preload_case_ids=[1],
|
|
pattern_ids=[3],
|
|
dt=ANALYSIS_DT,
|
|
n_steps=ANALYSIS_STEPS,
|
|
system="BandGeneral",
|
|
constraints="Plain",
|
|
integrator="Newmark",
|
|
integrator_params=(0.5, 0.25),
|
|
algorithm="Newton",
|
|
test="NormDispIncr",
|
|
tolerance=1e-8,
|
|
max_iter=10,
|
|
rayleigh_mode1_damping=DAMPING_RATIO,
|
|
),
|
|
],
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
project = build_ex1b_portal2d()
|
|
project.validate_references()
|
|
gm = next(ts for ts in project.time_series if ts.id == 3)
|
|
print(f"Built '{project.meta.name}'")
|
|
print(f" Gravity + pushover + earthquake cases: {len(project.analyses)}")
|
|
print(f" Ground motion file: {GROUND_MOTION_FILE.name}")
|
|
print(f" Reference Tcls: {REFERENCE_PUSH_TCL.name}, {REFERENCE_EQ_TCL.name}")
|
|
print(f" Record points: {len(gm.values)}, dt = {GROUND_DT}s, factor = {GROUND_FACTOR}")
|
|
out_path = Path(__file__).with_suffix(".osmodel")
|
|
save_project(project, out_path)
|
|
print(f"Saved -> {out_path}")
|
|
restored = load_project(out_path)
|
|
restored.validate_references()
|
|
assert restored.model_dump(by_alias=True) == project.model_dump(by_alias=True)
|
|
print("Round-trip OK.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|