Skip to content

Commit 868c9c8

Browse files
feat: witness Geometry Nodes bundle round-trip
5.x RNA is NodeCombineBundle; 4.5 skips via min_version 5.0. Count-only is not enough — bbox and named attribute are the second and third axes. Standing scalar-second-axis rule added to the example prompt. Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 7a6fac9 commit 868c9c8

11 files changed

Lines changed: 363 additions & 13 deletions

File tree

‎.cursor-plugin/plugin.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"examples/driver-wave",
7777
"examples/gltf-export-roundtrip",
7878
"examples/gltf-skin-roundtrip",
79+
"examples/gn-bundle-roundtrip",
7980
"examples/gn-instance-grid",
8081
"examples/gn-modifier-inputs",
8182
"examples/gn-sdf-remesh",

‎AGENTS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ a `.cursor-plugin/plugin.json` manifest so the ecosystem drift checker
2020
classifies it as a `cursor-plugin`. This is content the AI loads when the user
2121
asks Blender questions or works on Blender add-ons in Cursor or Claude Code.
2222

23-
The content base is 13 skills, 6 rules, 2 templates, 17 snippets, and 48
23+
The content base is 13 skills, 6 rules, 2 templates, 17 snippets, and 49
2424
examples (counts are CI-enforced against README.md and the manifest). The full
2525
inventory tables and per-item purposes live in `CLAUDE.md`. Example anatomy
2626
and authoring rules: copy `examples/bmesh-gear/`; the render look is specified
@@ -35,7 +35,7 @@ Blender-Developer-Tools/
3535
rules/<rule-name>.mdc # 6 rule files
3636
templates/<template-name>/ # 2 starter templates
3737
snippets/<snippet-name>.py # 17 standalone Python snippets
38-
examples/<name>/ # 48 runnable smoke-gated examples (+ gallery.json)
38+
examples/<name>/ # 49 runnable smoke-gated examples (+ gallery.json)
3939
examples/gallery_framing.py # shared Layer 1 framing measurement (render path only)
4040
scripts/build_gallery.py # generates docs/gallery/ (stdlib only)
4141
scripts/site/ # vendored landing-page build (build_site.py + template)

‎CLAUDE.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ skills/<skill-name>/SKILL.md - AI workflow definitions, 13 total
2121
rules/<rule-name>.mdc - Anti-pattern rules, 6 total
2222
templates/<template-name>/ - Starter projects, 2 total
2323
snippets/<snippet-name>.py - Standalone code patterns, 17 total
24-
examples/<name>/ - Runnable smoke-gated examples, 48 total (+ gallery.json)
24+
examples/<name>/ - Runnable smoke-gated examples, 49 total (+ gallery.json)
2525
scripts/build_gallery.py - Regenerates docs/gallery/ from gallery.json (stdlib only)
2626
scripts/site/ - Vendored landing-page build (Jinja2)
2727
docs/gallery/ - Committed generated gallery pages + hero renders
@@ -83,7 +83,7 @@ v0.1.0: canonical object creation and deletion, depsgraph evaluated mesh, bmesh
8383

8484
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'`.
8585

86-
## Examples (48)
86+
## Examples (49)
8787

8888
Runnable scripts at `examples/<name>/`, each asserting a real API contract with
8989
deterministic checks (exit non-zero on failure) and optionally rendering a still via

‎README.md‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
</p>
1919

2020
<p align="center">
21-
<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>
21+
<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>
2222
</p>
2323

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

379379
<details>
380-
<summary><strong>Geometry Nodes</strong> — 4 examples</summary>
380+
<summary><strong>Geometry Nodes</strong> — 5 examples</summary>
381381

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

439+
</td>
440+
</tr>
441+
<tr>
442+
<td width="46%" valign="middle">
443+
check-only, no gallery still — item-name round-trip is not thumbnail-legible
444+
</td>
445+
<td valign="middle">
446+
447+
### [gn-bundle-roundtrip](examples/gn-bundle-roundtrip/)
448+
449+
Combine / Separate Bundle on 5.x (`NodeCombineBundle`, not
450+
`GeometryNodeCombineBundle`). Asserts evaluated 8/6 plus x-extent
451+
`[0.5, 2.5]` and POINT `bundle_mark=0.314159` — count-only is a cube that
452+
never entered the bundle. Skips 4.5 (`min_version` 5.0); `--force-run`
453+
fails creating the 5.x RNA.
454+
439455
</td>
440456
</tr>
441457
</table>

‎ROADMAP.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
9494
- ~~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
9595
- ~~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
9696
- ~~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)
97+
- ~~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)
9798
- 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
9899
- ~~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.
99100
- `modal-operators` skill -- `invoke` returning `RUNNING_MODAL`, the `modal()` event handler, modal cancellation patterns

‎docs/new-example-prompt.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ you write code:
3535
pose, swap the buffer, use the legacy path) and confirm the check exits non-zero,
3636
then restore it. An assertion that cannot fail witnesses nothing. State in the
3737
README what failure each check would catch.
38+
- When the primary assertion is a scalar (a vert count, a file size, a single
39+
float), check whether a broken construction can produce the same scalar. If
40+
it can, the scalar is not a sufficient witness — add a second axis. Worked
41+
example: `examples/gn-zone-iterate/` `--no-offset` still hits Repeat
42+
`8×(1+N)` verts by joining N+1 cubes at the origin; X-centers are the
43+
second axis. Count-only would have been green.
3844
- Its correctness is independently derivable. The best checks compare Blender's
3945
output against a closed-form or independently computed expectation
4046
(re-implemented math, a round-trip through a raw buffer, a known geometric

‎docs/technical-audit.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ are 4.3+). Not a version split.
6060
- Taught: `skills/geometry-nodes-python/SKILL.md`
6161
- Witness: `examples/gn-zone-iterate/`
6262

63+
### 2026-09-09 — Bundle RNA renamed; 4.5 is experimental
64+
65+
5.x Combine / Separate Bundle RNA is `NodeCombineBundle` /
66+
`NodeSeparateBundle`. `GeometryNodeCombineBundle` is undefined on 5.2
67+
and is the 4.5 experimental id. 4.5 LTS evaluates empty unless
68+
`preferences.experimental.use_bundle_and_closure_nodes` is on; with the
69+
flag the same closed form lands. Official contract is 5.0+. Item names
70+
on `bundle_items` are load-bearing. Vert count alone is not a witness.
71+
72+
- Taught: `skills/geometry-nodes-python/SKILL.md`
73+
- Witness: `examples/gn-bundle-roundtrip/`
74+
6375
### Contrast — NodesModifier dict assignment (5.2, was in the notes)
6476

6577
`mod[identifier] = value` raises `TypeError` on 5.2 (`id properties not
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Geometry Nodes bundle round-trip
2+
3+
Combine Bundle / Separate Bundle packing a cube plus Scale, Offset, and
4+
Mark — witnessing [`geometry-nodes-python`](../../skills/geometry-nodes-python/SKILL.md)
5+
item-name round-trip, not tree structure.
6+
7+
5.x RNA is `NodeCombineBundle` / `NodeSeparateBundle`.
8+
`GeometryNodeCombineBundle` is the **4.5 experimental** id and is undefined
9+
on 5.2. 4.5 LTS keeps the old names behind
10+
`preferences.experimental.use_bundle_and_closure_nodes` (default **off**);
11+
unflagged evaluation is empty. This example skips on 4.5
12+
(`SMOKE_SKIP: Bundles require Blender 5.0+`, exit 77, catalog
13+
`min_version` 5.0). `--force-run` bypasses the skip and creates the 5.x
14+
RNA so 4.5 fails for that reason — not a hidden pass.
15+
16+
Closed form (1 m cube, Scale=2, Offset=`(1.5, 0, 0)`, Mark=`0.314159`):
17+
18+
- verts/faces = 8/6
19+
- x-extent `[0.5, 2.5]`
20+
- POINT `bundle_mark` = `0.314159` on all 8 verts
21+
22+
**Count alone is not enough.** A cube that never entered the bundle is
23+
still 8/6 (`--bypass`). Bbox from the unpacked Scale/Offset is the second
24+
axis; the named attribute from the unpacked Float is the third.
25+
26+
No gallery still. The contract is item names + typed round-trip; the
27+
visible mesh is one cube and the attribute is invisible. Failure would
28+
not read at thumbnail.
29+
30+
**What failure each check would catch:**
31+
32+
- exit 77 — Blender &lt; 5.0 and not `--force-run`
33+
- exit 2 — `GeometryNodeCombineBundle` on 5.x (`--legacy-rna`)
34+
- exit 3 — pairing/names broken (`--mismatch` → 0/0; `--force-run` on 4.5
35+
raises `NodeCombineBundle` undefined, caught as exit 1)
36+
- exit 4 — count matches but unpacked Scale/Offset did not (`--bypass`,
37+
`--pack-scale 1`)
38+
- exit 5 — bbox matches but `bundle_mark` did not travel through the bundle
39+
40+
## Run
41+
42+
```bash
43+
blender --background --python gn_bundle_roundtrip.py --
44+
blender --background --python gn_bundle_roundtrip.py -- --force-run
45+
```

0 commit comments

Comments
 (0)