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
232 lines
8.3 KiB
Python
232 lines
8.3 KiB
Python
"""RC Frame Gravity Analysis — OpenSees Examples Manual, Example 3.
|
||
|
||
Single-bay, single-storey RC portal frame under gravity (two 180-kip
|
||
nodal loads on top joints). Columns are nonlinear forceBeamColumn
|
||
elements with the fibre section from the Moment-Curvature example;
|
||
the beam is an elasticBeamColumn with stiffness (A, E, Iz) =
|
||
(360, 4030, 8640) — matching the Tcl reference at:
|
||
https://opensees.berkeley.edu/wiki/index.php?title=RC_Portal_Frame
|
||
|
||
Model (kip-in-ksi):
|
||
node 3 ─────── elasticBeam 3 ─────── node 4
|
||
│ │
|
||
forceBC 1 forceBC 2
|
||
│ │
|
||
node 1 node 2
|
||
(fixed) (fixed)
|
||
|
||
Width 360", height 144", columns fibre-section RC (Concrete01 +
|
||
Steel01). Load pattern: 180 kip ↓ at each top node, Linear time series,
|
||
LoadControl 0.1 × 10 steps = full gravity.
|
||
|
||
Expected terminal state (nodes 3 & 4): Uy ≈ -0.0203 in, Ux ≈ 0,
|
||
column axial force ≈ 180 kip compression.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
|
||
from otko.core import (
|
||
Concrete01,
|
||
CoordinateGridSystem,
|
||
ElasticBeamColumn,
|
||
ElasticSection,
|
||
FiberSection,
|
||
ForceBeamColumn,
|
||
GridSystem,
|
||
LinearTimeSeries,
|
||
NodalLoad,
|
||
Node,
|
||
PlainLoadPattern,
|
||
Project,
|
||
ProjectMeta,
|
||
RectangularPatch,
|
||
StaticCase,
|
||
Steel01,
|
||
StraightLayer,
|
||
UnitSystem,
|
||
make_grid_lines,
|
||
)
|
||
from otko.services import load_project, save_project
|
||
|
||
|
||
# Frame geometry (inches).
|
||
WIDTH = 360.0
|
||
HEIGHT = 144.0
|
||
|
||
# Column section parameters (same as Moment-Curvature example).
|
||
COL_WIDTH = 15.0
|
||
COL_DEPTH = 24.0
|
||
COVER = 1.5
|
||
AS_BAR = 0.60
|
||
|
||
# Material properties (kip, in, ksi).
|
||
CONC_CORE_FPC = -6.0
|
||
CONC_COVER_FPC = -5.0
|
||
STEEL_FY = 60.0
|
||
# Steel Young's modulus: 30000 ksi (matches OpenSees Wiki, which sources
|
||
# the MK example's $E = 30000). Some Tcl reprints show 3000 — that's a
|
||
# typo; 3000 gives Uy ≈ -0.0203 instead of the reference -0.01837.
|
||
STEEL_E = 30000.0
|
||
STEEL_B = 0.01
|
||
|
||
# Beam elastic properties.
|
||
BEAM_A = 360.0
|
||
BEAM_E = 4030.0
|
||
BEAM_IZ = 8640.0
|
||
|
||
# Gravity load.
|
||
P_LOAD = 180.0 # kip, pointing -Y (compression on columns)
|
||
|
||
|
||
def build_rc_frame_gravity() -> Project:
|
||
y1 = COL_DEPTH / 2.0 # 12
|
||
z1 = COL_WIDTH / 2.0 # 7.5
|
||
|
||
return Project(
|
||
meta=ProjectMeta(
|
||
name="RC Frame Gravity (OpenSees Ex 3)",
|
||
author="OpenSees Examples Manual",
|
||
description=(
|
||
"1-bay 1-storey portal frame, nonlinear fibre columns + "
|
||
"elastic beam, 10-step LoadControl gravity pushdown."
|
||
),
|
||
units=UnitSystem.IMPERIAL,
|
||
),
|
||
ndm=2, ndf=3,
|
||
coord_systems=[
|
||
CoordinateGridSystem(
|
||
name="Global",
|
||
grid=GridSystem(
|
||
x_grid_lines=make_grid_lines("X", [0.0, WIDTH]),
|
||
y_grid_lines=make_grid_lines("Y", [0.0, HEIGHT]),
|
||
z_grid_lines=make_grid_lines("Z", [0.0]),
|
||
),
|
||
),
|
||
],
|
||
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=(WIDTH, 0.0, 0.0),
|
||
restraint=(True, True, False, False, False, True)),
|
||
Node(id=3, name="Top-L", coords=(0.0, HEIGHT, 0.0)),
|
||
Node(id=4, name="Top-R", coords=(WIDTH, HEIGHT, 0.0)),
|
||
],
|
||
materials=[
|
||
Concrete01(id=1, name="Core-Conc",
|
||
fpc=CONC_CORE_FPC, epsc0=-0.004,
|
||
fpcu=-5.0, epsU=-0.014),
|
||
Concrete01(id=2, name="Cover-Conc",
|
||
fpc=CONC_COVER_FPC, epsc0=-0.002,
|
||
fpcu=0.0, epsU=-0.006),
|
||
Steel01(id=3, name="Steel-60",
|
||
Fy=STEEL_FY, E0=STEEL_E, b=STEEL_B),
|
||
],
|
||
sections=[
|
||
# Fibre section for the columns (MK recipe).
|
||
FiberSection(
|
||
id=1, name="RC-Col",
|
||
patches=[
|
||
RectangularPatch(
|
||
material_id=1, n_fib_y=10, n_fib_z=1,
|
||
y_i=COVER - y1, z_i=COVER - z1,
|
||
y_j=y1 - COVER, z_j=z1 - COVER,
|
||
),
|
||
RectangularPatch(
|
||
material_id=2, n_fib_y=10, n_fib_z=1,
|
||
y_i=-y1, z_i=z1 - COVER,
|
||
y_j=y1, z_j=z1,
|
||
),
|
||
RectangularPatch(
|
||
material_id=2, n_fib_y=10, n_fib_z=1,
|
||
y_i=-y1, z_i=-z1,
|
||
y_j=y1, z_j=COVER - z1,
|
||
),
|
||
RectangularPatch(
|
||
material_id=2, n_fib_y=2, n_fib_z=1,
|
||
y_i=-y1, z_i=COVER - z1,
|
||
y_j=COVER - y1, z_j=z1 - COVER,
|
||
),
|
||
RectangularPatch(
|
||
material_id=2, n_fib_y=2, n_fib_z=1,
|
||
y_i=y1 - COVER, z_i=COVER - z1,
|
||
y_j=y1, z_j=z1 - COVER,
|
||
),
|
||
],
|
||
layers=[
|
||
StraightLayer(
|
||
material_id=3, n_bars=3, bar_area=AS_BAR,
|
||
y_start=y1 - COVER, z_start=z1 - COVER,
|
||
y_end=y1 - COVER, z_end=COVER - z1,
|
||
),
|
||
StraightLayer(
|
||
material_id=3, n_bars=2, bar_area=AS_BAR,
|
||
y_start=0.0, z_start=z1 - COVER,
|
||
y_end=0.0, z_end=COVER - z1,
|
||
),
|
||
StraightLayer(
|
||
material_id=3, n_bars=3, bar_area=AS_BAR,
|
||
y_start=COVER - y1, z_start=z1 - COVER,
|
||
y_end=COVER - y1, z_end=COVER - z1,
|
||
),
|
||
],
|
||
),
|
||
# Elastic section for the beam.
|
||
ElasticSection(
|
||
id=2, name="Beam",
|
||
E=BEAM_E, A=BEAM_A, Iz=BEAM_IZ,
|
||
Iy=BEAM_IZ, G=1500.0, J=1.0, # placeholders for 3D round-trip
|
||
),
|
||
],
|
||
elements=[
|
||
# Columns — fibre-section forceBeamColumn.
|
||
ForceBeamColumn(id=1, name="Col-L",
|
||
nodes=(1, 3), section_id=1,
|
||
integration_points=5, geom_transf="Linear"),
|
||
ForceBeamColumn(id=2, name="Col-R",
|
||
nodes=(2, 4), section_id=1,
|
||
integration_points=5, geom_transf="Linear"),
|
||
# Beam — elastic.
|
||
ElasticBeamColumn(id=3, name="Beam",
|
||
nodes=(3, 4), section_id=2,
|
||
geom_transf="Linear"),
|
||
],
|
||
time_series=[LinearTimeSeries(id=1, name="Gravity")],
|
||
load_patterns=[PlainLoadPattern(
|
||
id=1, name="Gravity", time_series_id=1,
|
||
nodal_loads=[
|
||
NodalLoad(node_id=3, forces=(0, -P_LOAD, 0, 0, 0, 0)),
|
||
NodalLoad(node_id=4, forces=(0, -P_LOAD, 0, 0, 0, 0)),
|
||
],
|
||
)],
|
||
analyses=[StaticCase(
|
||
id=1, name="Gravity",
|
||
pattern_ids=[1],
|
||
n_steps=10, load_factor_increment=0.1,
|
||
system="BandGeneral", constraints="Transformation",
|
||
integrator="LoadControl", algorithm="Newton",
|
||
test="NormDispIncr", tolerance=1e-12, max_iter=10,
|
||
)],
|
||
)
|
||
|
||
|
||
def main() -> None:
|
||
project = build_rc_frame_gravity()
|
||
project.validate_references()
|
||
print(f"Built '{project.meta.name}'")
|
||
print(f" ndm={project.ndm}, ndf={project.ndf}, units={project.meta.units.value}")
|
||
print(f" Frame: {WIDTH}in x {HEIGHT}in, P = -{P_LOAD} kip at each top node")
|
||
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()
|