Blender 3D Automation
Automate Blender 3D modeling, animation, rendering, and scene management using Python's bpy API and command-line rendering. Supports headless operation, batch processing, and procedural generation.
Direct Control (CLI / API / Scripting)
Command-Line Rendering
Render single frame
blender scene.blend --background --render-frame 1
Render animation range
blender scene.blend --background --render-anim --frame-start 1 --frame-end 120
Render with custom output path
blender scene.blend --background --render-output /tmp/render_#### --render-anim
Render specific scene
blender file.blend --background --scene "Scene.001" --render-frame 1
Set render engine
blender scene.blend --background --engine CYCLES --render-frame 1 blender scene.blend --background --engine BLENDER_EEVEE --render-frame 1
Use GPU for rendering
blender scene.blend --background --python-expr "import bpy; bpy.context.preferences.addons['cycles'].preferences.compute_device_type = 'CUDA'; bpy.context.scene.cycles.device = 'GPU'" --render-frame 1
Python bpy Scripting
import bpy import math
Create new cube
bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0)) cube = bpy.context.active_object cube.name = "MyCube"
Set material
mat = bpy.data.materials.new(name="RedMaterial") mat.use_nodes = True mat.node_tree.nodes["Principled BSDF"].inputs[0].default_value = (1, 0, 0, 1) cube.data.materials.append(mat)
Add keyframe animation
cube.location = (0, 0, 0) cube.keyframe_insert(data_path="location", frame=1) cube.location = (5, 0, 0) cube.keyframe_insert(data_path="location", frame=120)
Set render settings
scene = bpy.context.scene scene.render.engine = 'CYCLES' scene.cycles.device = 'GPU' scene.render.resolution_x = 1920 scene.render.resolution_y = 1080 scene.render.fps = 24 scene.frame_start = 1 scene.frame_end = 120
Render current frame
bpy.ops.render.render(write_still=True)
Save blend file
bpy.ops.wm.save_as_mainfile(filepath="/path/to/output.blend")
Execute Python Script in Blender
Run Python script
blender --background --python script.py
Run script with arguments
blender --background --python script.py -- arg1 arg2
Run inline Python
blender --background --python-expr "import bpy; bpy.ops.mesh.primitive_cube_add()"
Run script and save
blender scene.blend --background --python modify_scene.py --save
Scene Manipulation
import bpy
List all objects
for obj in bpy.data.objects: print(f"{obj.name}: {obj.type}")
Delete object by name
obj = bpy.data.objects.get("Cube") if obj: bpy.data.objects.remove(obj, do_unlink=True)
Import FBX/OBJ
bpy.ops.import_scene.fbx(filepath="/path/to/model.fbx") bpy.ops.import_scene.obj(filepath="/path/to/model.obj")
Export FBX/OBJ
bpy.ops.export_scene.fbx(filepath="/path/to/output.fbx", use_selection=False) bpy.ops.export_scene.obj(filepath="/path/to/output.obj")
Camera setup
camera_data = bpy.data.cameras.new(name="Camera") camera_object = bpy.data.objects.new("Camera", camera_data) bpy.context.scene.collection.objects.link(camera_object) camera_object.location = (7.5, -6.5, 5.4) camera_object.rotation_euler = (math.radians(63), 0, math.radians(46)) bpy.context.scene.camera = camera_object
Lighting
light_data = bpy.data.lights.new(name="Light", type='SUN') light_object = bpy.data.objects.new(name="Sun", object_data=light_data) bpy.context.collection.objects.link(light_object) light_object.location = (5, 5, 10) light_data.energy = 2.0
Geometry Nodes (Procedural)
import bpy
Add geometry nodes modifier
obj = bpy.context.active_object modifier = obj.modifiers.new(name="GeometryNodes", type='NODES')
Create node group
node_group = bpy.data.node_groups.new('MyNodeTree', 'GeometryNodeTree') modifier.node_group = node_group
Add input/output nodes
input_node = node_group.nodes.new('NodeGroupInput') output_node = node_group.nodes.new('NodeGroupOutput')
Add geometry nodes (e.g., subdivide)
subdivide_node = node_group.nodes.new('GeometryNodeSubdivisionSurface') node_group.links.new(input_node.outputs[0], subdivide_node.inputs[0]) node_group.links.new(subdivide_node.outputs[0], output_node.inputs[0])
MCP Server Integration
Add to .codebuddy/mcp.json :
{ "mcpServers": { "blender": { "command": "npx", "args": ["-y", "@ahujasid/blender-mcp"], "env": { "BLENDER_PATH": "/usr/bin/blender" } } } }
Available MCP Tools
-
blender_execute_script
-
Execute Python bpy script in Blender
-
blender_render_frame
-
Render single frame or animation
-
blender_create_object
-
Create primitive objects (cube, sphere, plane, etc.)
-
blender_modify_object
-
Transform, scale, rotate objects
-
blender_set_material
-
Apply materials and shaders
-
blender_add_keyframe
-
Create animation keyframes
-
blender_import_model
-
Import FBX, OBJ, STL, GLTF
-
blender_export_model
-
Export to various formats
-
blender_list_objects
-
Get scene object hierarchy
-
blender_get_scene_info
-
Get render settings and scene data
Common Workflows
- Batch Render Multiple Scenes
Render all scenes in a blend file
for scene in $(blender file.blend --background --python-expr "import bpy; print(','.join([s.name for s in bpy.data.scenes]))" | tail -1 | tr ',' '\n'); do blender file.blend --background --scene "$scene" --render-output "/renders/${scene}_####" --render-anim done
Python version with progress
import bpy import os
output_dir = "/renders" os.makedirs(output_dir, exist_ok=True)
for scene in bpy.data.scenes: print(f"Rendering scene: {scene.name}") bpy.context.window.scene = scene scene.render.filepath = os.path.join(output_dir, f"{scene.name}_") bpy.ops.render.render(animation=True)
- Procedural Asset Generation
import bpy import random
Clear scene
bpy.ops.object.select_all(action='SELECT') bpy.ops.object.delete()
Generate random cubes
for i in range(10): x = random.uniform(-5, 5) y = random.uniform(-5, 5) z = random.uniform(0, 3) scale = random.uniform(0.5, 2.0)
bpy.ops.mesh.primitive_cube_add(location=(x, y, z), scale=(scale, scale, scale))
obj = bpy.context.active_object
obj.name = f"Cube_{i:03d}"
# Random color material
mat = bpy.data.materials.new(name=f"Mat_{i}")
mat.use_nodes = True
mat.node_tree.nodes["Principled BSDF"].inputs[0].default_value = (
random.random(), random.random(), random.random(), 1
)
obj.data.materials.append(mat)
Save
bpy.ops.wm.save_as_mainfile(filepath="/tmp/procedural_scene.blend")
- Camera Animation Sequence
import bpy import math
scene = bpy.context.scene camera = scene.camera
Circular camera path
center = (0, 0, 0) radius = 10 height = 5 num_frames = 240
for frame in range(1, num_frames + 1): angle = (frame / num_frames) * 2 * math.pi x = center[0] + radius * math.cos(angle) y = center[1] + radius * math.sin(angle) z = center[2] + height
camera.location = (x, y, z)
camera.keyframe_insert(data_path="location", frame=frame)
# Look at center
direction = (center[0] - x, center[1] - y, center[2] - z)
rot_quat = direction.to_track_quat('-Z', 'Y')
camera.rotation_euler = rot_quat.to_euler()
camera.keyframe_insert(data_path="rotation_euler", frame=frame)
scene.frame_end = num_frames
- Batch Model Import and Render
import bpy import os import glob
model_dir = "/path/to/models" output_dir = "/path/to/renders" os.makedirs(output_dir, exist_ok=True)
Setup scene once
scene = bpy.context.scene scene.render.resolution_x = 1920 scene.render.resolution_y = 1080 scene.render.engine = 'CYCLES'
Import and render each model
for model_path in glob.glob(os.path.join(model_dir, "*.fbx")): # Clear scene bpy.ops.object.select_all(action='SELECT') bpy.ops.object.delete()
# Import model
bpy.ops.import_scene.fbx(filepath=model_path)
# Center camera on object
obj = bpy.context.selected_objects[0]
bpy.ops.view3d.camera_to_view_selected()
# Render
model_name = os.path.splitext(os.path.basename(model_path))[0]
scene.render.filepath = os.path.join(output_dir, f"{model_name}.png")
bpy.ops.render.render(write_still=True)
print(f"Rendered: {model_name}")
5. Material Library Application
import bpy
Load material library
with bpy.data.libraries.load("/path/to/materials.blend") as (data_from, data_to): data_to.materials = data_from.materials
Apply material to selected objects
material_name = "GoldMetal" mat = bpy.data.materials.get(material_name)
if mat: for obj in bpy.context.selected_objects: if obj.type == 'MESH': if len(obj.data.materials) > 0: obj.data.materials[0] = mat else: obj.data.materials.append(mat) print(f"Applied {material_name} to {obj.name}")