Skip to content
Back to skills

Blender Scripting

ASecurity

Write and run Blender Python scripts for 3D automation and procedural modeling. Use when the user wants to automate Blender tasks, create 3D models from code, run headless scripts, manipulate scenes, batch process .blend files, build geometry with bmesh, apply modifiers, generate procedural shapes, or import/export 3D models using the bpy API.

  • 142 stars
  • 0 votes
  • 1 copy
  • 13 views
  • Added May 27, 2026
toolspythongoshellbashnodeapi

Works with

  • terminal
  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill blender-scripting --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Blender Scripting?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Blender Scripting
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-blender-scripting/badge)](https://www.skillsdirectory.com/skills/terminalskills-blender-scripting)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: blender-scripting
description: >-
  Write and run Blender Python scripts for 3D automation and procedural
  modeling. Use when the user wants to automate Blender tasks, create 3D models
  from code, run headless scripts, manipulate scenes, batch process .blend
  files, build geometry with bmesh, apply modifiers, generate procedural
  shapes, or import/export 3D models using the bpy API.
license: Apache-2.0
compatibility: >-
  Requires Blender 3.6+ on the command line (checked on 5.0.1; current stable
  is 5.2). Linux: snap install blender --classic or the Flatpak (distribution
  apt packages are often old). macOS: brew install --cask blender. Or
  pip install bpy (Python 3.13 for Blender 5.x) to use bpy without the app.
metadata:
  author: terminal-skills
  repository: https://projects.blender.org/blender/blender
  version: "1.2.0"
  category: automation
  tags: ["blender", "3d", "python", "automation", "procedural"]
---

# 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

```bash
blender --background --python script.py
blender myfile.blend --background --python script.py
blender --background --factory-startup --python-exit-code 1 --python script.py -- --output /tmp/result.png --scale 2.0
```

Your own arguments go after `--`; read them in the script with `sys.argv[sys.argv.index("--") + 1:]`. Without `--python-exit-code 1`, Blender exits with status 0 even when the script raised an exception, so CI and shell `&&` chains never notice the failure. `--factory-startup` ignores the user's saved preferences and add-ons for reproducible runs.

### 2. Scene setup and cleanup

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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_left_type = pt.handle_right_type = '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

```python
bpy.ops.wm.obj_import(filepath="assets/chair.obj")
bpy.ops.import_scene.fbx(filepath="assets/chair.fbx")
bpy.ops.import_scene.gltf(filepath="assets/chair.glb")
bpy.ops.wm.obj_export(filepath="build/chair_out.obj")
bpy.ops.export_scene.gltf(filepath="build/chair_out.glb", export_format='GLB')
bpy.ops.wm.stl_export(filepath="build/chair_out.stl")
```

The OBJ operators are `wm.obj_import` / `wm.obj_export`; the old `import_scene.obj` / `export_scene.obj` were removed in 4.0. FBX and glTF stay under `import_scene` / `export_scene`.

### 9. Assign materials

```python
mat_red = bpy.data.materials.new("Red")
bsdf = next(n for n in mat_red.node_tree.nodes if n.type == "BSDF_PRINCIPLED")
bsdf.inputs["Base Color"].default_value = (0.8, 0.05, 0.05, 1)
bsdf.inputs["Roughness"].default_value = 0.4
mat_red.diffuse_color = (0.8, 0.05, 0.05, 1)   # viewport colour only

mat_grey = bpy.data.materials.new("Grey")
obj.data.materials.append(mat_red)    # slot 0
obj.data.materials.append(mat_grey)   # slot 1
for i, poly in enumerate(obj.data.polygons):
    poly.material_index = 0 if i % 2 == 0 else 1
```

In Blender 5.0+ a new material already has a node tree with a Principled BSDF, and the `use_nodes` flag is deprecated (it warns and is due for removal in 6.0). On 4.x and older, set `mat_red.use_nodes = True` first. Find the node by `type`, not by its name, which is translated in non-English interfaces. `diffuse_color` affects only the viewport, not renders. A `material_index` needs a matching slot, so append both materials first.

### 10. Batch process files

```python
import glob
for filepath in glob.glob("scenes/*.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"

```python
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"

```python
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"

```python
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; the ones listed here were run on 5.0.1 and match the 3.6+ names

Files in this skill

  • SKILL.md7.7 KB
  • _scores.json1.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…