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
1 change: 1 addition & 0 deletions .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"examples/gltf-export-roundtrip",
"examples/gltf-skin-roundtrip",
"examples/gn-instance-grid",
"examples/gn-modifier-inputs",
"examples/gn-sdf-remesh",
"examples/gp-lineart-contour",
"examples/grease-pencil-rosette",
Expand Down
18 changes: 13 additions & 5 deletions .github/workflows/blender-smoke.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: Blender Smoke Test

# Executes the snippets' and skills' headline examples inside REAL Blender, headless,
# on the current stable (5.1.x) and the active LTS (4.5.x), and fails on any error or
# on the current stable (5.2.x LTS) and the fallback LTS (4.5.x) on every PR,
# plus 5.1.x on the weekly cron, and fails on any error or
# empty-output assertion. py_compile (in validate.yml) cannot catch API-level regressions
# like the EEVEE-id inversion, the slotted-actions boundary, the driver TypeError, or the
# dead SDF link -- this gate runs the code so those surface in CI, not in users' files.
Expand All @@ -24,13 +25,11 @@ jobs:
smoke:
name: Blender ${{ matrix.series }} smoke
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- series: "5.1" # current stable
- series: "4.5" # active LTS
series: ${{ github.event_name == 'schedule' && fromJSON('["5.2","5.1","4.5"]') || fromJSON('["5.2","4.5"]') }}
steps:
- uses: actions/checkout@v7

Expand Down Expand Up @@ -214,6 +213,15 @@ jobs:
xvfb-run -a "$BLENDER" --background \
--python examples/gn-instance-grid/gn_instance_grid.py --

- name: Shipped example - GN modifier inputs (5.1 dict vs 5.2 RNA)
run: |
set -euo pipefail
# Frame-independent check only (no render): one GN tree, three modifier
# copies; writes Scale 1/2/3 via version-appropriate path; asserts
# readback and evaluated Z-extent match. Exits non-zero on failure.
xvfb-run -a "$BLENDER" --background \
--python examples/gn-modifier-inputs/gn_modifier_inputs.py --

- name: Shipped example - shape-key blend (data API + evaluated mesh)
run: |
set -euo pipefail
Expand Down
24 changes: 13 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ repeats the other.
## Repository overview

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
for Blender Python development. The repo targets **Blender 5.2 LTS** (current
stable) with a **Blender 4.5 LTS** fallback. **Blender 5.1** is prior stable. 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 is 12 skills, 6 rules, 2 templates, 17 snippets, and 44
The content base is 12 skills, 6 rules, 2 templates, 17 snippets, and 45
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
Expand All @@ -35,7 +35,7 @@ Blender-Developer-Tools/
rules/<rule-name>.mdc # 6 rule files
templates/<template-name>/ # 2 starter templates
snippets/<snippet-name>.py # 17 standalone Python snippets
examples/<name>/ # 40 runnable smoke-gated examples (+ gallery.json)
examples/<name>/ # 45 runnable smoke-gated examples (+ gallery.json)
examples/gallery_framing.py # shared Layer 1 framing measurement (render path only)
scripts/build_gallery.py # generates docs/gallery/ (stdlib only)
scripts/site/ # vendored landing-page build (build_site.py + template)
Expand Down Expand Up @@ -76,7 +76,7 @@ Blender-Developer-Tools/
- **Smoke jobs do not re-run on the merge SHA.** `blender-smoke.yml` triggers
on `pull_request` (plus a weekly schedule and manual dispatch) — there is no
`push` trigger. The correct post-merge evidence for example changes is:
both Blender smoke jobs (4.5 LTS and 5.1) passed on the PR head SHA that
both Blender smoke jobs (5.2 LTS and 4.5 LTS) passed on the PR head SHA that
became the sole squash-merged commit, with the actual binary versions
confirmed in the job logs.
- **Post-merge, verify green on `main`:** Release (`release.yml`), Validate
Expand All @@ -97,12 +97,13 @@ Blender-Developer-Tools/

## Blender version targeting

- Primary: **Blender 5.1.x** (current stable). All examples assume 5.1
- Primary: **Blender 5.2 LTS** (current stable). All examples assume 5.2
unless otherwise stated.
- Prior stable: **Blender 5.1**. Skills document 5.1-only contracts where they
still matter; weekly smoke keeps a 5.1 leg.
- Fallback: **Blender 4.5 LTS**. Skills and the extension template note 4.5
compatibility where it matters (slotted actions bridge, property delete,
manifest fields).
- Future: a 5.2 LTS sweep is planned for July 2026 (see `ROADMAP.md`).
manifest fields, NodesModifier dict inputs).

When a 4.x and 5.x API genuinely diverge, skills must show both code paths,
not just the 5.x one. The `slotted-actions-animation` skill is the load-bearing
Expand Down Expand Up @@ -155,8 +156,8 @@ way, and a one-paragraph rationale. 30 to 80 lines is the right size.
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
plus snippet/template smoke tests inside REAL headless Blender, on
5.2 LTS and 4.5 LTS for every PR (5.1 on the weekly cron). 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.
Expand All @@ -176,7 +177,8 @@ way, and a one-paragraph rationale. 30 to 80 lines is the right size.

## Where to look for canonical references

- Blender 5.1 Python API: https://docs.blender.org/api/current/
- Blender 5.2 LTS Python API: https://docs.blender.org/api/current/
- Blender 5.1 Python API: https://docs.blender.org/api/5.1/
- Blender 4.5 LTS Python API: https://docs.blender.org/api/4.5/
- Extensions Platform reference: https://docs.blender.org/manual/en/latest/advanced/extensions/index.html
- Release notes (`developer.blender.org`): https://developer.blender.org/
Expand Down
19 changes: 10 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,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.46.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.
The **Blender Developer Tools** repository is at **v0.46.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.2 LTS** (current stable) with **Blender 4.5 LTS** fallback. **Blender 5.1** is prior stable. There is no MCP server; content is consumed directly by the AI when working in Blender add-on or scripting projects.

**Version:** 0.46.1
**License:** CC-BY-NC-ND-4.0
Expand All @@ -21,7 +21,7 @@ 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, 44 total (+ gallery.json)
examples/<name>/ - Runnable smoke-gated examples, 45 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
Expand Down Expand Up @@ -82,11 +82,11 @@ 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 (44)
## Examples (45)

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 4.5 LTS and 5.1 in `blender-smoke.yml`;
`--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);
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
Expand All @@ -100,8 +100,8 @@ cross-example import mechanism (see its docstring).

## Blender Runtime Discovery

- Local Blender binaries: check `.scratch/` at the repo root **first** — some machines have no system Blender install, and a prior agent run downloads official releases there (e.g. `.scratch/5.1/blender-5.1.x-.../blender[.exe]`). Then check system installs. Do not probe blindly; locate the binary, run it, and state the **exact binary path and the version the binary itself reports** in every report.
- **5.1 is the local check version. 4.5 LTS is exercised by CI when unavailable locally.** 4.4 is not a substitute for 4.5 and must never be reported as 4.5.
- Local Blender binaries: check `.scratch/` at the repo root **first** — some machines have no system Blender install, and a prior agent run downloads official releases there (e.g. `.scratch/5.2/blender-5.2.x-.../blender[.exe]`). Then check system installs. Do not probe blindly; locate the binary, run it, and state the **exact binary path and the version the binary itself reports** in every report.
- **5.2 LTS is the local check version. 4.5 LTS is exercised by CI when unavailable locally.** 4.4 is not a substitute for 4.5 and must never be reported as 4.5.
- **CI floats within each series:** `blender-smoke.yml` resolves the highest published point release at run time (`sort -V | tail -1` on the download listing), so a local 4.5.x may lag CI — its 4.5 job ran 4.5.12 LTS on PR #107 while local `.scratch/` held 4.5.11. Either way, state the exact version the binary reports.
- If `.scratch/` lacks a needed version, download an official release from download.blender.org into it. `.scratch` is gitignored.
- In scripts, version-branch on the `bpy.app.version` tuple, never on `bpy.app.version_string` — it reads e.g. `"4.5.11 LTS"`, not bare semver.
Expand All @@ -127,7 +127,7 @@ Stage with **explicit paths only** — never `git add -A` or `git add .`. Cursor
- **Asset-sheet gate (asset-type examples — game props/kits):** composite the hero asset rendered alone (neutral three-quarter view, plain studio lighting, no staging tricks, no labels, no comparison props) beside the pinned asset-quality reference set — currently `collision-hull-proxy`, `custom-normals-shade`, `vertex-weight-limit`, `lod-decimate-chain` — rendered the same way; commit under `docs/gallery/asset-sheets/`, link it in the PR body, and report a verdict. The asset ships only if it is not identifiable as the least-designed object in that lineup — a strong scene can carry a weak model; this gate removes the scene. **This list is the canonical home of the reference set** — update it here when a new asset outclasses a member. The measurable floors behind the gate (naming, material variation, edge treatment) live in `examples/gallery_asset_quality.py` — render path only, same call pattern as `gallery_framing`, exit 11 on violation — with the calibration table and dropped-floor evidence in `docs/VISUAL-STYLE.md` § Asset quality.
- **Falsification:** every check must be proven to fail once — break the contract, observe the non-zero exit, restore — with the probe and the measured error reported in the PR body. An assertion that cannot fail witnesses nothing.
- **After gallery regeneration** (`python scripts/build_gallery.py`), read the **generated HTML** character by character — the `<img alt>` text and witnesses callouts in `docs/gallery/index.html` and `docs/gallery/<name>/index.html` — not just `examples/gallery.json`. Precedent: the `teaches.split(".")[0]` bug truncated 14/21 card alts at dotted API paths like `bmesh.ops` while the source JSON looked fine (fixed in PR #68).
- **Playwright gallery captures:** gallery `<img>` tags lazy-load, so force them first (`document.querySelectorAll('img').forEach(i => i.loading = 'eager')`, then wait). **Scroll the target card into view and take a viewport capture** — `scrollIntoView({block:'center', behavior:'instant'})`, short wait, `browser_take_screenshot` with `fullPage` omitted. A `fullPage` capture is NOT a workaround: on a tall gallery page it renders every card image blank even when the images are verified loaded (`complete === true`, `naturalWidth === 1280`, `opacity === 1`) — measured on the 44-card grid at 1425x4516. Verify load state via `browser_evaluate` rather than trusting the pixels.
- **Playwright gallery captures:** gallery `<img>` tags lazy-load, so force them first (`document.querySelectorAll('img').forEach(i => i.loading = 'eager')`, then wait). **Scroll the target card into view and take a viewport capture** — `scrollIntoView({block:'center', behavior:'instant'})`, short wait, `browser_take_screenshot` with `fullPage` omitted. A `fullPage` capture is NOT a workaround: on a tall gallery page it renders every card image blank even when the images are verified loaded (`complete === true`, `naturalWidth === 1280`, `opacity === 1`) — measured on the 45-card grid at 1425x4516. Verify load state via `browser_evaluate` rather than trusting the pixels.

## Example-Run Process

Expand All @@ -149,7 +149,7 @@ The AI consumes content via:

## Key Conventions

- **Blender versions**: 5.1 primary, 4.5 LTS fallback. Skills must show both code paths when 4.x and 5.x APIs diverge.
- **Blender versions**: 5.2 LTS primary, 5.1 prior stable, 4.5 LTS fallback. Skills must show both code paths when 4.x and 5.x APIs diverge, and the 5.1-vs-5.2 NodesModifier input split.
- **Properties as annotations**: `my_prop: bpy.props.FloatProperty(...)` (correct), not `my_prop = bpy.props.FloatProperty(...)` (deprecated).
- **bmesh memory**: every `bmesh.new()` must be paired with `bm.free()` in a `try`/`finally`.
- **No `bpy.ops` in tight loops**: use `bpy.data.*` and `bmesh` for bulk work.
Expand All @@ -160,7 +160,8 @@ The AI consumes content via:

| Area | URL |
| --- | --- |
| Python API (5.1) | https://docs.blender.org/api/current/ |
| Python API (5.2 LTS) | https://docs.blender.org/api/current/ |
| Python API (5.1) | https://docs.blender.org/api/5.1/ |
| Python API (4.5 LTS) | https://docs.blender.org/api/4.5/ |
| Extensions Platform | https://docs.blender.org/manual/en/latest/advanced/extensions/index.html |
| Release notes | https://developer.blender.org/ |
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ templates/

## Blender Version Targeting

Content targets **Blender 5.1** as primary, with **Blender 4.5 LTS** as fallback. When the API differs, branch on `bpy.app.version` and document both paths. Example:
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:

```python
if bpy.app.version >= (5, 0, 0):
Expand Down
Loading
Loading