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.
First of all you need a place to interact with blender via python, for that we use the Python Console:
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 normal blender tooltips.
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.
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']
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 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.subdivision_set(3)
{'FINISHED'}
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.
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.
Using an already written template for a python blender operator called Operator Modal Timmer is the simplest option.
This script is designed to run any 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' # The same address of your computer
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))
s.settimeout(1)
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)
# Check if the data is complete (sometimes the app sends only some of the sensors)
if len(message.split(b',')) == 17:
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:
# Stop if no more data is received
self.cancel(context)
return {'CANCELLED'}
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.
To make it more responsive we can make the timer to execute more often, just change the value under the execute(self, context) to something smaller, 0.001 has worked nicely in our case.
def execute(self, context):
wm = context.window_manager
self._timer = wm.event_timer_add(0.001, window=context.window)
wm.modal_handler_add(self)
return {'RUNNING_MODAL'}
Now just run the script and if everything is Ok you will see Suzanne rotating!
To stop the script you can click the right mouse button , hit the esc key, or simply stop the data stream on your phone.gg
To start lets control the built in led on an arduino uno with a simple on/off switch
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 ')
Moving an arrow in blender we control a moving light through the led strip
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);
}
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 # «
References
A lot of content has been borrowed from different sources, mainly from Blender project documentation, thanks to all of them!