Skip to content

Commit dd6b45e

Browse files
feat: witness n-gon, unapplied-scale, and coincident-vert pathologies
Gallery examples build clean geometry. These three synthesize the defects, pre-assert they exist, then check how hygiene/tangents, glTF export_apply, and export-crossing duplicates actually behave. Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent cfaaed2 commit dd6b45e

15 files changed

Lines changed: 748 additions & 8 deletions

File tree

‎.cursor-plugin/plugin.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"examples/attribute-domain-shear",
6565
"examples/bmesh-gear",
6666
"examples/car-mirror-symmetry",
67+
"examples/coincident-vert-weld",
6768
"examples/collision-hull-proxy",
6869
"examples/color-attribute-wheel",
6970
"examples/compositor-glare",
@@ -90,6 +91,7 @@
9091
"examples/lod-decimate-chain",
9192
"examples/mesh-hygiene-audit",
9293
"examples/modular-kit-snap",
94+
"examples/ngon-triangulate",
9395
"examples/parent-inverse-orrery",
9496
"examples/png-exr-alpha",
9597
"examples/prop-origin-transform",
@@ -103,6 +105,7 @@
103105
"examples/text-version-stamp",
104106
"examples/triangulate-tangents",
105107
"examples/turntable",
108+
"examples/unapplied-scale-gltf",
106109
"examples/usd-export-evaluation-mode",
107110
"examples/uv-layer-grid",
108111
"examples/vertex-color-ao",

‎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 50
23+
The content base is 13 skills, 6 rules, 2 templates, 17 snippets, and 53
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>/ # 50 runnable smoke-gated examples (+ gallery.json)
38+
examples/<name>/ # 53 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, 50 total (+ gallery.json)
24+
examples/<name>/ - Runnable smoke-gated examples, 53 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 (50)
86+
## Examples (53)
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: 47 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>50 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>53 examples</strong>
2222
</p>
2323

2424
<p align="center">
@@ -627,7 +627,7 @@ portable path is `radius`.
627627
</details>
628628

629629
<details>
630-
<summary><strong>Game asset pipeline</strong> — 9 examples</summary>
630+
<summary><strong>Game asset pipeline</strong> — 20 examples</summary>
631631

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

957+
</td>
958+
</tr>
959+
<tr>
960+
<td width="46%" valign="middle">
961+
check-only, no gallery still — a hexagon on a cube is not thumbnail-legible
962+
</td>
963+
<td valign="middle">
964+
965+
### [ngon-triangulate](examples/ngon-triangulate/)
966+
967+
Synthesizes one 6-loop hexagon by dissolving a cube edge. Pre-asserts the
968+
n-gon exists (1 face / 6 loops / 5 faces), then `calc_tangents` aborts until
969+
`bmesh.ops.triangulate` yields 4 tris + 4 quads / 28 loops. glTF tri count
970+
is 12 either way. Inverse of [`mesh-hygiene-audit`](examples/mesh-hygiene-audit/).
971+
972+
</td>
973+
</tr>
974+
<tr>
975+
<td width="46%" valign="middle">
976+
check-only, no gallery still — unapplied scale looks like modeled non-uniform size
977+
</td>
978+
<td valign="middle">
979+
980+
### [unapplied-scale-gltf](examples/unapplied-scale-gltf/)
981+
982+
Unit cube with object scale `(2, 1, 0.5)`. Pre-asserts unapplied non-uniform
983+
scale and local ±1 verts, then glTF `export_apply=True` still writes Y-up
984+
node.scale `(2, 0.5, 1)` with local POSITION. `export_apply` is modifiers
985+
only. Inverse of [`gltf-export-roundtrip`](examples/gltf-export-roundtrip/).
986+
987+
</td>
988+
</tr>
989+
<tr>
990+
<td width="46%" valign="middle">
991+
check-only, no gallery still — coincident cubes look like one cube
992+
</td>
993+
<td valign="middle">
994+
995+
### [coincident-vert-weld](examples/coincident-vert-weld/)
996+
997+
Two cubes in one mesh: 16 verts / 8 unique / still manifold. Pre-asserts
998+
the duplicates, then glTF ships 24 tris / 48 positions / 8 unique.
999+
`remove_doubles` collapses to one cube. Inverse of
1000+
[`degenerate-bevel-weld`](examples/degenerate-bevel-weld/).
1001+
9571002
</td>
9581003
</tr>
9591004
</table>

‎ROADMAP.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,11 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
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)
9797
- ~~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)
9898
- 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
99-
- 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.
10099
- ~~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
100+
- ~~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
101+
- 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.
102+
- 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.
103+
- 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.
101104
- ~~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.
102105
- `modal-operators` skill -- `invoke` returning `RUNNING_MODAL`, the `modal()` event handler, modal cancellation patterns
103106
- `usd-pipelines` skill -- USD export options, `evaluation_mode`, instancing, the USD vs glTF tradeoffs

‎docs/technical-audit.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,21 @@ on `bundle_items` are load-bearing. Vert count alone is not a witness.
7272
- Taught: `skills/geometry-nodes-python/SKILL.md`
7373
- Witness: `examples/gn-bundle-roundtrip/`
7474

75+
### 2026-09-09 — glTF `export_apply` does not bake object scale
76+
77+
RNA: "Apply modifiers (excluding Armatures) to mesh objects". Unapplied
78+
object scale is written to the glTF node, Y-up permuted
79+
`(sx, sy, sz) → (sx, sz, sy)`. POSITION stays the local mesh. Baking
80+
scale in Blender (`mesh.transform` + identity `obj.scale`) is what
81+
removes `node.scale` and puts world extents in POSITION. Same on
82+
4.5 LTS and 5.2 LTS; not a version split. `gltf-export-roundtrip`
83+
asserts no node scale because that crate is identity. Not listed in
84+
the python_api notes as a behavior change because the RNA name did
85+
not move.
86+
87+
- Taught: `skills/depsgraph-and-evaluated-data/SKILL.md`
88+
- Witness: `examples/unapplied-scale-gltf/`
89+
7590
### Contrast — NodesModifier dict assignment (5.2, was in the notes)
7691

7792
`mod[identifier] = value` raises `TypeError` on 5.2 (`id properties not
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Coincident vert weld
2+
3+
Synthesizes two cubes occupying the same space as one mesh, then
4+
asserts the duplicates exist before asserting they cross glTF export.
5+
Inverse of [`degenerate-bevel-weld`](../degenerate-bevel-weld/)
6+
(coincidences from bevel pinch) and
7+
[`mesh-hygiene-audit`](../mesh-hygiene-audit/) (manifold on a clean
8+
solid). Neighbor of [`gltf-export-roundtrip`](../gltf-export-roundtrip/)
9+
(kit-bash face-plane welds on export).
10+
11+
**Why this pathology:** coincident shells are still manifold (every
12+
edge borders 2 faces). Hygiene can pass. The engine trap is extra
13+
triangles on disk.
14+
15+
**Pre-assertion (pathology exists):** **16** verts, **8** unique
16+
positions, **12** faces, **24** edges, valence all 2. `--no-duplicate`
17+
exits 3.
18+
19+
**Handling (second axis):** glTF ships **48** loop-split positions,
20+
**24** tris, **8** unique. Vert count 16 is not enough; unique=8 is
21+
the construction axis; 24 tris vs 12 after `remove_doubles` is the
22+
export axis. `--weld` after the pre-assert exits 4.
23+
24+
`remove_doubles` collapses to one cube (8/12/6), still manifold — not
25+
a 4-face-per-edge mesh. Same on 4.5 LTS and 5.2 LTS.
26+
27+
No gallery still. Two coincident cubes look like one cube.
28+
29+
## Run
30+
31+
```bash
32+
blender --background --python coincident_vert_weld.py --
33+
blender --background --python coincident_vert_weld.py -- --no-duplicate
34+
blender --background --python coincident_vert_weld.py -- --weld
35+
```

0 commit comments

Comments
 (0)