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.mdsource 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
when running from the terminal to skip the GUI--background - Use
for simple static meshes; usefrom_pydata()
for advanced operationsbmesh - Always call
aftermesh.update()
andfrom_pydata()
after bmeshbm.free() - Use
for direct data access (fast); usebpy.data
for complex operationsbpy.ops - 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
callsbpy.ops - 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
for vector math and matrix operations (bundled with Blender Python)mathutils - Import/export operator names vary by Blender version; listed ones work for 3.6+