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

6.5 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. 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)

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.