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
4.1 KiB
AGENTS.md — otko
Pre-alpha SAP2000-style desktop GUI for OpenSeesPy. Python 3.10+; Windows requires 3.12+ (openseespywin==3.8.0.0 has no 3.11 wheel). Solver pinned: openseespy==3.8.0.0.
Install
pip install -e ".[gui,dev]" # desktop: Qt + PyVista + dev tools
pip install -e . # headless: core + services only, no Qt (scripts, notebooks, web backends)
python -m otko # launch GUI (src/otko/app.py:run)
scipy/pandas in [gui] extras are phantom deps (not imported as of 2026-06) — do not add imports expecting them.
Architecture (enforced in review — see PR template)
Strict one-way MVVM + services: views → viewmodels → services → core.
core/(entities:project.py,geometry/,materials/,sections/,loads/,analysis/,catalog/): stdlib + numpy + pydantic only. No Qt, no openseespy. Period.services/(opensees_runner.py,persistence.py,results.py, ...): may use core + h5py + openseespy. No Qt.views/: PySide6/pyvistaqt only. No directimport openseespy— go through a service.viewmodels/bridges core↔Qt (signals,QUndoStack);commands/holdsQUndoCommandsubclasses.- Rules: public functions need type hints + docstring; new domain entities go through Pydantic validation; ops >50 ms run off the GUI thread (
AnalysisWorkerin QThread, cancel viaisInterruptionRequested(), results cross threads as lightweightResultsHandleto HDF5).
Runner emits OpenSeesPy commands in fixed order (docs/architecture.md): wipe → model → node → fix → material → section → geomTransf → element → timeSeries → pattern/load → recorder → system/numberer/... → analyze. Never reorder.
Verify (in this order)
ruff check src tests
ruff format src tests # line-length 100, E501 ignored
mypy src/otko/core src/otko/services
pytest -m "not slow" # CI gate: lint → this, on 3.10/3.11/3.12 × ubuntu/windows/macos
Focused runs: pytest tests/unit (pure logic, ms), pytest tests/gui -k <name> (pytest-qt, needs display), pytest tests/integration -k <name> (real OpenSeesPy runs). Single test: pytest tests/unit/test_project.py::test_name -q. Markers: gui, slow. Linux GUI tests need xvfb-run -a pytest ... plus system Qt libs (see ci.yml apt list).
Notes: tests/conftest.py auto-ops.wipe()s the OpenSees domain between tests (lazy import so core tests stay Qt/OpenSees-free). Coverage omits views/. Commit style: Conventional Commits (feat:, fix:, ...). pre-commit install runs ruff + mypy (mypy hook scoped to core|services|viewmodels).
Git & Forgejo
Remote is a self-hosted Forgejo, SSH-only:
git remote add origin ssh://git@smill-home.ddns.net/smill/otko.git
- Web UI / HTTPS clone:
https://smill-home.ddns.net/forgejo/smill/otko.git(note the/forgejosubpath — SSH URLs don't have it). - Forgejo SSH runs through the system sshd on port 22 (
SSH_PORT = 22in the server'sapp.ini). If the site ever advertises a:2222SSH URL again, that config regressed — push/pull with port 22 anyway and flag it. - Auth is by SSH key (Forgejo → Settings → SSH Keys). Test:
ssh -T git@smill-home.ddns.netshould answerHi there, <user>! ... Forgejo does not provide shell access. - Default branch
main. Work on short-lived branches (fix/<topic>,docs/<topic>), open a PR againstmain. Never commit directly tomainfrom an agent session. - Repo root is
otko/itself. (On the dev laptop there is also an empty, commit-less parent repo atSync/coding/— ignore it; all git work happens insideotko/.)
Examples & persistence
examples/*.pyare source of truth;examples/*.osmodelare generated artifacts (checked in). Never hand-edit.osmodel— change the script and regen:python examples/cantilever.py(each script saves, reloads, asserts clean round-trip).- Projects persist as single Pydantic-validated JSON
.osmodel(diffable); analysis output goes to*.osresults.h5(HDF5, one group per case). - Quick smoke: open
examples/cantilever.osmodel→ runTip-Load→ M3 peaks 50 kN·m at fixed end.