Skip to content

Turn 3-D data with a rotation matrix, independent of the camera - #80

Merged
CSSFrancis merged 2 commits into
mainfrom
feat/plot3d-model-rotation
Oct 15, 2026
Merged

CSSFrancis merged 2 commits into
mainfrom
feat/plot3d-model-rotation

Conversation

@CSSFrancis

@CSSFrancis CSSFrancis commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Why

The camera's azimuth and elevation can only aim it — two degrees of freedom. An orientation has three, so a crystal, a molecule, or a sample on a tilt stage could not be shown at an arbitrary pose, and nothing could be drawn fixed in the instrument while the object turned under it. This is the first of three changes for an EBSD phase viewer (master pattern on a sphere, detector fixed, crystal turned by dragging); the next adds regions/paths on the sphere.

What

  • Plot3D.set_rotation(matrix) — rotates the data about the centre of its bounds before the camera looks at it. Validated as a proper rotation (orthonormal, det +1; a reflection would turn a crystal into its mirror image). Targeted push: no geometry or texture resend, and its own _rotation_from_python flag so it never snaps the camera back to Python's last-known angles (and set_view never undoes a drag).
  • Plot3D.rotation — the current matrix, including viewer drags.
  • Plot3D.set_drag_mode("orbit" | "rotate") — "rotate" makes a left drag a trackball on the model; Shift spins about the line of sight. pointer_move / pointer_up carry event.rotation.
  • Renderer: draw3d builds R = camera · model once; every path (Canvas2D, GPU matrix + normals, axes, highlight, sphere, planes) already used that one R. Voxel caches key on the rotation.
  • Fix: elevation=0 was drawn from 30° up (st.elevation || 30). Found because the example's sphere and detector disagreed. Plots set to 0° will now render at 0°.
  • New example Examples/Interactive/plot_kikuchi_sphere.py: nickel's Kikuchi band sphere at 20 kV; drag to turn the crystal, and the detector pattern beside it is recomputed in Python from rotation (~20 ms/frame).
  • FIGURE_ESM.md anchors recomputed from the file (they shift with the insertions).

Tests

tests/test_plot3d/test_model_rotation.py — validation, light push + flags, dispatch writes back rotation, export keeps the viewer's rotation, and Playwright: the rotation moves a point where it should, a point turned toward the camera lands dead centre (pins the elevation fix), and a real rotate-drag edits the rotation (orthonormal, det 1, correct sense) while leaving the camera alone. Full suite: 2471 passed, 58 skipped.

The camera's azimuth and elevation can only aim it: two degrees of
freedom. An orientation has three, so a crystal, molecule or stage-mounted
sample could not be turned to an arbitrary pose. Plot3D.set_rotation
rotates the data about the centre of its bounds before the camera looks at
it; draw3d composes camera and model once, so every draw path (Canvas2D,
GPU matrix and normals, axes, highlight, sphere, planes) follows with no
other change. set_rotation is a targeted push with its own flag, so it
neither resends geometry nor snaps the camera back to Python's stale
angles.

set_drag_mode("rotate") turns a drag into a trackball on the model (Shift
spins about the line of sight) and reports event.rotation on pointer_move
and pointer_up; Figure dispatch writes it back so Plot3D.rotation follows
the viewer.

Also fixes elevation 0 being drawn from 30 degrees up: the renderer read it
with `|| 30`.

New example: a nickel Kikuchi band sphere whose detector pattern is
recomputed from the rotation as you drag.
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.22%. Comparing base (33bea90) to head (ae048a0).

Files with missing lines Patch % Lines
anyplotlib/plot3d/_plot3d.py 92.59% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #80      +/-   ##
==========================================
+ Coverage   91.20%   91.22%   +0.01%     
==========================================
  Files          41       41              
  Lines        4880     4910      +30     
==========================================
+ Hits         4451     4479      +28     
- Misses        429      431       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@CSSFrancis
CSSFrancis merged commit 6bb1cac into main Oct 15, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants