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
2 changes: 2 additions & 0 deletions .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"examples/degenerate-bevel-weld",
"examples/depsgraph-export",
"examples/driver-wave",
"examples/eval-mesh-datablock-name",
"examples/exit-pre-sidecar",
"examples/export-preset-axis",
"examples/gltf-export-roundtrip",
Expand All @@ -109,6 +110,7 @@
"examples/light-link-studio",
"examples/lightmap-uv-channel",
"examples/lod-decimate-chain",
"examples/mesh-automasking-settings",
"examples/mesh-hygiene-audit",
"examples/modular-kit-snap",
"examples/ngon-triangulate",
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 16 skills, 9 rules, 3 templates, 27 snippets, and 57
The content base is 16 skills, 9 rules, 3 templates, 27 snippets, and 59
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 # 9 rule files
templates/<template-name>/ # 3 starter templates
snippets/<snippet-name>.py # 27 standalone Python snippets
examples/<name>/ # 55 runnable smoke-gated examples (+ gallery.json)
examples/<name>/ # 59 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, 16 total
rules/<rule-name>.mdc - Anti-pattern rules, 9 total
templates/<template-name>/ - Starter projects, 3 total
snippets/<snippet-name>.py - Standalone code patterns, 27 total
examples/<name>/ - Runnable smoke-gated examples, 57 total (+ gallery.json)
examples/<name>/ - Runnable smoke-gated examples, 59 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 @@ -99,7 +99,7 @@ v0.2.0: Principled BSDF material, driver-with-custom-function via `driver_namesp

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`.

## Examples (57)
## Examples (59)

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
15 changes: 15 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,21 @@ and the product scripts that self-skip: `examples/gn-bundle-roundtrip/`,
`examples/exit-pre-sidecar/`). A script under test prints `SMOKE_SKIP:` and
exits 77; the runner records SKIP and returns 0 so the YAML step stays green.

**Version-gated falsifiers.** A falsifier for a cross-version removal or
rename is version-gated by nature. It exits its documented code on versions
where the API changed and exits 0 on versions where the old API still works.
That is the correct witness: the naive script is still valid on the older
binaries. It differs from every other falsifier in the tree (for example
`--same-axis`), which is red on all three versions.

Do not "fix" these flags to fail unconditionally. The examples that behave
this way:

- `examples/vse-linear-modifiers/` (`--assume-present`)
- `examples/gn-socket-rename/` (`--legacy-ids`)
- `examples/eval-mesh-datablock-name/` (`--assume-distinct-names`)
- `examples/mesh-automasking-settings/` (`--assume-brush-attrs`)

## Standards-version Markers

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

<p align="center">
<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>
<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>
</p>

<p align="center">
Expand All @@ -36,7 +36,7 @@

## Overview

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

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 Expand Up @@ -203,7 +203,7 @@ Gallery still is a dual-elevation diptych so the contract reads at thumbnail sca
</details>

<details>
<summary><strong>Mesh, curves &amp; text</strong> — 12 examples</summary>
<summary><strong>Mesh, curves &amp; text</strong> — 13 examples</summary>

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

</td>
</tr>
<tr>
<td width="46%" valign="middle">
check-only, no gallery still — no geometry
</td>
<td valign="middle">

### [eval-mesh-datablock-name](examples/eval-mesh-datablock-name/)

`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. `--assume-distinct-names` is red only on 5.2.

</td>
</tr>
<tr>
Expand Down Expand Up @@ -587,7 +601,7 @@ and every orbit lands on its closed form.
</details>

<details>
<summary><strong>Context &amp; Grease Pencil</strong> — 4 examples</summary>
<summary><strong>Context &amp; Grease Pencil</strong> — 5 examples</summary>

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

</td>
</tr>
<tr>
<td width="46%" valign="middle">
check-only, no gallery still — no geometry
</td>
<td valign="middle">

### [mesh-automasking-settings](examples/mesh-automasking-settings/)

`MeshAutomaskingSettings` is absent on 4.5.11 and 5.1.2, present on 5.2.1.
Old `Brush` automasking attributes are gone on 5.2; read
`.mesh_automasking_settings` instead. `--assume-brush-attrs` is red only
on 5.2.

</td>
</tr>
<tr>
Expand Down
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
- 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)
- ~~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
- ~~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
- GN evaluated `to_mesh().name` no longer equals the original mesh name (5.2 contract change)
- `MeshAutomaskingSettings` move: old `Brush` automasking attributes gone on 5.2
- ~~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
- ~~`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
- ~~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`
- 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`)
- ~~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
Expand Down
67 changes: 67 additions & 0 deletions examples/eval-mesh-datablock-name/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Evaluated mesh datablock name

Pathology witness for the 5.2 change in
`Object.evaluated_get(depsgraph).data.name`. A cube plus SUBSURF is
enough; there is no gallery still (same class as
[`vse-linear-modifiers`](../vse-linear-modifiers/),
[`ngon-triangulate`](../ngon-triangulate/)).

Follows [`depsgraph-and-evaluated-data`](../../skills/depsgraph-and-evaluated-data/SKILL.md)
and the version-gated assertion shape of
[`gn-modifier-inputs`](../gn-modifier-inputs/) (per-version contract, exit
0 on every matrix leg). Scaffolding matches
[`cross-version-property-delete`](../cross-version-property-delete/)
(`check()` returns, argparse naive-API flag, FATAL wrapper). That example
does **not** version-branch, so the gate itself is copied from
`gn-modifier-inputs`, not from `del`.

**What it witnesses:** with source mesh named `SourceMesh`,
`evaluated_get().data.name` is generic `Mesh` on 4.5.11 and 5.1.2, and
`SourceMesh` on 5.2.1. `to_mesh().name` is `SourceMesh` on all three —
that accessor is not the witness. SUBSURF `levels=1` on a cube is
load-bearing: 8 source verts vs 26 evaluated (Catmull-Clark). Name
comparison that treats inequality as "this is evaluated" is silently
wrong on 5.2; nothing raises.

**What failure each check would catch:**

- exit 3 — source cube was not 8 verts, or evaluated data is missing
- exit 4 — SUBSURF did not produce a distinct evaluated mesh (not 26 verts)
- exit 5 — source datablock name is not `SourceMesh`
- exit 6 — evaluated datablock name is wrong for this Blender
(`--assume-distinct-names` on 5.2 lands here: names match)
- exit 7 — `to_mesh().name` is not `SourceMesh`

`--assume-distinct-names` is the falsifier: skip the version gate and
demand the 4.5/5.1 inequality. It exits **0 on 4.5.11 and 5.1.2** (the
names still differ) and **6 on 5.2.1**. That is unlike `--same-axis`,
which is red on every binary.

No `SMOKE_SKIP`. Every matrix leg exercises the contract.

## Run

```bash
blender --background --python eval_mesh_datablock_name.py --
blender --background --python eval_mesh_datablock_name.py -- --assume-distinct-names
```

## Exit codes

Per-script sequential checks. `9` is a valid check code; there is no rule
against it.

| Code | Meaning |
| --- | --- |
| 0 | Success |
| 1 | Uncaught exception (FATAL wrapper) |
| 2 | argparse / usage |
| 3 | Source cube missing or not 8 verts |
| 4 | SUBSURF did not produce a distinct evaluated mesh |
| 5 | Source datablock name is not `SourceMesh` |
| 6 | Evaluated datablock name wrong for this version (`--assume-distinct-names` on 5.2) |
| 7 | `to_mesh().name` is not `SourceMesh` |

The `blender-smoke` workflow runs the check on Blender 5.2 LTS and 4.5 LTS
(5.1 on the weekly cron, the `needs-5.1` PR label, or manual dispatch).
Smoke does not pass `--assume-distinct-names`.
Loading
Loading