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 @@ -76,6 +76,7 @@
"examples/driver-wave",
"examples/gltf-export-roundtrip",
"examples/gltf-skin-roundtrip",
"examples/gn-bundle-roundtrip",
"examples/gn-instance-grid",
"examples/gn-modifier-inputs",
"examples/gn-sdf-remesh",
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ 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 13 skills, 6 rules, 2 templates, 17 snippets, and 48
The content base is 13 skills, 6 rules, 2 templates, 17 snippets, and 49
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>/ # 48 runnable smoke-gated examples (+ gallery.json)
examples/<name>/ # 49 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
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ skills/<skill-name>/SKILL.md - AI workflow definitions, 13 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, 48 total (+ gallery.json)
examples/<name>/ - Runnable smoke-gated examples, 49 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 @@ -83,7 +83,7 @@ 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 (48)
## Examples (49)

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
Expand Down
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</p>

<p align="center">
<strong>13 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>48 examples</strong>
<strong>13 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>49 examples</strong>
</p>

<p align="center">
Expand Down Expand Up @@ -377,7 +377,7 @@ inside the effect. Per-frame sample renders assert the closed form (mid dips
</details>

<details>
<summary><strong>Geometry Nodes</strong> — 4 examples</summary>
<summary><strong>Geometry Nodes</strong> — 5 examples</summary>

<table>
<tr>
Expand Down Expand Up @@ -436,6 +436,22 @@ evaluated cubes against closed forms — Repeat `8×(1+N)` with X-centers at
nodes exist. Unpaired evaluates empty; For Each's main Geometry socket is a
passthrough.

</td>
</tr>
<tr>
<td width="46%" valign="middle">
check-only, no gallery still — item-name round-trip is not thumbnail-legible
</td>
<td valign="middle">

### [gn-bundle-roundtrip](examples/gn-bundle-roundtrip/)

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.

</td>
</tr>
</table>
Expand Down
1 change: 1 addition & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
- ~~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)
- ~~Geometry Nodes bundle round-trip~~ **SHIPPED** as `examples/gn-bundle-roundtrip/` — 5.x RNA `NodeCombineBundle` (`GeometryNodeCombineBundle` undefined on 5.2); closed form 8/6 + x `[0.5, 2.5]` + `bundle_mark=0.314159`; 4.5 SKIP via catalog `min_version` 5.0 (experimental flag off evaluates empty; `--force-run` hits 5.x RNA undefined)
- Tighten inverted smoke canaries: assert the expected `[FAIL]` marker text (`skipped on ... should run` / `missing post-exit sidecar`) alongside wrapper exit 1, so a canary that dies for the wrong reason does not satisfy the gate
- ~~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
Expand Down
6 changes: 6 additions & 0 deletions docs/new-example-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ you write code:
pose, swap the buffer, use the legacy path) and confirm the check exits non-zero,
then restore it. An assertion that cannot fail witnesses nothing. State in the
README what failure each check would catch.
- When the primary assertion is a scalar (a vert count, a file size, a single
float), check whether a broken construction can produce the same scalar. If
it can, the scalar is not a sufficient witness — add a second axis. Worked
example: `examples/gn-zone-iterate/` `--no-offset` still hits Repeat
`8×(1+N)` verts by joining N+1 cubes at the origin; X-centers are the
second axis. Count-only would have been green.
- Its correctness is independently derivable. The best checks compare Blender's
output against a closed-form or independently computed expectation
(re-implemented math, a round-trip through a raw buffer, a known geometric
Expand Down
12 changes: 12 additions & 0 deletions docs/technical-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ are 4.3+). Not a version split.
- Taught: `skills/geometry-nodes-python/SKILL.md`
- Witness: `examples/gn-zone-iterate/`

### 2026-09-09 — Bundle RNA renamed; 4.5 is experimental

5.x Combine / Separate Bundle RNA is `NodeCombineBundle` /
`NodeSeparateBundle`. `GeometryNodeCombineBundle` is undefined on 5.2
and is the 4.5 experimental id. 4.5 LTS evaluates empty unless
`preferences.experimental.use_bundle_and_closure_nodes` is on; with the
flag the same closed form lands. Official contract is 5.0+. Item names
on `bundle_items` are load-bearing. Vert count alone is not a witness.

- Taught: `skills/geometry-nodes-python/SKILL.md`
- Witness: `examples/gn-bundle-roundtrip/`

### Contrast — NodesModifier dict assignment (5.2, was in the notes)

`mod[identifier] = value` raises `TypeError` on 5.2 (`id properties not
Expand Down
45 changes: 45 additions & 0 deletions examples/gn-bundle-roundtrip/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Geometry Nodes bundle round-trip

Combine Bundle / Separate Bundle packing a cube plus Scale, Offset, and
Mark — witnessing [`geometry-nodes-python`](../../skills/geometry-nodes-python/SKILL.md)
item-name round-trip, not tree structure.

5.x RNA is `NodeCombineBundle` / `NodeSeparateBundle`.
`GeometryNodeCombineBundle` is the **4.5 experimental** id and is undefined
on 5.2. 4.5 LTS keeps the old names behind
`preferences.experimental.use_bundle_and_closure_nodes` (default **off**);
unflagged evaluation is empty. This example skips on 4.5
(`SMOKE_SKIP: Bundles require Blender 5.0+`, exit 77, catalog
`min_version` 5.0). `--force-run` bypasses the skip and creates the 5.x
RNA so 4.5 fails for that reason — not a hidden pass.

Closed form (1 m cube, Scale=2, Offset=`(1.5, 0, 0)`, Mark=`0.314159`):

- verts/faces = 8/6
- x-extent `[0.5, 2.5]`
- POINT `bundle_mark` = `0.314159` on all 8 verts

**Count alone is not enough.** A cube that never entered the bundle is
still 8/6 (`--bypass`). Bbox from the unpacked Scale/Offset is the second
axis; the named attribute from the unpacked Float is the third.

No gallery still. The contract is item names + typed round-trip; the
visible mesh is one cube and the attribute is invisible. Failure would
not read at thumbnail.

**What failure each check would catch:**

- exit 77 — Blender &lt; 5.0 and not `--force-run`
- exit 2 — `GeometryNodeCombineBundle` on 5.x (`--legacy-rna`)
- exit 3 — pairing/names broken (`--mismatch` → 0/0; `--force-run` on 4.5
raises `NodeCombineBundle` undefined, caught as exit 1)
- exit 4 — count matches but unpacked Scale/Offset did not (`--bypass`,
`--pack-scale 1`)
- exit 5 — bbox matches but `bundle_mark` did not travel through the bundle

## Run

```bash
blender --background --python gn_bundle_roundtrip.py --
blender --background --python gn_bundle_roundtrip.py -- --force-run
```
Loading
Loading