You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This repository ships 12 skills, 6 rules, 2 templates, 17 snippets, and 22 runnable examples for Blender Python development targeting Blender 5.1 (current stable) with Blender 4.5 LTS fallback support.
The content is consumed by AI coding agents (Cursor, Claude Code, any MCP-capable client) when working on Blender add-ons, geometry nodes scripts, batch pipelines, or animation tooling. There is no build step. Edit the markdown and Python files directly.
Guardrails for the most common AI mistakes: ops-in-loops, bmesh leaks, legacy bl_info only, prop assignments, deprecated context-copy override, per-element loops over bulk mesh data
Templates
A working Extensions Platform add-on starter and a headless batch script starter
Snippets
17 small standalone Python files demonstrating canonical patterns
Cursor — point Cursor at the checkout (or symlink rules/ into your project). The .mdc rules apply automatically by glob scope; skills are referenced by name in chat.
Claude Code — copy skills/ and rules/ into your project workspace, or keep this repo as a checkout that Claude Code references directly.
Run an example — every example is a self-checking headless script (exit non-zero on failure, no GPU needed for the check):
Runnable, smoke-gated demos live in examples/ — each is executed headless on
both Blender 4.5 LTS and 5.1 by the blender-smoke workflow, so the screenshots reflect code
that actually runs. Browse them all with filters and full-size renders in the
examples gallery,
or expand a category below.
A procedural-materials swatch grid — Principled metal and dielectric, the emission pattern,
and the cross-version set_specular shim. Doubles as a live proof of the EEVEE engine-id
mapping (BLENDER_EEVEE on 5.x, BLENDER_EEVEE_NEXT on 4.2-4.5).
One reusable TintedGloss group declared via tree.interface.new_socket, instanced in two
materials with different Tint values. Witnesses the grouping contract: shared datablock
(users == 2), parameters on the group node — two spheres, one group, two colors.
Bloom through the compositor on both sides of the 5.0 rewrite — a Glare (Fog Glow)
node fed by Render Layers, wired via scene.compositing_node_group on 5.x and
scene.node_tree on 4.x. Witnesses with pixels that the halo falls off strictly with
the compositor on and is exactly zero with it off — and that EEVEE has no use_bloom.
The modern color-attributes API — mesh.color_attributes.new() on the CORNER
domain, not the deprecated vertex_colors alias, filled by expanding per-vertex
HSV across face corners with foreach_get/foreach_set. Asserts the attribute
is sized to loop count (not vertex count), is active_color, and that the
shader Attribute node is actually linked to Base Color.
A procedural broadcast test card written into bpy.data.images.new() with one
pixels.foreach_set() call. Asserts the buffer is always flat RGBA (channels == 4
even with alpha=False), that byte storage quantizes at exactly ≤ 0.5/255 and
strictly > 0 while float_buffer=True round-trips at float32 precision, that
scale() reallocates (stale-size reads raise), and the save() trap: source
silently flips to FILE, the buffer drops, and later pixels reads come from
whatever sits on disk — proven with an imposter file. save_render() is the
non-destructive path.
float_buffer=True images saved to PNG are written as RGBA16 and unpremultiplied
as if associated-alpha — straight-authored dark values at low alpha clamp to white
(closed-form error 0.98 at (0.02, a=1/255)). OpenEXR preserves float RGBA;
byte images stay straight 8-bit. Also witnesses EXR color_mode='RGB' dropping alpha.
A 14-tooth gear built entirely with bmesh — with bm.free() in a try/finally, as the
ownership contract demands. Asserts the closed-form vert/edge/face counts and that the
result is watertight (every edge borders exactly two faces).
bmesh.ops.create_grid(..., calc_uvs=True) is a silent no-op unless a UV layer already
exists — without one an Image Texture samples texel (0, 0) everywhere. Asserts the hazard,
the pre-create repair against closed-form grid UVs, and an explicit assignment fallback.
Bulk vertex IO at real scale — 9,409 vertices displaced into a standing wave with one
foreach_get and one foreach_set, no per-vertex access. Asserts the count is unchanged,
the Z span matches the amplitude, and a probe vertex matches the closed-form wave.
A relative Tall shape key that lifts and flares the top face — authored through
shape_key_add / key_blocks / .value. Witnesses that shape keys do not rewrite
mesh.vertices: every evaluated vert matches basis + value × (key − basis).
A beveled Bezier semicircle authored on bpy.types.Curve — splines.new('BEZIER'),
bezier_points, bevel_depth, use_fill_caps — so the curve renders as a solid tube
without a prior mesh conversion. Asserts eight points, bevel_depth == 0.15, and
evaluated topology 1044 verts / 1028 faces.
A beveled 3D stamp of the running Blender version — a TextCurve whose body is the
live bpy.app.version_string, so every render self-documents which Blender made it.
Asserts the TextCurve solids closed form (evaluated z-extent = 2 × (extrude +
bevel_depth), bevel widening the outline by 2 × bevel_depth), that flat text is filled
but planar, that body edits regenerate geometry, that version_string is not bare
semver on LTS builds ("4.5.11 LTS"), and that a Mesh reference dies at
to_mesh_clear().
A depsgraph-evaluated export — builds a cube with SUBSURF, measures the evaluated mesh via
evaluated_get().to_mesh() / to_mesh_clear(), and asserts wm.obj_export ships the
modifier-applied geometry (exported vertex count == evaluated > base).
A Geometry Nodes SDF remesh (MeshToSDFGrid → GridToMesh at the SDF zero-level).
Witnesses the fix that an SDF grid is meshed with Grid to Mesh, not Volume to Mesh,
and that a Set Material node carries the material through the remesh.
A generative Geometry Nodes tree — Mesh Grid → Instance on Points → Realize Instances —
attached as a NODES modifier with no Group Input. Asserts evaluated topology is
verts = 72, faces = 54, and Set Material carries the lime accent.
A slotted-actions Z-rotation turntable keyed through the cross-version channelbag path
(get_channelbag_for_slot). Witnesses the slotted-actions fix: ensure-helper channelbag on
5.x, strip.channelbag on 4.4/4.5.
A driver_namespace function driving sixteen column heights through SCRIPTED drivers.
Witnesses the evaluation contract: driven values appear after a view-layer update on the
evaluated copy and the flushed-back original, and both must match the closed form.
Aim constraints via the data API — Object.constraints.new('DAMPED_TRACK') with
target and TRACK_Z, not bpy.ops.object.constraint_add in a headless loop.
Asserts twelve unmuted Damped Track constraints and evaluated local +Z alignment
toward the core (dot ≥ 0.998).
A four-bone chain built with edit_bones skins a tapered tube through name-bound
vertex groups, posed into a curl and read back through the depsgraph. Asserts that
edit_bones is empty outside edit mode, and that the armature modifier is exactly
linear blend skinning — every evaluated vertex equals
Σ wᵢ · (pose_bone.matrix @ bone.matrix_local.inverted()) @ rest, with the root
ring pinned and the tip deflected. A straight tube is a failure.
A brass orrery parented entirely through the data API — the keep-world idiom
child.parent = pivot; child.matrix_parent_inverse = pivot.matrix_world.inverted()
carries arms, planets, and a two-level moon through spinning pivots. Asserts bare
.parent = really teleports, matrix_world is stale until view_layer.update(),
and every orbit lands on its closed form.
Three unit cubes joined into a staircase under bpy.context.temp_override — the supported
replacement for the removed context.copy() dict-pass form. Asserts one mesh remains,
sources are gone, and local Z spans all three steps.
Five nested rose curves drawn with the Grease Pencil v3 attribute API — layer →
frames.new(1).drawing → add_strokes → per-point position, radius, opacity, and
vertex color. Asserts the GPv3 address break: on 4.5 GPv3 is grease_pencils_v3
while grease_pencils is still legacy; on 5.x legacy is gone and GPv3 owns the
name. Point writes lazily materialize attribute layers, and every position
round-trips through the raw POINT buffer.
How content is organized
skills/<name>/SKILL.md - 12 skill files, YAML frontmatter, one canonical pattern each
rules/<name>.mdc - 6 rule files, anti-pattern + correction
templates/<name>/ - 2 template directories (extension-addon-template, headless-batch-script-template)
snippets/<name>.py - 17 standalone Python snippets, 5 to 50 lines each
Using rules in Cursor
The .mdc files in rules/ apply automatically when Cursor opens a Blender Python project, scoped by the globs in each rule's frontmatter. The six rules are:
type-annotate-props-and-defend-context: flags bpy.props assignment form and unguarded context.active_object
prefer-temp-override-over-context-copy: flags bpy.context.copy() passed to operators (deprecated 4.x, removed 5.x)
use-foreach-set-for-bulk-data: flags Python loops over mesh.vertices setting co, normals, or other per-element bulk data
Symlink or clone this repo, then point Cursor at it as a skills/rules source.
Using the templates
templates/extension-addon-template/ is a working Blender extension. Copy the directory, edit blender_manifest.toml (id, version, name, maintainer), and install via Edit > Preferences > Get Extensions > Install From Disk. The template registers an Operator, a Panel, and a PropertyGroup, and demonstrates the register_classes_factory pattern with symmetric register() and unregister().
templates/headless-batch-script-template/ is a working starter for unattended Blender batch jobs. It opens a .blend, optionally adds and applies a modifier to every mesh, and exports to glTF, with explicit exit codes for CI integration. Run with blender --background <input.blend> --python script.py -- --output ....
Snippets
Each snippet is a standalone Python file under snippets/. They are not loaded as a package. Open one, copy the relevant lines into your script, and adapt the names. Each file's header comment cites the Blender doc URL or research section the pattern came from.
When community content (Stack Overflow, older add-on source) conflicts with the official docs, prefer the docs. The 2.x to 4.x to 5.x churn around Actions, Extensions, and property handling has invalidated a lot of older material.
Roadmap
See ROADMAP.md for the candidate pool and what ships next — including the
planned Blender 5.2 LTS sweep (July 2026). Releases are cut automatically from
conventional commits; the full history lives in CHANGELOG.md.