Blender, Python, Arduino and Animation

~5 minutes read


How to use a Blender animation to control a servo.


Someone asked me how to use a Blender animation to code a kind of robot. I will try to give an example in this post.

The first thing that came into my mind is that I knew Blender has a Python API, since I tried to use it few times. Starting from that, I guess 50% of the work was already done.

To test this out, I will use a SG-90 servo with an Arduino Uno, the Firmata protocol and some Python. The idea is to draw a sort of disk in Blender, which rotate on itself through Z-axis and to recopy the disk rotation angle to the servo.

Arduino

Wiring

Arduino Wiring Schematic

For some servo, wiring colors might be different such as:

  • Orange: PWM, Arduino pin 9,
  • Red: 5V,
  • Brown: ground,

Any doubt, refer to the datasheet of the one you’re using.

Code

On this one, we will just download the StandardFirmata example. If you don’t find it, check that you have the library installed.

Arduino IDE library

It’s in File -> Examples -> Firmata -> StandardFirmata. Open it, Ctrl + U to download it and we’re good to go on Arduino side.

Python

Open your favorite coding editor and I propose you this small code to get started:

import pyfirmata
import time
# We are connecting to Arduino through serial port.
# If not 'COM3', check in with Arduino IDE the port.
board = pyfirmata.Arduino('COM3')
# We define the servo pin here.
servo = board.get_pin('d:9:s')
# Continuing move the servo from 0 to 90°, 90 to 0°.
while True:
time.sleep(3)
servo.write(0)
time.sleep(3)
servo.write(90)

It appeared that Firmata has a special element for servo’s declaration: ‘s’ (see Resources 1.). Then we can directly write the angle value to the pin.

If your servo is rotating back and forth, then everything is correctly setup.

Note: Be careful if you modify the angle value, check what your servo can do. Some goes from 0 to 180°, others to 360°. Don’t try to write something that your servo can’t do.

Blender

Blender is a very powerful soft. If you’re really seeking to learn more about Blender, I advise you the Blender Guru YouTube channel (see Resources 2.) which is excellent to learn all the basics and more.

So, let’s download and install Blender. Once opened, you will get a cube. I would prefer something which looks like a small disk.

Setting up a quick disk in Blender

At the end, I added a little visual aid to actually see the disk rotate. If you want to get the vertex moving along an axis, press the axis you want to go along, Z in this case.

Now we have our rotating object, we will use the Scripting tab.

Blender Scripting interface

The interface looks like this:

  1. The 3D model view: we can still interact with it;
  2. Python console;
  3. Function historic describes, in Python, everything you might change through UI;
  4. Script editor;
  5. If you want to update the object’s properties.

To start, we will just try with: print(‘Bonjour’). When we launch the script by pressing the Play button above the script editor, the Bonjour is not shown in the Python console (2). To see it, go to: Window -> Toogle System Console. Then, it’s in this one that everything will be shown.

Now, we want to get the rotation of the object. If we look at Blender’s Python API, there is a function called rotation_euler (see Resources 3.) which will give us the object rotation through the 3-axis.

# We get the current rotation of the object.
rotation = bpy.context.object.rotation_euler
# We display the Z-axis value converted in degrees (rounded).
print(round((rotation[2]/(2*math.pi))*360))

If you go in System Console, you will see the rotation through Z-axis displayed. It’s the same as in object’s property window on the right (5). We can send this value to the Arduino now.

Install Additional Modules with Blender

Blender uses his own version of Python which is available in the installation folder Blender/2.91/python/bin. If we want to use pip to install modules, we need to navigate to Blender/2.91/python/bin with PowerShell to use its version of Python and not the system one. Once there, let’s check it’s working: .\python.exe -m pip list

Checking Python modules

We can install the PyFirmata module to communicate with the Arduino: .\python.exe -m pip install pyfirmata

Checking Python modules

Now, we can use it from Blender's script.

Script

We need some upgrade on the previous Python code to rotate the servo because using while True: will freeze Blender since it’s the same process. To avoid that, modal operators are required.

The timer is created with wm.event_timer_add (see Resources 4.). In the modal() method, we use it to insert the code that we want to be executed in response to the timer event.

Furthermore, we need to check the rotation value from Blender, because if you look at it when you move the disk in 3D view, it can go really far (from 0° to 10000°, even more). Like the servo can only go from 0° to 180°, we need to map this value to one acceptable (normalize_angle function).

import bpy
import math
import time
import pyfirmata

class ModalTimerOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Modal Timer Operator"

    _timer = None

    def modal(self, context, event):
        if event.type in {'RIGHTMOUSE', 'ESC'}:
            self.cancel(context)
            return {'CANCELLED'}

        if event.type == 'TIMER':
            # Code we want to be executed.
            rotation = bpy.context.object.rotation_euler
            z_ang = round((rotation[2]/(2*math.pi))*360)
            print(z_ang)
            znor_ang = normalize_angle(z_ang)
            print("normalized: ", znor_ang)
            servo.write(znor_ang)

        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(2, window=context.window)
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)

def register():
    bpy.utils.register_class(ModalTimerOperator)

def unregister():
    bpy.utils.unregister_class(ModalTimerOperator)

def normalize_angle(angle):
    """Convert angles to 0°/180° range."""
    new_angle = angle
    while new_angle <= -180:
        new_angle += 180
    while new_angle > 180:
        new_angle -= 180
    return abs(new_angle)

if __name__ == "__main__":
    register()
    # We are connecting to Arduino through serial port.
    # If not 'COM3', check in with Arduino IDE the port.
    board = pyfirmata.Arduino('COM3')
    # We define the servo pin here.
    servo = board.get_pin('d:9:s')
    # test call
    bpy.ops.wm.modal_timer_operator()

If we rotate through Z-axis in Blender in 3D view (R key and Z), and move slowly, we will see the servo also get its angle updated.

What About Blender Animations?

Blender includes an animation functionality and it’s available at the bottom of the Layout view. We will add key-frames all along the timeline and specify the rotation following Z-axis.

Adding animation keyframes

If you start the script and then you play the animation, you should see servo angle get updated.

Animation disk rotation

Servo rotating

Note: GIF above are not synchronized.


Resources

  1. Servo Control from pyfirmata + arduino (scruss.com)
  2. Blender Guru Channel (youtube.com)
  3. Blender Python API (docs.blender.org)
  4. Blender Python API (docs.blender.org)

Published

Stay Connected