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 16 skills, 9 rules, 3 templates, 27 snippets, 59 examples, and 34 showcase pieces for Blender Python development targeting Blender 5.2 LTS (current stable) with Blender 4.5 LTS fallback support. Blender 5.1 is prior stable.
The content is consumed by AI coding agents reading these files directly from a checkout — there is no MCP server in this repository, and none is required. Cursor applies rules/*.mdc automatically wherever their scope globs match and takes skills by name in chat; Claude Code reads skills/ and rules/ from the project workspace, or from this repo kept as a referenced checkout. Any agent that can read files in a workspace can use it the same way. There is no build step for the content — 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, import without scale check, export without evaluated geometry, mixed glTF/FBX axis RNA
Templates
A working Extensions Platform add-on starter, a headless batch script starter, and a GLB-in engine-ready asset pipeline
Snippets
27 small standalone Python files demonstrating canonical patterns
Examples
Runnable headless scripts under examples/. Each asserts an API contract and exits non-zero on failure.
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.
Get Blender — download 5.2 LTS (primary target) or 4.5 LTS (supported fallback) from blender.org/download/lts; current stable lives at blender.org/download. The blender command below is that binary — on macOS it is inside the app bundle at /Applications/Blender.app/Contents/MacOS/Blender.
Run an example — every example is a self-checking headless script (exit non-zero on failure, no GPU needed for the check):
Primary target (current stable; all examples assume 5.2 unless a 4.5 path is shown)
Blender 5.1
Prior stable (weekly cron; PR via needs-5.1 or manual dispatch)
Blender 4.5 LTS
Fallback supported (skills show both code paths where 4.x and 5.x APIs diverge)
Falsifiers
Every one of the 59 examples carries a falsifier: a flag that changes the
input so a real assertion fails. It never disables the assertion, skips the
check, or short-circuits to an error — it feeds the script something the
contract says must not pass, and the same check that guards the happy path
catches it.
# The contract holds: exit 0
blender --background --python examples/bmesh-gear/bmesh_gear.py --
# The falsifier: skip the extrude, so the topology no longer matches the# closed form. The topology check fires and the script exits 3.
blender --background --python examples/bmesh-gear/bmesh_gear.py -- --no-extrude
This is what makes a green run mean something. An assertion that has only
ever passed witnesses nothing — it could be comparing a constant to itself.
Proving each one fails once, on demand, is the difference between a test
suite and a set of scripts that print "OK". Shipping an example requires
demonstrating the falsifier's non-zero exit and reporting the measured error.
--api, --check-pixels, and --output are not falsifiers; they select a
code path rather than break a contract. Full conventions, including the
per-script exit-code model, are in
CONTRIBUTING.md.
Showcase
Budget-conformance props. Not examples. Conventions: showcase/README.md.
34 showcase pieces — click to expand the preview grid
shipping-crate — procedural crate through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets. Falsifier --skip-decimate exits 9.
stone-well — procedural round stone well, shingled roof, windlass, rope and bucket through the same pipeline. Falsifier --skip-decimate exits 9.
wooden-barrel — procedural 20-stave wine-cask with boarded, croze-seated heads and chord-lofted hoops through the same pipeline. Falsifiers --round-band exits 18, --one-piece-head exits 17.
campfire — procedural fieldstone ring, logs, and ash through the same pipeline. Falsifiers --skip-decimate exits 9, --uniform-stones exits 20.
market-stall — procedural timber stall with striped awning and counter through the same pipeline. Falsifiers --skip-decimate exits 9, --short-brace exits 17.
street-lantern — procedural hanging lantern with iron post, brass fittings, and amber cage through the same pipeline. Falsifiers --skip-decimate exits 9, --shallow-brace exits 20.
treasure-chest — procedural slatted chest with iron bands, corner brackets, and an open lid through the same pipeline. Falsifier --skip-decimate exits 9.
terrain-scatter — Geometry Nodes hill tile with bevelled masonry scatter through the same pipeline. Falsifier --skip-decimate exits 9.
fence-kit — procedural post-and-rail fence section through the same pipeline. Falsifier --skip-decimate exits 9.
watchtower — procedural timber lookout with X-braces, hatch ladder, platform, and coursed shake roof through the same pipeline. Falsifiers --skip-decimate exits 9, --short-joists exits 17.
cart — procedural two-wheel wooden cart with spoked wheels, slatted bed, and shafts through the same pipeline. Falsifier --skip-decimate exits 9.
park-bench — procedural wrought-iron park bench with scrolled legs and slatted seat through the same pipeline. Falsifier --skip-decimate exits 9.
wheelbarrow — procedural wheelbarrow with board-built tray walls, a spoked wheel, iron straps, legs, and handles through the same pipeline. Falsifiers --skip-decimate exits 9, --wide-seams exits 17.
anvil — procedural London-pattern anvil on a timber stump through the same pipeline. Falsifiers --skip-decimate exits 9, --round-waist exits 19.
water-trough — procedural watertight water trough on a timber stand through the same pipeline. Falsifier --skip-decimate exits 9.
hitching-post — procedural timber hitching post with a cross-arm, wrapped iron bands and iron rings through the same pipeline. Falsifiers --skip-decimate exits 9, --sunk-bands exits 18.
grindstone — procedural grindstone on a timber trestle with iron axle and crank through the same pipeline. Falsifier --skip-decimate exits 9.
hand-pump — procedural cast-iron village hand pump through the same pipeline. Falsifier --skip-decimate exits 9.
signpost — procedural timber fingerboard signpost through the same pipeline. Falsifiers --skip-decimate (9), --stray-vert (15), --yaw-boards (19).
chopping-block — procedural hooped timber stump with an embedded axe through the same pipeline. Falsifier --skip-decimate exits 9.
wooden-bucket — procedural coopered pail with a three-strand rope bail through the ear rings through the same pipeline. Falsifiers --float-handle and --sunk-rope exit 17.
wall-torch — procedural wall-mounted torch sconce with a stone plaque, iron bracket, and flame through the same pipeline. Falsifiers --skip-decimate (9), --stray-vert (15), --float-arm (17).
tavern-stool — procedural tavern stool with turned legs, stretchers, and iron ferrules through the same pipeline. Falsifier --skip-decimate exits 9.
iron-cauldron — procedural round-bellied iron cauldron on a timber tripod through the same pipeline. Falsifiers --skip-decimate exits 9, --pointed-pot exits 19.
wooden-ladder — procedural timber ladder with raked stiles, evenly pitched rungs, and iron shoes through the same pipeline. Falsifiers --skip-decimate exits 9, --drift-rungs exits 19.
hay-bale — procedural bound hay bale with sisal twine through the same pipeline. Falsifier --skip-decimate exits 9.
rope-bridge — procedural rope footbridge whose plank deck is fitted to a parabola recomputed from the plank tops, with lashed hand ropes and staked foot ropes, through the same pipeline. Falsifiers --vee-deck exits 19, --float-suspenders exits 18.
grain-sacks — procedural pile of burlap grain sacks settled onto flat bases and tied with twine, through the same pipeline. Falsifiers --round-bottom exits 19, --slip-tie exits 17.
brazier — procedural iron brazier with ash and glowing charcoal whose tip angle is recomputed from a density-weighted mass centre and the feet on the floor, through the same pipeline. Falsifiers --tuck-legs exits 19, --overfill exits 18.
apothecary-shelf — procedural apothecary shelf stocked with fifteen labelled bottles, flasks and jars, whose headroom under the shelf above is recomputed per vessel, through the same pipeline. Falsifiers --tall-flask exits 20, --short-shelves exits 17.
wooden-yoke — procedural double ox yoke on two bent hickory bows with a hung ring, whose neck openings are measured between the bow legs and up to the saddles, through the same pipeline. Falsifiers --pinch-bows exits 19, --edge-on-ring exits 17.
butter-churn — procedural staved plunge churn whose dasher stroke is recomputed from the inner wall read off the staves and the plunger's reach, through the same pipeline. Falsifiers --wide-dasher exits 20, --tight-hole exits 18.
Examples
Runnable, smoke-gated demos live in examples/ — each is executed headless on
Blender 5.2 LTS and 4.5 LTS by the blender-smoke workflow (5.1 on the weekly cron, the needs-5.1 PR label, or manual dispatch), 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 DippedGlaze group declared via tree.interface.new_socket, instanced in five
materials with different Tint values. Witnesses the grouping contract: shared datablock
(users == 5), parameters on the group node — five mugs share one foot band, dip line
and speckle, and differ only in colour.
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.
World ShaderNodeTexSky driving Background Color — the sky contract AI lighting
code misses across 4.5 → 5.1. sky_type is NISHITA on 4.5 LTS and
MULTIPLE_SCATTERING on 5.1 (NISHITA gone); dust_density exists only on 4.5
(aerosol_density on 5.1). Two tiny Cycles EXR zenith probes assert
sun_elevation 8° → 55° brightens zenith (rise 2.25x / 1.50x, gate ≥ 1.25).
Gallery still is a sky-lit obelisk diptych (8° dusk | 55° midday) so the contract reads at thumbnail scale.
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 —
then re-reads its own render and fails unless the pixels prove the flat-vs-checker split.
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 turns a squat ceramic jar into a trumpet vase,
lifting and flaring the rim — 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 — staged as a horseshoe magnet whose capped ends
are the pole faces. 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 game controller whose shell is a 90-vertex quad cage
under SUBSURF, measures every mesh via evaluated_get().to_mesh() / to_mesh_clear(),
asserts the evaluated shell matches the Catmull-Clark closed form (1,410 vertices), and asserts
wm.obj_export ships the modifier-applied geometry (exported vertex count == evaluated > base).
evaluated_get().data.name is generic Mesh on 4.5.11 and 5.1.2 and equals
the source name on 5.2.1. to_mesh().name stays the source name on all
three. --assume-distinct-names is red only on 5.2.
wm.usd_exportevaluation_mode='RENDER' versus 'VIEWPORT'. TESSELLATE writes
the closed-form Catmull-Clark counts (26/24 vs 98/96); default BEST_MATCH writes
the 8-vert cage and the mode is silent. Asserts the USDA point counts, not the
still.
The sequencer API rename from 4.5 LTS to 5.x — strips (never .sequences), new_effect
ending in length= vs frame_end=, and left_handle/right_handle/duration replacing
the deprecated frame_final_*. Asserts closed-form spans, GC wiring and clamping, the
consumed-input compositing contract, and a save/reload round-trip.
Object light linking proven in pixels: a key linked to the hero's collection
lights only the hero (4.0x luminance ratio at projected centers), and
unlinking in the same check raises the decoy 244% with 0.0% hero drift. The
API is obj.light_linking — the Light datablock has none — and EEVEE Next
honors linking too (measured), with Cycles pinned for deterministic samples.
The GAMMA_CROSS fade is not the naive linear mix: it blends in a gamma-0.5
space, ((1-t)·√A + t·√B)² with t = (frame − start) / duration — never 1
inside the effect. Per-frame sample renders assert the closed form (mid dips
0.250 below the sRGB lerp), and the AgX-default sampling trap is documented
(view_transform = 'Standard' is mandatory for any pixel witness).
ColorStrip.use_linear_modifiers is a bool on 4.5.11 and 5.1.2; the same
getattr is AttributeError on 5.2.1. Version-guarded hasattr then read
exits 0 on all three. --assume-present is red only on 5.2.
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 (a modeled keycap via
Object Info) → Realize Instances — attached as a NODES modifier with no Group Input.
Asserts evaluated topology is verts = faces = 1089 (9 keycaps × 121), and a position-field
Set Material lands the orange accent on exactly one keycap.
One Geometry Nodes spiral-staircase tree, three modifier copies. Writes a Float Height
input through mod.properties.inputs on 5.2 and mod[identifier] on 4.5/5.1; the tree
turns 1 / 2 / 3 m into 7 / 17 / 27 treads. Asserts readback and evaluated Z-extent equal
1 / 2 / 3. The 5.1 dict form raises TypeError on 5.2.
Repeat Zone and For Each Element only iterate after pair_with_output. Asserts
evaluated cubes against closed forms — Repeat 8×(1+N) with X-centers at
k×1.2, For Each 8×P with Z-centers at i×0.6+0.21 — not that the zone
nodes exist. Unpaired evaluates empty; For Each's main Geometry socket is a
passthrough.
Compare INT and Random Value FLOAT socket identifiers collapsed in 5.2
(A_INT / Min_001 gone; A / Min reused). Enabled-name lookup wires
on 4.5, 5.1, and 5.2. Asserts 16 verts and POINT gauge_h=1.80 on eight
column verts. --legacy-ids is red only on 5.2.
check-only, no gallery still — item-name round-trip is not thumbnail-legible
Combine / Separate Bundle on 5.x (NodeCombineBundle, not
GeometryNodeCombineBundle). Asserts evaluated 8/6 plus x-extent
[0.5, 2.5] and POINT bundle_mark=0.314159 — count-only is a cube that
never entered the bundle. Skips 4.5 (min_version 5.0); --force-run
fails creating the 5.x RNA.
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.
bpy.app.handlers.exit_pre writes $BDT_SMOKE_SIDECAR as Blender dies; the
harness asserts exit_pre-ok after the process exits. Skips 4.5
(min_version 5.1). --silent-handler / --no-handler miss the file;
--wrong-text / --write-in-main fail the content check.
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.
A hurricane lantern joined from seven part objects under bpy.context.temp_override — the
supported replacement for the removed context.copy() dict-pass form. Asserts one mesh
remains, sources are gone, and the five part materials merge with every face kept on its own.
Custom ID properties are removed with del id_block[key], not property_unset.
IDs are built with bpy.data.objects.new — the snippet __main__ keys off
active_object and is dark headless. Same del on 4.5 LTS and 5.x.
MeshAutomaskingSettings is absent on 4.5.11 and 5.1.2, present on 5.2.1.
Old Brush automasking attributes are gone on 5.2; read
.mesh_automasking_settings instead. --assume-brush-attrs is red only
on 5.2.
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.
Grease Pencil LINEART modifier ink via the depsgraph on a cel-shaded
lighthouse diorama — not Freestyle and not hand-drawn strokes. source_object
is load-bearing (clear → 0 strokes); every edge type off → 0; the drawing is
255 strokes / 1393 points on all three binaries, gated so dropping any
one edge type fails. Stroke width: thickness exists on 4.5, AttributeError
on 5.1 — portable path is radius.
A game-prop supply crate round-tripped through bpy.ops.export_scene.gltf and
the importer — the check reads the .gltf JSON and .bin buffer directly.
Witnesses the +Y-up convention baked into vertex data with no node rotation,
export_apply shipping the evaluated mesh (one disk vertex per evaluated
loop), V-flipped UVs, and per-triangle material bindings — all against the
depsgraph-evaluated mesh. The exporter/importer RNA signatures are probed
byte-identical on 4.5.11 and 5.1.2 and guarded against future renames.
The same radio-mast mesh under the Unity (export_yup=True) and Godot
(export_yup=False) glTF presets. Re-importing each file proves the axis
conversion: Unity stands, Godot lies along -Y. --same-axis exports both
Y-up and the differ check exits 9. Neighbor of
gltf-export-roundtrip.
One recognizable asset at three LODs via DECIMATE modifiers evaluated through
the depsgraph. Asserts the reduction is non-destructive (the original datablock
keeps its closed-form counts), each LOD's evaluated triangle count lands within
5% of ratio x base (measured 0.00–0.13%), and the silhouette-critical bbox
survives within 1e-3 — with a stacked Decimate and an oblique fin's shaved
plate corner as the caught failure modes. A wireframe of each evaluated mesh
makes the density drop visible.
Cycles cage-bakes a ribbed hatch onto a DECIMATE COLLAPSE LOD. Asserts the
map is not flat (frac 0.7211, MAD 0.09356 vs (0.5, 0.5, 1.0)) while a
flat-source control is (frac 0.0000). --flat-source exits 5. Byte-identity
across 4.5 / 5.1 / 5.2 is not the contract — Cycles bake is stochastic.
Neighbor of lod-decimate-chain.
The game-engine max-four-bone-influences constraint, enforced through the data
API (v.groups + VertexGroup.remove + renormalize) rather than the
bpy.ops.object.vertex_group_limit_total context path. Asserts the pre-limit
cables really carry five influences, no vertex ends over the cap, weights still
sum to one, the pose survives pruning, and the modifier is still exact linear
blend skinning read back from the mesh's own deform layer. The render paints
those post-limit weights onto the arm as a colour attribute.
The normal-mapping tangent-space contract: deterministic triangulation
(calc_tangents aborts on any ngon), unit orthogonal tangent frames,
bitangent == sign * (n x t), and mikktspace matching the independent
edge/UV-delta formula on smooth fields. Documents the planar-on-cylinder UV
degeneracy (tangent collapses onto the normal) and the stale layer-handle
hazard that silently corrupts measurements on 4.5.
The skinning counterpart to gltf-export-roundtrip: a 21-bone rigged mech
scorpion exported with export_skins and re-imported, asserting the joint
list, JOINTS_0/WEIGHTS_0 unit sums, bone/parent/rest-matrix round-trip,
bit-exact weights, and identical deformation of the re-imported rig — plus
the parenting hazard: unparented skinned meshes let the exporter bind an
armature by name.
The compound-collision contract prop pipelines (engines generally,
FiveM/GTA-style prop workflows specifically) ingest: one convex hull piece
per part group, built with bmesh.ops.convex_hull from a coarse
sec(π/n)-inflated cage — never the dense render mesh, whose hull measures
380 faces, over the 255-face per-piece engine budget. Closed-form plane
tests prove containment (4.4e-08), convexity, watertightness, outward
winding, and Euler characteristic 2 per piece. Proud details cost cage rows;
concave grooves are free.
The shading contract a prop's silhouette depends on: since Blender 4.1,
hard edges are mesh data (face smooth flags + sharp_edge attribute), and
use_auto_smooth / use_custom_normals / calc_normals are AttributeError
on both 4.5 LTS and 5.1. set_sharp_from_angle marks sharp exactly the
edges an independent dihedral recompute predicts; evaluated loop normals
weld across smooth edges and split by the dihedral across sharp ones;
custom split normals survive depsgraph evaluation within their int16
quantization (3.904e-05, not float-exact). Documents the legacy
shade_auto_smooth operator trap: CANCELLED headless on 4.5, FINISHED
with the Smooth-by-Angle modifier on 5.1.
The mesh-cleanliness contract a prop pipeline relies on before engine ingest:
no ngons, no loose vertices, every edge bordering exactly two faces, no
zero-area faces, contiguous and outward winding on all eight parts of a
flanged street valve, and Euler V − E + F == 2 on its body casting
(measured 802/1632/832, volume 0.453344). Companion to
collision-hull-proxy (hull watertightness)
and bmesh-gear (parametric closed solids). Still: a
dirty copy with a hole, flipped patch, ngon and loose verts marked from live
audit data beside the intact valve; the paint glows red on any back face.
The origin / scale-apply / MPI contract a prop pipeline relies on before
engine ingest: data-API scale bake to exactly (1,1,1), local bbox
min.z == 0 (origin at base), world AABB unchanged across the bake, and
matrix_parent_inverse so a flanged conduit elbow stays on its mount.
Extends parent-inverse-orrery without
retreading orbits. Dual-panel still: TRAP (bare parent — empty socket +
teleported flange) vs MPI KEEP.
The truncated icosahedron as a bmesh contract: every icosphere edge cut at
exactly 1/3, faces ordered by link-topology walks (nothing hand-listed),
and every invariant asserted as a closed form — 60/90/32, Euler 2, uniform
degree 3, edge lengths within 3e-5, planar faces against an independent
Newell normal, one circumsphere, and the black pentagons / white hexagons
bound by face vertex count, never enumeration order. Sibling to
bmesh-gear (parametric extrusion): this one
witnesses polyhedral invariants plus per-face-class material binding. The
smoothed render inverts on sight if the panel binding does.
The Mirror + depsgraph contract: the datablock holds only the authored half
(676/1289/614, 104 centerline verts) while the evaluated mesh is the welded
whole — exactly 2n − c verts, watertight with Euler 2, every vertex paired
at negated X (deviation 0.0). Wheels, lamps, grille, mirrors and handles mirror
about object origins parked on the symmetry plane — offset the data, never the object.
Companion to depsgraph-export (evaluated-vs-
original) and shape-key-blend (non-destructive
evaluation). Break the mirror and the render is literally half a car.
What POINT versus CORNERmeans on Mesh.color_attributes once a mesh
has shared vertices. An eight-wedge pinwheel around one raised hub: CORNER
authoring holds each wedge's exact color (hub corners disagree by face,
err ≤ 1e-6), while the naive per-wedge POINT loop rewrites every shared
vertex once per neighbor and the last write wins — the hub reads
palette[K−1], and the measured shear matches the palette closed form
(0.751031) exactly. Companion to
color-attribute-wheel (domain sizing,
active_color, the shader Attribute node). The render paints two striped
parasols with the same authoring functions. The broken state is in frame:
the right parasol's stripes smear along the seams and one crimson panel
turns cream.
The half-dimension bevel collapse, isolated. A slab beveled at
offset == min_dim/2 pinches its band into exactly 12 zero-area faces
(4 min-axis edges × 3 segments) with 16 coincident verts and min_area
down 3.2e5× — and a stdlib re-parse of the exported GLB counts 32
degenerate triangles shipped to disk, where an engine merge-by-distance
welds their loops. Companion to
gltf-export-roundtrip (where the count
check first caught the weld) and mesh-hygiene-audit
(topology gates). Seam markers are placed from live mesh data.
A tiling corridor kit where the snap is the contract. 16 boundary verts
on x ∈ {0, 4} within 1e-6 m, opposing end loops coincident under the
tile offset, and a linked duplicate's joint positions matching within 1e-6 —
zero gap, zero overlap. Shell bbox equals the declared tile exactly, the
tube is manifold except its two open ends, and all twelve detail parts are
contained inside the tile. The unsnapped probe (3 mm skew) fails with the
measured error printed. Companion to
prop-origin-transform (pivot discipline)
and mesh-hygiene-audit (topology gates).
The second UV layer engines need for baked lighting, on a reusable market
cart. Per-part UVMap/UVLight with active vs active_render pinned
(the edit-mode UV ops clear both flags — re-assert or lose them), channel
zero untouched by the second unwrap (drift 0.0, the clobber probe
measures 2.068), every island inside [0,1], 0 overlapping islands
by an independent SAT scan, and a measured min island distance of
0.00401 against the stated margin. Companion to
uv-layer-grid (UV authoring) and
triangulate-tangents (tangents from UVs).
Named SKT_ empties as the spawn contract, on a reusable survey drone. All
7 socket world matrices land within 1.788e-07 of the authored
transform with orthonormal right-handed bases, each socket's +Z equals its
mount pad's Newell normal (1.794e-07) by a derivation independent of
the basis construction, and modules seat at offset exactly 0.0 with
identity local transforms. Freezing the root preserves world matrices but
clears 7/7 parent-inverses and pushes the root scale into the children —
both halves asserted. The no-parent-inverse probe jumps 0.690128 m.
Companion to prop-origin-transform
(pivot discipline) and
parent-inverse-orrery (parent-inverse
under animation).
Baked occlusion in a colour attribute, checked against a formula instead
of a captured value: the cosine-weighted hemisphere integral for a wall of
height H at distance d is 1 - ½(1 - 1/√(1+k²)), k = H/d, and the
integrator matches it to 6.760e-04 across two decades of k. An
unoccluded plate bakes to exactly 1.0. FLOAT_COLOR round-trips exactly
while BYTE_COLOR is sRGB-encoded 8-bit — 0.735 reads back
0.7379107, matching an independent encode/quantise/decode model to
3.189e-07. Both survive depsgraph evaluation at deviation 0.0.
Companion to color-attribute-wheel and
attribute-domain-shear.
check-only, no gallery still — a hexagon on a cube is not thumbnail-legible
Synthesizes one 6-loop hexagon by dissolving a cube edge. Pre-asserts the
n-gon exists (1 face / 6 loops / 5 faces), then calc_tangents aborts until
bmesh.ops.triangulate yields 4 tris + 4 quads / 28 loops. glTF tri count
is 12 either way. Inverse of mesh-hygiene-audit.
check-only, no gallery still — unapplied scale looks like modeled non-uniform size
Unit cube with object scale (2, 1, 0.5). Pre-asserts unapplied non-uniform
scale and local ±1 verts, then glTF export_apply=True still writes Y-up
node.scale (2, 0.5, 1) with local POSITION. export_apply is modifiers
only. Inverse of gltf-export-roundtrip.
check-only, no gallery still — coincident cubes look like one cube
Two cubes in one mesh: 16 verts / 8 unique / still manifold. Pre-asserts
the duplicates, then glTF ships 24 tris / 48 positions / 8 unique.
remove_doubles collapses to one cube. Inverse of
degenerate-bevel-weld.
How content is organized
skills/<name>/SKILL.md - 16 skill files, YAML frontmatter, one canonical pattern each
rules/<name>.mdc - 9 rule files, anti-pattern + correction
templates/<name>/ - 3 template directories (extension-addon-template, headless-batch-script-template, ai-asset-pipeline-template)
snippets/<name>.py - 27 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 nine 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
validate-imported-mesh-scale: flags glTF/FBX import then mesh work with no transform_apply and no unit-scale check
no-unapplied-modifiers-on-export: flags export of objects with live modifiers when the export does not request evaluated geometry
use-correct-axis-rna-per-exporter: flags export_scene.gltf calls that pass FBX axis_forward / axis_up, and export_scene.fbx calls that pass glTF export_yup
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 ....
templates/ai-asset-pipeline-template/ is a working starter for a headless GLB-in / engine-ready-out job. It imports a GLB, runs the ai-mesh-cleanup order, emits an LOD chain and optional collider, and exports under a Unity, Godot, or Unreal glTF preset. Run with blender --background --python pipeline.py -- --input ... --outdir ... --preset unity.
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. Releases are cut automatically from
conventional commits; the full history lives in CHANGELOG.md.