This library helps to control servos based on an exported Blender animation. It is specifically designed to work with the Blender Servo Animation Add-on.
Please refer to the official Arduino documentation to see how you can install this library.
To start using this library, add the following include statement to your script or sketch:
#include <BlenderServoAnimation.h>
To avoid naming conflicts, the library uses the BlenderServoAnimation
namespace. For example, this allows to use the standard Arduino Servo library to control the servos while using the Servo
class of this library to represent the servo within the Blender animation:
// Standard library servo object
Servo(...);
// Blender servo object
BlenderServoAnimation::Servo(...);
// Blender animation object
BlenderServoAnimation::Animation(...);
// Blender show object
BlenderServoAnimation::Show();
When not using the standard servo library, you can use the namespace and therefore skip the namespace prefix:
using namespace BlenderServoAnimation;
// Blender servo object
Servo(...);
// Blender animation object
Animation(...);
// Blender show object
Show();
Before we can play back and control an animation, we first have to create and attach representations of the animated servos. There are 4 possible syntaxes to create a new servo object:
Servo(id, positions, callback);
Servo(id, positions, callback, threshold);
Servo(id, callback);
Servo(id, callback, threshold);
Note: servos without positions will only be considered when in live mode.
Parameter | Type | Description |
---|---|---|
id | byte | Unique servo ID as specified via the Add-on |
positions | const int[] | Exported positions per frame |
callback | void (byte, int) | Function to trigger when a servo is moved |
threshold | byte | Max allowed position diff (default=0 / no threshold handling) |
Note: the threshold is also used to define the speed for moving a servo to its neutral position when stopping an animation.
The callback function is used to specify what should happen when a servo needs to be moved to a new position. It will be automatically triggered by the animation instance and receives 2 arguments - the servo ID as byte
and the new position as int
:
void myServoCallback(byte servoID, int position) {
// Do something
}
This allows to implement any kind of logic to handle the actual servo control. When using this library outside of the Arduino IDE, it is recommended to define the callback function first before passing it as an argument when creating a servo object:
#include "simple.h"
#include <BlenderServoAnimation.h>
using namespace BlenderServoAnimation;
void move(byte servoID, int position) {
// Do something
}
Servo myBlenderServo(0, Bone, move);
The animation object serves as a control instance to play back the servo movement. Just like in Blender, an animation can be played, paused and stopped. To do so, we need to provide information about the speed and length of the animation. To just use control servos via the live mode (serial connection), we can omit this information. Therefore, an animation can be created via the following 2 syntaxes:
Animation();
Animation(fps, frames);
Note: animations without fps and frames will only be able to handle the live mode.
Parameter | Type | Description |
---|---|---|
fps | byte | Frames per second as specified in Blender |
frames | int | Total amount of frames as specified in Blender |
In general, the provided values should align with the Blender animation you would like to export or work on. Given that the Blender animation consists of 1000 frames and plays back at a rate of 30 frames per second, the animation object should be created as follows:
Animation myBlenderAnimation(30, 1000);
To actually trigger servo movement, the animation needs to know about the individual servos. After defining the servos as mentioned above, we therefore have to register them to the animation by calling the addServo
method:
myBlenderAnimation.addServo(myBlenderServo);
This is usually done inside the setup
function after the servo objects have been defined globally (outside of any function like setup
or loop
).
Alternatively, we can also create an array of servos and call the addServos
method instead:
Animation myBlenderAnimation(30, 1000);
Servo myBlenderServos[] = {
Servo(0, BoneA, move),
Servo(1, BoneB, move),
Servo(2, BoneC, move),
}
void setup() {
myBlenderAnimation.addServos(myBlenderServos, 3);
}
Note: the
addServos
function expects the amount of servos in the array to be passed via the second argument.
The animation needs to be triggered regularly in order to update its state and check if any servos have to be moved. We therefore need to call the run
method during each loop
:
void loop() {
myBlenderAnimation.run();
}
At first, an animation will be in the default mode. In this mode, the animation is simply not doing anything and waits until the mode has changed.
Constant | Method | Description |
---|---|---|
MODE_DEFAULT | Not playing / waiting | |
MODE_PLAY | play() | Start or resume playing the animation once |
MODE_PAUSE | pause() | Pausing the animation at the current frame |
MODE_STOP | stop() | Slowly moving the servos to their neutral position |
MODE_LOOP | loop() | Start or resume playing the animation in a loop |
MODE_LIVE | live(stream) | Reading serial commands to move the servos in real-time |
The modes can be changed or triggered by calling the above methods on the animation object:
myBlenderAnimation.play();
myBlenderAnimation.pause();
myBlenderAnimation.loop();
myBlenderAnimation.stop();
myBlenderAnimation.live(stream);
Note: the default mode can not be triggered as it is only handled internally.
When calling the stop
method, the threshold values of the animation's servos are considered to control how fast or smooth they are moving towards their neutral position. Keep in mind that the servos will not have a threshold value by default which results in the stop mode to immediately trigger the neutral position of the servos. A slower and safer movement can be achieved by setting the threshold values as low as possible with the actual animation still able to run properly.
To use the live
method, we have to pass a stream instance which will be used for reading serial commands. For example, we can pass Serial
if we want to use the standard USB connection of an Arduino compatible board:
void setup() {
Serial.begin(115200);
myBlenderAnimation.live(Serial);
}
This library also comes with a LiveStream
class which allows for a more generic way to listen to live commands. For example, it can be used as part of the web socket based live mode for which you can find a dedicated example here.
To get the current animation mode, we can simply call the getMode
method. This will return a byte
representing one of the mode constants mentioned in the table above. We can then compare the return value to those constants to act according to the current mode:
byte currentMode = myBlenderAnimation.getMode();
switch (currentMode) {
case Animation::MODE_DEFAULT:
// Do something
break;
case Animation::MODE_PLAY:
// Do something else
break;
...
}
On top of manually checking the animation mode, we can also register a callback function which is triggered as soon as the animation mode has changed. The function will receive both the previous mode and the new mode as byte
values. To register the function, we can call the onModeChange
method:
void modeChanged(byte prevMode, byte newMode) {
// Do something (e.g. using a switch statement)
}
void setup() {
myBlenderAnimation.onModeChange(modeChanged);
}
The SwitchModeButton example shows how to combine all mode methods to control an animation based on a single button.
A show object allows you to combine multiple animations and control their play back in an easy way. You can also think of a show as a playlist of animations. Since the show object does not expect any arguments, the initialization is very simple:
Show myBlenderShow;
After defining some animations as shown above, we have to register them to the show object by calling the addAnimation
method:
myBlenderShow.addAnimation(myBlenderAnimation);
This is usually done inside the setup
function after the animation and servo objects have been defined globally (outside of any function like setup
or loop
).
Alternatively, we can also create an array of animations and call the addAnimations
method instead:
Show myBlenderShow;
Animation animations[3] = {
{FPS, FRAMES_A},
{FPS, FRAMES_B},
{FPS, FRAMES_C},
};
void setup() {
myBlenderShow.addAnimations(animations, 3);
}
Note: the
addAnimations
function expects the amount of servos in the array to be passed via the second argument.
Just like single animations, we have to regularly trigger the show instance in order to update its state and internally handle the servo movement of the current animation. We therefore need to call the run
method during each loop
:
void loop() {
myBlenderShow.run();
}
The show modes are similar to the previously mentioned animation modes. In addition to those, there are various playback modes to handle a multitude of animations.
Just like with animation modes, a show will be in the default mode at first. The following table is focusing on the differences and additions of the show modes compared to the animation modes:
Constant | Method | Description |
---|---|---|
MODE_PLAY | play() | Start or resume playing the show once |
MODE_PLAY_SINGLE | playSingle(index) | Start or resume playing a single animation once |
MODE_PLAY_RANDOM | playRandom() | Start or resume randomly playing animations of the show |
The modes can be changed or triggered by calling the respective methods on the show object:
myBlenderShow.play();
myBlenderShow.playSingle(index);
myBlenderShow.playRandom();
myBlenderShow.pause();
myBlenderShow.loop();
myBlenderShow.stop();
myBlenderShow.live(stream);
Note: the default mode can not be triggered as it is only handled internally.
To get the current show mode, we can again call a getMode
method. This will return a byte
representing one of the mode constants mentioned in the table above. We can then compare the return value to those constants to act according to the current mode:
byte currentMode = myBlenderShow.getMode();
switch (currentMode) {
case Show::MODE_DEFAULT:
// Do something
break;
case Show::MODE_PLAY:
// Do something else
break;
...
}
Note: The actual byte values of the show modes differ from the animation modes. For example,
Show::MODE_PAUSE != Animation::MODE_PAUSE
.
As with animations, we can also register an onModeChange
callback function:
void modeChanged(byte prevMode, byte newMode) {
// Do something (e.g. using a switch statement)
}
void setup() {
myBlenderShow.onModeChange(modeChanged);
}
There is also a specific Show example to illustrate a simple setup based on 2 different animations.
Make sure to also check out the other examples to get started more quickly.