🎥 Generate Page Flip Animation Via Blender Python API
Have you ever wanted to automate animations in Blender using Python? In this beginner-friendly tutorial, I’ll show you how to create a smooth page flip animation using the Blender 4.4 Python API — no prior coding experience required!
This script is perfect for students, artists, animators, and developers curious about Blender scripting. You’ll also find resources below if you’re just getting started with Python programming.
📚 From My Book: Mastering Blender Python API
This tutorial is inspired by concepts from my book, Mastering Blender Python API, where I walk you through real-world projects like:
- Automating 3D animation tasks
- Creating procedural geometry
- Developing custom Blender tools
If you’re serious about becoming a power user in Blender, this book is a must-have on your shelf!
🔧 What You’ll Learn
- How to use the Blender Python API to animate a plane object
- Create bending deformation using modifiers
- Animate rotation and modifiers across frames
- Use Eevee with correct lighting for clean rendering
📦 Requirements
- Blender 4.4 or later
- Basic familiarity with Blender UI
- No coding experience necessary!
🎬 Watch the Tutorial Video
📷 Screenshots


👨🏫 Need Help Learning Python?
If Blender scripting feels overwhelming, I also offer a complete beginner course called Learning Python — designed for artists and non-programmers.
🎓 Check out the course and the book »
🤝 Book a One-on-One Tutorial
Need personalized help? I’m available for 1-on-1 online tutoring sessions focused on:
- Blender scripting
- Python fundamentals
- Automating creative workflows
📅 Click here to schedule a session »
📥 Get the Script
You can find the full working script with camera, lighting, Eevee-ready rendering, and procedural animation in the Python script:
import bpy import math import mathutils from mathutils import Quaternion # Frame Rate # fps = 30 frames = 100 # Clear Existing Objects # bpy.ops.object.select_all(action='SELECT') bpy.ops.object.delete(use_global=False) # Create A Plane As The Page # bpy.ops.mesh.primitive_plane_add(size=1) page = bpy.context.object page.name = "Page" page.scale.x = 0.5 bpy.ops.object.transform_apply(scale=True) # Subdivide For Smooth Deformation # bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.subdivide(number_cuts=32) bpy.ops.object.mode_set(mode='OBJECT') # Create Empties For Bending And Rotation Pivots # bpy.ops.object.empty_add(type='PLAIN_AXES', location=page.location) bend_empty = bpy.context.object bend_empty.rotation_euler.x = math.radians(90) bend_empty.scale = (0.25, 0.25, 0.25) bend_empty.name = "BendPivot" bpy.ops.object.empty_add(type='PLAIN_AXES', location=page.location) rot_empty = bpy.context.object rot_empty.name = "RotPivot" # Parent The Page To The Rotation Empty # page.parent = rot_empty # Add Child Of Constraint On Page Targeting bend_empty # child_of = page.constraints.new('CHILD_OF') child_of.target = bend_empty # Add Simple Deform (Bend) Modifier To Page # mod = page.modifiers.new(name="SimpleBend", type='SIMPLE_DEFORM') mod.deform_method = 'BEND' mod.origin = bend_empty mod.deform_axis = 'Z' # Animation Settings # scene = bpy.context.scene scene.frame_start = 1 scene.frame_end = frames # Quaternion Rotation Mode For Smooth Rotation # rot_empty.rotation_mode = 'QUATERNION' # Insert Rotation Keyframes (Quaternion) # rot_empty.rotation_quaternion = Quaternion((1, 0, 0, 0)) rot_empty.keyframe_insert(data_path="rotation_quaternion", frame=1) rot_empty.rotation_quaternion = Quaternion((0, 1, 0), math.radians(90)) rot_empty.keyframe_insert(data_path="rotation_quaternion", frame=50) rot_empty.rotation_quaternion = Quaternion((0, 1, 0), math.radians(180)) rot_empty.keyframe_insert(data_path="rotation_quaternion", frame=100) # Bending Keyframes # mod.angle = 0 mod.keyframe_insert(data_path="angle", frame=1) mod.angle = math.radians(-75) mod.keyframe_insert(data_path="angle", frame=45) mod.angle = math.radians(-135) mod.keyframe_insert(data_path="angle", frame=50) mod.angle = math.radians(-75) mod.keyframe_insert(data_path="angle", frame=55) mod.angle = 0 mod.keyframe_insert(data_path="angle", frame=100) # Smooth Shading # bpy.ops.object.mode_set(mode='OBJECT') bpy.context.view_layer.objects.active = page page.select_set(True) bpy.ops.object.shade_smooth() # Set Interpolation # if rot_empty.animation_data and rot_empty.animation_data.action: for fcurve in rot_empty.animation_data.action.fcurves: for kp in fcurve.keyframe_points: kp.interpolation = 'LINEAR' if page.animation_data and page.animation_data.action: for fcurve in page.animation_data.action.fcurves: if fcurve.data_path.startswith('modifiers["SimpleBend"].angle'): for kp in fcurve.keyframe_points: kp.interpolation = 'BEZIER' # Add A Camera # bpy.ops.object.camera_add(location=(0, -3, 1), rotation=(math.radians(75), 0, 0)) camera = bpy.context.object camera.name = "Camera" # Set Camera As Active # scene.camera = camera # Helper to aim a light def aim_light(light_obj, target_loc): direction = target_loc - light_obj.location rot_quat = direction.to_track_quat('-Z', 'Y') light_obj.rotation_euler = rot_quat.to_euler() # Bright key light bpy.ops.object.light_add(type='AREA', location=(4, -3, 5)) key_light = bpy.context.object key_light.name = "KeyLight" key_light.data.energy = 5000 key_light.data.size = 4 aim_light(key_light, mathutils.Vector((0, 0, 0))) # Fill light bpy.ops.object.light_add(type='AREA', location=(-4, -3, 3)) fill_light = bpy.context.object fill_light.name = "FillLight" fill_light.data.energy = 2000 fill_light.data.size = 5 aim_light(fill_light, mathutils.Vector((0, 0, 0))) # Backlight for depth bpy.ops.object.light_add(type='AREA', location=(0, 3, 2)) rim_light = bpy.context.object rim_light.name = "RimLight" rim_light.data.energy = 1000 rim_light.data.size = 5 aim_light(rim_light, mathutils.Vector((0, 0, 0))) # Set world ambient lighting (environment) world = bpy.data.worlds["World"] world.use_nodes = True bg = world.node_tree.nodes["Background"] bg.inputs["Color"].default_value = (1, 1, 1, 1) bg.inputs["Strength"].default_value = 1.5 # Increase for brighter ambient # Set render engine to EEVEE scene = bpy.context.scene # World ambient lighting world = bpy.data.worlds["World"] world.use_nodes = True bg = world.node_tree.nodes["Background"] bg.inputs["Strength"].default_value = 2.0 # brighten scene # Create better paper material mat = bpy.data.materials.new(name="PaperWhite") mat.use_nodes = True nodes = mat.node_tree.nodes links = mat.node_tree.links # Clear default nodes for node in nodes: nodes.remove(node) # Nodes output = nodes.new('ShaderNodeOutputMaterial') diffuse = nodes.new('ShaderNodeBsdfDiffuse') glossy = nodes.new('ShaderNodeBsdfGlossy') mix = nodes.new('ShaderNodeMixShader') output.location = (600, 0) diffuse.location = (0, 100) glossy.location = (0, -100) mix.location = (300, 0) # Inputs diffuse.inputs['Color'].default_value = (1, 1, 1, 1) glossy.inputs['Color'].default_value = (1, 1, 1, 1) glossy.inputs['Roughness'].default_value = 0.05 mix.inputs['Fac'].default_value = 0.2 # Mostly diffuse, some gloss # Connect links.new(diffuse.outputs['BSDF'], mix.inputs[1]) links.new(glossy.outputs['BSDF'], mix.inputs[2]) links.new(mix.outputs['Shader'], output.inputs['Surface']) # Assign to page if page.data.materials: page.data.materials[0] = mat else: page.data.materials.append(mat)