Blender IO

Intro

Blender has a python interface that you can use to interface with the external world, you can connect networked sensors to blender and use the inputs in real-time, you can control some leds connected to your arduino, or plan the path your robot is going to follow.

Scripting interface

Python console

First of all you need a place to interact with blender via python, for that we use the Python Console:

Interacting

As a start point you can start playing with the default cube:

cube = bpy.data.objects['Cube']
cube.location.x
cube.location = (2,3,4.5)

Remember that the dir() python function can give a lot of information about what on object can do and hit properties.

You can use auto-completion, if you hit the Tab key on an incomplete line Blender will ofer you the possible options to complete your sentence:

You can copy blender operators from the interface: if you open the add menu and hover your mouse over some object, you can type Ctrl-c to copy the python operator

and then got to the console or text editor and hit Ctrl-v

If you want to know more about the function you just copied you can go to the Blender Python API Documentation and in the search field type Ctrl-V

Enable python tooltips

In the preferences window Interface tabyou can enable Python Tooltips, this will give you extra python information on onrmal blender tooltips.

The blender python API

The blender documentation has a lot of information on python scripting inside blender.

The bpy python object will give you access to all the data inside blender.

Python accesses Blender’s data in the same way as the animation system and user interface; this implies that any setting that can be changed via a button can also be changed from Python.

Data Access (bpy.data)

Accessing data from the currently loaded blend file is done with the module bpy.data. This gives access to library data.

>>> bpy.data.objects['Cube']

Context Access (bpy.context)

While it’s useful to be able to access data directly by name or as a list, it’s more common to operate on the user’s selection. The context is always available from bpy.context and can be used to get the active object, scene, tool settings along with many other attributes.

>>> bpy.context.object
>>> bpy.context.selected_objects
>>> bpy.context.visible_bones

Operators (bpy.ops)

Operators are tools generally accessed by the user from buttons, menu items or key shortcuts. From the user perspective they are a tool but Python can run these with its own settings through the bpy.ops module.

>>> bpy.ops.mesh.flip_normals()
{'FINISHED'}
>>> bpy.ops.mesh.hide(unselected=False)
{'FINISHED'}
>>> bpy.ops.object.scale_apply()
{'FINISHED'}

Hands on

Reading mobile phone sensors »

To send SmartPhone sensor data via UDP packets to our computer in order to use it inside blender we can use any of the available apps, for this example we are going to use Sensor stream IMU+GPS android app.

Phone side

On the phone side we only need to set the ip address of our computer (in my case is 192.168.0.12), select a port (5555) and check UDP Stream. On the Toggle Sensors tab we turn on the desired sensors (the first tree are always enabled) in our case we are going to use Orientation, we also need to check the Include User-Checked… option.

Testing this app, we get the best results using the Fast speed option. To start sending data just click the Switch Stream ON button.

Blender side

Using an already written template for a python blender operator called Operator Modal Timmer is the simplest option.

This script is designed to run nay python instructions based on a timer until the user press de Esc key or the right mouse button.

We only need to add the code that allow us to connect to the UDP stream and some code to parse the data we receive. On the network side we use the socket python library, we need to add a line for importing this library:

import bpy
import socket

Some code to setup the connection, this goes inside the Class but outside the defined methods, just under the timer = None line.

# Setup UDP socket
host = '192.168.0.12'
port = 5555
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.bind((host, port))

And inside the modal method under TIMER event we substitute the default code with our code to parse the received data.

# Receive data
try:
    message, address = self.s.recvfrom(8192)

    x = float(message.split(b',')[-2]) * 0.01745
    y = float(message.split(b',')[-1]) * 0.01745
    z = float(message.split(b',')[-3]) * 0.01745
    print(x,y,z)

    context.object.rotation_euler.x = x
    context.object.rotation_euler.y = y
    context.object.rotation_euler.z = z
except:
    pass

The data is received in the form of a long text string comma separated.

So we split the readings, take the last 3 and convert them to radians. Then we update the rotation of the selected object.

Controlling arduino built in led «

To start lets control the built in led on an arduino uno with a simple on/off switch

Arduino side

int state = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
  Serial.begin(115200);
}

void loop() {

  if (Serial.available()) state = Serial.parseInt(); // Get the value
  while (Serial.available()) Serial.read();  // Clean the Serial port

  if (state > 0) digitalWrite(LED_BUILTIN, HIGH);  // Turn on the led 
  else digitalWrite(LED_BUILTIN, LOW);    // or off

  delay(200);
}

The green box has movement constrains so it can only move in X axis and in limited amount. In the python script we check his position and when it arrives to one size we send a value via the serial port to the arduino.

import bpy
from bpy.props import IntProperty, FloatProperty
import serial

class ModalOperator(bpy.types.Operator):
    """Move an object with the mouse, example"""
    bl_idname = "object.modal_operator"
    bl_label = "Led control"

    first_mouse_x: IntProperty()
    first_value: FloatProperty()

    # Setup serial port
    ser = serial.Serial('/dev/ttyUSB1', 115200)

    def modal(self, context, event):
        if event.type == 'MOUSEMOVE':
            delta = self.first_mouse_x - event.mouse_x
            context.object.location.x = self.first_value - delta * 0.01
            if context.object.location.x <= -3: 
                context.object.location.x = -3
                self.ser.write(b'0 ')
            if context.object.location.x > 3:
                context.object.location.x = 3
                self.ser.write(b'1 ')

Led strip fun

Moving an arrow in blender we control a moving light through the led strip

Arduino code

Turns on the leds depending on the value it receives on the serial port.

#include <Adafruit_NeoPixel.h>

#define PIN 6
#define NUMPIXELS 20
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

int value = 0;

void setup() {
  Serial.begin(115200);
  pixels.begin();
}

void loop() {

  if (Serial.available()) value = Serial.parseInt();
  while (Serial.available()) Serial.read();

  for(int i=0; i<NUMPIXELS; i++) {
    if (i == value) {
       pixels.setPixelColor(i, pixels.Color(0, 250, 0));
    } else if (abs(i - value) == 1) {
       pixels.setPixelColor(i, pixels.Color(0,15, 0));
    } else if (abs(i - value) == 2) {
       pixels.setPixelColor(i, pixels.Color(0, 1, 0));
    } else {
       pixels.setPixelColor(i, pixels.Color(0, 0, 0));
    }
    pixels.show();
  }

  delay(20);
}

Blender python script

Based on Operator Modal template (you can load this templates in blender text editor via the Templates menu). The added lines are indicated with # « This script sends a value from 0 to 20 depending on the position of the selected object (the arrow in this case), it only sends data when it changes. The result is pretty responsive and robust.

import bpy
from bpy.props import IntProperty, FloatProperty
import serial # «

class ModalOperator(bpy.types.Operator):
    """Move an object with the mouse, example"""
    bl_idname = "object.modal_operator"
    bl_label = "Led Strip control"

    first_mouse_x: IntProperty()
    first_value: FloatProperty()

    led = 0 # «
    prevLed = 0 # «
    ser = serial.Serial('/dev/ttyUSB1', 115200)  # «  # Setup serial port


    def modal(self, context, event):
        if event.type == 'MOUSEMOVE':
            delta = self.first_mouse_x - event.mouse_x

            context.object.location.x = self.first_value - delta * 0.1
            if context.object.location.x < -50: context.object.location.x = -50  # «
            elif context.object.location.x > 50: context.object.location.x = 50  # «
            self.led = int(((context.object.location.x + 50) * 2) /10)   # «

            if self.led != self.prevLed: # «
                send = str(self.led) + ' '  # «
                self.ser.write(send.encode('utf8'))  # «
                print(self.led)  # «
                self.prevLed = self.led  # «

More ideas for trying

References