Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/roboticstoolbox/mobile/PoseGraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,8 @@ def plot_scan(self, n: int | ArrayLike, pause: float = 1, **kwargs: Any) -> None

.. seealso:: :meth:`scanxy`
"""
if pause <= 0:
raise ValueError("pause must be greater than zero")
style = dict(markersize=10) | kwargs
for i in smb.getvector(n):
x, y = self.scanxy(int(i))
Expand Down
2 changes: 2 additions & 0 deletions src/roboticstoolbox/mobile/Vehicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ def _refresh_animation_frame(
except Exception:
pass

if dt <= 0:
raise ValueError("animation pause duration must be greater than zero")
plt.pause(dt)


Expand Down
49 changes: 49 additions & 0 deletions tests/test_step_dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
A time step of zero used to hang: ``plt.pause(0)`` waits until the event loop is stopped.
"""

from unittest.mock import Mock, patch

import matplotlib

matplotlib.use("Agg")
Expand All @@ -12,6 +14,8 @@
import roboticstoolbox as rtb # noqa: E402
from roboticstoolbox import ET2 # noqa: E402
from roboticstoolbox.backends.PyPlot import PyPlot, PyPlot2 # noqa: E402
from roboticstoolbox.mobile.PoseGraph import PoseGraph # noqa: E402
from roboticstoolbox.mobile.Vehicle import _refresh_animation_frame # noqa: E402

pytestmark = pytest.mark.timeout(20)

Expand Down Expand Up @@ -80,3 +84,48 @@ def test_plot_with_a_zero_dt_raises_and_does_not_hang():
puma = rtb.models.DH.Puma560()
with pytest.raises(ValueError, match="greater than zero"):
puma.plot(puma.qr, backend="pyplot", dt=0, block=False)



@pytest.mark.parametrize("pause", [0, 0.0, -0.1])
def test_plot_scan_rejects_non_positive_pause(pause):
graph = Mock()
graph.scanxy.return_value = ([0.0, 1.0], [0.0, 1.0])
with patch.object(plt, "pause") as paused:
with pytest.raises(ValueError, match="greater than zero"):
PoseGraph.plot_scan(graph, 0, pause=pause)
paused.assert_not_called()


def test_plot_scan_positive_pause():
graph = Mock()
graph.scanxy.return_value = ([0.0, 1.0], [0.0, 1.0])
with patch.object(plt, "pause") as paused:
PoseGraph.plot_scan(graph, 0, pause=0.01)
paused.assert_called_once_with(0.01)


@pytest.mark.parametrize("dt", [0, 0.0, -0.1])
def test_vehicle_animation_rejects_non_positive_pause(dt):
fig = plt.figure()
with patch("roboticstoolbox.mobile.Vehicle._isnotebook", return_value=False):
with patch.object(plt, "pause") as paused:
with pytest.raises(ValueError, match="greater than zero"):
_refresh_animation_frame(fig, dt, pause=True)
paused.assert_not_called()


@pytest.mark.parametrize("dt", [0, 0.0, -0.1])
def test_vehicle_nonblocking_animation_skips_pause(dt):
fig = plt.figure()
with patch.object(plt, "pause") as paused:
_refresh_animation_frame(fig, dt, pause=False)
paused.assert_not_called()


def test_vehicle_animation_positive_pause():
fig = plt.figure()
with patch("roboticstoolbox.mobile.Vehicle._isnotebook", return_value=False):
with patch.object(plt, "pause") as paused:
_refresh_animation_frame(fig, 0.01, pause=True)
paused.assert_called_once_with(0.01)
Loading