otko/AGENTS.md
smillmorel 429ebc0790
Some checks failed
CI / lint (pull_request) Has been cancelled
CI / type (pull_request) Has been cancelled
CI / test-headless (macos-latest, 3.10) (pull_request) Has been cancelled
CI / test-headless (macos-latest, 3.11) (pull_request) Has been cancelled
CI / test-headless (macos-latest, 3.12) (pull_request) Has been cancelled
CI / test-headless (ubuntu-latest, 3.10) (pull_request) Has been cancelled
CI / test-headless (ubuntu-latest, 3.11) (pull_request) Has been cancelled
CI / test-headless (ubuntu-latest, 3.12) (pull_request) Has been cancelled
CI / test-headless (windows-latest, 3.10) (pull_request) Has been cancelled
CI / test-headless (windows-latest, 3.11) (pull_request) Has been cancelled
CI / test-headless (windows-latest, 3.12) (pull_request) Has been cancelled
CI / test-gui (pull_request) Has been cancelled
CI / test-integration (macos-latest) (pull_request) Has been cancelled
CI / test-integration (ubuntu-latest) (pull_request) Has been cancelled
CI / test-integration (windows-latest) (pull_request) Has been cancelled
docs: repair AGENTS.md NUL corruption and record the canvas contracts
The previous AGENTS.md edit wrote 478 NUL bytes instead of the intended
sentence, so git and grep treated the file as binary. Restored the clean
UTF-8 text and documented both Plotly canvas invariants: data-only pushes
must pass preserveView (never re-send the camera), and the hover/snap
marker must only be restyled when its visible state changes.
2026-09-16 20:33:30 -04:00

74 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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. `PlotlyCanvas` pushes with `Plotly.react`, which resets scene attributes the layout omits: data-only pushes pass `preserveView` so `html.py` carries the live camera and padded axis ranges forward, and the hover/snap marker is restyled only when its visible state changes (a restyle per hover event redraws per mouse move until the stack blows). Regression tests: `tests/gui/test_plotly_view_preservation.py`, `tests/gui/test_plotly_hover.py`.
- `views/canvas3d/style.py` (`RenderStyle`) is the single source of truth for colours/sizes on **both** backends; renderers must read it rather than hard-coding a colour. The `STYLE_FIELDS` subset is user-editable via **Options → Plot Properties…** (`views/dialogs/plot_properties.py`), previews live through `MainWindow.set_plot_style(..., persist=False)`, and persists as JSON in `QSettings` under `plot/props`.
- `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.