(py_script)= # API Modules You can find everything about the Python API in its [documentation](https://docs.blender.org/api/current/index.html), 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](console_cube) from the last section. ::::{tab-set} :::{tab-item} Add Object ````{tip} :class: margin - 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. ```{figure} ../../assets/scripting/tab_doc.png :width: 100% ``` ```` Apart from mesh objects, you can add curves, cameras, lights, etc., check the tooltips in the `Add` menu for the respective functions. ```python 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)) ``` ```{figure} ../../assets/scripting/ops_add.png ``` ::: :::{tab-item} Clear Scene You may want to delete everything before running the main part of the script, the following code can help: ```{tip} :class: margin This is good enough for the starting scene, but when applied to a complex scene it may not clean up everything. ``` ```python 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. ```{figure} ../../assets/scripting/clean_scene.gif ``` ::: :::{tab-item} Render To Render a scene, run the following: ```python bpy.ops.render.render(write_still=True) ``` The `write_still=True` option saves the rendered image to the `Output Path`. ::: :::{tab-item} Load .blend File For complex setups, it might be easier to make it in Blender GUI and load the .blend file in Python script. ```python bpy.ops.wm.open_mainfile(filepath="PATH_TO_BLEND_FILE") ``` ::: :::{tab-item} Others 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`. ```{figure} ../../assets/scripting/outliner_data.png ``` For example, you can access the `Cube` object ```python bpy.data.objects["Cube"] ``` ```{figure} ../../assets/scripting/data_cube_1.png :align: left ``` assign it to a variable ```python cube_0 = bpy.data.objects["Cube"] ``` and get/set its properties (location in this example) ```{tip} :class: margin Rotation angle is in radian, you can use `math.radians()` to turn degrees into radians. ``` ```python cube_0.location cube_0.rotation_euler cube_0.location.x = 1.0 cube_0.rotation_euler.x = 0.5 ``` ```{figure} ../../assets/scripting/data_cube_2.png :align: left ``` ::::{tab-set} :::{tab-item} Object and Collection You can add an object directly into the data. First, create a data-block for the object ```python cam_db = bpy.data.cameras.new("new_cam") ``` then use the data-block to construct a new object ```python 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. ```python bpy.data.collections['Collection'].objects.link(cam_0) ``` Now it should be visible in the viewport. ```{tip} :class: margin - This camera will not automatically become the active one. ``` ```{figure} ../../assets/scripting/data_obj_add.gif ``` Adding a collection to a scene is similar. Make a new collection in the data ```python ico_coll = bpy.data.collections.new("ico_collection") ``` then link the collection to the scene ```python bpy.data.scenes['Scene'].collection.children.link(ico_coll) ``` You can also loop through the objects in a collection ```python for obj in bpy.data.collections['ico_collection'].objects: obj.location.x += 1 ``` ```{figure} ../../assets/scripting/coll_loop.gif ``` ::: :::{tab-item} Mesh 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: ```python 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} :class: margin - Turning on [`indices`](face_types) 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 ```python 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 ```python cube_obj = bpy.data.objects.new("Cube_1", cube_mesh) bpy.data.collections['Collection'].objects.link(cube_obj) ``` ```{tip} :class: margin The added edge is not part of any faces. ``` ```{figure} ../../assets/scripting/data_mesh.gif :width: 100% ``` ::: :::{tab-item} Modifier and Constraint You can add a modifier to an object in the data directly ```{tip} :class: margin - Check [here](https://docs.blender.org/api/current/bpy_types_enum_items/object_modifier_type_items.html) for the modifier types and [here](https://docs.blender.org/api/current/bpy.types.Modifier.html) for the details on the modifier subclasses. ``` ```python cube_0.modifiers.new(name = "mod1", type = "SUBSURF") ``` And you can tweak it too ```python cube_0.modifiers['mod1'].levels = 3 cube_0.modifiers['mod1'].render_levels = 5 ``` ```{figure} ../../assets/scripting/data_mod.gif :width: 100% ``` Adding a constraint is similar but it does not require a `name`. ```{tip} :class: margin Check [here](https://docs.blender.org/api/current/bpy_types_enum_items/constraint_type_items.html) for the constraint types and [here](https://docs.blender.org/api/current/bpy.types.Constraint.html) for the details on the constraint subclasses. ``` ```python 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 ``` ```{figure} ../../assets/scripting/data_constr.gif :width: 100% ``` ::: :::{tab-item} Keyframe To add keyframes, you need to specify the data path and frame ```{tip} :class: margin Data path: - Location: "location" - Rotation: "rotation_euler" - Scale: "scale" ``` ```python 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 ```python cube_0.keyframe_delete(data_path = "location", frame = 1) cube_0.keyframe_delete(data_path = "location", frame = 250) ``` ```{figure} ../../assets/scripting/data_anim.gif :width: 100% ``` ::: :::{tab-item} Material To add material to an object, first we need to create one in the data ```python mat = bpy.data.materials.new("new_mat") ``` then append it to the object ```{tip} :class: margin This creates a new material slot for the object. ``` ```python cube_0.data.materials.append(mat) ``` Change the base color to see the effect ```{tip} :class: margin The diffuse color uses RGBA format. ``` ```python mat.diffuse_color = (255.0, 0.0, 0.0, 1.0) ``` ```{figure} ../../assets/scripting/data_mat.gif :width: 100% ``` 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} :class: margin More details in the coming [Node Scripting](node_scripting) section. ``` ```python mat.use_nodes = True ``` ::: :::{tab-item} Scene You can change various scene properties with `bpy.data`. For example, you can change the end frame and frame rate ```{tip} :class: margin The `index` is `0` for the default scene, you can also use its name "Scene". ``` ```python bpy.data.scenes[index].frame_end = 120 bpy.data.scenes[index].render.fps = 30 ``` You can also set the active camera ```{tip} :class: margin This set the camera object named "Camera" as active. ``` ```python bpy.data.scenes[index].camera = bpy.data.objects['Camera'] ``` and the `Output Path`, `Render Engine`, etc. ```python bpy.data.scenes[index].render.filepath = "YOUR_PATH" bpy.data.scenes[index].render.engine = "CYCLES" bpy.data.scenes[index].cycles.device = "GPU" ``` ::: :::{tab-item} Driver 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} :class: margin Check on which level you are adding the driver, for example, this does not work ```python cube_0.driver_add("modifiers['Bevel'].width") ``` ```` ```python 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 ```python cube_0.driver_add("location", 0) ``` To make the driver work, first you need to add variables ```python 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 ```python loc_driver.driver.expression = var_0.name + "/100" ``` ```{figure} ../../assets/scripting/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 ```python cube_0 = bpy.data.objects["Cube"] ``` you can do ```python cube_0 = bpy.context.active_object ``` because it points to the same object in the data. ```{figure} ../../assets/scripting/c_cube.png :align: left ``` Similarly, to get the current scene, you can use ```python bpy.context.scene ``` which is the same as ```python bpy.data.scenes[index] ``` and we also have `bpy.context.collection` for the collection, `bpy.context.engine` for the render engine, etc.