Blender Python Programming

@for Developers
@author Kai Ruhl, Matthias Überheide
@since 2015-01
@support [keys] [render_all_cams.py]

Go to the blender.org and download the latest version. Open the API reference for your version (as of 2015-01: 2.73).

Start blender from terminal and press Alt+F11 for fullscreen. Press t/n for toggling the toolbar left and the object properties bar right. Press Ctrl+RightArrow until you arrive in the "Scripting" perspective.

Minimum Script

In the Text view, open a new text block and type:

import time; ts = time.time()
import bpy
print("----\nHello world : %s" % (bpy.data.objects,))
print("This took %.4f seconds" % (time.time() - ts,))

Then press "Run Script" and look at the terminal you started blender from. Next, back to blender and the Python console [docs] (or for fun, press Shift+F4 over any window). Type bpy. and press Ctrl+Space for completion. Explore a bit, e.g. to bpy.data.cameras[0].name [api]. Also it seems somewhat intricate to show a popup dialog instead of terminal messages (you would need to define an Operator [docs], the existing operators do not offer anything as far as I can see [api]).

Cameras

There are two types of camera objects: One in the data camera list, and one in the scene object list.

camcount = len(bpy.data.cameras)
camdat = bpy.data.cameras[0]; assert camdat.type == 'PERSP'
camobj = bpy.context.scene.objects['Camera']; assert camobj.type == 'CAMERA'
assert camobj.data == camdat

The scene object camera has an association to the data camera, but not vice versa. The two cameras might not have the same name, i.e. camdat.name != camobj.name is well possible -- see Object and Camera tab in the Properties view. So we can choose the more convoluted way to get all cameras (in their two respective incarnations dat and obj):

scene_camobj_list = [ob for ob in list(bpy.context.scene.objects) if ob.type == 'CAMERA']
camdat_list = sorted(list(bpy.data.cameras), key=lambda camdat: camdat.name)
camobj_list = [[ob for ob in scene_camobj_list if ob.data == camdat][0] for camdat in camdat_list]
camidx = 0; camobj, camdat = camobj_list[camidx], camdat_list[camidx]

We get the data cameras first, then the object cameras by checking whether their data entry is the data camera. Next, we are interested in the project matrixes. The matrix_world matrix is camera-to-world, not the other way round. So, in the VisualSFM/bundler sense, we need the following:

RT = camobj.matrix_world.inverted()
RT = RT * Matrix(np.diag([1., 1., -1., 1.]))
sr = bpy.context.scene.render
img_wid_px = sr.resolution_x * sr.resolution_percentage/100.
img_hei_px = sr
.resolution_y * sr.resolution_percentage/100.
sensor_width_in_mm = camdat.sensor_width
focal_length_in_px = camdat.lens.real / sensor_width_in_mm * img_wid_px

Extrinsic matrix (i.e. inverted position of the camera) comes from inverting the camera-to-world matrix. Since blender works with a RHS (right-hand side) coordinate system [wiki], we flip the z-axis to arrive at a LHS [coords]. Intrinsic matrix (i.e. focal length, same for x and y) requires knowledge about the rendered image pixel size as well as the virtual camera sensor.

Export Depth/Motion

As a side note for ground truth (GT) export: When you export depth/motion, RenderLayer.Z will contain the depth in camera space, RenderLayer.Vector.Z/W the motion to the next frame in camera space, and RenderLayer.Vector.X/Y the motion to the previous frame in camera space.

So if you want a (forward) scene flow in world coordinates, you need to project the camera space points at t=0 and t=1 into world space and the subtract them from each other.

u = Vector.Z * -1.
v = Vector.W * -1.
p_t0 = [xgrid, ygrid, Z0]
p_t1 = [xgrid+u, ygrid+v, Z1[xgrid+u, ygrid+v]]

Do a perspective multiplication and (K*Rt)-1p [coords] and subtract them from each other to get dU, dV, dW in world space. Note that the motion is given in reverse sign.

Deploying a Script

Before we write a script, we assume that you might have gotten one from someone and want to install it.

Press Ctrl+Alt+U to open the user preferences. Choose the "Add-Ons" tab and at the bottom, "Install from File". But wait, you still need to allow it! If it not visible, search for its name (fun game: find the minimal search string that shows only your plugin) and enable the checkbox to the right. Then at the bottom, press "Save User Settings". You are good to go.

When reloading, choose again "Install from File" and then disable and enable the checkbox. No need to press "Save User Settings", and you can as well leave the window open. Look at the terminal to see whether the reload happened.

Writing a Script

That is slightly convoluted [docs], but the gist is: In your blender-plugin Python file, you first have to provide some metadata:

bl_info = {
    "name": "RenderAllCameras",
    "category": "Render",
}

Then you need to create a new operator class with more metadata:

class OBJECT_OT_RenderAllCamerasButton(bpy.types.Operator):

    bl_idname = "renderallcams.renderallcamerasbutton"
    bl_label = "Render all Cameras"

Our new class needs at least an execute method (the context is mostly the same as bpy.context, but can be different for some special cases), which has to return either CANCELLED or FINISHED.

    def execute(self, context):
        print("Huhu, I was called")
        return {'FINISHED'}

And finally, the script (not the class) needs methods ro register and unregister itself.

def register():
    bpy.utils.register_class(OBJECT_OT_RenderAllCamerasButton)
 
def unregister():
    bpy.utils.unregister_class(OBJECT_OT_RenderAllCamerasButton)

But really, the blender API/tutorials [docs] do a much better job than I could here. So go there, read some more blender/Python documentation, and have fun!

EOF (Jan:2015)