otko/AGENTS.md
smillmorel 9a1baa07c2 feat: live canvas backend switching (Options → Canvas Backend)
Both backends share one SelectionState owned by MainWindow and live
side by side in a QStackedWidget — switching is setCurrentWidget, so
no widget is destroyed mid-session (tearing a VTK window down leaves
dangling make-current callbacks). The choice persists in QSettings.
CanvasCapabilities declares per-backend gaps (force diagrams and
video export stay PyVista-only, both are documented and greyed out)
and the UI gates on capabilities rather than the backend name. The
architecture gate now allows the canvas_plotly package.
2026-09-16 18:43:59 -04:00

4.9 KiB
Raw Blame History

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 + plotly.js backend + 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 direct import openseespy — go through a service.
  • views/canvas3d/ (PyVista/VTK, default) and views/canvas_plotly/ (plotly.js in a QWebEngineView) are two backends for the same central 3D view. Both satisfy the CanvasBackend protocol in views/canvas_base.py, share one SelectionState owned by MainWindow, and are swapped live via Options → Canvas Backend (persisted in QSettings under canvas/backend). MainWindow._activate_canvas keeps both widgets in a QStackedWidget — never destroy a canvas mid-session (VTK leaves dangling make-current callbacks). Backend-specific gaps are declared by CanvasCapabilities (e.g. Plotly has no force-diagram overlay or video export yet); gate UI on canvas.capabilities, never on the backend name. canvas_plotly/trace_builder.py is pure (no Qt, no pyvista) and unit-tested headless.
  • viewmodels/ bridges core↔Qt (signals, QUndoStack); commands/ holds QUndoCommand subclasses.
  • Rules: public functions need type hints + docstring; new domain entities go through Pydantic validation; ops >50 ms run off the GUI thread (AnalysisWorker in QThread, cancel via isInterruptionRequested(), results cross threads as lightweight ResultsHandle to 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 /forgejo subpath — SSH URLs don't have it).
  • Forgejo SSH runs through the system sshd on port 22 (SSH_PORT = 22 in the server's app.ini). If the site ever advertises a :2222 SSH 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.net should answer Hi there, <user>! ... Forgejo does not provide shell access.
  • Default branch main. Work on short-lived branches (fix/<topic>, docs/<topic>), open a PR against main. Never commit directly to main from an agent session.
  • Repo root is otko/ itself. (On the dev laptop there is also an empty, commit-less parent repo at Sync/coding/ — ignore it; all git work happens inside otko/.)

Examples & persistence

  • examples/*.py are source of truth; examples/*.osmodel are 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 → run Tip-Load → M3 peaks 50 kN·m at fixed end.