4
4
import matplotlib .pyplot as plt
5
5
import time
6
6
7
- from bdsim .components import TransferBlock
7
+ from bdsim .components import TransferBlock , FunctionBlock
8
8
from bdsim .graphics import GraphicsBlock
9
9
# ------------------------------------------------------------------------ #
10
10
@@ -135,11 +135,13 @@ def __init__(self, model, *inputs, groundcheck=True, speedcheck=True, x0=None, *
135
135
self .speedcheck = speedcheck
136
136
137
137
self .D = np .zeros ((3 ,self .nrotors ))
138
+ self .theta = np .zeros ((self .nrotors ,))
138
139
for i in range (0 , self .nrotors ):
139
140
theta = i / self .nrotors * 2 * pi
140
141
# Di Rotor hub displacements (1x3)
141
142
# first rotor is on the x-axis, clockwise order looking down from above
142
143
self .D [:,i ] = np .r_ [ model ['d' ] * cos (theta ), model ['d' ] * sin (theta ), model ['h' ]]
144
+ self .theta [i ] = theta
143
145
144
146
self .a1s = np .zeros ((self .nrotors ,))
145
147
self .b1s = np .zeros ((self .nrotors ,))
@@ -181,6 +183,7 @@ def output(self, t=None):
181
183
out ['w' ] = iW @ self ._x [9 :12 ] # RPY rates mapped to body frame
182
184
out ['a1s' ] = self .a1s
183
185
out ['b1s' ] = self .b1s
186
+ out ['X' ] = self ._x
184
187
185
188
return [out ]
186
189
@@ -295,12 +298,91 @@ def deriv(self):
295
298
296
299
do = np .linalg .inv (model ['J' ]) @ (np .cross (- o , model ['J' ] @ o ) + np .sum (tau , axis = 1 ) + np .sum (Q , axis = 1 )) # row sum of torques
297
300
298
- # stash the flapping information for plotting
299
- self .a1s = a1s
300
- self .b1s = b1s
301
+ # # stash the flapping information for plotting
302
+ # self.a1s = a1s
303
+ # self.b1s = b1s
301
304
302
305
return np .r_ [dz , dn , dv , do ] # This is the state derivative vector
303
306
307
+ # ------------------------------------------------------------------------ #
308
+
309
+ class MultiRotorMixer (FunctionBlock ):
310
+ """
311
+ :blockname:`MULTIROTORMIXER`
312
+
313
+ .. table::
314
+ :align: left
315
+
316
+ +--------+---------+---------+
317
+ | inputs | outputs | states |
318
+ +--------+---------+---------+
319
+ | 4 | 1 | 0 |
320
+ +--------+---------+---------+
321
+ | float | | |
322
+ +--------+---------+---------+
323
+ """
324
+
325
+ nin = 4
326
+ nout = 1
327
+ inlabels = ('𝛕r' , '𝛕p' , '𝛕y' , 'T' )
328
+ outlabels = ('ω' ,)
329
+
330
+ def __init__ (self , maxw = 1000 , minw = 5 , ** kwargs ):
331
+ """
332
+ Create a block that displays/animates a multi-rotor flying vehicle.
333
+
334
+ :param maxw: maximum rotor speed in rad/s, defaults to 1000
335
+ :type maxw: float
336
+ :param minw: minimum rotor speed in rad/s, defaults to 5
337
+ :type minw: float
338
+ :param ``**kwargs``: common Block options
339
+ :return: a MULTIROTORMIXER block
340
+ :rtype: MultiRotorMixer instance
341
+
342
+
343
+ **Block ports**
344
+
345
+ :input 𝛕r: roll torque
346
+ :input 𝛕p: pitch torque
347
+ :input 𝛕y: yaw torque
348
+ :input T: total thrust
349
+
350
+ :output ω: 1D array of rotor speeds
351
+
352
+ Derived from Simulink model by Pauline Pounds 2004
353
+ """
354
+ super ().__init__ (inputs = inputs , ** kwargs )
355
+ self .type = 'multirotormixer'
356
+ self .minw = minw
357
+ self .maxw = maxw
358
+
359
+ def output (self , t ):
360
+ w = np .zeros ((self .nrotors ,))
361
+ tau = self .inputs
362
+ for i in self .nrotors :
363
+ # roll and pitch coupling
364
+ w [i ] += - tau [
57AE
0 ] * sin (self .theta [i ]) + tau [1 ] * cos (self .theta [i ])
365
+
366
+ # yaw coupling
367
+ sign = 1 if i % 1 == 0 else - 1
368
+ w [i ] += sign * tau [2 ]
369
+
370
+ # overall thrust
371
+ w [i ] += tau [3 ] / self .nrotors
372
+
373
+ # clip the rotor speeds to the range [minw, maxw]
374
+ w = np .clip (w , self .minw , self .maxw )
375
+
376
+ # convert to thrust
377
+ w = np .sqrt (w ) / self .model ['b' ]
378
+
379
+ # negate alterate rotors to indicate counter-rotation
380
+ for i in self .nrotors :
381
+ if i % 1 == 0 :
382
+ w [i ] = - w [i ]
383
+
384
+ return [w ]
385
+
304
386
305
387
# ------------------------------------------------------------------------ #
306
388
@@ -360,15 +442,18 @@ def __init__(self, model, *inputs, scale=[-2, 2, -2, 2, 10], flapscale=1, projec
360
442
:param projection: 3D projection, one of: 'ortho' [default], 'perspective'
361
443
:type projection: str
362
444
:param ``**kwargs``: common Block options
363
- :return: a MULTIROTOPLOT block
364
- :rtype: MultiRobotPlot instance
445
+ :return: a MULTIROTORPLOT block
446
+ :rtype: MultiRotorPlot instance
365
447
366
448
367
449
**Block ports**
368
450
369
- :input ω : a dictionary signal that includes the item:
451
+ :input y : a dictionary signal that includes the item:
370
452
371
453
- ``x`` pose in the world frame as :math:`[x, y, z, \t heta_Y, \t heta_P, \t heta_R]`
454
+ - ``X`` pose in the world frame as :math:`[x, y, z, \t heta_Y, \t heta_P, \t heta_R]`
455
+ - ``a1s``
456
+ - ``b1s``
372
457
373
458
.. figure:: ../../figs/multirotorplot.png
374
459
:width: 500px
@@ -386,7 +471,7 @@ def __init__(self, model, *inputs, scale=[-2, 2, -2, 2, 10], flapscale=1, projec
386
471
self .projection = projection
387
472
self .flapscale = flapscale
388
473
389
- def start (self ):
474
+ def start (self , state ):
390
475
quad = self .model
391
476
392
477
# vehicle dimensons
@@ -411,15 +496,20 @@ def start(self):
411
496
self .ax .set_xlabel ('X' )
412
497
self .ax .set_ylabel ('Y' )
413
498
self .ax .set_zlabel ('-Z (height above ground)' )
499
+
500
+ self .panel = self .ax .text2D (0.05 , 0.95 , '' , transform = self .ax .transAxes ,
501
+ fontsize = 10 , family = 'monospace' , verticalalignment = 'top' ,
502
+ bbox = dict (boxstyle = 'round' , facecolor = 'white' , edgecolor = 'black' ))
503
+
414
504
415
505
# TODO allow user to set maximum height of plot volume
416
506
self .ax .set_xlim (self .scale [0 ], self .scale [1 ])
417
507
self .ax .set_ylim (self .scale [2 ], self .scale [3 ])
418
508
self .ax .set_zlim (0 , self .scale [4 ])
419
509
420
510
# plot the ground boundaries and the big cross
421
- self .ax .plot ([self .scale [0 ], self .scale [2 ]], [self .scale [1 ], self .scale [3 ]], [0 , 0 ], 'b-' )
422
- self .ax .plot ([self .scale [0 ], self .scale [3 ]], [self .scale [1 ], self .scale [2 ]], [0 , 0 ], 'b-' )
511
+ self .ax .plot ([self .scale [0 ], self .scale [1 ]], [self .scale [2 ], self .scale [3 ]], [0 , 0 ], 'b-' )
512
+ self .ax .plot ([self .scale [0 ], self .scale [1 ]], [self .scale [3 ], self .scale [2 ]], [0 , 0 ], 'b-' )
423
513
self .ax .grid (True )
424
514
425
515
self .shadow , = self .ax .plot ([0 , 0 ], [0 , 0 ], 'k--' )
@@ -440,32 +530,32 @@ def start(self):
440
530
self .a1s = np .zeros ((self .nrotors ,))
441
531
self .b1s = np .zeros ((self .nrotors ,))
442
532
443
- def step (self ):
533
+
534
+ def step (self , state ):
444
535
445
536
def plot3 (h , x , y , z ):
446
- h .set_data (x , y )
447
- h .set_3d_properties (z )
537
+ h .set_data_3d (x , y , z )
538
+ # h.set_data(x, y)
539
+ # h.set_3d_properties(np.r_[z])
448
540
449
541
# READ STATE
450
- z = self .inputs [0 ][0 :3 ]
451
- n = self .inputs [0 ][3 :6 ]
542
+ z = self .inputs [0 ]['x' ][ 0 :3 ]
543
+ n = self .inputs [0 ]['x' ][ 3 :6 ]
452
544
453
545
# TODO, check input dimensions, 12 or 12+2N, deal with flapping
454
546
455
- a1s = self .a1s
456
- b1s = self .b1s
547
+ a1s = self .inputs [ 0 ][ ' a1s' ]
548
+ b1s = self .inputs [ 0 ][ ' b1s' ]
457
549
458
550
quad = self .model
459
551
460
552
# vehicle dimensons
461
- d = quad ['d' ]; # Hub displacement from COG
462
- r = quad ['r' ]; # Rotor radius
553
+ d = quad ['d' ] # Hub displacement from COG
554
+ r = quad ['r' ] # Rotor radius
463
555
464
556
# PREPROCESS ROTATION MATRIX
465
- phi = n [0 ]; # Euler angles
466
- the = n [1 ];
467
- psi = n [2 ];
468
-
557
+ phi , the , psi = n # Euler angles
558
+
469
559
# BBF > Inertial rotation matrix
470
560
R = np .array ([
471
561
[cos (the ) * cos (phi ), sin (psi ) * sin (the ) * cos (phi ) - cos (psi ) * sin (phi ), cos (psi ) * sin (the ) * cos (phi ) + sin (psi ) * sin (phi )],
@@ -512,7 +602,7 @@ def plot3(h, x, y, z):
512
602
hub0 = F @ z # centre of vehicle
513
603
for i in range (0 , self .nrotors ):
514
604
# line from hub to centre plot3([hub(1,N) hub(1,S)],[hub(2,N) hub(2,S)],[hub(3,N) hub(3,S)],'-b')
515
- plot3 (self .arm [i ], [hub [0 ,i ], hub0 [0 ]],[hub [1 ,i ], hub0 [1 ]],[hub [2 ,i ], hub0 [2 ]])
605
+ plot3 (self .arm [i ], [hub [0 ,i ], hub0 [0 ]], [hub [1 ,i ], hub0 [1 ]], [hub [2 ,i ], hub0 [2 ]])
516
606
517
607
# plot a circle at the hub itself
518
608
#plot3([hub(1,i)],[hub(2,i)],[hub(3,i)],'o')
@@ -521,4 +611,9 @@ def plot3(h, x, y, z):
521
611
plot3 (self .shadow , [z [0 ], 0 ], [- z [1 ], 0 ], [0 , 0 ])
522
612
plot3 (self .groundmark , z [0 ], - z [1 ], 0 )
523
613
614
+ textstr = f"t={ state .t : .2f} \n h={ z [2 ]: .2f} \n γ={ n [0 ]: .2f} "
615
+ self .panel .set_text (textstr )
616
+
617
+ super ().step (state = state )
618
+
524
619
0 commit comments