🎥 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)
Disclosure: Some of the links above are referral (affiliate) links. I may earn a commission if you purchase through them - at no extra cost to you.