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.
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.
PlotlyCanvas hosts plotly.js in a QWebEngineView driven over
QWebChannel: figures update with Plotly.react (the camera survives
unless a view preset asks for it), and clicks round-trip as
node/element picks or grid-snap clicks. Assets are written to a temp
dir and loaded from file:// because the ~5 MB bundle is past
setHtml's data-URL limit. render() forwards QWidget's overload so
grab() and painting keep working.
Project → plotly.js figure dicts with no Qt/pyvista/plotly import, so
it is unit-tested in the headless job. Mirrors the PyVista renderer's
geometry (grid, nodes, frames, supports, loads, extrusions, local
axes, labels, deformation) while respecting plotly.js's medium:
pixel-sized markers, None-separated line segments, selection as a
second trace (per-segment line colours are impossible), cone traces
for arrows. Pickable traces carry meta.kind + customdata.
Supplies the offline plotly.js bundle for the new canvas backend. The
GUI CI job now installs the gui extra so the Qt/WebEngine tests run
instead of being import-skipped.
Auto-fixes (UP037, UP038, F401, I001, SIM105, RUF100) plus hand-fixes:
drop the now-unused `Union` imports, annotate mutable class attributes
with ClassVar, raise ValidationError instead of blind Exception in the
zero-length test, delete two dead locals, and bind the per-iteration
grid-builder closures to their loop variables (B023).
RUF001-003 are ignored repo-wide: × / ≈ / ² are deliberate in the
engineering docs and formulas, which also makes the scattered inline
noqa suppressions redundant. Tests use engineering symbols (A, I, L, E),
`importorskip` before imports and capitalised test names; Qt requires
camelCase signal names and PascalCase event overrides; OpenSees material
fields (cR1, epsU) mirror the solver verbatim. N806 stays exempt only for
the numeric modules that use L / Iz / Iy as symbols.
Reconstructs the workflow stripped during the tree copy to match the
meta-gates: separate type job, headless command over
tests/unit+tests/services, and dedicated GUI / integration jobs.
Tool versions pinned to the pre-commit revisions (ruff 0.4.4,
mypy 1.10.0) so CI and local hooks agree.
The form still passed flat b/h/d/tw/tf/diameter fields, which
ElasticSection (extra='forbid') rejects, so reading any elastic
section crashed. Rebuild the display-only hint as a PipeShape /
AngleShape / RectShape member instead; an incomplete shape degrades
to None rather than raising.
Catch-all for the intermixed residue of the unpushed otko-development
work ported into this tree: combinations/console-dock/quick-guide wiring
across commands, core, services, views and tests; repo-wide ruff-format
normalization; README/CONTRIBUTING updates; and the toolbar default
(both toolbars now open in the top area, quick guide text updated).
Splitting this further would require hunk-level surgery with low
confidence; the preceding commits in this branch isolate the
self-contained features.
Single source of truth for the package + pinned solver versions in
src/otko/_const.py (hatch dynamic version). scipy/pandas were listed in
[gui] but never imported. mypy strict mode rescoped to
core/services/viewmodels; tomli added for tomllib-less Pythons.