Bring the Plotly canvas in line with opstool's visualisation recipes.
Camera / interaction:
- frame the camera in plotly's normalized scene units, using the model
bounds (grid excluded); a data-unit eye rendered the model as a speck
- use turntable dragmode so Z stays up and the horizon stays level
- align mouse bindings with the PyVista/VTK backend and document them in
Help → Mouse Controls
Visualisation:
- colour deformed / modal shapes by response with a shared colourbar
- scale load arrows by |F|, tint per load pattern, hover the magnitude,
and fix cones rendering as oversized fins (sizemode scaled, not absolute)
- draw DOF-accurate support glyphs (ported from opstool; see NOTICE)
- add an undeformed-reference overlay on both canvas backends
- play mode shapes with plotly frame animation in-page
Tests: builder unit tests plus GUI regression tests for gestures, the
deformed push, the undeformed reference and animation payloads.
Orbiting and the snap preview felt far behind the cursor. Measured in the
page: a single Plotly.restyle on a gl3d plot costs about 90 ms even for a
one-trace figure, so every marker update stalled the scene and the marker
trailed the mouse; each one also queued another redraw while the user was
dragging.
The marker is now a pointer-events-none div positioned by projecting the
snapped world point through glplot.cameraParams (validated: the camera
centre lands at the canvas centre), updated with one style write and
re-projected on plotly_relayout so it stays glued to the target during
orbit. No plotly calls at all on the hover path - 200 mouse-move events
now cost 0 restyles - and the empty hover trace is gone from the figure.
Also logs the WebGL renderer once (via the console bridge) since hardware
acceleration decides how smooth orbit feels and is otherwise invisible.
plotly_hover/plotly_unhover fire continuously while the mouse moves, and
the unhover handler restyled the snap marker unconditionally - even with
snapping off and nothing visible. That meant a full Plotly.restyle plus
redraw per mouse event, which re-fired hover until the page died with
'RangeError: Maximum call stack size exceeded' and the canvas froze
(reported live after opening a model).
The marker is now updated only when its visible state actually changes: no
marker when snapping is off, and a restyle only when the snapped point
differs from the one already shown. Deliberately synchronous - timers are
throttled to about a second by WebEngine when the page is not compositing,
which stalled the preview. Measured: 200 mouse-move events went from 200
restyles to 0; 200 hovers on one grid dot cost a single restyle.