feat: named case-result load combinations with full GUI support
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
This commit is contained in:
commit
f361fee969
560 changed files with 178701 additions and 0 deletions
54
tests/integration/test_basic_truss.py
Normal file
54
tests/integration/test_basic_truss.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"""OpenSees integration test for the Basic Truss example.
|
||||
|
||||
Verifies the Example 1 (3-bar truss) model builds, runs, and produces
|
||||
a physically reasonable tip deflection. This is the first of the
|
||||
OpenSees Examples Manual regression tests.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("openseespy")
|
||||
|
||||
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
|
||||
def test_basic_truss_matches_opensees_tcl_reference() -> None:
|
||||
"""Crown displacement must match the OpenSees Tcl Example-1 reference.
|
||||
|
||||
The Tcl model (kip-in) reports node 4 disp = (+0.5301, -0.1779) in.
|
||||
Our SI model converts to that: 0.5301" = 0.01346 m,
|
||||
0.1779" = 0.004518 m. Tolerance is tight because the SI model is
|
||||
a linear rescale of the Tcl model — any deviation would flag a
|
||||
real solver/translation issue.
|
||||
"""
|
||||
from examples.basic_truss import build_basic_truss, IN_TO_M
|
||||
|
||||
proj = build_basic_truss()
|
||||
runner = OpenSeesRunner(proj)
|
||||
result = runner.run(proj.analyses[0])
|
||||
|
||||
assert 4 in result.node_disp
|
||||
ux, uy = result.node_disp[4][-1]
|
||||
ux_in = ux / IN_TO_M
|
||||
uy_in = uy / IN_TO_M
|
||||
assert ux_in == pytest.approx(+0.5301, abs=1e-3), f"Ux = {ux_in} in"
|
||||
assert uy_in == pytest.approx(-0.1779, abs=1e-3), f"Uy = {uy_in} in"
|
||||
|
||||
# All three truss bars must have recorded forces.
|
||||
assert set(result.element_forces.keys()) == {1, 2, 3}
|
||||
|
||||
|
||||
def test_basic_truss_round_trips(tmp_path) -> None: # type: ignore[no-untyped-def]
|
||||
"""The example project must survive save/load without any information loss."""
|
||||
from examples.basic_truss import build_basic_truss
|
||||
from otko.services import load_project, save_project
|
||||
|
||||
p = build_basic_truss()
|
||||
path = tmp_path / "basic_truss.osmodel"
|
||||
save_project(p, path)
|
||||
r = load_project(path)
|
||||
assert r.model_dump(by_alias=True) == p.model_dump(by_alias=True)
|
||||
Loading…
Reference in a new issue