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.
73 lines
5.7 KiB
Markdown
73 lines
5.7 KiB
Markdown
# 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
|
||
|
||
```bash
|
||
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)
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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.
|
||
|