otko/AGENTS.md
smillmorel d09e72e95d chore: clone opstool for visualization reference
opstool v1.0.26 (GPL-3.0) is cloned read-only under
.slim/clonedeps/repos/ to inspect its PyVista/Plotly visualization
settings. GPLv3 section 13 permits combining it with this AGPL-3.0
project. The clone itself is git-ignored; the manifest and the AGENTS.md
pointer are committed.
2026-09-16 19:15:48 -04:00

5.7 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.

Cloned Dependency Source

Read-only dependency source repositories are available under .slim/clonedeps/repos/ for inspection. Do not edit these clones. The structured manifest is .slim/clonedeps.json.

  • .slim/clonedeps/repos/yexiang92__opstool/ — yexiang92/opstool at v1.0.26; the OpenSeesPy pre/post-processor whose PyVista and Plotly visualization settings (opstool/vis/{pyvista,plotly}/plot_utils.py, plot_resp_base.py, vis_model.py) are the reference for otko's canvas look-and-feel. GPL-3.0, and GPLv3 §13 explicitly permits combining it with otko's AGPL-3.0. Any code actually ported from it must keep the opstool copyright notice and record that it was modified (GPLv3 §5a/b) — add that entry to NOTICE when the port lands.