otko/examples/concrete04_cantilever.py
smillmorel d01a5957b7
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
feat: consolidate units to Metric/Imperial with unit-aware dialogs
- 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
2026-09-08 16:33:30 -04:00

215 lines
6.8 KiB
Python

"""Concrete04 (Popovics) fiber-section cantilever.
A single reinforced-concrete column cantilever using Concrete04 as the
fiber material, mirroring the ex2c_canti2d_inelastic_fiber_section example
but with Popovics concrete in place of Kent-Scott-Park (Concrete02).
Run from the repository root:
python examples/concrete04_cantilever.py
Produces ``examples/concrete04_cantilever.osmodel``.
"""
from __future__ import annotations
import math
import sys
from pathlib import Path
if __package__ is None or __package__ == "":
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from otko.core import ( # noqa: E402
Concrete04,
FiberSection,
ForceBeamColumn,
LinearTimeSeries,
NodalLoad,
Node,
PlainLoadPattern,
Project,
ProjectMeta,
PushoverCase,
RectangularPatch,
StaticCase,
Steel02,
StraightLayer,
UnitSystem,
)
from otko.services import load_project, save_project # noqa: E402
# ── Section geometry (SI units: m, N, Pa) ─────────────────────────────────────
L_COL = 3.0 # column height [m]
B_COL = 0.30 # section width [m]
H_COL = 0.30 # section depth [m]
COVER = 0.03 # clear cover [m]
# ── Material parameters (SI) ───────────────────────────────────────────────────
FC = -30e6 # peak compressive strength [Pa] (negative)
EPSC0 = -0.002 # strain at peak
EPSCU = -0.005 # ultimate compressive strain
# Initial tangent: Ec = 4700 * sqrt(|fc| / 1e6) MPa (ACI 318 formula, SI)
EC = 4700.0 * math.sqrt(abs(FC) / 1e6) * 1e6 # ≈ 25.74 GPa
FCT = 2.2e6 # tensile strength [Pa]
ET = 1e-4 # ultimate tensile strain
FY = 420e6 # rebar yield stress [Pa]
ES = 200e9 # rebar elastic modulus [Pa]
BS = 0.01 # strain-hardening ratio
NUM_INT_PTS = 5
N_BARS = 4
BAR_AREA = 314e-6 # m² (≈ 20 mm diameter rebar)
P_GRAVITY = -300e3 # gravity axial load [N] (negative = compressive)
H_LOAD = 50e3 # lateral load at tip [N]
N_GRAVITY = 10
GRAVITY_STEP = 1.0 / N_GRAVITY
PUSH_TARGET = 0.05 * L_COL # [m]
PUSH_STEP = 0.001 * L_COL # [m]
def build_concrete04_cantilever() -> Project:
core_y = H_COL / 2.0 - COVER
core_z = B_COL / 2.0 - COVER
return Project(
meta=ProjectMeta(
name="Concrete04 Cantilever",
description=(
"Single RC column cantilever using Concrete04 (Popovics) "
"fiber section. Demonstrates the Concrete04 material "
"end-to-end: schema → model → runner → results."
),
units=UnitSystem.METRIC,
),
ndm=2,
ndf=3,
nodes=[
Node(
id=1, name="Base",
coords=(0.0, 0.0, 0.0),
restraint=(True, True, True, False, False, False),
),
Node(id=2, name="Top", coords=(0.0, L_COL, 0.0)),
],
materials=[
Concrete04(
id=1, name="C30-Popovics",
fpc=FC, epsc0=EPSC0, epscu=EPSCU, Ec=EC,
fct=FCT, et=ET,
),
Steel02(
id=2, name="Rebar-B500",
Fy=FY, E0=ES, b=BS,
),
],
sections=[
FiberSection(
id=1, name="RC-Fiber-C04",
patches=[
RectangularPatch(
material_id=1,
n_fib_y=8, n_fib_z=4,
y_i=-H_COL / 2, z_i=-B_COL / 2,
y_j= H_COL / 2, z_j= B_COL / 2,
),
],
layers=[
StraightLayer(
material_id=2,
n_bars=N_BARS, bar_area=BAR_AREA,
y_start=-core_y, z_start= core_z,
y_end =-core_y, z_end =-core_z,
),
StraightLayer(
material_id=2,
n_bars=N_BARS, bar_area=BAR_AREA,
y_start= core_y, z_start= core_z,
y_end = core_y, z_end =-core_z,
),
],
),
],
elements=[
ForceBeamColumn(
id=1, name="Column",
nodes=(1, 2),
section_id=1,
integration_points=NUM_INT_PTS,
geom_transf="Linear",
),
],
time_series=[
LinearTimeSeries(id=1, name="Gravity"),
LinearTimeSeries(id=2, name="Lateral"),
],
load_patterns=[
PlainLoadPattern(
id=1, name="Gravity", time_series_id=1,
nodal_loads=[
NodalLoad(node_id=2, forces=(0.0, P_GRAVITY, 0.0, 0.0, 0.0, 0.0)),
],
),
PlainLoadPattern(
id=2, name="Pushover-X", time_series_id=2,
nodal_loads=[
NodalLoad(node_id=2, forces=(H_LOAD, 0.0, 0.0, 0.0, 0.0, 0.0)),
],
),
],
analyses=[
StaticCase(
id=1, name="Gravity",
pattern_ids=[1],
n_steps=N_GRAVITY,
load_factor_increment=GRAVITY_STEP,
system="BandGeneral",
constraints="Plain",
integrator="LoadControl",
algorithm="Newton",
test="NormDispIncr",
tolerance=1e-8,
max_iter=10,
),
PushoverCase(
id=2, name="Push-X",
preload_case_ids=[1],
pattern_ids=[2],
control_node=2,
control_dof=1,
target_disp=PUSH_TARGET,
step_size=PUSH_STEP,
base_nodes=[1],
system="BandGeneral",
constraints="Plain",
algorithm="Newton",
test="EnergyIncr",
tolerance=1e-8,
max_iter=10,
),
],
)
def main() -> None:
project = build_concrete04_cantilever()
project.validate_references()
print(f"Built '{project.meta.name}'")
print(f" L={L_COL} m, BxH={B_COL}x{H_COL} m, Ec={EC/1e9:.2f} GPa")
print(f" Gravity + pushover cases: {len(project.analyses)}")
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()