visualdynamics.rotate¶
rotate
¶
Turning a coordinate system by dragging a ring around one of its axes.
The geometry and the angle arithmetic live here, apart from Qt and VTK, so the fiddly part — which way a drag turns the frame, and by how much — can be tested without a window.
A coordinate system is stored as four rows: three orthonormal basis vectors and an origin. Rotation only ever touches the basis; the origin stays put.
Functions:
| Name | Description |
|---|---|
rotation_about |
The 3x3 rotation of |
ring_points |
A closed ring of points around one principal axis of a frame. |
rotate_frame |
A copy of |
angle_in_plane |
Where a world point sits around one principal axis, in radians. |
wrapped |
An angle folded into [-pi, pi), so a drag past the seam stays small. |
ring_under_cursor |
Which ring a pixel is over, or None. |
plane_hit |
Where a ray meets the plane through |
identity_frame |
The frame with its rotation undone, keeping where it sits. |
Functions:¶
rotation_about
¶
The 3x3 rotation of angle radians about a unit axis.
Rodrigues' formula, written out rather than pulled from a library: scipy is not a dependency and this is four lines.
Source code in src/visualdynamics/rotate.py
ring_points
¶
A closed ring of points around one principal axis of a frame.
The ring for the X axis lies in the frame's own Y-Z plane, so it is the circle you would sweep by turning about X.
Source code in src/visualdynamics/rotate.py
rotate_frame
¶
A copy of matrix with its basis turned about one of its own axes.
The axis travels with the frame — turning about X twice by 45 degrees is turning about that same X by 90, not about the world's X.
Source code in src/visualdynamics/rotate.py
angle_in_plane
¶
Where a world point sits around one principal axis, in radians.
Measured in the ring's own plane from the first of the two axes it
spans, so it pairs with ring_points.
Source code in src/visualdynamics/rotate.py
wrapped
¶
An angle folded into [-pi, pi), so a drag past the seam stays small.
A drag from 179 to -179 degrees is two degrees, not 358.
ring_under_cursor
¶
ring_under_cursor(
rings: Sequence[tuple[int, ndarray]],
cursor: ArrayLike,
tolerance: float = 14.0,
) -> int | None
Which ring a pixel is over, or None.
rings is a list of (axis, screen points). The nearest ring within the
tolerance wins; the rings cross each other, so ties go to whichever is
genuinely closer rather than to whichever was drawn first.
Source code in src/visualdynamics/rotate.py
plane_hit
¶
plane_hit(
origin: ArrayLike,
normal: ArrayLike,
eye: ArrayLike,
direction: ArrayLike,
) -> ndarray | None
Where a ray meets the plane through origin, or None if parallel.
Dragging a ring means following the cursor around the plane the ring lies in, which is what this finds.
Source code in src/visualdynamics/rotate.py
identity_frame
¶
The frame with its rotation undone, keeping where it sits.