52
52
53
53
"""
54
54
55
+
55
56
class VehicleAnimationBase (ABC ):
56
57
"""
57
58
Class to support animation of a vehicle on Matplotlib plot
@@ -66,7 +67,7 @@ class VehicleAnimationBase(ABC):
66
67
67
68
a.add()
68
69
a.update(q)
69
-
70
+
70
71
adds an instance of the animation shape to the plot and subsequent calls
71
72
to ``update`` will animate it.
72
73
@@ -86,7 +87,7 @@ class VehicleAnimationBase(ABC):
86
87
veh.run(animation=a)
87
88
88
89
"""
89
-
90
+
90
91
def __init__ (self ):
91
92
self ._object = None
92
93
self ._ax = None
@@ -142,10 +143,11 @@ def __del__(self):
142
143
if self ._object is not None :
143
144
self ._object .remove ()
144
145
146
+
145
147
# ========================================================================= #
146
148
147
- class VehicleMarker (VehicleAnimationBase ):
148
149
150
+ class VehicleMarker (VehicleAnimationBase ):
149
151
def __init__ (self , ** kwargs ):
150
152
"""
151
153
Create graphical animation of vehicle as a Matplotlib marker
@@ -170,13 +172,12 @@ def __init__(self, **kwargs):
170
172
super ().__init__ ()
171
173
if len (kwargs ) == 0 :
172
174
kwargs = {
173
- ' marker' : 'o' ,
174
- ' markerfacecolor' : 'r' ,
175
- ' markeredgecolor' : 'w' ,
176
- ' markersize' : 12
175
+ " marker" : "o" ,
176
+ " markerfacecolor" : "r" ,
177
+ " markeredgecolor" : "w" ,
178
+ " markersize" : 12 ,
177
179
}
178
180
self ._args = kwargs
179
-
180
181
181
182
def _update (self , x ):
182
183
self ._object .set_xdata (x [0 ])
@@ -187,11 +188,12 @@ def _add(self, x=None, **kwargs):
187
188
x = (0 , 0 )
188
189
self ._object = plt .plot (x [0 ], x [1 ], ** self ._args )[0 ]
189
190
191
+
190
192
# ========================================================================= #
191
193
192
- class VehiclePolygon (VehicleAnimationBase ):
193
194
194
- def __init__ (self , shape = 'car' , scale = 1 , ** kwargs ):
195
+ class VehiclePolygon (VehicleAnimationBase ):
196
+ def __init__ (self , shape = "car" , scale = 1 , ** kwargs ):
195
197
"""
196
198
Create graphical animation of vehicle as a polygon
197
199
@@ -207,7 +209,7 @@ def __init__(self, shape='car', scale=1, **kwargs):
207
209
:return: animation object
208
210
:rtype: VehicleAnimation
209
211
210
- Creates an object that can be passed to a ``Vehicle`` subclass to
212
+ Creates an object that can be passed to a ``Vehicle`` subclass to
211
213
depict the moving robot as a polygon during simulation.
212
214
213
215
For example, a red filled car-shaped polygon is::
@@ -233,30 +235,34 @@ def __init__(self, shape='car', scale=1, **kwargs):
233
235
h = 0.3
234
236
t = 0.8 # start of head taper
235
237
c = 0.5 # centre x coordinate
236
- w = 1 # width in x direction
238
+ w = 1 # width in x direction
237
239
238
240
if isinstance (shape , str ):
239
- if shape == 'car' :
240
- self ._coords = np .array ([
241
- [- c , h ],
242
- [t - c , h ],
243
- [w - c , 0 ],
244
- [t - c , - h ],
245
- [- c , - h ],
246
- ]).T
247
- elif shape == 'triangle' :
248
- self ._coords = np .array ([
249
- [- c , h ],
250
- [ w , 0 ],
251
- [- c , - h ],
252
- ]).T
241
+ if shape == "car" :
242
+ self ._coords = np .array (
243
+ [
244
+ [- c , h ],
245
+ [t - c , h ],
246
+ [w - c , 0 ],
247
+ [t - c , - h ],
248
+ [- c , - h ],
249
+ ]
250
+ ).T
251
+ elif shape == "triangle" :
252
+ self ._coords = np .array (
253
+ [
254
+ [- c , h ],
255
+ [w , 0 ],
256
+ [- c , - h ],
257
+ ]
258
+ ).T
253
259
else :
254
- raise ValueError (' unknown vehicle shape name' )
255
-
260
+ raise ValueError (" unknown vehicle shape name" )
261
+
256
262
elif isinstance (shape , np .ndarray ) and shape .shape [1 ] == 2 :
257
263
self ._coords = shape
258
264
else :
259
- raise TypeError (' unknown shape argument' )
265
+ raise TypeError (" unknown shape argument" )
260
266
self ._coords *= scale
261
267
self ._args = kwargs
262
268
@@ -268,16 +274,17 @@ def _add(self, **kwargs):
268
274
self ._ax .add_patch (self ._object )
269
275
270
276
def _update (self , x ):
271
-
277
+
272
278
if self ._object is not None :
273
279
# if animation is initialized
274
280
xy = SE2 (x ) * self ._coords
275
281
self ._object .set_xy (xy .T )
276
282
283
+
277
284
# ========================================================================= #
278
285
279
- class VehicleIcon (VehicleAnimationBase ):
280
286
287
+ class VehicleIcon (VehicleAnimationBase ):
281
288
def __init__ (self , filename , origin = None , scale = 1 , rotation = 0 ):
282
289
"""
283
290
Create graphical animation of vehicle as an icon
@@ -294,7 +301,7 @@ def __init__(self, filename, origin=None, scale=1, rotation=0):
294
301
:return: animation object
295
302
:rtype: VehicleAnimation
296
303
297
- Creates an object that can be passed to a ``Vehicle`` subclass to
304
+ Creates an object that can be passed to a ``Vehicle`` subclass to
298
305
depict the moving robot as a polygon during simulation.
299
306
300
307
For example, the image of a red car is::
@@ -331,8 +338,8 @@ def __init__(self, filename, origin=None, scale=1, rotation=0):
331
338
middle of the icon image, (0.2, 0.5) moves it toward the back of the
332
339
vehicle, (0.8, 0.5) moves it toward the front of the vehicle.
333
340
334
- .. note::
335
-
341
+ .. note::
342
+
336
343
- The standard icons are kept in ``roboticstoolbox/data``
337
344
- By default the image is assumed to contain a car parallel to
338
345
the x-axis and facing right. If the vehicle is facing upward
@@ -341,18 +348,20 @@ def __init__(self, filename, origin=None, scale=1, rotation=0):
341
348
:seealso: :func:`~Vehicle`
342
349
"""
343
350
super ().__init__ ()
344
- if '.' not in filename :
351
+ if "." not in filename :
345
352
try :
346
353
# try the default folder first
347
- image = rtb_load_data (Path ("data" ) / Path (filename + ".png" ), plt .imread )
354
+ image = rtb_load_data (
355
+ Path ("data" ) / Path (filename + ".png" ), plt .imread
356
+ )
348
357
except FileNotFoundError :
349
358
raise ValueError (f"{ filename } is not a provided icon" )
350
359
else :
351
360
try :
352
361
image = plt .imread (filename )
353
362
except FileNotFoundError :
354
363
raise ValueError (f"icon file { filename } not found" )
355
-
364
+
356
365
self ._rotation = rotation
357
366
self ._image = image
358
367
@@ -364,49 +373,53 @@ def __init__(self, filename, origin=None, scale=1, rotation=0):
364
373
if image .shape [0 ] >= image .shape [1 ]:
365
374
# width >= height
366
375
self ._width = scale
367
- self ._height = scale * image .shape [1 ] / image .shape [0 ]
376
+ self ._height = scale * image .shape [1 ] / image .shape [0 ]
368
377
else :
369
378
# width < height
370
379
self ._height = scale
371
380
self ._width = scale * image .shape [0 ] / image .shape [1 ]
372
381
373
-
374
382
def _add (self , ax = None , ** kwargs ):
375
-
376
383
def imshow_affine (ax , z , * args , ** kwargs ):
377
384
im = ax .imshow (z , * args , ** kwargs )
378
385
x1 , x2 , y1 , y2 = im .get_extent ()
379
386
# im._image_skew_coordinate = (x2, y1)
380
387
return im
381
388
382
389
self ._ax = plt .gca ()
383
- extent = [ - self ._origin [0 ] * self ._height ,
384
- (1 - self ._origin [0 ]) * self ._height ,
385
- - self ._origin [1 ] * self ._width ,
386
- (1 - self ._origin [1 ]) * self ._width
387
- ]
390
+ extent = [
391
+ - self ._origin [0 ] * self ._height ,
392
+ (1 - self ._origin [0 ]) * self ._height ,
393
+ - self ._origin [1 ] * self ._width ,
394
+ (1 - self ._origin [1 ]) * self ._width ,
395
+ ]
388
396
self ._ax = plt .gca ()
389
397
390
398
args = {}
391
- if ' color' in kwargs and self ._image .ndim == 2 :
392
- color = kwargs [' color' ]
393
- del kwargs [' color' ]
399
+ if " color" in kwargs and self ._image .ndim == 2 :
400
+ color = kwargs [" color" ]
401
+ del kwargs [" color" ]
394
402
rgb = colors .to_rgb (color )
395
- cmapdata = {'red' : [(0.0 , 0.0 , 0.0 ), (1.0 , rgb [0 ], 0.0 )],
396
- 'green' : [(0.0 , 0.0 , 0.0 ), (1.0 , rgb [1 ], 0.0 )],
397
- 'blue' : [(0.0 , 0.0 , 0.0 ), (1.0 , rgb [2 ], 0.0 )]
398
- }
399
- cmap = colors .LinearSegmentedColormap ('linear' , segmentdata = cmapdata , N = 256 )
400
- args = {'cmap' : cmap }
403
+ cmapdata = {
404
+ "red" : [(0.0 , 0.0 , 0.0 ), (1.0 , rgb [0 ], 0.0 )],
405
+ "green" : [(0.0 , 0.0 , 0.0 ), (1.0 , rgb [1 ], 0.0 )],
406
+ "blue" : [(0.0 , 0.0 , 0.0 ), (1.0 , rgb [2 ], 0.0 )],
407
+ }
408
+ cmap = colors .LinearSegmentedColormap ("linear" , segmentdata = cmapdata , N = 256 )
409
+ args = {"cmap" : cmap }
401
410
elif self ._image .ndim == 2 :
402
- args = {'cmap' : 'gray' }
403
- if 'zorder' not in kwargs :
404
- args ['zorder' ] = 3
405
-
406
- self ._object = imshow_affine (self ._ax , self ._image ,
407
- interpolation = 'none' ,
408
- extent = extent , clip_on = True ,
409
- ** {** kwargs , ** args })
411
+ args = {"cmap" : "gray" }
412
+ if "zorder" not in kwargs :
413
+ args ["zorder" ] = 3
414
+
415
+ self ._object = imshow_affine (
416
+ self ._ax ,
417
+ self ._image ,
418
+ interpolation = "none" ,
419
+ extent = extent ,
420
+ clip_on = True ,
421
+ ** {** kwargs , ** args },
422
+ )
410
423
411
424
def _update (self , x ):
412
425
@@ -415,12 +428,15 @@ def _update(self, x):
415
428
center_x = 0
416
429
center_y = 0
417
430
418
- T = (mtransforms .Affine2D ()
419
- .rotate_deg_around (center_x , center_y , np .degrees (x [2 ]) - self ._rotation )
420
- .translate (x [0 ], x [1 ])
421
- + self ._ax .transData )
431
+ T = (
432
+ mtransforms .Affine2D ()
433
+ .rotate_deg_around (center_x , center_y , np .degrees (x [2 ]) - self ._rotation )
434
+ .translate (x [0 ], x [1 ])
435
+ + self ._ax .transData
436
+ )
422
437
self ._object .set_transform (T )
423
438
439
+
424
440
if __name__ == "__main__" :
425
441
from math import pi
426
442
@@ -442,14 +458,14 @@ def _update(self, x):
442
458
print (veh .f ([0 , 0 , 0 ], odo ))
443
459
444
460
def control (v , t , x ):
445
- goal = (6 ,6 )
446
- goal_heading = atan2 (goal [1 ]- x [1 ], goal [0 ]- x [0 ])
461
+ goal = (6 , 6 )
462
+ goal_heading = atan2 (goal [1 ] - x [1 ], goal [0 ] - x [0 ])
447
463
d_heading = base .angdiff (goal_heading , x [2 ])
448
464
v .stopif (base .norm (x [0 :2 ] - goal ) < 0.1 )
449
465
450
466
return (1 , d_heading )
451
467
452
- veh .control = RandomPath (10 )
468
+ veh .control = RandomPath (10 )
453
469
p = veh .run (1000 , plot = True )
454
470
# plt.show()
455
471
print (p )
@@ -465,7 +481,6 @@ def control(v, t, x):
465
481
# ax.set_xlim(-5, 5)
466
482
# ax.set_ylim(-5, 5)
467
483
468
-
469
484
# v = VehicleAnimation.Polygon(shape='triangle', maxdim=0.1, color='r')
470
485
# v = VehicleAnimation.Icon('car3.png', maxdim=2, centre=[0.3, 0.5])
471
486
# v = VehicleAnimation.Icon('/Users/corkep/Dropbox/code/robotics-toolbox-python/roboticstoolbox/data/car1.png', maxdim=2, centre=[0.3, 0.5])
@@ -477,4 +492,4 @@ def control(v, t, x):
477
492
478
493
# for theta in np.linspace(0, 2 * np.pi, 100):
479
494
# v.update([0, 0, theta])
480
- # plt.pause(0.1)
495
+ # plt.pause(0.1)
0 commit comments