Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@
"examples/vse-cut-list",
"examples/vse-gamma-cross",
"examples/vse-linear-modifiers",
"examples/wave-displace"
"examples/wave-displace",
"examples/ray-cast-space",
"examples/lattice-deform"
],
"showcase": [
"showcase/shipping-crate",
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ a `.cursor-plugin/plugin.json` manifest so the ecosystem drift checker
classifies it as a `cursor-plugin`. This is content the AI loads when the user
asks Blender questions or works on Blender add-ons in Cursor or Claude Code.

The content base is 16 skills, 9 rules, 3 templates, 27 snippets, 60
examples, and 67 showcase pieces (counts are CI-enforced against README.md)
The content base is 16 skills, 9 rules, 3 templates, 27 snippets, 62
examples, and 72 showcase pieces (counts are CI-enforced against README.md)
and the manifest). The full inventory tables and per-item purposes live in
`CLAUDE.md`. Example anatomy and authoring rules: copy `examples/bmesh-gear/`;
showcase conventions: `showcase/README.md`. The render look is specified
Expand All @@ -36,7 +36,7 @@ Blender-Developer-Tools/
rules/<rule-name>.mdc # 9 rule files
templates/<template-name>/ # 3 starter templates
snippets/<snippet-name>.py # 27 standalone Python snippets
examples/<name>/ # 60 runnable smoke-gated examples (+ gallery.json)
examples/<name>/ # 62 runnable smoke-gated examples (+ gallery.json)
examples/gallery_framing.py # shared Layer 1 framing measurement (render path only)
showcase/<name>/ # budget-conformance props (sibling of examples/)
showcase/gallery.json # this tree's gallery index; merged into docs/gallery/
Expand Down
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ skills/<skill-name>/SKILL.md - AI workflow definitions, 16 total
rules/<rule-name>.mdc - Anti-pattern rules, 9 total
templates/<template-name>/ - Starter projects, 3 total
snippets/<snippet-name>.py - Standalone code patterns, 27 total
examples/<name>/ - Runnable smoke-gated examples, 60 total (+ gallery.json)
showcase/<name>/ - Budget-conformance props, 67 pieces, each with a gallery `category` (sibling of examples/; see showcase/README.md § Categories)
examples/<name>/ - Runnable smoke-gated examples, 62 total (+ gallery.json)
showcase/<name>/ - Budget-conformance props, 72 pieces, each with a gallery `category` (sibling of examples/; see showcase/README.md § Categories)
scripts/build_gallery.py - Regenerates docs/gallery/ from examples/gallery.json + showcase/gallery.json
scripts/site/ - Vendored landing-page build (Jinja2); tokens.css is the shared palette
tests/check_site_links.py - Internal link/anchor/alt gate over the built site (docs/)
Expand Down Expand Up @@ -105,12 +105,12 @@ v0.2.0: Principled BSDF material, driver-with-custom-function via `driver_namesp

AI asset pipeline track: `decimate_to_budget.py`, `convex_hull_collider.py`, `lod_chain.py` (helper duplicated, not imported), `gltf_draco_export.py`, `export_preset_unity.py`, `export_preset_godot.py`, `export_preset_unreal.py`, `setup_bake_target_image.py`, `bake_normal_high_to_low.py`, `save_baked_image.py`.

## Examples (60)
## Examples (62)

Runnable scripts at `examples/<name>/`, each asserting a real API contract with
deterministic checks (exit non-zero on failure) and optionally rendering a still via
`--output`. All of them run headless on Blender 5.2 LTS and 4.5 LTS in `blender-smoke.yml` (5.1 on the weekly cron, the `needs-5.1` PR label, or manual dispatch);
**52 of the 60 ship a render in the site gallery** at `docs/gallery/`. The other
**54 of the 62 ship a render in the site gallery** at `docs/gallery/`. The other
eight are **check-only**: they carry no `--output` path, no gallery entry, and no
hero asset. The criterion is whether the contract is expressible in pixels. An
example is check-only when its witness is a data or state fact that no scene
Expand Down
39 changes: 35 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</p>

<p align="center">
<strong>16 skills</strong> &nbsp;&bull;&nbsp; <strong>9 rules</strong> &nbsp;&bull;&nbsp; <strong>3 templates</strong> &nbsp;&bull;&nbsp; <strong>27 snippets</strong> &nbsp;&bull;&nbsp; <strong>60 examples</strong> &nbsp;&bull;&nbsp; <strong>72 showcase pieces</strong>
<strong>16 skills</strong> &nbsp;&bull;&nbsp; <strong>9 rules</strong> &nbsp;&bull;&nbsp; <strong>3 templates</strong> &nbsp;&bull;&nbsp; <strong>27 snippets</strong> &nbsp;&bull;&nbsp; <strong>62 examples</strong> &nbsp;&bull;&nbsp; <strong>72 showcase pieces</strong>
</p>

<p align="center">
Expand All @@ -37,7 +37,7 @@

## Overview

This repository ships **16 skills, 9 rules, 3 templates, 27 snippets, 60 examples, and 72 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.
This repository ships **16 skills, 9 rules, 3 templates, 27 snippets, 62 examples, and 72 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.

Expand Down Expand Up @@ -75,7 +75,7 @@ blender --background --python examples/bmesh-gear/bmesh_gear.py --

## Falsifiers

Every one of the 60 examples carries a **falsifier**: a flag that changes the
Every one of the 62 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
Expand Down Expand Up @@ -451,7 +451,7 @@ Gallery still is a sky-lit obelisk diptych (8° dusk | 55° midday) so the contr
</details>

<details>
<summary><strong>Mesh, curves &amp; text</strong> — 13 examples</summary>
<summary><strong>Mesh, curves &amp; text</strong> — 15 examples</summary>

<table>
<tr>
Expand Down Expand Up @@ -648,6 +648,37 @@ check-only, no gallery still — no geometry
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.

</td>
</tr>
<tr>
<td width="46%" valign="middle">
<a href="examples/ray-cast-space/"><img src="examples/ray-cast-space/preview.webp" alt="Ray cast space: a teal-checkered block turned and stretched on a walnut plinth, five orange rays from brass emitter balls ending in orange hit rings on three of its faces, and one red ray from the world-coordinate trap striking it from somewhere else, on a dark studio floor" /></a>
</td>
<td valign="middle">

### [ray-cast-space](examples/ray-cast-space/)

`Object.ray_cast` vs `Scene.ray_cast` — five closed-form rays on a translated, Z-rotated,
non-uniformly scaled block. `Scene.ray_cast` hits each world point and normal exactly.
`Object.ray_cast`, fed `matrix_world.inverted()` origins and 3×3-only directions, hits the
same polygons and maps back within 5e-7 m. Raw world coordinates handed to `Object.ray_cast`
land 0.287 m or more away (`--world-to-object` exits 4).

</td>
</tr>
<tr>
<td width="46%" valign="middle">
<a href="examples/lattice-deform/"><img src="examples/lattice-deform/preview.webp" alt="Lattice deform: two checker-glazed columns on a walnut plinth, each in a steel-rod cage - the left upright in its rest cage, the right sheared and tapered where two orange cage corners have been pulled out" /></a>
</td>
<td valign="middle">

### [lattice-deform](examples/lattice-deform/)

A 2×2×2 Lattice modifier set to `'KEY_LINEAR'`, with two corners moved through
`LatticePoint.co_deform`. Witnesses that every evaluated vert lands on the trilinear
blend of the eight deformed corners, and that the default `'KEY_BSPLINE'` misses it
(`--bspline` exits 4).

</td>
</tr>
</table>
Expand Down
5 changes: 3 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,14 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
- `persistent` app-handler witness — handlers registered without `@bpy.app.handlers.persistent` are dropped by `wm.read_homefile`/file load while persistent ones survive; assert the registered-handler set before and after a reload (silent loss AI code hits)
- Link vs append witness — `bpy.data.libraries.load(link=True)` yields a linked, non-editable datablock (`library` set, `is_editable` False) while append yields a local copy; write a temp .blend with `bpy.data.libraries.write`, then assert both paths
- Orphan purge witness — `bpy.data.orphans_purge(do_recursive=...)` removes exactly the zero-user datablocks computed independently beforehand, and `bpy.data.batch_remove` removes a given set in one call (counts closed-form)
- Local vs world ray-cast witness — `Object.ray_cast` takes object-local origin/direction while `Scene.ray_cast` takes world space; a transformed target hit at closed-form points both ways, and passing world coords to `Object.ray_cast` misses (the trap)
- ~~Local vs world ray-cast witness~~ **SHIPPED** as `examples/ray-cast-space/` — five closed-form rays on a translated, Z-rotated, non-uniformly scaled block: `Scene.ray_cast` exact in world space, `Object.ray_cast` exact through `matrix_world.inverted()` (3×3-only directions, inverse-transpose normals, same polygon index); world coords fed to `Object.ray_cast` land ≥ 0.287 m off; `--world-to-object` exits 4
- `mathutils.bvhtree` witness — `BVHTree.FromObject` on evaluated geometry: `find_nearest` distances and `overlap` pairs match closed forms on a known arrangement
- Curves datablock witness — the modern `bpy.types.Curves` hair API (`add_curves`, `position` attribute, `curve_offset_data`) vs legacy particle hair: point and curve counts closed-form, every root on the emitter surface
- Non-Color normal-map witness — an image used as a normal map must be `colorspace_settings.name = 'Non-Color'`; a baked/sampled value round-trips only then, and sRGB shifts it by the transfer curve (a silent shading error AI code ships)
- Solidify even-thickness witness — `use_even_offset` keeps shell thickness constant at sharp corners, while without it the corner thins by 1/cos(θ/2): measured thickness at a known angle matches both closed forms
- Boolean solver witness — EXACT solver union/difference volumes match closed forms on overlapping primitives, and the result is manifold; FAST on coplanar faces is the documented failure case
- Lattice deform witness — a 2×2×2 lattice with one displaced point deforms interior verts by exact trilinear weights (`interpolation_type_u/v/w = 'KEY_LINEAR'`)
- ~~Lattice deform witness — a 2×2×2 lattice with one displaced point deforms interior verts by exact trilinear weights (`interpolation_type_u/v/w = 'KEY_LINEAR'`)~~ **SHIPPED** as `examples/lattice-deform/` — 602 evaluated verts on the trilinear closed form (5.6e-7) through a translated, non-uniformly scaled lattice; `--bspline` (the default interpolation) exits 4 at 0.2145
- Lattice `use_outside` / outside-vertex witness — verts outside a KEY_LINEAR lattice's volume: assert the documented clamp or extrapolation closed form, and `use_outside` (deform only the outer points) against an interior-only control move
- FBX unit-scale witness — `export_scene.fbx` with `apply_unit_scale` / `apply_scale_options` changes exported coordinates by the closed-form ×100 cm factor; re-import restores metres only on the matching option
- Collection-instance witness — `instance_type = 'COLLECTION'` instances appear only in `depsgraph.object_instances` (never in `scene.objects`); instance world matrices match the closed-form offsets
- Asset-marking witness — `ID.asset_mark()`, `asset_data.tags` and catalog UUIDs written to `blender_assets.cats.txt`, re-read from a saved library file (check-only if no legible still)
Expand Down
4 changes: 2 additions & 2 deletions docs/gallery/armature-bend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ <h1>Armature Bend</h1>
<p>Rigging end to end in the data API — edit_bones chain construction, name-bound vertex groups with smoothstep blend zones, posing, and depsgraph evaluation — bending a ribbed bellows hose through rest, half, and full curl.</p>
</header>
<main id="main">
<nav class="pager" aria-label="Examples"><a rel="prev" href="../grease-pencil-rosette/" aria-label="Previous example: grease-pencil-rosette"><span aria-hidden="true">&larr;</span> grease-pencil-rosette</a><span class="pager-pos hud">19 of 52 examples<span class="pager-keys"> &middot; <kbd>&larr;</kbd><kbd>&rarr;</kbd></span></span><a rel="next" href="../text-version-stamp/" aria-label="Next example: text-version-stamp">text-version-stamp <span aria-hidden="true">&rarr;</span></a></nav>
<nav class="pager" aria-label="Examples"><a rel="prev" href="../grease-pencil-rosette/" aria-label="Previous example: grease-pencil-rosette"><span aria-hidden="true">&larr;</span> grease-pencil-rosette</a><span class="pager-pos hud">19 of 54 examples<span class="pager-keys"> &middot; <kbd>&larr;</kbd><kbd>&rarr;</kbd></span></span><a rel="next" href="../text-version-stamp/" aria-label="Next example: text-version-stamp">text-version-stamp <span aria-hidden="true">&rarr;</span></a></nav>
<button class="detail-hero" id="heroZoom" type="button" aria-label="View full size: Three ribbed bellows hoses with brass collars on steel foot flanges, banded teal to amber to coral, standing straight, half curled and fully curled by an armature.">
<img src="../assets/armature-bend-hero.webp" alt="Three ribbed bellows hoses with brass collars on steel foot flanges, banded teal to amber to coral, standing straight, half curled and fully curled by an armature." width="1280" height="720" fetchpriority="high" />
</button>
Expand Down Expand Up @@ -1050,7 +1050,7 @@ <h3><a class="stretch" href="../lod-decimate-chain/">LOD Decimate Chain</a></h3>
</article>
</div>
</section>
<nav class="pager pager-foot" aria-label="Examples"><a rel="prev" href="../grease-pencil-rosette/" aria-label="Previous example: grease-pencil-rosette"><span aria-hidden="true">&larr;</span> grease-pencil-rosette</a><span class="pager-pos hud">19 of 52 examples</span><a rel="next" href="../text-version-stamp/" aria-label="Next example: text-version-stamp">text-version-stamp <span aria-hidden="true">&rarr;</span></a></nav>
<nav class="pager pager-foot" aria-label="Examples"><a rel="prev" href="../grease-pencil-rosette/" aria-label="Previous example: grease-pencil-rosette"><span aria-hidden="true">&larr;</span> grease-pencil-rosette</a><span class="pager-pos hud">19 of 54 examples</span><a rel="next" href="../text-version-stamp/" aria-label="Next example: text-version-stamp">text-version-stamp <span aria-hidden="true">&rarr;</span></a></nav>
</main>
<dialog class="lightbox" id="lightbox" aria-label="Full-size render">
<div class="lightbox-bar"><button class="lightbox-close" id="lightboxNative" type="button" aria-pressed="false">Actual size</button><button class="lightbox-close" id="lightboxClose" type="button" autofocus>Close</button></div>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/gallery/assets/lattice-deform-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/gallery/assets/ray-cast-space-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/gallery/attribute-domain-shear/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ <h1>Attribute Domain Shear</h1>
<p>POINT vs CORNER color-attribute domains on a shared-vertex fan: CORNER stays exact per face while a naive per-wedge POINT loop shears to the last write at the hub. A striped parasol keeps crisp stripes under CORNER and smears, with one panel turned the wrong color, under POINT; the measured shear equals the palette closed form.</p>
</header>
<main id="main">
<nav class="pager" aria-label="Examples"><a rel="prev" href="../car-mirror-symmetry/" aria-label="Previous example: car-mirror-symmetry"><span aria-hidden="true">&larr;</span> car-mirror-symmetry</a><span class="pager-pos hud">41 of 52 examples<span class="pager-keys"> &middot; <kbd>&larr;</kbd><kbd>&rarr;</kbd></span></span><a rel="next" href="../degenerate-bevel-weld/" aria-label="Next example: degenerate-bevel-weld">degenerate-bevel-weld <span aria-hidden="true">&rarr;</span></a></nav>
<nav class="pager" aria-label="Examples"><a rel="prev" href="../car-mirror-symmetry/" aria-label="Previous example: car-mirror-symmetry"><span aria-hidden="true">&larr;</span> car-mirror-symmetry</a><span class="pager-pos hud">41 of 54 examples<span class="pager-keys"> &middot; <kbd>&larr;</kbd><kbd>&rarr;</kbd></span></span><a rel="next" href="../degenerate-bevel-weld/" aria-label="Next example: degenerate-bevel-weld">degenerate-bevel-weld <span aria-hidden="true">&rarr;</span></a></nav>
<button class="detail-hero" id="heroZoom" type="button" aria-label="View full size: Two patio parasols on a terracotta paver deck behind CORNER and POINT slate placards: the left has crisp crimson and cream stripes, the right has its stripes smeared pink.">
<img src="../assets/attribute-domain-shear-hero.webp" alt="Two patio parasols on a terracotta paver deck behind CORNER and POINT slate placards: the left has crisp crimson and cream stripes, the right has its stripes smeared pink." width="1280" height="720" fetchpriority="high" />
</button>
Expand Down Expand Up @@ -1507,7 +1507,7 @@ <h3><a class="stretch" href="../custom-normals-shade/">Custom Normals Shade</a><
</article>
</div>
</section>
<nav class="pager pager-foot" aria-label="Examples"><a rel="prev" href="../car-mirror-symmetry/" aria-label="Previous example: car-mirror-symmetry"><span aria-hidden="true">&larr;</span> car-mirror-symmetry</a><span class="pager-pos hud">41 of 52 examples</span><a rel="next" href="../degenerate-bevel-weld/" aria-label="Next example: degenerate-bevel-weld">degenerate-bevel-weld <span aria-hidden="true">&rarr;</span></a></nav>
<nav class="pager pager-foot" aria-label="Examples"><a rel="prev" href="../car-mirror-symmetry/" aria-label="Previous example: car-mirror-symmetry"><span aria-hidden="true">&larr;</span> car-mirror-symmetry</a><span class="pager-pos hud">41 of 54 examples</span><a rel="next" href="../degenerate-bevel-weld/" aria-label="Next example: degenerate-bevel-weld">degenerate-bevel-weld <span aria-hidden="true">&rarr;</span></a></nav>
</main>
<dialog class="lightbox" id="lightbox" aria-label="Full-size render">
<div class="lightbox-bar"><button class="lightbox-close" id="lightboxNative" type="button" aria-pressed="false">Actual size</button><button class="lightbox-close" id="lightboxClose" type="button" autofocus>Close</button></div>
Expand Down
Loading
Loading