Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions doc/release/next_whats_new/snap_rotation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Snapping 3D rotation angles with Control key
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

3D axes rotation now supports snapping to fixed angular increments
when holding the ``Control`` key during mouse rotation.

The snap step size is controlled by the new
``axes3d.snap_rotation`` rcParam (default: 5.0 degrees).
Setting it to 0 disables snapping.

For example::

mpl.rcParams["axes3d.snap_rotation"] = 10

will snap elevation, azimuth, and roll angles to multiples
of 10 degrees while rotating with the mouse.
2 changes: 1 addition & 1 deletion lib/matplotlib/mpl-data/matplotlibrc
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@
# See also https://matplotlib.org/stable/api/toolkits/mplot3d/view_angles.html#rotation-with-mouse
#axes3d.trackballsize: 0.667 # trackball diameter, in units of the Axes bbox
#axes3d.trackballborder: 0.2 # trackball border width, in units of the Axes bbox (only for 'sphere' and 'arcball' style)

#axes3d.snap_rotation: 5.0 # Snap angle (degrees) for 3D rotation when holding Control.
## ***************************************************************************
## * AXIS *
## ***************************************************************************
Expand Down
7 changes: 7 additions & 0 deletions lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,7 @@ def _convert_validator_spec(key, conv):
"axes3d.mouserotationstyle": ["azel", "trackball", "sphere", "arcball"],
"axes3d.trackballsize": validate_float,
"axes3d.trackballborder": validate_float,
"axes3d.snap_rotation": validate_float,

# scatter props
"scatter.marker": _validate_marker,
Expand Down Expand Up @@ -2143,6 +2144,12 @@ class _Param:
description="trackball border width, in units of the Axes bbox (only for "
"'sphere' and 'arcball' style)"
),
_Param(
"axes3d.snap_rotation",
default=5.0,
validator=validate_float,
description="Snap angle (in degrees) for 3D rotation when holding Control."
),
_Param(
"xaxis.labellocation",
default="center",
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@
"axes3d.grid",
"axes3d.mouserotationstyle",
"axes3d.trackballborder",
"axes3d.snap_rotation",
"axes3d.trackballsize",
"axes3d.xaxis.panecolor",
"axes3d.yaxis.panecolor",
Expand Down
5 changes: 5 additions & 0 deletions lib/mpl_toolkits/mplot3d/axes3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -1597,6 +1597,11 @@ def _on_move(self, event):

q = dq * q
elev, azim, roll = np.rad2deg(q.as_cardan_angles())
step = mpl.rcParams["axes3d.snap_rotation"]
if step > 0 and getattr(event, "key", None) == "control":
elev = step * round(elev / step)
azim = step * round(azim / step)
Comment thread
scottshambaugh marked this conversation as resolved.
roll = step * round(roll / step)

# update view
vertical_axis = self._axis_names[self._vertical_axis]
Expand Down
61 changes: 61 additions & 0 deletions lib/mpl_toolkits/mplot3d/tests/test_axes3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -2785,3 +2785,64 @@ def test_axis_get_tightbbox_includes_offset_text():
f"bbox.x1 ({bbox.x1}) should be >= offset_bbox.x1 ({offset_bbox.x1})"
assert bbox.y1 >= offset_bbox.y1 - 1e-6, \
f"bbox.y1 ({bbox.y1}) should be >= offset_bbox.y1 ({offset_bbox.y1})"


def test_ctrl_rotation_snaps_to_5deg():
fig = plt.figure()
ax = fig.add_subplot(projection='3d')

initial = (12.3, 33.7, 2.2)
ax.view_init(*initial)
fig.canvas.draw()

s = 0.25
step = plt.rcParams["axes3d.snap_rotation"]

# First rotation without Ctrl
with mpl.rc_context({'axes3d.mouserotationstyle': 'azel'}):
MouseEvent._from_ax_coords(
"button_press_event", ax, (0, 0), MouseButton.LEFT
)._process()

MouseEvent._from_ax_coords(
"motion_notify_event",
ax,
(s * ax._pseudo_w, s * ax._pseudo_h),
MouseButton.LEFT,
)._process()

fig.canvas.draw()

rotated_elev = ax.elev
rotated_azim = ax.azim
rotated_roll = ax.roll

# Reset before ctrl rotation
ax.view_init(*initial)
fig.canvas.draw()

# Now rotate with Ctrl
with mpl.rc_context({'axes3d.mouserotationstyle': 'azel'}):
MouseEvent._from_ax_coords(
"button_press_event", ax, (0, 0), MouseButton.LEFT
)._process()

MouseEvent._from_ax_coords(
"motion_notify_event",
ax,
(s * ax._pseudo_w, s * ax._pseudo_h),
MouseButton.LEFT,
key="control"
)._process()

fig.canvas.draw()

expected_elev = step * round(rotated_elev / step)
expected_azim = step * round(rotated_azim / step)
expected_roll = step * round(rotated_roll / step)

assert ax.elev == pytest.approx(expected_elev)
assert ax.azim == pytest.approx(expected_azim)
assert ax.roll == pytest.approx(expected_roll)

plt.close(fig)
Loading