8000 documented and integrated animation code · ZombyDogs/spatialmath-python@2760507 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2760507

Browse files
committed
documented and integrated animation code
1 parent 9679743 commit 2760507

File tree

2 files changed

+203
-39
lines changed

2 files changed

+203
-39
lines changed

spatialmath/base/animate.py

Lines changed: 167 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,46 @@
2121

2222

2323
class Animate:
24+
"""
25+
Animate objects for matplotlib 3d
26+
27+
An instance of this class behaves like an Axes3D and supports proxies for
28+
29+
- ``plot``
30+
- ``quiver``
31+
- ``text``
32+
33+
which renders them and also places corresponding objects into a display list.
34+
These objects are ``Line``, ``Quiver`` and ``Text``. Only these primitives will
35+
be animated.
36+
37+
The objects are all drawn relative to the origin, and will be transformed according
38+
to the transform that is being animated.
39+
40+
Example::
41+
42+
anim = animate.Animate(dims=[0,2]) # set up the 3D axes
43+
anim.trplot(T, frame='A', color='green') # draw the frame
44+
anim.run(loop=True) # animate it
45+
"""
2446

2547
def __init__(self, axes=None, dims=None, projection='ortho', labels=['X', 'Y', 'Z'], **kwargs):
48+
"""
49+
Construct an Animate object
50+
51+
:param axes: the axes to plot into, defaults to current axes
52+
:type axes: Axes3D reference
53+
:param dims: dimension of plot volume as [xmin, xmax, ymin, ymax,zmin, zmax].
54+
If dims is [min, max] those limits are applied to the x-, y- and z-axes.
55+
:type dims: array_like
56+
:param projection: 3D projection: ortho [default] or persp
57+
:type projection: str
58+
:param labels: labels for the axes, defaults to X, Y and Z
59+
:type labels: 3-tuple of strings
60+
61+
Will setup to plot into an existing or a new Axes3D instance.
62+
63+
"""
2664
self.displaylist = []
2765

2866
if axes is None:
@@ -50,17 +88,52 @@ def __init__(self, axes=None, dims=None, projection='ortho', labels=['X', 'Y', '
5088
self.ax = axes
5189

5290
# set flag for 2d or 3d axes, flag errors on the methods called later
53-
54-
def draw(self, T):
55-
for x in self.displaylist:
56-
x.draw(T)
91+
92+
def trplot(self, T, **kwargs):
93+
"""
94+
Define the transform to animate
95+
96+
:param T: an SO(3) or SE(3) pose to be displayed as coordinate frame
97+
:type: numpy.ndarray, shape=(3,3) or (4,4)
98+
99+
Is polymorphic with ``base.trplot`` and accepts the same parameters. This sets
100+
up the animation but doesn't execute it.
101+
102+
:seealso: :func:`run`
103+
104+
"""
105+
# stash the final value
106+
if tr.isrot(T):
107+
self.T = tr.r2t(T)
108+
else:
109+
self.T = T
110+
# draw axes at the origin
111+
tr.trplot(np.eye(4), axes=self, **kwargs)
57112

58113
def run(self, movie=None, axes=None, repeat=True, interval=50, nframes=100, **kwargs):
59-
114+
"""
115+
Run the animation
116+
117+
:param interval: number of steps in the animation [defaault 100]
118+
:type interval: int
119+
:param repeat: animate in endless loop [default False]
120+
:type repeat: bool
121+
:param interval: number of milliseconds between frames [default 50]
122+
:type interval: int
123+
:param movie: name of file to write MP4 movie into
124+
:type movie: str
125+
126+
Animates a 3D coordinate frame moving from the world frame to a frame represented by the SO(3) or SE(3) matrix to the current axes.
127+
128+
Notes:
129+
130+
- the ``movie`` option requires the ffmpeg package to be installed: ``conda install -c conda-forge ffmpeg``
131+
- invokes the draw() method of every object in the display list
132+
"""
133+
60134
def update(frame, a):
61-
s = frame / 100.0
62-
T = tr.transl(0.5 * s, 0.5 * s, 0.5 * s) @ tr.trotx(math.pi * s)
63-
a.draw(T)
135+
T = tr.trinterp(self.T, s = frame / nframes)
136+
a._draw(T)
64137
return a.artists()
65138

66139
# blit leaves a trail and first frame
@@ -75,17 +148,38 @@ def update(frame, a):
75148
print('creating movie', movie)
76149
FFwriter = animation.FFMpegWriter(fps=10, extra_args=['-vcodec', 'libx264'])
77150
ani.save(movie, writer=FFwriter)
78-
# TODO needs conda install -c conda-forge ffmpeg
79151

80152
def __repr__(self):
153+
"""
154+
Human readable version of the display list
155+
156+
:param self: the animation
157+
:type self: Animate
158+
:returns: readable version of the display list
159+
:rtype: str
160+
"""
81161
return ', '.join([x.type for x in self.displaylist])
82162

83163
def artists(self):
164+
"""
165+
List of artists that need to be updated
166+
167+
:param self: the animation
168+
:type self: Animate
169+
:returns: list of artists
170+
:rtype: list
171+
"""
84172
return [x.h for x in self.displaylist]
173+
174+
175+
def _draw(self, T):
176+
for x in self.displaylist:
177+
x.draw(T)
178+
85179

86180
#------------------- plot()
87181

88-
class Line:
182+
class _Line:
89183

90184
def __init__(self, anim, h, xs, ys, zs):
91185
p = zip(xs, ys, zs)
@@ -99,13 +193,30 @@ def draw(self, T):
99193
self.h.set_data(p[0, :], p[1, :])
100194
self.h.set_3d_properties(p[2, :])
101195

102-
def plot(self, xs, ys, zs, *args, **kwargs):
103-
h, = self.ax.plot(xs, ys, zs, *args, **kwargs)
104-
self.displaylist.append(Animate.Line(self, h, xs, ys, zs))
196+
def plot(self, x, y, z, *args, **kwargs):
197+
"""
198+
Plot a polyline
199+
200+
:param x: list of x-coordinates
201+
:type x: array_like
202+
:param y: list of y-coordinates
203+
:type y: array_like
204+
:param z: list of z-coordinates
205+
:type z: array_like
206+
207+
Other arguments as accepted by the matplotlib method.
208+
209+
All arrays must have the same length.
210+
211+
:seealso: :func:`matplotlib.pyplot.plot`
212+
"""
213+
214+
h, = self.ax.plot(x, y, z, *args, **kwargs)
215+
self.displaylist.append(Animate._Line(self, h, x, y, z))
105216

106217
#------------------- quiver()
107218

108-
class Quiver:
219+
class _Quiver:
109220

110221
def __init__(self, anim, h):
111222
self.type = 'quiver'
@@ -130,12 +241,36 @@ def draw(self, T):
130241
self.h.set_segments(p)
131242

132243
def quiver(self, x, y, z, u, v, w, *args, **kwargs):
244+
"""
245+
Plot a quiver
246+
247+
:param x: list of base x-coordinates
248+
:type x: array_like
249+
:param y: list of base y-coordinates
250+
:type y: array_like
251+
:param z: list of base z-coordinates
252+
:type z: array_like
253+
:param u: list of vector x-coordinates
254+
:type u: array_like
255+
:param v: list of vector y-coordinates
256+
:type v: array_like
257+
:param w: list of vector z-coordinates
258+
:type w: array_like
259+
260+
Draws a series of arrows, the bases defined by corresponding elements of
261+
(x,y,z) and the vector has components defined by corresponding elements of
262+
(u,v,w).
263+
264+
Other arguments as accepted by the matplotlib method.
265+
266+
:seealso: :func:`matplotlib.pyplot.quiver`
267+
"""
133268
h = self.ax.quiver(x, y, z, u, v, w, *args, **kwargs)
134-
self.displaylist.append(Animate.Quiver(self, h))
269+
self.displaylist.append(Animate._Quiver(self, h))
135270

136271
#------------------- text()
137272

138-
class Text:
273+
class _Text:
139274

140275
def __init__(self, anim, h, x, y, z):
141276
self.type = 'text'
@@ -148,11 +283,25 @@ def draw(self, T):
148283
# x2, y2, _ = proj3d.proj_transform(p[0], p[1], p[2], self.anim.ax.get_proj())
149284
# self.h.set_position((x2, y2))
150285
self.h.set_position((p[0], p[1]))
151-
self.h.set_3d_properties(p[2])
286+
self.h.set_3d_properties(z=p[2], zdir='x')
152287

153288
def text(self, x, y, z, *args, **kwargs):
289+
"""
290+
Plot text
291+
292+
:param x: x-coordinate
293+
:type x: float
294+
:param y: float
295+
:type y: array_like
296+
:param z: z-coordinate
297+
:type z: float
298+
299+
Other arguments as accepted by the matplotlib method.
300+
301+
:seealso: :func:`matplotlib.pyplot.text`
302+
"""
154303
h = self.ax.text3D(x, y, z, *args, **kwargs)
155-
self.displaylist.append(Animate.Text(self, h, x, y, z))
304+
self.displaylist.append(Animate._Text(self, h, x, y, z))
156305

157306
#------------------- scatter()
158307

@@ -178,19 +327,3 @@ def set_ylabel(self, *args, **kwargs):
178327

179328
def set_zlabel(self, *args, **kwargs):
180329
self.ax.set_zlabel(*args, **kwargs)
181-
182-
183-
def tranimate(T, **kwargs):
184-
anim = Animate(**kwargs)
185-
tr.trplot(T, axes=anim, **kwargs)
186-
anim.run(**kwargs)
187-
188-
189-
tranimate(tr.transl(0, 0, 0), frame='A', arrow=False, dims=[0, 5]) # , movie='bob.mp4')
190-
191-
192-
# a = trplot_a( tr.transl(1,2,3), frame='A', rviz=True, width=1)
193-
# print(a)
194-
# a.draw(tr.transl(0, 0, -1))
195-
# trplot_a( tr.transl(3,1, 2), color='red', width=3, frame='B')
196-
# trplot_a( tr.transl(4, 3, 1)@tr.trotx(math.pi/3), color='green', frame='c')

spatialmath/base/transforms3d.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,8 +1388,8 @@ def trplot(T, axes=None, dims=None, color='blue', frame=None, textcolor=None, la
13881388
13891389
:param T: an SO(3) or SE(3) pose to be displayed as coordinate frame
13901390
:type: numpy.ndarray, shape=(3,3) or (4,4)
1391-
:param X: the axes to plot into, defaults to current axes
1392-
:type ax: Axes3D reference
1391+
:param axes: the axes to plot into, defaults to current axes
1392+
:type axes: Axes3D reference
13931393
:param dims: dimension of plot volume as [xmin, xmax, ymin, ymax,zmin, zmax].
13941394
If dims is [min, max] those limits are applied to the x-, y- and z-axes.
13951395
:type dims: array_like
@@ -1505,6 +1505,38 @@ def trplot(T, axes=None, dims=None, color='blue', frame=None, textcolor=None, la
15051505
ax.text(y[0], y[1], y[2], "$%c_{%s}$" % (labels[1], frame), color=color, horizontalalignment='center', verticalalignment='center')
15061506
ax.text(z[0], z[1], z[2], "$%c_{%s}$" % (labels[2], frame), color=color, horizontalalignment='center', verticalalignment='center')
15071507

1508+
import animate
1509+
1510+
def tranimate(T, **kwargs):
1511+
"""
1512+
Animate a 3D coordinate frame
1513+
1514+
:param T: an SO(3) or SE(3) pose to be displayed as coordinate frame
1515+
:type: numpy.ndarray, shape=(3,3) or (4,4)
1516+
:param nframes: number of steps in the animation [defaault 100]
1517+
:type nframes: int
1518+
:param repeat: animate in endless loop [default False]
1519+
:type repeat: bool
1520+
:param interval: number of milliseconds between frames [default 50]
1521+
:type interval: int
1522+
:param movie: name of file to write MP4 movie into
1523+
:type movie: str
1524+
1525+
Animates a 3D coordinate frame moving from the world frame to a frame represented by the SO(3) or SE(3) matrix to the current axes.
1526+
1527+
- If no current figure, one is created
1528+
- If current figure, but no axes, a 3d Axes is created
1529+
1530+
1531+
Examples:
1532+
1533+
tranimate(transl(1,2,3)@trotx(1), frame='A', arrow=False, dims=[0, 5])
1534+
tranimate(transl(1,2,3)@trotx(1), frame='A', arrow=False, dims=[0, 5], movie='spin.mp4')
1535+
"""
1536+
anim = animate.Animate(**kwargs)
1537+
anim.trplot(T, **kwargs)
1538+
anim.run(**kwargs)
1539+
15081540
except BaseException: # pragma: no cover
15091541
def trplot(*args, **kwargs):
15101542
print('** trplot: no plot produced -- matplotlib not installed')
@@ -1513,8 +1545,7 @@ def trplot(*args, **kwargs):
15131545
import pathlib
15141546
import os.path
15151547

1516-
# trplot( transl(1,2,3), frame='A', rviz=True, width=1, dims=[0, 10, 0, 10, 0, 10])
1517-
# trplot( transl(3,1, 2), color='red', width=3, frame='B')
1518-
# trplot( transl(4, 3, 1)@trotx(math.pi/3), color='green', frame='c', dims=[0,4,0,4,0,4])
1548+
tranimate(transl(4, 3, 4)@trotx(2)@troty(-2), frame='A', arrow=False, dims=[0, 5], nframes=200, movie='bob.mp4')
1549+
15191550

15201551
exec(open(os.path.join(pathlib.Path(__file__).parent.absolute(), "test_transforms.py")).read())

0 commit comments

Comments
 (0)
0