Skills blender-scripting

install
source · Clone the upstream repo
git clone https://github.com/TerminalSkills/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/TerminalSkills/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/blender-scripting" ~/.claude/skills/terminalskills-skills-blender-scripting && rm -rf "$T"
manifest: skills/blender-scripting/SKILL.md
source content

Blender Scripting

Overview

Automate Blender tasks and create 3D models procedurally using Python and the

bpy
API. Run scripts headlessly from the terminal to manipulate scenes, build geometry with bmesh, apply modifiers, batch process files, and import/export models.

Instructions

1. Run scripts from the terminal

blender --background --python script.py
blender myfile.blend --background --python script.py
blender --background --python script.py -- --output /tmp/result.png --scale 2.0

2. Scene setup and cleanup

import bpy

def clear_scene():
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete(use_global=False)
    for block in bpy.data.meshes:
        if block.users == 0:
            bpy.data.meshes.remove(block)

3. Create and transform objects

import bpy, math
from mathutils import Vector

bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0))
cube = bpy.context.active_object
cube.location = (3, 0, 1)
cube.rotation_euler = (0, 0, math.radians(45))
cube.scale = (1, 2, 0.5)

4. Build meshes from raw data

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

mesh = bpy.data.meshes.new("CustomBox")
mesh.from_pydata(vertices, [], faces)
mesh.update()
obj = bpy.data.objects.new("CustomBox", mesh)
bpy.context.collection.objects.link(obj)

5. Use bmesh for advanced mesh editing

import bmesh

bm = bmesh.new()
v1 = bm.verts.new((0, 0, 0))
v2 = bm.verts.new((1, 0, 0))
v3 = bm.verts.new((1, 1, 0))
v4 = bm.verts.new((0, 1, 0))
bm.faces.new((v1, v2, v3, v4))

bmesh.ops.extrude_face_region(bm, geom=bm.faces[:])
bmesh.ops.subdivide_edges(bm, edges=bm.edges[:], cuts=2)

mesh = bpy.data.meshes.new("Result")
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new("Result", mesh)
bpy.context.collection.objects.link(obj)

6. Apply modifiers

obj = bpy.context.active_object

sub = obj.modifiers.new("Subdivision", 'SUBSURF')
sub.levels = 2

mirror = obj.modifiers.new("Mirror", 'MIRROR')
mirror.use_axis = (True, False, False)

array = obj.modifiers.new("Array", 'ARRAY')
array.count = 5
array.relative_offset_displace = (1.1, 0, 0)

solid = obj.modifiers.new("Solidify", 'SOLIDIFY')
solid.thickness = 0.1

bevel = obj.modifiers.new("Bevel", 'BEVEL')
bevel.width = 0.05
bevel.segments = 3

# Apply permanently
bpy.context.view_layer.objects.active = obj
bpy.ops.object.modifier_apply(modifier="Subdivision")

7. Create curves

curve_data = bpy.data.curves.new("MyCurve", type='CURVE')
curve_data.dimensions = '3D'
spline = curve_data.splines.new('BEZIER')
spline.bezier_points.add(3)
for i, (x, y, z) in enumerate([(0,0,0), (1,1,0), (2,0,1), (3,1,1)]):
    pt = spline.bezier_points[i]
    pt.co = (x, y, z)
    pt.handle_type_left = pt.handle_type_right = 'AUTO'
curve_data.bevel_depth = 0.1
obj = bpy.data.objects.new("MyCurve", curve_data)
bpy.context.collection.objects.link(obj)

8. Import and export

bpy.ops.wm.obj_import(filepath="/path/model.obj")
bpy.ops.import_scene.fbx(filepath="/path/model.fbx")
bpy.ops.import_scene.gltf(filepath="/path/model.glb")
bpy.ops.wm.obj_export(filepath="/path/output.obj")
bpy.ops.export_scene.gltf(filepath="/path/output.glb", export_format='GLB')

9. Assign materials

mat_red = bpy.data.materials.new("Red")
mat_red.diffuse_color = (1, 0, 0, 1)
obj.data.materials.append(mat_red)
for i, poly in enumerate(obj.data.polygons):
    poly.material_index = 0 if i % 2 == 0 else 1

10. Batch process files

import glob
for filepath in glob.glob("/path/to/*.blend"):
    bpy.ops.wm.open_mainfile(filepath=filepath)
    for obj in bpy.data.objects:
        if obj.type == 'MESH':
            print(f"  {obj.name}: {len(obj.data.vertices)} verts")
    bpy.ops.wm.save_as_mainfile(filepath=filepath.replace(".blend", "_processed.blend"))

Examples

Example 1: Procedural spiral staircase

User request: "Generate a spiral staircase with 20 steps"

import bpy, math

def create_spiral_staircase(steps=20, radius=3, height=6):
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete()
    step_height = height / steps
    for i in range(steps):
        angle = (2 * math.pi * i) / steps
        x, y = radius * math.cos(angle), radius * math.sin(angle)
        bpy.ops.mesh.primitive_cube_add(size=1, location=(x, y, i * step_height),
                                         scale=(1.5, 0.4, step_height * 0.8))
        bpy.context.active_object.rotation_euler.z = angle
    bpy.ops.mesh.primitive_cylinder_add(radius=0.2, depth=height, location=(0, 0, height/2))

create_spiral_staircase()
bpy.ops.wm.save_as_mainfile(filepath="/tmp/staircase.blend")

Example 2: Export all mesh objects as separate OBJ files

User request: "Export every mesh in my .blend as a separate OBJ"

import bpy, os
output_dir = "/tmp/exports"
os.makedirs(output_dir, exist_ok=True)
for obj in bpy.data.objects:
    if obj.type == 'MESH':
        bpy.ops.object.select_all(action='DESELECT')
        obj.select_set(True)
        bpy.context.view_layer.objects.active = obj
        bpy.ops.wm.obj_export(filepath=os.path.join(output_dir, f"{obj.name}.obj"),
                               export_selected_objects=True)

Run:

blender scene.blend --background --python export_all.py

Example 3: Procedural terrain from heightmap

User request: "Generate a terrain mesh using sine waves"

import bpy, bmesh, math, random
bm = bmesh.new()
res, size = 50, 20
verts = []
for i in range(res):
    row = []
    for j in range(res):
        x, y = (i/res - 0.5) * size, (j/res - 0.5) * size
        z = math.sin(x*0.5) * math.cos(y*0.5) * 2 + random.uniform(-0.2, 0.2)
        row.append(bm.verts.new((x, y, z)))
    verts.append(row)
for i in range(res-1):
    for j in range(res-1):
        bm.faces.new((verts[i][j], verts[i+1][j], verts[i+1][j+1], verts[i][j+1]))
mesh = bpy.data.meshes.new("Terrain")
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new("Terrain", mesh)
bpy.context.collection.objects.link(obj)

Guidelines

  • Always use
    --background
    when running from the terminal to skip the GUI
  • Use
    from_pydata()
    for simple static meshes; use
    bmesh
    for advanced operations
  • Always call
    mesh.update()
    after
    from_pydata()
    and
    bm.free()
    after bmesh
  • Use
    bpy.data
    for direct data access (fast); use
    bpy.ops
    for complex operations
  • Ensure correct object is active and selected before using operators
  • For batch processing, save to new files (not overwrite originals) unless requested
  • For large meshes (10k+ faces), prefer bmesh over repeated
    bpy.ops
    calls
  • Link objects to a collection — unlinked objects won't appear in the scene
  • Blender uses Z-up coordinates; account for this when importing from Y-up systems
  • Use
    mathutils
    for vector math and matrix operations (bundled with Blender Python)
  • Import/export operator names vary by Blender version; listed ones work for 3.6+