Featured post

new redirect for blender.org bpy docs.

http://www.blender.org/api/blender_python_api_current/ As of 10/11 november 2015 we can now link to the current api docs and not be worr...

September 20, 2012

Python Driven Animation

Not everything can be keyframed, but almost everything can. There are benefits to keyframing, but it isn't always necessary. This short example shows how to use frame_change_pre handler, it is called on every frame change. This means if you have something you want to change as a function of time or frame number, that you can do this very easily. This works on text body too.
import bpy
import math
# for demo it creates a cube if it doesn't exist,
# but this can be any existing named object.
if not 'Cube' in bpy.data.objects:
bpy.ops.mesh.primitive_cube_add()
frames_per_revolution = 120.0
step_size = 2*math.pi / frames_per_revolution
def set_object_location(n):
x = math.sin(n) * 5
y = math.cos(n) * 5
z = 0.0
ob = bpy.data.objects.get("Cube")
ob.location = (x, y, z)
# every frame change, this function is called.
def my_handler(scene):
frame = scene.frame_current
n = frame % frames_per_revolution
if n == 0:
set_object_location(n)
else:
set_object_location(n*step_size)
bpy.app.handlers.frame_change_pre.append(my_handler)
Search blender API docs for more information about frame_change_pre. You could even combine the frame_change_pre with setting and adding keyframes, but unless you want to export the animation - that might be overkill.

While debugging, you might find that you are calling successive versions of a function from the event handler. There might be more efficient ways of fixing this, but i've found this method removes 'old' code from the event handler list just fine.
# if i ran the script twice, the functions are daisy-chained
>> bpy.app.handlers.frame_change_pre
[<function my_handler at 0x48a7d10>, <function my_handler at 0x5406490>]
# this will removal all 'frame_change_pre' handlers,
bpy.app.handlers.frame_change_pre.clear()
# there are scenarios when you might want this, but sometimes it will be too broad.
# one way to remove them is by doing a pop() on those items that match
# the name of the function you want to remove, this needs to happen in reverse
my_handler_list = bpy.app.handlers.frame_change_pre # alias
fin = len(my_handler_list)
for idx, func in enumerate(reversed(my_handler_list)):
if func.__name__ == 'my_handler':
my_handler_list.pop(fin-1-idx)