Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 07b9b26

Browse filesBrowse files
committed
fix bugs around animation
add anaglyph capability general tidyup
1 parent facfb53 commit 07b9b26
Copy full SHA for 07b9b26

File tree

Expand file treeCollapse file tree

3 files changed

+175
-67
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+175
-67
lines changed

‎spatialmath/base/animate.py

Copy file name to clipboardExpand all lines: spatialmath/base/animate.py
+50-28Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@
88
# text.set_position()
99
# quiver.set_offsets(), quiver.set_UVC()
1010
# FancyArrow.set_xy()
11-
11+
import os.path
1212
import numpy as np
1313
import matplotlib.pyplot as plt
1414
from matplotlib import animation
15+
from numpy.lib.arraysetops import isin
1516
from spatialmath import base
1617
from collections.abc import Iterable, Generator, Iterator
18+
import time
1719

20+
# global variable holds reference to FuncAnimation object, this is essential
21+
# for animatiion to work
22+
_ani = None
1823

1924
class Animate:
2025
"""
@@ -131,9 +136,9 @@ def trplot(self, end, start=None, **kwargs):
131136
self.start = start
132137

133138
# draw axes at the origin
134-
base.trplot(self.start, axes=self, block=None, **kwargs)
139+
base.trplot(self.start, axes=self, **kwargs)
135140

136-
def run(self, movie=None, axes=None, repeat=False, interval=50, nframes=100, pause=0, **kwargs):
141+
def run(self, movie=None, axes=None, repeat=False, interval=50, nframes=100, wait=False, **kwargs):
137142
"""
138143
Run the animation
139144
@@ -147,6 +152,8 @@ def run(self, movie=None, axes=None, repeat=False, interval=50, nframes=100, pau
147152
:type interval: int
148153
:param movie: name of file to write MP4 movie into
149154
:type movie: str
155+
:param wait: wait until animation is complete, default False
156+
:type wait: bool
150157
151158
Animates a 3D coordinate frame moving from the world frame to a frame
152159
represented by the SO(3) or SE(3) matrix to the current axes.
@@ -159,51 +166,63 @@ def run(self, movie=None, axes=None, repeat=False, interval=50, nframes=100, pau
159166
"""
160167

161168
def update(frame, animation):
162-
if self.trajectory is not None:
163-
# passed a trajectory as an iterator or generator, get next
164-
try:
165-
T = next(self.trajectory)
166-
except StopIteration:
167-
animation.done = True
168-
return
169-
else:
169+
170+
# frame is the result of calling next() on a iterator or generator
171+
# seemingly the animation framework isn't checking StopException
172+
# so there is no way to know when this is no longer called.
173+
# we implement a rather hacky heartbeat style timeout
174+
175+
if isinstance(frame, float):
170176
# passed a single transform, interpolate it
171-
T = base.trinterp(start=self.start, end=self.end, s=frame / nframes)
177+
T = base.trinterp(start=self.start, end=self.end, s=frame)
178+
else:
179+
# assume it is an SO(3) or SE(3)
180+
T = frame
181+
172182
# ensure result is SE(3)
173183
if T.shape == (3,3):
174184
T = base.r2t(T)
175185

176186
# update the scene
177187
animation._draw(T)
178188

179-
# are we done yet
180-
if frame == nframes - 1:
181-
animation.done = True
189+
self.count += 1 # say we're still running
190+
182191
return animation.artists()
183192

193+
global _ani
194+
184195
# blit leaves a trail and first frame
185196
if movie is not None:
186197
repeat = False
187198

188-
self.done = False
199+
self.count = 1
189200
if self.trajectory is not None:
190201
if not isinstance(self.trajectory, Iterator):
191202
# make it iterable, eg. if a list or tuple
192203
self.trajectory = iter(self.trajectory)
193-
frames = None
204+
frames = self.trajectory
194205
else:
195-
frames = range(0, nframes)
206+
frames = iter(np.linspace(0, 1, nframes))
196207

197-
# ani = animation.FuncAnimation(fig=plt.gcf(), func=update, frames=range(0, nframes), fargs=(self,), blit=False, interval=interval, repeat=repeat)
198-
ani = animation.FuncAnimation(fig=plt.gcf(), func=update, frames=frames, fargs=(self,), blit=False, interval=interval, repeat=repeat)
199-
if movie is None:
200-
while repeat or not self.done:
201-
plt.pause(0.1)
202-
else:
208+
_ani = animation.FuncAnimation(fig=plt.gcf(), func=update, frames=frames, fargs=(self,), blit=False, interval=interval, repeat=repeat)
209+
210+
if movie is not None:
203211
# Set up formatting for the movie files
204-
print('creating movie', movie)
205-
FFwriter = animation.FFMpegWriter(fps=10, extra_args=['-vcodec', 'libx264'])
206-
ani.save(movie, writer=FFwriter)
212+
if os.path.exists(movie):
213+
print('overwriting movie', movie)
214+
else:
215+
print('creating movie', movie)
216+
FFwriter = animation.FFMpegWriter(fps=1000 / interval, extra_args=['-vcodec', 'libx264'])
217+
_ani.save(movie, writer=FFwriter)
218+
219+
if wait:
220+
# wait for the animation to finish. Dig into the timer for this
221+
# animation and wait for its callback to be deregistered.
222+
while True:
223+
plt.pause(0.25)
224+
if len(_ani.event_source.callbacks) == 0:
225+
break
207226

208227
def __repr__(self):
209228
"""
@@ -550,7 +569,10 @@ def update(frame, a):
550569
plt.pause(1)
551570
else:
552571
# Set up formatting for the movie files
553-
print('creating movie', movie)
572+
if os.path.exists(movie):
573+
print('overwriting movie', movie)
574+
else:
575+
print('creating movie', movie)
554576
FFwriter = animation.FFMpegWriter(fps=10, extra_args=['-vcodec', 'libx264'])
555577
ani.save(movie, writer=FFwriter)
556578

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.