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

Skip to content

Commit 726af38

Browse files
authored
ENH: Snap 3D view angle changes when holding Control key (#31145)
* Snap 3D rotation to 5 degree increments with Ctrl * Snap 3D rotation to 5 degree increments with Ctrl * Trigger CI rerun * Add rcParam axes3d.snap_rotation for configurable 3D rotation snapping * Add axes3d.snap_rotation rcParam and rotation snapping test * Add axes3d.snap_rotation rcParam and rotation snapping test * Update snap rotation test using MouseEvent interaction * Update snap rotation test * Add snap rotation interaction test * update test file * Include roll in snap_rotation and simplify snapping test * changes in test file * test update * set default to five * set default to five * use test_pan method * test file updated * test file debug * add next whats new * Re-trigger CI for coverage sync * test exact val
1 parent e3d6dfc commit 726af38

6 files changed

Lines changed: 91 additions & 1 deletion

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Snapping 3D rotation angles with Control key
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
3D axes rotation now supports snapping to fixed angular increments
5+
when holding the ``Control`` key during mouse rotation.
6+
7+
The snap step size is controlled by the new
8+
``axes3d.snap_rotation`` rcParam (default: 5.0 degrees).
9+
Setting it to 0 disables snapping.
10+
11+
For example::
12+
13+
mpl.rcParams["axes3d.snap_rotation"] = 10
14+
15+
will snap elevation, azimuth, and roll angles to multiples
16+
of 10 degrees while rotating with the mouse.

lib/matplotlib/mpl-data/matplotlibrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@
451451
# See also https://matplotlib.org/stable/api/toolkits/mplot3d/view_angles.html#rotation-with-mouse
452452
#axes3d.trackballsize: 0.667 # trackball diameter, in units of the Axes bbox
453453
#axes3d.trackballborder: 0.2 # trackball border width, in units of the Axes bbox (only for 'sphere' and 'arcball' style)
454-
454+
#axes3d.snap_rotation: 5.0 # Snap angle (degrees) for 3D rotation when holding Control.
455455
## ***************************************************************************
456456
## * AXIS *
457457
## ***************************************************************************

lib/matplotlib/rcsetup.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,7 @@ def _convert_validator_spec(key, conv):
11841184
"axes3d.mouserotationstyle": ["azel", "trackball", "sphere", "arcball"],
11851185
"axes3d.trackballsize": validate_float,
11861186
"axes3d.trackballborder": validate_float,
1187+
"axes3d.snap_rotation": validate_float,
11871188

11881189
# scatter props
11891190
"scatter.marker": _validate_marker,
@@ -2193,6 +2194,12 @@ class _Subsection:
21932194
"'sphere' and 'arcball' style)"
21942195
),
21952196
_Section("Axis"),
2197+
_Param(
2198+
"axes3d.snap_rotation",
2199+
default=5.0,
2200+
validator=validate_float,
2201+
description="Snap angle (in degrees) for 3D rotation when holding Control."
2202+
),
21962203
_Param(
21972204
"xaxis.labellocation",
21982205
default="center",

lib/matplotlib/typing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@
222222
"axes3d.grid",
223223
"axes3d.mouserotationstyle",
224224
"axes3d.trackballborder",
225+
"axes3d.snap_rotation",
225226
"axes3d.trackballsize",
226227
"axes3d.xaxis.panecolor",
227228
"axes3d.yaxis.panecolor",

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,11 @@ def _on_move(self, event):
15981598

15991599
q = dq * q
16001600
elev, azim, roll = np.rad2deg(q.as_cardan_angles())
1601+
step = mpl.rcParams["axes3d.snap_rotation"]
1602+
if step > 0 and getattr(event, "key", None) == "control":
1603+
elev = step * round(elev / step)
1604+
azim = step * round(azim / step)
1605+
roll = step * round(roll / step)
16011606

16021607
# update view
16031608
vertical_axis = self._axis_names[self._vertical_axis]

lib/mpl_toolkits/mplot3d/tests/test_axes3d.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2783,3 +2783,64 @@ def test_axis_get_tightbbox_includes_offset_text():
27832783
f"bbox.x1 ({bbox.x1}) should be >= offset_bbox.x1 ({offset_bbox.x1})"
27842784
assert bbox.y1 >= offset_bbox.y1 - 1e-6, \
27852785
f"bbox.y1 ({bbox.y1}) should be >= offset_bbox.y1 ({offset_bbox.y1})"
2786+
2787+
2788+
def test_ctrl_rotation_snaps_to_5deg():
2789+
fig = plt.figure()
2790+
ax = fig.add_subplot(projection='3d')
2791+
2792+
initial = (12.3, 33.7, 2.2)
2793+
ax.view_init(*initial)
2794+
fig.canvas.draw()
2795+
2796+
s = 0.25
2797+
step = plt.rcParams["axes3d.snap_rotation"]
2798+
2799+
# First rotation without Ctrl
2800+
with mpl.rc_context({'axes3d.mouserotationstyle': 'azel'}):
2801+
MouseEvent._from_ax_coords(
2802+
"button_press_event", ax, (0, 0), MouseButton.LEFT
2803+
)._process()
2804+
2805+
MouseEvent._from_ax_coords(
2806+
"motion_notify_event",
2807+
ax,
2808+
(s * ax._pseudo_w, s * ax._pseudo_h),
2809+
MouseButton.LEFT,
2810+
)._process()
2811+
2812+
fig.canvas.draw()
2813+
2814+
rotated_elev = ax.elev
2815+
rotated_azim = ax.azim
2816+
rotated_roll = ax.roll
2817+
2818+
# Reset before ctrl rotation
2819+
ax.view_init(*initial)
2820+
fig.canvas.draw()
2821+
2822+
# Now rotate with Ctrl
2823+
with mpl.rc_context({'axes3d.mouserotationstyle': 'azel'}):
2824+
MouseEvent._from_ax_coords(
2825+
"button_press_event", ax, (0, 0), MouseButton.LEFT
2826+
)._process()
2827+
2828+
MouseEvent._from_ax_coords(
2829+
"motion_notify_event",
2830+
ax,
2831+
(s * ax._pseudo_w, s * ax._pseudo_h),
2832+
MouseButton.LEFT,
2833+
key="control"
2834+
)._process()
2835+
2836+
fig.canvas.draw()
2837+
2838+
expected_elev = step * round(rotated_elev / step)
2839+
expected_azim = step * round(rotated_azim / step)
2840+
expected_roll = step * round(rotated_roll / step)
2841+
2842+
assert ax.elev == pytest.approx(expected_elev)
2843+
assert ax.azim == pytest.approx(expected_azim)
2844+
assert ax.roll == pytest.approx(expected_roll)
2845+
2846+
plt.close(fig)

0 commit comments

Comments
 (0)