API Modules#

You can find everything about the Python API in its documentation, but the amount of information there is overwhelming. This section focuses on the three modules you will use the most.

Operators(bpy.ops)#

Operators include functions that perform the same task as the buttons and menu options in the Blender GUI. They only return the status of the operations, like in this image from the last section.

Tip

  • Camera added this way will not automatically become the active camera.

  • In the Python Console area, pressing Tab after typing the name of the function will give you a short documentation.

../../_images/tab_doc.png

Apart from mesh objects, you can add curves, cameras, lights, etc., check the tooltips in the Add menu for the respective functions.

bpy.ops.curve.primitive_bezier_curve_add()
bpy.ops.object.camera_add(location = (0, 0, 5))
bpy.ops.object.light_add(type='POINT', location = (1, 0, 1))
../../_images/ops_add.png

You may want to delete everything before running the main part of the script, the following code can help:

Tip

This is good enough for the starting scene, but when applied to a complex scene it may not clean up everything.

bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete()
bpy.ops.outliner.orphans_purge()

The first line selects everything in the scene (same as pressing A), the second deletes selected objects, and the last remove unused data from the file.

../../_images/clean_scene.gif

To Render a scene, run the following:

bpy.ops.render.render(write_still=True)

The write_still=True option saves the rendered image to the Output Path.

For complex setups, it might be easier to make it in Blender GUI and load the .blend file in Python script.

bpy.ops.wm.open_mainfile(filepath="PATH_TO_BLEND_FILE")

There are a lot more operators that can add modifiers, make keyframes, etc., but generally speaking, if you can achieve the same goal using both bpy.ops and bpy.data module, you should do it with bpy.data. Following that principle, we skip those operators in this section.

Data Access(bpy.data)#

The Data Access is the most important module in the API, it allows you to access/create/modify data in the current file. An easy way to check the internal data structure is to switch the Display mode of an Outliner area to Data API.

../../_images/outliner_data.png

For example, you can access the Cube object

bpy.data.objects["Cube"]
../../_images/data_cube_1.png

assign it to a variable

cube_0 = bpy.data.objects["Cube"]

and get/set its properties (location in this example)

Tip

Rotation angle is in radian, you can use math.radians() to turn degrees into radians.

cube_0.location
cube_0.rotation_euler
cube_0.location.x = 1.0
cube_0.rotation_euler.x = 0.5
../../_images/data_cube_2.png

You can add an object directly into the data. First, create a data-block for the object

cam_db = bpy.data.cameras.new("new_cam")

then use the data-block to construct a new object

cam_0 = bpy.data.objects.new("camera_0", cam_db)

and nothing happens in the viewport. But if you check the Data API, you will find both of them are there. To make it show up in the current scene, you need to link the object to a collection in the scene.

bpy.data.collections['Collection'].objects.link(cam_0)

Now it should be visible in the viewport.

Tip

  • This camera will not automatically become the active one.

../../_images/data_obj_add.gif

Adding a collection to a scene is similar. Make a new collection in the data

ico_coll = bpy.data.collections.new("ico_collection")

then link the collection to the scene

bpy.data.scenes['Scene'].collection.children.link(ico_coll)

You can also loop through the objects in a collection

for obj in bpy.data.collections['ico_collection'].objects:
    obj.location.x += 1
../../_images/coll_loop.gif

Making a mesh object in bpy.data takes a bit more work than creating other objects. To make a mesh datablock, you need to define vertices, edges, and faces. Let’s look at this example:

verts = [
    (-1.0, -1.0, -1.0),
    (-1.0, 1.0, -1.0),
    (1.0, 1.0, -1.0),
    (1.0, -1.0, -1.0),
    (-1.0, -1.0, 1.0),
    (-1.0, 1.0, 1.0),
    (1.0, 1.0, 1.0),
    (1.0, -1.0, 1.0),
    ]

edges = [
    (0, 2),
]

faces = [
    (0, 1, 2, 3),
    (4, 5, 1, 0),
    (7, 6, 5, 4),
    (7, 4, 0, 3),
    (6, 7, 3, 2),
    (5, 6, 2, 1),   
]

Tip

  • Turning on indices helps visualizing the process.

  • The order of the indices decides the how the face is constructed, and its normal follows the right-hand rule.

Vertices are naturally dictated by their coordinates, and faces are defined by the indices of vertices. The order of the indices is important for the faces, and the normal is determined by the right-hand rule. The edges are automatically generated where faces are defined, only additional edges are necessary. With these three lists ready, you can make the data-block

cube_mesh = bpy.data.meshes.new("cube_data")
cube_mesh.from_pydata(verts, edges, faces)

then create the mesh object and link it to the collection

cube_obj = bpy.data.objects.new("Cube_1", cube_mesh)
bpy.data.collections['Collection'].objects.link(cube_obj)

Tip

The added edge is not part of any faces.

../../_images/data_mesh.gif

You can add a modifier to an object in the data directly

Tip

  • Check here for the modifier types and here for the details on the modifier subclasses.

cube_0.modifiers.new(name = "mod1", type = "SUBSURF")

And you can tweak it too

cube_0.modifiers['mod1'].levels = 3
cube_0.modifiers['mod1'].render_levels = 5
../../_images/data_mod.gif

Adding a constraint is similar but it does not require a name.

Tip

Check here for the constraint types and here for the details on the constraint subclasses.

cube_0.constraints.new(type = "LIMIT_LOCATION")
cube_0.constraints['Limit Location'].use_max_x = True
cube_0.constraints['Limit Location'].max_x = 1.0
cube_0.constraints['Limit Location'].use_transform_limit = True
../../_images/data_constr.gif

To add keyframes, you need to specify the data path and frame

Tip

Data path:

  • Location: “location”

  • Rotation: “rotation_euler”

  • Scale: “scale”

cube_0.location = (2.0, 0.0, 0.0)
cube_0.keyframe_insert(data_path = "location", frame = 1)
cube_0.location = (-2.0, 0.0, 0.0)
cube_0.keyframe_insert(data_path = "location", frame = 250)

You can also delete keyframes

cube_0.keyframe_delete(data_path = "location", frame = 1)
cube_0.keyframe_delete(data_path = "location", frame = 250)
../../_images/data_anim.gif

To add material to an object, first we need to create one in the data

mat = bpy.data.materials.new("new_mat")

then append it to the object

Tip

This creates a new material slot for the object.

cube_0.data.materials.append(mat)

Change the base color to see the effect

Tip

The diffuse color uses RGBA format.

mat.diffuse_color = (255.0, 0.0, 0.0, 1.0)
../../_images/data_mat.gif

Unlike adding new material in the Material Properties, this material does not have a principled BSDF shader and does not use shader nodes. To make it consistent with what we did in the previous chapters, turn on Use Nodes.

Tip

More details in the coming Node Scripting section.

mat.use_nodes = True

You can change various scene properties with bpy.data. For example, you can change the end frame and frame rate

Tip

The index is 0 for the default scene, you can also use its name “Scene”.

bpy.data.scenes[index].frame_end = 120
bpy.data.scenes[index].render.fps = 30

You can also set the active camera

Tip

This set the camera object named “Camera” as active.

bpy.data.scenes[index].camera = bpy.data.objects['Camera']

and the Output Path, Render Engine, etc.

bpy.data.scenes[index].render.filepath = "YOUR_PATH"
bpy.data.scenes[index].render.engine = "CYCLES"
bpy.data.scenes[index].cycles.device = "GPU"

Drivers can be added through data access too. For example, if the cube has a Bevel modifier, you can add a driver to its Amount property like this

Tip

Check on which level you are adding the driver, for example, this does not work

cube_0.driver_add("modifiers['Bevel'].width")
cube_0.modifiers['Bevel'].driver_add("width")

For paths with indices such as location, rotation and scale, you can specify the index as the second argument

cube_0.driver_add("location", 0)

To make the driver work, first you need to add variables

loc_driver = cube_0.driver_add("location", 0)
var_0 = loc_driver.driver.variables.new()
var_0.name = "c_frame"
var_0.targets[0].id_type = "SCENE"
var_0.targets[0].id = C.scene
var_0.targets[0].data_path = "frame_current"

then use it to construct the expression

loc_driver.driver.expression = var_0.name + "/100" 
../../_images/data_driver.gif

Context Access(bpy.context)#

The Context Access module gives you easy access to the elements in the area currently being used. For example, when the cube is the active object (e.g. just added to the scene), instead of

cube_0 = bpy.data.objects["Cube"]

you can do

cube_0 = bpy.context.active_object

because it points to the same object in the data.

../../_images/c_cube.png

Similarly, to get the current scene, you can use

bpy.context.scene

which is the same as

bpy.data.scenes[index]

and we also have bpy.context.collection for the collection, bpy.context.engine for the render engine, etc.