Snapshots the current development tree, headlined by proper load combinations (user request): a reusable LoadCombination entity of weighted completed static-case results (e.g. 1.2xDead + 1.6xLive). - core: LoadCombination/LoadCombinationItem entities, Project integration (lookup, unique ids, reference validation) - services: combinations.py (linear superposition + envelope), exported via services __init__ - commands: undoable Add/Delete/Update for combinations - GUI: Load Combinations manager dialog, Run-dialog evaluation, envelope display in Results panel, Combinations tab in Table dock - tests: unit coverage (validation, math, error paths) + integration superposition check vs a single factored run
139 lines
5.2 KiB
Python
139 lines
5.2 KiB
Python
"""RC Frame Pushover Analysis — OpenSees Examples Manual, Example 3.2.
|
||
|
||
Sources the Example 3 gravity model and extends it with a lateral
|
||
reference load pattern + DisplacementControl pushover on the top-left
|
||
joint (node 3, DOF 1 = Ux). Target displacement 15 in with dU = 0.1
|
||
in per step. Matches the Tcl walkthrough at:
|
||
https://opensees.berkeley.edu/wiki/index.php?title=RC_Portal_Frame_Pushover_Analysis
|
||
|
||
Model (kip-in-ksi):
|
||
- Geometry + section + elements = Example 3 (rc_frame_gravity).
|
||
- Gravity preload = a ``StaticCase`` with 10 × LoadControl(0.1)
|
||
steps under a Linear TS — same recipe the Tcl uses
|
||
(``analyze 10; loadConst -time 0.0``). Referenced by the
|
||
PushoverCase via ``preload_case_ids``.
|
||
- Lateral pattern: H = 10 kip at nodes 3 and 4 in +X, Linear TS,
|
||
scaled by DisplacementControl.
|
||
|
||
GUI walkthrough: File → Open → rc_frame_pushover.osmodel → Analyze →
|
||
Run → Pushover → Display → Show Pushover Curve.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
|
||
from otko.core import (
|
||
LinearTimeSeries,
|
||
NodalLoad,
|
||
PlainLoadPattern,
|
||
PushoverCase,
|
||
StaticCase,
|
||
)
|
||
from otko.services import load_project, save_project
|
||
|
||
# Reuse the Example 3 gravity project as the foundation — identical
|
||
# geometry, materials, sections, elements. Only the load patterns and
|
||
# analysis case change for the pushover. Works both when importing as
|
||
# ``examples.rc_frame_pushover`` (pytest) and when running this file
|
||
# directly (``python examples/rc_frame_pushover.py``).
|
||
try:
|
||
from examples.rc_frame_gravity import build_rc_frame_gravity, P_LOAD
|
||
except ImportError:
|
||
import sys
|
||
sys.path.insert(0, str(Path(__file__).parent))
|
||
from rc_frame_gravity import build_rc_frame_gravity, P_LOAD # type: ignore
|
||
|
||
# Pushover parameters from the Tcl reference.
|
||
H_LATERAL = 10.0 # kip — reference lateral load
|
||
D_STEP = 0.1 # in — DisplacementControl increment
|
||
D_TARGET = 15.0 # in — total pushover displacement
|
||
|
||
|
||
def build_rc_frame_pushover(): # type: ignore[no-untyped-def]
|
||
"""Start from the Ex3 gravity model and re-plumb for pushover.
|
||
|
||
Three edits to the gravity project:
|
||
1. Add a ``LinearTimeSeries`` + ``PlainLoadPattern`` for the
|
||
lateral reference load (H = 10 kip at nodes 3 & 4, +X).
|
||
2. Keep the gravity pattern on its own Linear TS — the preload
|
||
runs as a 10-step ``LoadControl(0.1)`` ramp, exactly as the
|
||
Tcl walkthrough does.
|
||
3. Replace the StaticCase with two cases: a preload ``StaticCase``
|
||
(id 100) for gravity, and a ``PushoverCase`` whose
|
||
``preload_case_ids=[100]`` references it. The pushover's own
|
||
``pattern_ids`` holds only the lateral reference.
|
||
"""
|
||
proj = build_rc_frame_gravity()
|
||
proj.meta.name = "RC Frame Pushover (OpenSees Ex 3.2)"
|
||
proj.meta.description = (
|
||
"Ex 3 gravity preload (StaticCase) + lateral reference load + "
|
||
"DisplacementControl pushover on node 3 (DOF 1) to 15 in, "
|
||
"chained via preload_case_ids."
|
||
)
|
||
|
||
proj.time_series = [
|
||
LinearTimeSeries(id=1, name="Gravity"),
|
||
LinearTimeSeries(id=2, name="Lateral"),
|
||
]
|
||
proj.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)),
|
||
],
|
||
),
|
||
# Lateral reference — scaled by the DisplacementControl factor.
|
||
PlainLoadPattern(
|
||
id=2, name="Lateral",
|
||
time_series_id=2,
|
||
nodal_loads=[
|
||
NodalLoad(node_id=3, forces=(H_LATERAL, 0, 0, 0, 0, 0)),
|
||
NodalLoad(node_id=4, forces=(H_LATERAL, 0, 0, 0, 0, 0)),
|
||
],
|
||
),
|
||
]
|
||
proj.analyses = [
|
||
StaticCase(
|
||
id=100, name="Gravity-Preload",
|
||
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,
|
||
),
|
||
PushoverCase(
|
||
id=1, name="Pushover",
|
||
preload_case_ids=[100],
|
||
pattern_ids=[2], # lateral only
|
||
control_node=3, control_dof=1, # Ux at top-left joint
|
||
target_disp=D_TARGET,
|
||
step_size=D_STEP,
|
||
base_nodes=[1, 2],
|
||
system="BandGeneral", constraints="Transformation",
|
||
algorithm="Newton",
|
||
test="NormDispIncr", tolerance=1e-12, max_iter=10,
|
||
),
|
||
]
|
||
return proj
|
||
|
||
|
||
def main() -> None:
|
||
project = build_rc_frame_pushover()
|
||
project.validate_references()
|
||
print(f"Built '{project.meta.name}'")
|
||
print(f" H = {H_LATERAL} kip reference, dU = {D_STEP} in, target = {D_TARGET} in")
|
||
print(f" Steps = {int(D_TARGET / D_STEP)}")
|
||
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()
|