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: 14 additions & 2 deletions src/roboticstoolbox/backends/PyPlot/PyPlot.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@ def step(self, dt: float = 0.05) -> None:

:param dt: time step in seconds, defaults to 50 (0.05 s)
:type dt: int, optional
:raises ValueError: if ``dt`` is not greater than zero in a window, where the
plot pauses for ``dt``

``env.step(args)`` triggers an update of the 3D scene in the matplotlib
window referenced by ``env``.
Expand All @@ -295,10 +297,20 @@ def step(self, dt: float = 0.05) -> None:
updated in the internal state of the robot object.
- The control type is defined by the robot object, and not all
robot objects support all control types.
- Execution is blocked for the specified interval
- In a window the execution is blocked for the specified interval, which
must be greater than zero: Matplotlib's ``pause(0)`` does not return,
since a timeout of zero means to wait until the event loop is stopped.
In a notebook there is no pause and ``dt`` of zero is fine.

.. versionchanged:: 1.5.0
In a window, ``dt`` of zero or less raises ``ValueError``, it used to wait
for ever.
"""

if dt <= 0 and self.render_mode == "window":
raise ValueError(
f"dt is {dt}, it must be greater than zero: a pause of zero seconds "
"does not return"
)

# update the robot's state
for rpl in self.robots:
Expand Down
11 changes: 10 additions & 1 deletion src/roboticstoolbox/backends/PyPlot/PyPlot2.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,17 @@ def step(self, dt: float = 0.05) -> None:
by the robot object, and not all robot objects support all control
types.

:param dt: time step in seconds, which must be greater than zero in a window,
where the plot pauses for ``dt``; in a notebook there is no pause
:raises ValueError: if ``dt`` is not greater than zero in a window:
Matplotlib's ``pause(0)`` does not return, since a timeout of zero means
to wait until the event loop is stopped
"""

if dt <= 0 and self.render_mode == "window":
raise ValueError(
f"dt is {dt}, it must be greater than zero: a pause of zero seconds "
"does not return"
)

self._step_robots(dt)

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

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt # noqa: E402
import pytest # noqa: E402

import roboticstoolbox as rtb # noqa: E402
from roboticstoolbox import ET2 # noqa: E402
from roboticstoolbox.backends.PyPlot import PyPlot, PyPlot2 # noqa: E402

pytestmark = pytest.mark.timeout(20)


@pytest.fixture(autouse=True)
def close_figures():
plt.close("all")
yield
plt.close("all")


@pytest.fixture
def env3d():
env = PyPlot()
env.launch()
env.add(rtb.models.DH.Puma560(), readonly=True)
return env


@pytest.fixture
def env2d():
env = PyPlot2()
env.launch()
env.add(rtb.Robot2(ET2.R() * ET2.tx(1) * ET2.R() * ET2.tx(1)), readonly=True)
return env


@pytest.mark.parametrize("dt", [0, 0.0, -0.05])
def test_3d_step_needs_a_positive_dt(env3d, dt):
with pytest.raises(ValueError, match="greater than zero"):
env3d.step(dt)


@pytest.mark.parametrize("dt", [0, 0.0, -0.05])
def test_2d_step_needs_a_positive_dt(env2d, dt):
with pytest.raises(ValueError, match="greater than zero"):
env2d.step(dt)


def inline_env(cls, robot):
env = cls()
env.launch(render_mode="notebook-inline")
env.add(robot, readonly=True)
return env


@pytest.mark.parametrize("dt", [0, 0.0])
def test_in_a_notebook_there_is_no_pause_and_zero_is_fine(dt):
# the notebook animation draws a frame for each step and does not pause
inline_env(PyPlot, rtb.models.DH.Puma560()).step(dt)
inline_env(
PyPlot2, rtb.Robot2(ET2.R() * ET2.tx(1) * ET2.R() * ET2.tx(1))
).step(dt)


def test_a_small_dt_is_fine(env3d, env2d):
env3d.step(0.001)
env2d.step(0.001)


def test_the_default_dt_is_fine(env3d):
env3d.step()


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)
Loading