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
20 changes: 17 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ Guidance for AI coding agents working on the Blender Developer Tools repository.

## Repository overview

Skills, rules, snippets, and a starter template for Blender Python development.
Skills, rules, snippets, starter templates, and runnable smoke-gated examples
for Blender Python development.
The repo targets **Blender 5.1** (current stable) with a **Blender 4.5 LTS**
fallback. There is no MCP server. It ships 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 as of v0.2.0:
The content base (counts are CI-enforced against README.md and the manifest):

- 12 skills covering scaffolding, operators, panels, properties, mesh and
bmesh, headless batch scripts, slotted-actions animation (5.x), programmatic
Expand All @@ -26,6 +27,11 @@ The content base as of v0.2.0:
- 2 templates: `extension-addon-template` for Extensions Platform add-ons,
and `headless-batch-script-template` for unattended batch jobs.
- 17 snippets covering canonical patterns.
- 12 examples under `examples/<name>/`: runnable scripts that assert a real
API contract with deterministic checks, exit non-zero on failure, and
optionally render a still via `--output`. Each is executed headless on
Blender 4.5 LTS and 5.1 by `blender-smoke.yml`; its render ships in the
site gallery. Anatomy and authoring rules: copy `examples/bmesh-gear/`.

## Repository structure

Expand All @@ -35,7 +41,11 @@ Blender-Developer-Tools/
rules/<rule-name>.mdc # 6 rule files
templates/<template-name>/ # 2 starter templates
snippets/<snippet-name>.py # 17 standalone Python snippets
.github/workflows/ # validate, drift-check, release, label-sync
examples/<name>/ # 12 runnable smoke-gated examples (+ gallery.json)
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
.github/workflows/ # validate, blender-smoke, drift-check, release, pages, label-sync
.github/dependabot.yml
AGENTS.md, CLAUDE.md, README.md, ROADMAP.md, CHANGELOG.md
CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md
Expand Down Expand Up @@ -116,6 +126,10 @@ way, and a one-paragraph rationale. 30 to 80 lines is the right size.
every skill, rule, snippet, template, and example on disk must be listed,
and the manifest `version` must equal `VERSION`. The release pipeline owns
the manifest `version` line (see `release.yml` below) — never hand-edit it.
- `blender-smoke.yml` executes every shipped example (check-only, no render)
plus snippet/template smoke tests inside REAL headless Blender, on both
4.5 LTS and 5.1, on every PR and a weekly schedule. A new example is not
shipped until it has a step here.
- `drift-check.yml` consumes `Developer-Tools-Directory/.github/actions/
drift-check@v1.15` to enforce ecosystem standards-version markers.
- `release.yml` auto-bumps the version, tags, force-updates floating tags
Expand Down
25 changes: 22 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

The **Blender Developer Tools** repository is at **v0.9.1**. It packages skills, rules, snippets, and starter templates for Blender Python development with Cursor and Claude Code. Coverage targets **Blender 5.1** (current stable) with **Blender 4.5 LTS** fallback. There is no MCP server; content is consumed directly by the AI when working in Blender add-on or scripting projects.
The **Blender Developer Tools** repository is at **v0.9.1**. It packages skills, rules, snippets, starter templates, and runnable smoke-gated examples for Blender Python development with Cursor and Claude Code. Coverage targets **Blender 5.1** (current stable) with **Blender 4.5 LTS** fallback. There is no MCP server; content is consumed directly by the AI when working in Blender add-on or scripting projects.

**Version:** 0.9.1
**License:** CC-BY-NC-ND-4.0
Expand All @@ -19,6 +19,10 @@ skills/<skill-name>/SKILL.md - AI workflow definitions, 12 total
rules/<rule-name>.mdc - Anti-pattern rules, 6 total
templates/<template-name>/ - Starter projects, 2 total
snippets/<snippet-name>.py - Standalone code patterns, 17 total
examples/<name>/ - Runnable smoke-gated examples, 12 total (+ gallery.json)
scripts/build_gallery.py - Regenerates docs/gallery/ from gallery.json (stdlib only)
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 Expand Up @@ -76,9 +80,24 @@ v0.1.0: canonical object creation and deletion, depsgraph evaluated mesh, bmesh

v0.2.0: Principled BSDF material, driver-with-custom-function via `driver_namespace`, application handler registration, shader node group with cross-version `interface` API, `foreach_get` bulk vertex read, version-branch skeleton, and USD export with `evaluation_mode='RENDER'`.

## Examples (12)

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 twelve run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`;
their renders ship in the site gallery at `docs/gallery/`. `examples/gallery.json` is the
gallery's source of truth. When authoring a new one, copy the anatomy of
`examples/bmesh-gear/` (script structure, README shape, dark-studio render recipe) and
wire all of: gallery.json entry, `.cursor-plugin/plugin.json` examples array (CI-gated),
a `blender-smoke.yml` step, a README gallery row, hero webp (1280×720) in
`docs/gallery/assets/` + preview webp (1200×675), then run `python scripts/build_gallery.py`.

## Development Workflow

This is a content repository, no build step. Edit `SKILL.md`, `.mdc`, `.py`, and `.toml` files directly.
This is a content repository, no build step for skills/rules/snippets/templates — edit
`SKILL.md`, `.mdc`, `.py`, and `.toml` files directly. The website is generated:
`scripts/build_gallery.py` (stdlib) regenerates `docs/gallery/` and must be re-run after
touching `examples/`; the landing page builds from `scripts/site/` at deploy time.

The AI consumes content via:

Expand Down Expand Up @@ -109,7 +128,7 @@ The release pipeline is automated via `release.yml` on push to `main` for conten

When adding content to a future version:

1. Add files under `skills/`, `rules/`, `snippets/`, or `templates/`.
1. Add files under `skills/`, `rules/`, `snippets/`, `templates/`, or `examples/`.
2. Update README.md aggregate counts (the `validate-counts` job enforces correctness).
3. Update ROADMAP.md candidate pool entries.
4. Use `feat:` for new content, `fix:` for corrections.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
---

<p align="center">
<strong>Skills, rules, snippets, and a starter template for Blender Python development</strong>
<strong>Skills, rules, snippets, templates, and runnable examples for Blender Python development</strong>
</p>

<p align="center">
Expand All @@ -17,14 +17,14 @@
</p>

<p align="center">
<strong>12 skills</strong> &nbsp;&bull;&nbsp; <strong>6 rules</strong> &nbsp;&bull;&nbsp; <strong>2 templates</strong> &nbsp;&bull;&nbsp; <strong>17 snippets</strong>
<strong>12 skills</strong> &nbsp;&bull;&nbsp; <strong>6 rules</strong> &nbsp;&bull;&nbsp; <strong>2 templates</strong> &nbsp;&bull;&nbsp; <strong>17 snippets</strong> &nbsp;&bull;&nbsp; <strong>12 examples</strong>
</p>

---

## Overview

This repository ships **12 skills, 6 rules, 2 templates, and 17 snippets** for Blender Python development targeting Blender 5.1 (current stable) with Blender 4.5 LTS fallback support.
This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 12 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.

Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Audit pass on v0.1.0 content: standards-version markers bumped from `1.9.1` to `

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

- **Fleet Pages facelift + examples support** (infra) -- ship as ONE coordinated change to the meta-repo `site-template/template.html.j2` + `build_site.py`, since both need a template edit and two fleet-wide pushes is worse than one. (a) examples discovery: load `examples/gallery.json` and render an Examples grid + a nav link to it (the nav link closes the landing->gallery cross-link gap, impossible today without a template edit); (b) landing facelift adopting the direction proven by this repo's gallery (shared light/dark tokens, fluid hero type scale, the card system, `:focus-visible`, reduced-motion). This repo's local gallery (`examples/gallery.json` + `docs/gallery/`, see `docs/gallery/DESIGN_NOTES.md`) is the **prototype and convergence target**: once the shared template reads the same `gallery.json` schema, the local generator (`scripts/build_gallery.py`) and page are retired -- a lift-and-shift, not a rewrite. The Option-2 cycle must read the full template-consumer set, account for floating-main consumption (every repo updates on next deploy), and prove backward compatibility (a repo with no `gallery.json` renders unchanged) before any meta-repo merge. No confirmed rendering bug today (the suspected Skills/Rules overlap was verified to be a normal collapsed accordion), so the cross-link discoverability gap is the main driver.
- ~~Fleet Pages facelift + examples support~~ **RESOLVED differently (2026-07-03)**: the meta-repo migration was dropped — the fleet template only scaffolds new tools, and each tool's site evolves independently after that. This repo vendored the site build into `scripts/site/`, redesigned landing + gallery as the Blender-viewport system (see `docs/gallery/DESIGN_NOTES.md`), added the examples grid, nav link, and full hero stats locally. `scripts/build_gallery.py` and `examples/gallery.json` are now permanent, not a prototype awaiting lift-and-shift.
- `modal-operators` skill -- `invoke` returning `RUNNING_MODAL`, the `modal()` event handler, modal cancellation patterns
- `usd-pipelines` skill -- USD export options, `evaluation_mode`, instancing, the USD vs glTF tradeoffs
- `mathutils-patterns` skill -- `mathutils.Vector`, `Matrix`, `Quaternion`, common transforms, the `@` operator
Expand Down
Binary file modified docs/gallery/assets/gn-instance-grid-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/gallery/assets/temp-override-join-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 9 additions & 4 deletions docs/gallery/curve-bevel-arc/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,11 @@ <h2>Source</h2>
Witnesses that renderable tubes are authored on `bpy.types.Curve` directly
(`splines.new(&#x27;BEZIER&#x27;)`, `bezier_points`, `bevel_depth`, `use_fill_caps`) —
not by meshing first or calling curve operators. The check asserts the
closed-form point count, bevel depth, filled-cap topology, and that the
depsgraph-evaluated mesh has a Z span of a tube whose centerline sits at
`z = bevel_depth` (resting on the floor).
closed-form point count and bevel depth, the closed-form Z span (tube
centerline at `z = bevel_depth`, resting on the floor) and X span, plus the
evaluated vert/face counts as a MEASURED regression gate — curve tessellation
has no simple closed form, so those two constants pin today&#x27;s behavior (see
EXPECT_VERTS below for how to re-measure if a future Blender retessellates).

By default it runs only the correctness check (no render) — the CI smoke
check. Pass --output to also render a still:
Expand All @@ -222,7 +224,10 @@ <h2>Source</h2>
BEVEL = <span class="n">0.15</span>
BEVEL_RES = <span class="n">4</span>
RES_U = <span class="n">12</span>
<span class="c"># measured for the parameters above with use_fill_caps=True — identical on 4.4 and 5.1</span>
<span class="c"># MEASURED regression constants, not closed-form: curve-to-mesh tessellation</span>
<span class="c"># (rings x bevel segments + cap fans) has no simple formula. Verified identical</span>
<span class="c"># on 4.4, 4.5 LTS, and 5.1. If a future Blender changes tessellation, re-measure</span>
<span class="c"># by printing len(em.vertices)/len(em.polygons) in check() and update these.</span>
EXPECT_VERTS = <span class="n">1044</span>
EXPECT_FACES = <span class="n">1028</span>

Expand Down
4 changes: 2 additions & 2 deletions docs/gallery/gn-instance-grid/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,9 @@ <h2>Source</h2>
light(<span class="s">&quot;Rim&quot;</span>, (<span class="n">1.5</span>, <span class="n">4.5</span>, <span class="n">2.0</span>), <span class="n">480.0</span>, <span class="n">4.0</span>, (<span class="n">1.0</span>, <span class="n">0.75</span>, <span class="n">0.45</span>))

cam_data = bpy.data.cameras.new(<span class="s">&quot;Cam&quot;</span>)
cam_data.lens = <span class="n">55.0</span>
cam_data.lens = <span class="n">50.0</span>
cam = bpy.data.objects.new(<span class="s">&quot;Cam&quot;</span>, cam_data)
cam.location = (<span class="n">3.6</span>, -<span class="n">4.2</span>, <span class="n">2.8</span>)
cam.location = (<span class="n">4.6</span>, -<span class="n">5.4</span>, <span class="n">3.4</span>)
scene.collection.objects.link(cam)
scene.camera = cam
track = cam.constraints.new(<span class="s">&#x27;TRACK_TO&#x27;</span>)
Expand Down
14 changes: 5 additions & 9 deletions docs/gallery/temp-override-join/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,11 @@ <h2>Source</h2>


<span class="k">def</span> join_with_temp_override(target, sources):
<span class="s">&quot;&quot;&quot;The contract this example witnesses: temp_override, not context.copy().&quot;&quot;&quot;</span>
view_layer = bpy.context.view_layer
<span class="k">for</span> obj <span class="k">in</span> bpy.data.objects:
obj.select_set(<span class="k">False</span>)
target.select_set(<span class="k">True</span>)
<span class="k">for</span> src <span class="k">in</span> sources:
src.select_set(<span class="k">True</span>)
view_layer.objects.active = target
<span class="s">&quot;&quot;&quot;The contract this example witnesses: temp_override, not context.copy().

The override alone fabricates the whole operator context — no select_set,
no view_layer.objects.active. That is the point: the scene&#x27;s real
selection state stays untouched.&quot;&quot;&quot;</span>
<span class="k">with</span> bpy.context.temp_override(
active_object=target,
selected_objects=[target, *sources],
Expand Down Expand Up @@ -374,7 +370,7 @@ <h2>Source</h2>
cam_data = bpy.data.cameras.new(<span class="s">&quot;Cam&quot;</span>)
cam_data.lens = <span class="n">50.0</span>
cam = bpy.data.objects.new(<span class="s">&quot;Cam&quot;</span>, cam_data)
cam.location = (<span class="n">4.2</span>, -<span class="n">5.2</span>, <span class="n">4.0</span>)
cam.location = (<span class="n">5.0</span>, -<span class="n">6.4</span>, <span class="n">4.6</span>)
scene.collection.objects.link(cam)
scene.camera = cam
track = cam.constraints.new(<span class="s">&#x27;TRACK_TO&#x27;</span>)
Expand Down
13 changes: 9 additions & 4 deletions examples/curve-bevel-arc/curve_bevel_arc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
Witnesses that renderable tubes are authored on `bpy.types.Curve` directly
(`splines.new('BEZIER')`, `bezier_points`, `bevel_depth`, `use_fill_caps`) —
not by meshing first or calling curve operators. The check asserts the
closed-form point count, bevel depth, filled-cap topology, and that the
depsgraph-evaluated mesh has a Z span of a tube whose centerline sits at
`z = bevel_depth` (resting on the floor).
closed-form point count and bevel depth, the closed-form Z span (tube
centerline at `z = bevel_depth`, resting on the floor) and X span, plus the
evaluated vert/face counts as a MEASURED regression gate — curve tessellation
has no simple closed form, so those two constants pin today's behavior (see
EXPECT_VERTS below for how to re-measure if a future Blender retessellates).

By default it runs only the correctness check (no render) — the CI smoke
check. Pass --output to also render a still:
Expand All @@ -20,7 +22,10 @@
BEVEL = 0.15
BEVEL_RES = 4
RES_U = 12
# measured for the parameters above with use_fill_caps=True — identical on 4.4 and 5.1
# MEASURED regression constants, not closed-form: curve-to-mesh tessellation
# (rings x bevel segments + cap fans) has no simple formula. Verified identical
# on 4.4, 4.5 LTS, and 5.1. If a future Blender changes tessellation, re-measure
# by printing len(em.vertices)/len(em.polygons) in check() and update these.
EXPECT_VERTS = 1044
EXPECT_FACES = 1028

Expand Down
4 changes: 2 additions & 2 deletions examples/gn-instance-grid/gn_instance_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,9 @@ def light(name, loc, energy, size, col):
light("Rim", (1.5, 4.5, 2.0), 480.0, 4.0, (1.0, 0.75, 0.45))

cam_data = bpy.data.cameras.new("Cam")
cam_data.lens = 55.0
cam_data.lens = 50.0
cam = bpy.data.objects.new("Cam", cam_data)
cam.location = (3.6, -4.2, 2.8)
cam.location = (4.6, -5.4, 3.4)
scene.collection.objects.link(cam)
scene.camera = cam
track = cam.constraints.new('TRACK_TO')
Expand Down
Binary file modified examples/gn-instance-grid/preview.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/temp-override-join/preview.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: 5 additions & 9 deletions examples/temp-override-join/temp_override_join.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,11 @@ def build_cubes():


def join_with_temp_override(target, sources):
"""The contract this example witnesses: temp_override, not context.copy()."""
view_layer = bpy.context.view_layer
for obj in bpy.data.objects:
obj.select_set(False)
target.select_set(True)
for src in sources:
src.select_set(True)
view_layer.objects.active = target
"""The contract this example witnesses: temp_override, not context.copy().

The override alone fabricates the whole operator context — no select_set,
no view_layer.objects.active. That is the point: the scene's real
selection state stays untouched."""
with bpy.context.temp_override(
active_object=target,
selected_objects=[target, *sources],
Expand Down Expand Up @@ -172,7 +168,7 @@ def light(name, loc, energy, size, col):
cam_data = bpy.data.cameras.new("Cam")
cam_data.lens = 50.0
cam = bpy.data.objects.new("Cam", cam_data)
cam.location = (4.2, -5.2, 4.0)
cam.location = (5.0, -6.4, 4.6)
scene.collection.objects.link(cam)
scene.camera = cam
track = cam.constraints.new('TRACK_TO')
Expand Down
Loading