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
3 changes: 3 additions & 0 deletions .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"examples/attribute-domain-shear",
"examples/bmesh-gear",
"examples/car-mirror-symmetry",
"examples/coincident-vert-weld",
"examples/collision-hull-proxy",
"examples/color-attribute-wheel",
"examples/compositor-glare",
Expand All @@ -90,6 +91,7 @@
"examples/lod-decimate-chain",
"examples/mesh-hygiene-audit",
"examples/modular-kit-snap",
"examples/ngon-triangulate",
"examples/parent-inverse-orrery",
"examples/png-exr-alpha",
"examples/prop-origin-transform",
Expand All @@ -103,6 +105,7 @@
"examples/text-version-stamp",
"examples/triangulate-tangents",
"examples/turntable",
"examples/unapplied-scale-gltf",
"examples/usd-export-evaluation-mode",
"examples/uv-layer-grid",
"examples/vertex-color-ao",
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 50
The content base is 13 skills, 6 rules, 2 templates, 17 snippets, and 53
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>/ # 50 runnable smoke-gated examples (+ gallery.json)
examples/<name>/ # 53 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, 50 total (+ gallery.json)
examples/<name>/ - Runnable smoke-gated examples, 53 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 (50)
## Examples (53)

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
49 changes: 47 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>50 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>53 examples</strong>
</p>

<p align="center">
Expand Down Expand Up @@ -627,7 +627,7 @@ portable path is `radius`.
</details>

<details>
<summary><strong>Game asset pipeline</strong> — 9 examples</summary>
<summary><strong>Game asset pipeline</strong> — 20 examples</summary>

<table>
<tr>
Expand Down Expand Up @@ -954,6 +954,51 @@ while **`BYTE_COLOR` is sRGB-encoded 8-bit** — 0.735 reads back
Companion to [`color-attribute-wheel`](examples/color-attribute-wheel/) and
[`attribute-domain-shear`](examples/attribute-domain-shear/).

</td>
</tr>
<tr>
<td width="46%" valign="middle">
check-only, no gallery still — a hexagon on a cube is not thumbnail-legible
</td>
<td valign="middle">

### [ngon-triangulate](examples/ngon-triangulate/)

Synthesizes one 6-loop hexagon by dissolving a cube edge. Pre-asserts the
n-gon exists (1 face / 6 loops / 5 faces), then `calc_tangents` aborts until
`bmesh.ops.triangulate` yields 4 tris + 4 quads / 28 loops. glTF tri count
is 12 either way. Inverse of [`mesh-hygiene-audit`](examples/mesh-hygiene-audit/).

</td>
</tr>
<tr>
<td width="46%" valign="middle">
check-only, no gallery still — unapplied scale looks like modeled non-uniform size
</td>
<td valign="middle">

### [unapplied-scale-gltf](examples/unapplied-scale-gltf/)

Unit cube with object scale `(2, 1, 0.5)`. Pre-asserts unapplied non-uniform
scale and local ±1 verts, then glTF `export_apply=True` still writes Y-up
node.scale `(2, 0.5, 1)` with local POSITION. `export_apply` is modifiers
only. Inverse of [`gltf-export-roundtrip`](examples/gltf-export-roundtrip/).

</td>
</tr>
<tr>
<td width="46%" valign="middle">
check-only, no gallery still — coincident cubes look like one cube
</td>
<td valign="middle">

### [coincident-vert-weld](examples/coincident-vert-weld/)

Two cubes in one mesh: 16 verts / 8 unique / still manifold. Pre-asserts
the duplicates, then glTF ships 24 tris / 48 positions / 8 unique.
`remove_doubles` collapses to one cube. Inverse of
[`degenerate-bevel-weld`](examples/degenerate-bevel-weld/).

</td>
</tr>
</table>
Expand Down
5 changes: 4 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,11 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
- ~~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
- Falsification flags on shipped examples (Phases 6–8 `--skip-delete` / `--unpair-*` / `--bypass`, Phase 9 `--silent-handler` / `--wrong-text`) were proven local-Windows only — chat and `.scratch`, not CI history. Decide later whether a cron should exercise them or whether flags in the scripts are sufficient record.
- ~~exit_pre sidecar witness~~ **SHIPPED** as `examples/exit-pre-sidecar/` — 5.1+ `handlers.exit_pre` writes `$BDT_SMOKE_SIDECAR`; harness `--expect-sidecar` + `sidecar_contains=exit_pre-ok` after process exit; 4.5 SKIP `min_version` 5.1; `--force-run` is AttributeError; `--silent-handler` / `--no-handler` missing sidecar; `--wrong-text` / `--write-in-main` / `--atexit-instead` wrong contents
- ~~N-gon / unapplied-scale / coincident-vert pathology witnesses~~ **SHIPPED** as `examples/ngon-triangulate/`, `examples/unapplied-scale-gltf/`, `examples/coincident-vert-weld/` — two-stage pre-assert then handling; no gallery stills (defects not thumbnail-legible); `--no-dissolve` / `--identity` / `--no-duplicate` red on detection; `--skip-triangulate` / `--bake` / `--weld` red on handling
- Overlapping / mirrored UV islands as a *shipped* pathology (not lightmap `--falsify`): SAT hits matching a constructed overlap, then glTF TEXCOORD survival. Deferred — `lightmap-uv-channel` already owns the zero-overlap gate.
- Vendor CC0 asset fixtures (Kenney, Quaternius, ambientCG only — no aggregators) as `.glb` not `.blend`, with per-fixture provenance records mirroring the Free-Game-Dev-Assets frontmatter schema. Permitted only where the assertion is an invariant over the fixture rather than a measurement of it. Deferred pending a decision on repo weight and CI fetch policy.
- Falsification flags on shipped examples (Phases 6–8 `--skip-delete` / `--unpair-*` / `--bypass`, Phase 9 `--silent-handler` / `--wrong-text`, Phase 10 `--no-dissolve` / `--identity` / `--no-duplicate`) were proven local-Windows only — chat and `.scratch`, not CI history. Decide later whether a cron should exercise them or whether flags in the scripts are sufficient record.
- ~~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
- `usd-pipelines` skill -- USD export options, `evaluation_mode`, instancing, the USD vs glTF tradeoffs
Expand Down
15 changes: 15 additions & 0 deletions docs/technical-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ 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/`

### 2026-09-09 — glTF `export_apply` does not bake object scale

RNA: "Apply modifiers (excluding Armatures) to mesh objects". Unapplied
object scale is written to the glTF node, Y-up permuted
`(sx, sy, sz) → (sx, sz, sy)`. POSITION stays the local mesh. Baking
scale in Blender (`mesh.transform` + identity `obj.scale`) is what
removes `node.scale` and puts world extents in POSITION. Same on
4.5 LTS and 5.2 LTS; not a version split. `gltf-export-roundtrip`
asserts no node scale because that crate is identity. Not listed in
the python_api notes as a behavior change because the RNA name did
not move.

- Taught: `skills/depsgraph-and-evaluated-data/SKILL.md`
- Witness: `examples/unapplied-scale-gltf/`

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

`mod[identifier] = value` raises `TypeError` on 5.2 (`id properties not
Expand Down
35 changes: 35 additions & 0 deletions examples/coincident-vert-weld/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Coincident vert weld

Synthesizes two cubes occupying the same space as one mesh, then
asserts the duplicates exist before asserting they cross glTF export.
Inverse of [`degenerate-bevel-weld`](../degenerate-bevel-weld/)
(coincidences from bevel pinch) and
[`mesh-hygiene-audit`](../mesh-hygiene-audit/) (manifold on a clean
solid). Neighbor of [`gltf-export-roundtrip`](../gltf-export-roundtrip/)
(kit-bash face-plane welds on export).

**Why this pathology:** coincident shells are still manifold (every
edge borders 2 faces). Hygiene can pass. The engine trap is extra
triangles on disk.

**Pre-assertion (pathology exists):** **16** verts, **8** unique
positions, **12** faces, **24** edges, valence all 2. `--no-duplicate`
exits 3.

**Handling (second axis):** glTF ships **48** loop-split positions,
**24** tris, **8** unique. Vert count 16 is not enough; unique=8 is
the construction axis; 24 tris vs 12 after `remove_doubles` is the
export axis. `--weld` after the pre-assert exits 4.

`remove_doubles` collapses to one cube (8/12/6), still manifold — not
a 4-face-per-edge mesh. Same on 4.5 LTS and 5.2 LTS.

No gallery still. Two coincident cubes look like one cube.

## Run

```bash
blender --background --python coincident_vert_weld.py --
blender --background --python coincident_vert_weld.py -- --no-duplicate
blender --background --python coincident_vert_weld.py -- --weld
```
Loading
Loading