Skip to content

Commit cb1f2bc

Browse files
feat: add eval-mesh-datablock-name and mesh-automasking-settings examples (#147)
* docs: correct evaluated mesh datablock name claim in ROADMAP Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> * feat: add eval-mesh-datablock-name cross-version example Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> * feat: add mesh-automasking-settings move example Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> * docs: document version-gated falsifiers Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> --------- Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 7fc9a17 commit cb1f2bc

11 files changed

Lines changed: 565 additions & 11 deletions

File tree

‎.cursor-plugin/plugin.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"examples/degenerate-bevel-weld",
9494
"examples/depsgraph-export",
9595
"examples/driver-wave",
96+
"examples/eval-mesh-datablock-name",
9697
"examples/exit-pre-sidecar",
9798
"examples/export-preset-axis",
9899
"examples/gltf-export-roundtrip",
@@ -109,6 +110,7 @@
109110
"examples/light-link-studio",
110111
"examples/lightmap-uv-channel",
111112
"examples/lod-decimate-chain",
113+
"examples/mesh-automasking-settings",
112114
"examples/mesh-hygiene-audit",
113115
"examples/modular-kit-snap",
114116
"examples/ngon-triangulate",

‎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 16 skills, 9 rules, 3 templates, 27 snippets, and 57
23+
The content base is 16 skills, 9 rules, 3 templates, 27 snippets, and 59
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 # 9 rule files
3636
templates/<template-name>/ # 3 starter templates
3737
snippets/<snippet-name>.py # 27 standalone Python snippets
38-
examples/<name>/ # 55 runnable smoke-gated examples (+ gallery.json)
38+
examples/<name>/ # 59 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, 16 total
2121
rules/<rule-name>.mdc - Anti-pattern rules, 9 total
2222
templates/<template-name>/ - Starter projects, 3 total
2323
snippets/<snippet-name>.py - Standalone code patterns, 27 total
24-
examples/<name>/ - Runnable smoke-gated examples, 57 total (+ gallery.json)
24+
examples/<name>/ - Runnable smoke-gated examples, 59 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
@@ -99,7 +99,7 @@ v0.2.0: Principled BSDF material, driver-with-custom-function via `driver_namesp
9999

100100
AI asset pipeline track: `decimate_to_budget.py`, `convex_hull_collider.py`, `lod_chain.py` (helper duplicated, not imported), `gltf_draco_export.py`, `export_preset_unity.py`, `export_preset_godot.py`, `export_preset_unreal.py`, `setup_bake_target_image.py`, `bake_normal_high_to_low.py`, `save_baked_image.py`.
101101

102-
## Examples (57)
102+
## Examples (59)
103103

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

‎CONTRIBUTING.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,21 @@ and the product scripts that self-skip: `examples/gn-bundle-roundtrip/`,
143143
`examples/exit-pre-sidecar/`). A script under test prints `SMOKE_SKIP:` and
144144
exits 77; the runner records SKIP and returns 0 so the YAML step stays green.
145145

146+
**Version-gated falsifiers.** A falsifier for a cross-version removal or
147+
rename is version-gated by nature. It exits its documented code on versions
148+
where the API changed and exits 0 on versions where the old API still works.
149+
That is the correct witness: the naive script is still valid on the older
150+
binaries. It differs from every other falsifier in the tree (for example
151+
`--same-axis`), which is red on all three versions.
152+
153+
Do not "fix" these flags to fail unconditionally. The examples that behave
154+
this way:
155+
156+
- `examples/vse-linear-modifiers/` (`--assume-present`)
157+
- `examples/gn-socket-rename/` (`--legacy-ids`)
158+
- `examples/eval-mesh-datablock-name/` (`--assume-distinct-names`)
159+
- `examples/mesh-automasking-settings/` (`--assume-brush-attrs`)
160+
146161
## Standards-version Markers
147162

148163
Files that participate in ecosystem drift checking must carry a `standards-version` marker matching the current meta-repo `STANDARDS_VERSION` (which is decoupled from this repo's `VERSION`):

‎README.md‎

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

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

2424
<p align="center">
@@ -36,7 +36,7 @@
3636

3737
## Overview
3838

39-
This repository ships **16 skills, 9 rules, 3 templates, 27 snippets, and 57 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.
39+
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.
4040

4141
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.
4242

@@ -203,7 +203,7 @@ Gallery still is a dual-elevation diptych so the contract reads at thumbnail sca
203203
</details>
204204

205205
<details>
206-
<summary><strong>Mesh, curves &amp; text</strong> — 12 examples</summary>
206+
<summary><strong>Mesh, curves &amp; text</strong> — 13 examples</summary>
207207

208208
<table>
209209
<tr>
@@ -308,6 +308,20 @@ A depsgraph-evaluated export — builds a cube with `SUBSURF`, measures the eval
308308
`evaluated_get().to_mesh()` / `to_mesh_clear()`, and asserts `wm.obj_export` ships the
309309
modifier-applied geometry (exported vertex count == evaluated > base).
310310

311+
</td>
312+
</tr>
313+
<tr>
314+
<td width="46%" valign="middle">
315+
check-only, no gallery still — no geometry
316+
</td>
317+
<td valign="middle">
318+
319+
### [eval-mesh-datablock-name](examples/eval-mesh-datablock-name/)
320+
321+
`evaluated_get().data.name` is generic `Mesh` on 4.5.11 and 5.1.2 and equals
322+
the source name on 5.2.1. `to_mesh().name` stays the source name on all
323+
three. `--assume-distinct-names` is red only on 5.2.
324+
311325
</td>
312326
</tr>
313327
<tr>
@@ -587,7 +601,7 @@ and every orbit lands on its closed form.
587601
</details>
588602

589603
<details>
590-
<summary><strong>Context &amp; Grease Pencil</strong> — 4 examples</summary>
604+
<summary><strong>Context &amp; Grease Pencil</strong> — 5 examples</summary>
591605

592606
<table>
593607
<tr>
@@ -616,6 +630,21 @@ Custom ID properties are removed with `del id_block[key]`, not `property_unset`.
616630
IDs are built with `bpy.data.objects.new` — the snippet `__main__` keys off
617631
`active_object` and is dark headless. Same `del` on 4.5 LTS and 5.x.
618632

633+
</td>
634+
</tr>
635+
<tr>
636+
<td width="46%" valign="middle">
637+
check-only, no gallery still — no geometry
638+
</td>
639+
<td valign="middle">
640+
641+
### [mesh-automasking-settings](examples/mesh-automasking-settings/)
642+
643+
`MeshAutomaskingSettings` is absent on 4.5.11 and 5.1.2, present on 5.2.1.
644+
Old `Brush` automasking attributes are gone on 5.2; read
645+
`.mesh_automasking_settings` instead. `--assume-brush-attrs` is red only
646+
on 5.2.
647+
619648
</td>
620649
</tr>
621650
<tr>

‎ROADMAP.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
142142
- UV atlas **utilization** witness — coverage/wasted-texel closed forms for a packed lightmap atlas (the non-overlap, unit-square, margin, and active/active_render contracts shipped in `lightmap-uv-channel`; utilization is the remaining unbuilt slice of the old "UV atlas pack" candidate)
143143
- ~~VSE `use_linear_modifiers` removal~~ **SHIPPED** as `examples/vse-linear-modifiers/` — bool on 4.5.11 and 5.1.2, `AttributeError` on 5.2.1; `--assume-present` red only on 5.2; pathology, no gallery still
144144
- ~~GN Compare / Random Value socket identifier rename~~ **SHIPPED** as `examples/gn-socket-rename/` — `A_INT` / `Min_001` present on 4.5.11 and 5.1.2, absent on 5.2.1; enabled-name lookup wires all three; `--legacy-ids` red only on 5.2
145-
- GN evaluated `to_mesh().name` no longer equals the original mesh name (5.2 contract change)
146-
- `MeshAutomaskingSettings` move: old `Brush` automasking attributes gone on 5.2
145+
- ~~Evaluated mesh datablock `evaluated_get().data.name` is generic `Mesh` on 4.5.11 and 5.1.2 and equals the source name on 5.2.1 (`to_mesh().name` stays the source name on all three)~~ **SHIPPED** as `examples/eval-mesh-datablock-name/` — `evaluated_get().data.name` is `Mesh` on 4.5.11 and 5.1.2, `SourceMesh` on 5.2.1; `to_mesh().name` stays `SourceMesh` on all three; `--assume-distinct-names` red only on 5.2; pathology, no gallery still
146+
- ~~`MeshAutomaskingSettings` move: old `Brush` automasking attributes gone on 5.2~~ **SHIPPED** as `examples/mesh-automasking-settings/` — type absent on 4.5.11 and 5.1.2, present on 5.2.1; old `Brush` attrs gone; `--assume-brush-attrs` red only on 5.2; pathology, no gallery still
147147
- ~~GAMMA_CROSS blend-curve witness~~ **SHIPPED** as `examples/vse-gamma-cross/` — the cross blends in a gamma-0.5 space: `((1-t)·√A + t·√B)²` with `t = (frame − start)/duration`, never 1 inside the effect; mid-cross dips 0.115 below the sRGB lerp from crimson/teal (closed form (0.341, 0.349, 0.463) confirmed per frame); AgX-default sampling poisons the fit (0.146 red-channel error, `view_transform='Standard'` mandatory); deleting a consumed input orphans-and-deletes the effect — follow-up to `vse-cut-list`
148148
- Falsy `bpy_prop_collection` trap snippet: an empty collection is falsy, so `editor.strips or editor.sequences` silently falls through to the legacy accessor on an empty timeline — always branch on `hasattr`; likely generalizes across the API (found authoring `vse-cut-list`)
149149
- ~~Collision compound witness~~ **SHIPPED** as `examples/collision-hull-proxy/` — game-prop collision as a compound of convex pieces, each a `bmesh.ops.convex_hull` of a coarse `sec(π/n)`-inflated cage (containment 4.4e-08, watertight, positive signed volume, Euler 2, per-piece 255-face budget: body 70, caps 60×3, compound 250); a hull of the dense render mesh measures 380 faces — over budget — which is why pipelines hull cages; proud details cost cage rows, concave grooves are free; byte-identical on 4.5.11 and 5.1.2
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Evaluated mesh datablock name
2+
3+
Pathology witness for the 5.2 change in
4+
`Object.evaluated_get(depsgraph).data.name`. A cube plus SUBSURF is
5+
enough; there is no gallery still (same class as
6+
[`vse-linear-modifiers`](../vse-linear-modifiers/),
7+
[`ngon-triangulate`](../ngon-triangulate/)).
8+
9+
Follows [`depsgraph-and-evaluated-data`](../../skills/depsgraph-and-evaluated-data/SKILL.md)
10+
and the version-gated assertion shape of
11+
[`gn-modifier-inputs`](../gn-modifier-inputs/) (per-version contract, exit
12+
0 on every matrix leg). Scaffolding matches
13+
[`cross-version-property-delete`](../cross-version-property-delete/)
14+
(`check()` returns, argparse naive-API flag, FATAL wrapper). That example
15+
does **not** version-branch, so the gate itself is copied from
16+
`gn-modifier-inputs`, not from `del`.
17+
18+
**What it witnesses:** with source mesh named `SourceMesh`,
19+
`evaluated_get().data.name` is generic `Mesh` on 4.5.11 and 5.1.2, and
20+
`SourceMesh` on 5.2.1. `to_mesh().name` is `SourceMesh` on all three —
21+
that accessor is not the witness. SUBSURF `levels=1` on a cube is
22+
load-bearing: 8 source verts vs 26 evaluated (Catmull-Clark). Name
23+
comparison that treats inequality as "this is evaluated" is silently
24+
wrong on 5.2; nothing raises.
25+
26+
**What failure each check would catch:**
27+
28+
- exit 3 — source cube was not 8 verts, or evaluated data is missing
29+
- exit 4 — SUBSURF did not produce a distinct evaluated mesh (not 26 verts)
30+
- exit 5 — source datablock name is not `SourceMesh`
31+
- exit 6 — evaluated datablock name is wrong for this Blender
32+
(`--assume-distinct-names` on 5.2 lands here: names match)
33+
- exit 7 — `to_mesh().name` is not `SourceMesh`
34+
35+
`--assume-distinct-names` is the falsifier: skip the version gate and
36+
demand the 4.5/5.1 inequality. It exits **0 on 4.5.11 and 5.1.2** (the
37+
names still differ) and **6 on 5.2.1**. That is unlike `--same-axis`,
38+
which is red on every binary.
39+
40+
No `SMOKE_SKIP`. Every matrix leg exercises the contract.
41+
42+
## Run
43+
44+
```bash
45+
blender --background --python eval_mesh_datablock_name.py --
46+
blender --background --python eval_mesh_datablock_name.py -- --assume-distinct-names
47+
```
48+
49+
## Exit codes
50+
51+
Per-script sequential checks. `9` is a valid check code; there is no rule
52+
against it.
53+
54+
| Code | Meaning |
55+
| --- | --- |
56+
| 0 | Success |
57+
| 1 | Uncaught exception (FATAL wrapper) |
58+
| 2 | argparse / usage |
59+
| 3 | Source cube missing or not 8 verts |
60+
| 4 | SUBSURF did not produce a distinct evaluated mesh |
61+
| 5 | Source datablock name is not `SourceMesh` |
62+
| 6 | Evaluated datablock name wrong for this version (`--assume-distinct-names` on 5.2) |
63+
| 7 | `to_mesh().name` is not `SourceMesh` |
64+
65+
The `blender-smoke` workflow runs the check on Blender 5.2 LTS and 4.5 LTS
66+
(5.1 on the weekly cron, the `needs-5.1` PR label, or manual dispatch).
67+
Smoke does not pass `--assume-distinct-names`.

0 commit comments

Comments
 (0)