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
3 changes: 3 additions & 0 deletions .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,8 @@
"examples/vse-gamma-cross",
"examples/vse-linear-modifiers",
"examples/wave-displace"
],
"showcase": [
"showcase/shipping-crate"
]
}
1 change: 1 addition & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ on:
- ".cursor-plugin/plugin.json"
- "assets/**"
- "examples/**"
- "showcase/**"
- "docs/gallery/**"
- "scripts/build_gallery.py"
- "scripts/site/**"
Expand Down
20 changes: 18 additions & 2 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ jobs:
)

# Every manifest path must exist on disk.
for key in ('skills', 'rules', 'snippets', 'templates', 'examples'):
for key in ('skills', 'rules', 'snippets', 'templates', 'examples', 'showcase'):
for path in manifest.get(key, []):
if not os.path.exists(path):
errors.append(f'{key}: manifest lists missing path {path}')
Expand All @@ -200,6 +200,9 @@ jobs:
'examples': sorted(
d for d in glob.glob('examples/*') if os.path.isdir(d)
),
'showcase': sorted(
d for d in glob.glob('showcase/*') if os.path.isdir(d)
),
}
for key, paths in expected.items():
listed = {p.replace('\\', '/') for p in manifest.get(key, [])}
Expand Down Expand Up @@ -252,6 +255,13 @@ jobs:
if os.path.isdir(os.path.join('examples', d))
and os.path.exists(os.path.join('examples', d, 'README.md'))
])
showcase_count = 0
if os.path.isdir('showcase'):
showcase_count = len([
d for d in os.listdir('showcase')
if os.path.isdir(os.path.join('showcase', d))
and os.path.exists(os.path.join('showcase', d, 'README.md'))
])

readme = open('README.md').read()
if f'{skill_count} skills' not in readme:
Expand All @@ -265,13 +275,19 @@ jobs:
errors.append(f'README snippet count mismatch (expected "{snippet_count} snippets" substring)')
if f'{example_count} examples' not in readme:
errors.append(f'README example count mismatch (expected "{example_count} examples" substring)')
showcase_word = 'piece' if showcase_count == 1 else 'pieces'
showcase_needle = f'{showcase_count} showcase {showcase_word}'
if showcase_needle not in readme:
errors.append(
f'README showcase count mismatch (expected "{showcase_needle}" substring)'
)

if errors:
for e in errors:
print(f'::error::{e}', file=sys.stderr)
sys.exit(1)

print(f'Counts verified: {skill_count} skills, {rule_count} rules, {template_count} {template_word}, {snippet_count} snippets, {example_count} examples')
print(f'Counts verified: {skill_count} skills, {rule_count} rules, {template_count} {template_word}, {snippet_count} snippets, {example_count} examples, {showcase_needle}')
PYEOF

validate-harness:
Expand Down
11 changes: 7 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ 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, and 59
examples (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/`; the render look is specified
The content base is 16 skills, 9 rules, 3 templates, 27 snippets, 59
examples, and 1 showcase piece (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
in `docs/VISUAL-STYLE.md`; the canonical run prompt is
`docs/new-example-prompt.md`.

Expand All @@ -37,6 +38,8 @@ Blender-Developer-Tools/
snippets/<snippet-name>.py # 27 standalone Python snippets
examples/<name>/ # 59 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/
scripts/build_gallery.py # generates docs/gallery/ (stdlib only)
scripts/site/ # vendored landing-page build (build_site.py + template)
docs/gallery/ # committed generated gallery pages + hero assets
Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ 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, 59 total (+ gallery.json)
scripts/build_gallery.py - Regenerates docs/gallery/ from gallery.json (stdlib only)
showcase/<name>/ - Budget-conformance props, 1 piece (sibling of examples/; see showcase/README.md)
scripts/build_gallery.py - Regenerates docs/gallery/ from examples/gallery.json + showcase/gallery.json
scripts/site/ - Vendored landing-page build (Jinja2)
docs/gallery/ - Committed generated gallery pages + hero renders
VERSION - Source of truth for the repo version
Expand Down
24 changes: 23 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,19 @@ templates/
blender_manifest.toml
__init__.py
README.md
showcase/
README.md
gallery.json
<piece-name>/
README.md
```

- **`skills/`** - one directory per skill, each containing `SKILL.md` with YAML frontmatter (`name`, `description`, `standards-version`).
- **`rules/`** - Cursor-style rules as `.mdc` files with YAML frontmatter (`description`, `alwaysApply`, `globs`, `standards-version`).
- **`snippets/`** - small standalone `.py` files (5 to 50 lines) demonstrating a single canonical pattern.
- **`templates/`** - copy-paste starting points; one directory per template.
- **`showcase/`** - budget-conformance props, sibling of `examples/`. Not API
contracts. Conventions: [`showcase/README.md`](showcase/README.md).

## Adding a Skill

Expand Down Expand Up @@ -86,6 +93,21 @@ templates/
1. Add a directory under `templates/`, e.g. `templates/headless-batch-script-template/`.
2. Include all files needed for an immediate copy-paste starting point. For add-on templates, include `blender_manifest.toml`, `__init__.py`, and a brief `README.md`.

## Adding a Showcase Piece

Read [`showcase/README.md`](showcase/README.md) first. Showcase asserts
budget conformance, never an API contract.

1. Add `showcase/<kebab-name>/` with a script, a README that includes an
exit-code table, and a falsifier that breaks one pipeline stage so a
**named** budget fails.
2. List the directory in `.cursor-plugin/plugin.json` `"showcase"` and add a
`tests/smoke/catalog.json` row. The runner takes opaque script paths.
3. Add a `showcase/gallery.json` `pieces[]` entry, render a still, and run
`python scripts/build_gallery.py`. Do not hand-edit `docs/gallery/` HTML.
4. Update the README showcase-piece count. `validate-counts` checks it
separately from the example total.

## Blender Version Targeting

Content targets **Blender 5.2 LTS** as primary, **Blender 5.1** as prior stable, and **Blender 4.5 LTS** as fallback. When the API differs, branch on `bpy.app.version` and document both paths. Example:
Expand Down Expand Up @@ -169,7 +191,7 @@ The drift-check workflow enforces these on every push and PR.

## Aggregate Counts

`README.md` declares aggregate counts (e.g. "8 skills, 4 rules, 1 template, and 10 snippets"). The `validate-counts` job in `.github/workflows/validate.yml` enforces these substrings against the filesystem on every push and PR. When you add or remove content, update the README counts in the same commit.
`README.md` declares aggregate counts (e.g. "16 skills, 9 rules, 3 templates, 27 snippets, 59 examples, and 1 showcase piece"). The `validate-counts` job in `.github/workflows/validate.yml` enforces these substrings against the filesystem on every push and PR. Showcase pieces are counted separately from examples. When you add or remove content, update the README counts in the same commit.

## Pull Request Process

Expand Down
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@
</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>59 examples</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>59 examples</strong> &nbsp;&bull;&nbsp; <strong>1 showcase piece</strong>
</p>

<p align="center">
<a href="https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/">Examples Gallery</a>
&nbsp;&bull;&nbsp; <a href="#quick-start">Quick start</a>
&nbsp;&bull;&nbsp; <a href="#examples">Examples</a>
&nbsp;&bull;&nbsp; <a href="#examples">Examples</a>
&nbsp;&bull;&nbsp; <a href="showcase/">Showcase</a>
&nbsp;&bull;&nbsp; <a href="skills/">Skills</a>
&nbsp;&bull;&nbsp; <a href="rules/">Rules</a>
&nbsp;&bull;&nbsp; <a href="templates/">Templates</a>
Expand All @@ -36,7 +37,7 @@

## Overview

This repository ships **16 skills, 9 rules, 3 templates, 27 snippets, and 59 examples** 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, 59 examples, and 1 showcase piece** 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 (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.

Expand All @@ -45,7 +46,8 @@ The content is consumed by AI coding agents (Cursor, Claude Code, any MCP-capabl
| **Skills** | Guided workflows: scaffolding, operators, panels, properties, mesh and bmesh, headless batch, slotted actions, geometry nodes, procedural materials, depsgraph queries, drivers and handlers, `bl_info` migration, video sequencer, imported-mesh cleanup, engine export presets |
| **Rules** | 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** | 24 small standalone Python files demonstrating canonical patterns |
| **Snippets** | 27 small standalone Python files demonstrating canonical patterns |
| **Showcase** | Budget-conformance props under [`showcase/`](showcase/). Not examples. Conventions: [`showcase/README.md`](showcase/README.md) |

## Quick start

Expand All @@ -69,6 +71,14 @@ blender --background --python examples/bmesh-gear/bmesh_gear.py --
| 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) |

## Showcase

Budget-conformance props. Not examples. Conventions: [`showcase/README.md`](showcase/README.md).

<a href="showcase/shipping-crate/"><img src="showcase/shipping-crate/preview.webp" alt="Shipping crate: a wooden slat crate with iron corner brackets on a dark studio floor, warm wedge on the back wall" /></a>

First piece: [`shipping-crate`](showcase/shipping-crate/) — procedural crate through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets. Falsifier `--skip-decimate` exits 9.

## Examples

Runnable, smoke-gated demos live in [`examples/`](examples/) — each is executed headless on
Expand Down
5 changes: 5 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ Provider-agnostic GLB-in / engine-ready-out. This repo does not generate meshes.

Not committed; target list for the next content version. (v0.3.0 shipped the smoke-gated `examples/` track.)

- ~~Showcase sibling tree + shipping-crate pilot~~ **SHIPPED** as `showcase/shipping-crate/` — budget-conformance crate composing bake, LOD, collider, Unity glTF; `--skip-decimate` exits 9 on the LOD1 ratio band; DECIMATE COLLAPSE ratios diverge on 5.2 vs 4.5/5.1
- Procedural terrain or landscape showcase using Geometry Nodes scatter
- Hero prop with a more complex silhouette (lantern or treasure chest) as a second showcase piece
- Small modular kit showcase on recognizable geometry (`modular-kit-snap` contract)

- ~~Custom ID-property delete witness~~ **SHIPPED** as `examples/cross-version-property-delete/` — IDs built via `bpy.data.objects.new` (not `active_object`); `property_unset` is TypeError and leaves the key; `del` removes it on 4.5 LTS and 5.x
- ~~USD export evaluation_mode witness~~ **SHIPPED** as `examples/usd-export-evaluation-mode/` — probed `wm.usd_export` on CI Linux portables 5.2.1 (`9e2066aef7ef`) and 4.5.13 (`daeeeca98fb0`); TESSELLATE+VIEWPORT 26/24, TESSELLATE+RENDER 98/96; BEST_MATCH writes the 8-vert catmullClark cage so the mode is silent
- ~~Repeat Zone / For Each Element pairing witness~~ **SHIPPED** as `examples/gn-zone-iterate/` — `pair_with_output` is load-bearing (unpaired evaluates 0 verts); Repeat `8×(1+N)` with X-centers at `k×STEP`; For Each `8×P` with Z-centers at `i×STEP`; For Each main Geometry is a passthrough (6 verts); count-only is insufficient (`--no-offset` still 32 verts, one X-center)
Expand Down
Binary file added docs/gallery/assets/shipping-crate-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion docs/gallery/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ <h1>Examples Gallery</h1>
autocomplete="off" spellcheck="false" aria-label="Search examples" />
<button class="q-clear" id="qClear" type="button" aria-label="Clear search" hidden>&times;</button>
</div>
<span class="count" id="count" role="status" aria-live="polite">51 examples</span>
<span class="count" id="count" role="status" aria-live="polite">52 examples</span>
<div class="density" role="group" aria-label="Card density">
<button class="density-btn" data-density="compact" type="button" aria-pressed="false">Compact</button>
<button class="density-btn" data-density="detailed" type="button" aria-pressed="false">Detailed</button>
Expand Down Expand Up @@ -313,6 +313,7 @@ <h1>Examples Gallery</h1>
<button class="chip" data-tag="rendering" type="button">rendering</button>
<button class="chip" data-tag="sequencer" type="button">sequencer</button>
<button class="chip" data-tag="shape-keys" type="button">shape-keys</button>
<button class="chip" data-tag="showcase" type="button">showcase</button>
<button class="chip" data-tag="sky" type="button">sky</button>
<button class="chip" data-tag="transform" type="button">transform</button>
<button class="chip" data-tag="transforms" type="button">transforms</button>
Expand Down Expand Up @@ -884,6 +885,17 @@ <h2><a href="gn-socket-rename/">gn-socket-rename</a></h2>
<a class="card-link" href="gn-socket-rename/">View example <span aria-hidden="true">&rarr;</span></a>
</div>
</article>
<article class="card" data-tags="mesh export showcase">
<a class="card-media" href="shipping-crate/" aria-label="shipping-crate example detail page">
<img src="assets/shipping-crate-hero.webp" alt="shipping-crate — A procedural shipping crate through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="shipping-crate/">shipping-crate</a></h2>
<p class="teaches">A procedural shipping crate through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.</p>
<p class="witnesses"><span class="tag">witnesses</span> Recomputed: 552 tris, two materials, UVs in 0..1 with zero AABB overlap, outer AABB 1.256×0.856×0.748 m, LOD ratios in band (5.2 COLLAPSE more aggressive), convex collider 20 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.</p>
<a class="card-link" href="shipping-crate/">View example <span aria-hidden="true">&rarr;</span></a>
</div>
</article>
</div>
<p class="noresults" id="noResults" hidden>No examples match the current filters.
<button class="chip" id="resetFilters" type="button">Clear search and tags</button></p>
Expand Down
Loading
Loading