| name | blender-3d-modeling |
| description | Create 3D models procedurally with Blender Python. Use when the user wants to generate meshes from code, build geometry with bmesh, apply modifiers, create parametric shapes, procedural landscapes, grids, curves, or any programmatic 3D modeling in Blender. |
| license | Apache-2.0 |
| compatibility | Requires Blender 3.0+ with Python bpy module. Run scripts via: blender --background --python script.py |
| metadata | {"author":"terminal-skills","version":"1.0.0","category":"design","tags":["blender","3d-modeling","procedural","bmesh","geometry"]} |
Blender 3D Modeling
Overview
Create 3D geometry procedurally using Blender's Python API. Build meshes from vertices and faces, use bmesh for advanced editing, apply modifiers, generate curves, and create parametric or procedural models entirely from code.
Instructions
1. Create a mesh from raw vertex data
import bpy
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)
from_pydata(vertices, edges, faces) is the primary way to build meshes. Pass empty lists [] for edges or faces if not needed.
2. Use bmesh for advanced mesh editing
import bpy
import bmesh
bm = bmesh.new()
obj = bpy.context.active_object
bm = bmesh.new()
bm.from_mesh(obj.data)
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.translate(bm, vec=(0, 0, 1), verts=[v for v in bm.verts if v.select])
bmesh.ops.subdivide_edges(bm, edges=bm.edges[:], cuts=2)
bm.to_mesh(obj.data)
bm.free()
obj.data.update()
Always call bm.free() when done to release memory.
3. Apply modifiers programmatically
import bpy
obj = bpy.context.active_object
sub = obj.modifiers.new(name="Subdivision", type='SUBSURF')
sub.levels = 2
sub.render_levels = 3
mirror = obj.modifiers.new(name="Mirror", type='MIRROR')
mirror.use_axis = (True, False, False)
mirror.use_clip = True
array = obj.modifiers.new(name="Array", type='ARRAY')
array.count = 5
array.relative_offset_displace = (1.1, 0, 0)
bool_mod = obj.modifiers.new(name="Boolean", type='BOOLEAN')
bool_mod.operation = 'DIFFERENCE'
bool_mod.object = bpy.data.objects["Cutter"]
solid = obj.modifiers.new(name="Solidify", type='SOLIDIFY')
solid.thickness = 0.1
bevel = obj.modifiers.new(name="Bevel", type='BEVEL')
bevel.width = 0.05
bevel.segments = 3
bpy.context.view_layer.objects.active = obj
bpy.ops.object.modifier_apply(modifier="Subdivision")
4. Create curves and surfaces
import bpy
import math
curve_data = bpy.data.curves.new("MyCurve", type='CURVE')
curve_data.dimensions = '3D'
curve_data.resolution_u = 24
spline = curve_data.splines.new('BEZIER')
spline.bezier_points.add(3)
coords = [(0, 0, 0), (1, 1, 0), (2, 0, 1), (3, 1, 1)]
for i, (x, y, z) in enumerate(coords):
pt = spline.bezier_points[i]
pt.co = (x, y, z)
pt.handle_type_left = 'AUTO'
pt.handle_type_right = 'AUTO'
curve_data.bevel_depth = 0.1
curve_data.bevel_resolution = 4
obj = bpy.data.objects.new("MyCurve", curve_data)
bpy.context.collection.objects.link(obj)
bpy.ops.curve.primitive_nurbs_circle_add(radius=2)
circle = bpy.context.active_object
circle.data.bevel_depth = 0.05
5. Procedural generation patterns
Grid of objects:
import bpy
rows, cols = 10, 10
spacing = 2.5
for i in range(rows):
for j in range(cols):
bpy.ops.mesh.primitive_cube_add(
size=1,
location=(i * spacing, j * spacing, 0)
)
obj = bpy.context.active_object
obj.name = f"Grid_{i}_{j}"
Circular array:
import bpy
import math
count = 12
radius = 5
for i in range(count):
angle = (2 * math.pi * i) / count
x = radius * math.cos(angle)
y = radius * math.sin(angle)
bpy.ops.mesh.primitive_cylinder_add(radius=0.3, depth=2, location=(x, y, 0))
obj = bpy.context.active_object
obj.rotation_euler.z = angle
Terrain from heightmap:
import bpy
import bmesh
import math
import random
size = 20
res = 50
bm = bmesh.new()
verts = []
for i in range(res):
row = []
for j in range(res):
x = (i / res - 0.5) * size
y = (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)
sub = obj.modifiers.new("Smooth", 'SUBSURF')
sub.levels = 1
6. Assign materials to faces
import bpy
import bmesh
obj = bpy.context.active_object
mat_red = bpy.data.materials.new("Red")
mat_red.diffuse_color = (1, 0, 0, 1)
mat_blue = bpy.data.materials.new("Blue")
mat_blue.diffuse_color = (0, 0, 1, 1)
obj.data.materials.append(mat_red)
obj.data.materials.append(mat_blue)
for i, poly in enumerate(obj.data.polygons):
poly.material_index = 0 if i % 2 == 0 else 1
Examples
Example 1: Parametric staircase
User request: "Generate a spiral staircase with 20 steps"
import bpy
import math
def create_spiral_staircase(steps=20, radius=3, height=6, step_width=1.5):
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
step_height = height / steps
angle_per_step = (2 * math.pi) / steps
for i in range(steps):
angle = i * angle_per_step
x = radius * math.cos(angle)
y = radius * math.sin(angle)
z = i * step_height
bpy.ops.mesh.primitive_cube_add(
size=1,
location=(x, y, z),
scale=(step_width, 0.4, step_height * 0.8)
)
step = bpy.context.active_object
step.rotation_euler.z = angle
step.name = f"Step_{i+1:02d}"
bpy.ops.mesh.primitive_cylinder_add(
radius=0.2, depth=height, location=(0, 0, height / 2)
)
bpy.context.active_object.name = "CentralColumn"
create_spiral_staircase(steps=20)
bpy.ops.wm.save_as_mainfile(filepath="/tmp/staircase.blend")
Example 2: Honeycomb panel
User request: "Create a flat honeycomb pattern panel"
import bpy
import bmesh
import math
def create_hexagon(bm, cx, cy, radius):
verts = []
for i in range(6):
angle = math.radians(60 * i + 30)
x = cx + radius * math.cos(angle)
y = cy + radius * math.sin(angle)
verts.append(bm.verts.new((x, y, 0)))
bm.faces.new(verts)
radius = 0.5
rows, cols = 8, 10
bm = bmesh.new()
for row in range(rows):
for col in range(cols):
cx = col * radius * 1.75
cy = row * radius * 1.52
if col % 2 == 1:
cy += radius * 0.76
create_hexagon(bm, cx, cy, radius * 0.9)
mesh = bpy.data.meshes.new("Honeycomb")
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new("Honeycomb", mesh)
bpy.context.collection.objects.link(obj)
solid = obj.modifiers.new("Solidify", 'SOLIDIFY')
solid.thickness = 0.1
Guidelines
- Use
from_pydata() for simple static meshes. Use bmesh when you need to build geometry with operations like extrude, subdivide, or per-face manipulation.
- Always call
mesh.update() after from_pydata() and bm.free() after bmesh operations.
- When applying modifiers via
bpy.ops.object.modifier_apply(), ensure the object is active and selected.
- For large procedural meshes (10k+ faces), prefer bmesh over repeated
bpy.ops calls — it's significantly faster since operators have per-call overhead.
- Link new objects to a collection with
bpy.context.collection.objects.link(obj) — objects not linked to any collection won't appear in the scene.
- Blender uses Z-up coordinate system. Keep this in mind when importing from Y-up systems (most game engines).
- For smooth shading:
bpy.ops.object.shade_smooth() or set per-face polygon.use_smooth = True.
- Test procedural scripts with small counts first, then scale up. A 100x100 grid with subdivision modifiers can freeze Blender.