Blender, Python, Arduino and Animation

Someone asked how to use a Blender animation to code a kind of robot. I will try to answer this question 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

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.

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’ (thanks to scruss). 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 which is excellent (to me) 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.

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.

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 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
If you use .\python.exe, it’s Blender’s one. If you use python, it will use the system one.

We can install the pyFirmata module to communicate with Arduino.

.\python.exe -m pip install pyfirmata

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 class example is here.

The timer is created with wm.event_timer_add. 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.

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

Note: GIF above are not synchronized.