Skip to content

Commit 44cdf87

Browse files
feat: add high-to-low tangent normal bake skill, snippets, and example (#140)
Cycles cage-bake is stochastic and CI is CPU-only, so the witness uses tolerance gates and a --flat-source falsifier rather than byte-identity. Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 20ab92d commit 44cdf87

18 files changed

Lines changed: 1755 additions & 18 deletions

File tree

‎.cursor-plugin/plugin.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"skills": [
1717
"skills/addon-scaffolding/SKILL.md",
1818
"skills/ai-mesh-cleanup/SKILL.md",
19+
"skills/bake-high-to-low/SKILL.md",
1920
"skills/engine-export-presets/SKILL.md",
2021
"skills/operators/SKILL.md",
2122
"skills/ui-panels/SKILL.md",
@@ -44,6 +45,7 @@
4445
"snippets": [
4546
"snippets/action-ensure-channelbag-for-slot.py",
4647
"snippets/app-handler-registration.py",
48+
"snippets/bake_normal_high_to_low.py",
4749
"snippets/bmesh-load-edit-free.py",
4850
"snippets/canonical-object-creation.py",
4951
"snippets/canonical-object-deletion.py",
@@ -62,7 +64,9 @@
6264
"snippets/pointerproperty-binding.py",
6365
"snippets/principled-bsdf-material.py",
6466
"snippets/register-classes-factory.py",
67+
"snippets/save_baked_image.py",
6568
"snippets/shader-node-group.py",
69+
"snippets/setup_bake_target_image.py",
6670
"snippets/temp-override-context.py",
6771
"snippets/usd-export-evaluation-mode.py",
6872
"snippets/version-branch-skeleton.py"
@@ -75,6 +79,7 @@
7579
"examples": [
7680
"examples/armature-bend",
7781
"examples/attribute-domain-shear",
82+
"examples/bake-normal-high-to-low",
7883
"examples/bmesh-gear",
7984
"examples/car-mirror-symmetry",
8085
"examples/coincident-vert-weld",

‎AGENTS.md‎

Lines changed: 4 additions & 4 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 15 skills, 9 rules, 3 templates, 24 snippets, and 54
23+
The content base is 16 skills, 9 rules, 3 templates, 27 snippets, and 55
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
@@ -31,11 +31,11 @@ in `docs/VISUAL-STYLE.md`; the canonical run prompt is
3131

3232
```
3333
Blender-Developer-Tools/
34-
skills/<skill-name>/SKILL.md # 15 skill files
34+
skills/<skill-name>/SKILL.md # 16 skill files
3535
rules/<rule-name>.mdc # 9 rule files
3636
templates/<template-name>/ # 3 starter templates
37-
snippets/<snippet-name>.py # 24 standalone Python snippets
38-
examples/<name>/ # 54 runnable smoke-gated examples (+ gallery.json)
37+
snippets/<snippet-name>.py # 27 standalone Python snippets
38+
examples/<name>/ # 55 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: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,24 @@ The **Blender Developer Tools** repository is at **v0.56.0**. It packages skills
1717
## Repository Architecture
1818

1919
```
20-
skills/<skill-name>/SKILL.md - AI workflow definitions, 15 total
20+
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
23-
snippets/<snippet-name>.py - Standalone code patterns, 24 total
24-
examples/<name>/ - Runnable smoke-gated examples, 54 total (+ gallery.json)
23+
snippets/<snippet-name>.py - Standalone code patterns, 27 total
24+
examples/<name>/ - Runnable smoke-gated examples, 55 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
2828
VERSION - Source of truth for the repo version
2929
```
3030

31-
## Skills (15)
31+
## Skills (16)
3232

3333
| Skill | Purpose |
3434
| --- | --- |
3535
| addon-scaffolding | Extensions Platform manifest, file layout, register/unregister symmetry |
3636
| ai-mesh-cleanup | Ordered cleanup for imported generated meshes: units, transform apply, origin, normals, budget, collider |
37+
| bake-high-to-low | Cycles cage-bake of high-poly detail onto a low-poly target as a tangent-space normal map |
3738
| engine-export-presets | Unity Y-up, Godot Z-up, and Unreal centimeter glTF/FBX presets; glTF uses export_yup, FBX uses axis_forward/axis_up |
3839
| operators | `bpy.types.Operator` lifecycle, `bl_idname`, redo, defensive context handling |
3940
| ui-panels | `bpy.types.Panel` declarative `draw()`, layout primitives, conditional UI |
@@ -88,17 +89,17 @@ VERSION - Source of truth for the repo version
8889
- Unity / Godot / Unreal glTF export via the engine-export-presets contract
8990
- Explicit exit codes matching `headless-batch-script-template` (0, then 2+)
9091

91-
## Snippets (24)
92+
## Snippets (27)
9293

9394
Small standalone `.py` files at `snippets/<name>.py`, each 5 to 50 lines.
9495

9596
v0.1.0: canonical object creation and deletion, depsgraph evaluated mesh, bmesh load-edit-free, temp_override context, foreach_set vertex bulk write, register_classes_factory, PointerProperty binding, cross-version property delete, and the `action_ensure_channelbag_for_slot` slotted-actions bridge.
9697

9798
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'`.
9899

99-
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`.
100+
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`.
100101

101-
## Examples (54)
102+
## Examples (55)
102103

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

‎README.md‎

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

2020
<p align="center">
21-
<strong>15 skills</strong> &nbsp;&bull;&nbsp; <strong>9 rules</strong> &nbsp;&bull;&nbsp; <strong>3 templates</strong> &nbsp;&bull;&nbsp; <strong>24 snippets</strong> &nbsp;&bull;&nbsp; <strong>54 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>55 examples</strong>
2222
</p>
2323

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

3737
## Overview
3838

39-
This repository ships **15 skills, 9 rules, 3 templates, 24 snippets, and 54 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 55 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

@@ -679,6 +679,22 @@ keeps its closed-form counts), each LOD's evaluated triangle count lands within
679679
survives within 1e-3 — with the aggressive-ratio nose-tip collapse documented
680680
as the caught failure mode.
681681

682+
</td>
683+
</tr>
684+
<tr>
685+
<td width="46%" valign="middle">
686+
<a href="examples/bake-normal-high-to-low/"><img src="examples/bake-normal-high-to-low/preview.webp" alt="Bake normal high to low: an unlit tangent-space normal map card of a six-lobe riveted hatch beside the collapse-decimated bronze plate wearing that map, dark studio" /></a>
687+
</td>
688+
<td valign="middle">
689+
690+
### [bake-normal-high-to-low](examples/bake-normal-high-to-low/)
691+
692+
Cycles cage-bakes a ribbed hatch onto a `DECIMATE COLLAPSE` LOD. Asserts the
693+
map is not flat (frac 0.7211, MAD 0.09356 vs `(0.5, 0.5, 1.0)`) while a
694+
flat-source control is (frac 0.0000). `--flat-source` exits 5. Byte-identity
695+
across 4.5 / 5.1 / 5.2 is not the contract — Cycles bake is stochastic.
696+
Neighbor of [`lod-decimate-chain`](examples/lod-decimate-chain/).
697+
682698
</td>
683699
</tr>
684700
<tr>
@@ -1024,10 +1040,10 @@ the duplicates, then glTF ships 24 tris / 48 positions / 8 unique.
10241040
## How content is organized
10251041

10261042
```
1027-
skills/<name>/SKILL.md - 15 skill files, YAML frontmatter, one canonical pattern each
1043+
skills/<name>/SKILL.md - 16 skill files, YAML frontmatter, one canonical pattern each
10281044
rules/<name>.mdc - 9 rule files, anti-pattern + correction
10291045
templates/<name>/ - 3 template directories (extension-addon-template, headless-batch-script-template, ai-asset-pipeline-template)
1030-
snippets/<name>.py - 24 standalone Python snippets, 5 to 50 lines each
1046+
snippets/<name>.py - 27 standalone Python snippets, 5 to 50 lines each
10311047
```
10321048

10331049
## Using rules in Cursor

‎ROADMAP.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ derives the actual version from conventional-commit types.
2121
| AI asset pipeline: post-generation cleanup | 14 | 8 | 2 | 21 | Shipped (v0.54.0) |
2222
| AI asset pipeline: engine export presets | 15 | 9 | 2 | 24 | Shipped |
2323
| AI asset pipeline: headless template | 15 | 9 | 3 | 24 | Shipped |
24+
| AI asset pipeline: high-to-low bake | 16 | 9 | 3 | 27 | Shipped |
2425
| AI asset pipeline: live-session bridge (spike) | - | - | - | - | Upcoming |
2526
| Stable | — | — | — | — | Upcoming |
2627

@@ -95,7 +96,8 @@ Audit pass on v0.1.0 content: standards-version markers bumped from `1.9.1` to `
9596

9697
Provider-agnostic GLB-in / engine-ready-out. This repo does not generate meshes.
9798

98-
- **Post-generation cleanup skills.** Import and unit-scale normalization, transform apply and origin, poly-budget decimate, LOD chain, collision mesh. Phase 1 shipped in v0.54.0 as `ai-mesh-cleanup`, four snippets, two rules. Bake/UV/atlas follow on.
99+
- **Post-generation cleanup skills.** Import and unit-scale normalization, transform apply and origin, poly-budget decimate, LOD chain, collision mesh. Phase 1 shipped in v0.54.0 as `ai-mesh-cleanup`, four snippets, two rules.
100+
- **High-to-low normal bake.** **Delivered.** Skill `bake-high-to-low`, snippets `setup_bake_target_image.py` / `bake_normal_high_to_low.py` / `save_baked_image.py`, witness `examples/bake-normal-high-to-low/`. Cycles CPU cage bake; statistical gates, not byte-identity. UV transfer and atlas packing remain a later phase. Template integration remains a later phase.
99101
- **Engine export presets.** **Delivered.** Unity (Y-up glTF), Godot (Z-up glTF, meters), Unreal (centimeter glTF bake and FBX `global_scale`). Skill `engine-export-presets`, three snippets, rule `use-correct-axis-rna-per-exporter`, witness `examples/export-preset-axis/`. Draco remains opt-in via `gltf_draco_export.py`.
100102
- **`ai-asset-pipeline-template/`.** **Delivered.** Third template. Headless: GLB path in; LOD set, convex or box collider, engine-preset export; explicit CI exit codes. Pattern: `templates/headless-batch-script-template/`.
101103
- **Live-session agent bridge.** Research spike, not a committed deliverable. MCP server or socket listener so an agent can execute against a running Blender instance instead of blind `--background` scripts. Built on `templates/extension-addon-template/`. Needs its own design pass. Unpinned.
@@ -136,6 +138,7 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
136138
- ~~Lightmap UV channel witness~~ **SHIPPED** as `examples/lightmap-uv-channel/` — two-channel UV contract on a market cart: per-part `UVMap`/`UVLight` with `active`/`active_render` re-asserted after the ops (edit-mode UV ops clear both flags; `uv_layers.new()` never moves them), UV0 drift 0.0 (clobber trap measured 2.068), every UV1 loop in [0,1], 0 overlapping islands via independent binned strict-SAT (15 hits when falsified), min island distance 0.00401 vs nominal margin 0.002 (`MARGIN_DIV × 0.01` semantics measured live); held `MeshUVLoopLayer.data` after edit-mode UV ops segfaults 4.5.11 (5/5, EXCEPTION_ACCESS_VIOLATION), silent no-op pack without prior unwrap; byte-identical on 4.5.11 and 5.1.2 (3680 islands)
137139
- ~~Socket attach-point witness~~ **SHIPPED** as `examples/socket-attach-points/` — named `SKT_` empties as the spawn contract on a survey drone: 7 socket world matrices within 1.788e-07 of the authored transform with orthonormal right-handed bases (Gram 3.576e-07, det err 2.980e-07), socket +Z == the mount pad's Newell normal (1.794e-07) by a quaternion-swing construction independent of the Gram-Schmidt basis, origin on the mount-face centroid (6.687e-08), documented up-axis fallback for the +Z/−Z mounts, module seating offset exactly 0.0 with strictly identity local matrices, rigid invariance under a re-pose (2.384e-07); `transform_apply` on an **Empty** root preserves world matrices (2.235e-07) but clears **7/7** `matrix_parent_inverse` and pushes the root scale down into every child (0.35 for an applied 1.35) — both halves asserted; a child left selected during the apply drifts sockets **2.335 m**; `bm.normal_update()` does not fix inward winding (`recalc_face_normals` does — the lofted fuselage rendered as a flat white panel); no-parent-inverse probe jumps 0.690128 m; byte-identical on 4.5.11 and 5.1.2
138140
- ~~Vertex-colour AO witness~~ **SHIPPED** as `examples/vertex-color-ao/` — baked occlusion in a colour attribute on a stone village well, checked against a closed form rather than a captured value: the cosine-weighted hemisphere integral for an infinitely wide wall of height H at distance d is `AO = 1 − ½(1 − 1/√(1+k²))`, `k = H/d`, matched to **6.760e-04** across k = 60.0…0.60 (gate 2.5e-03, QMC noise ~1/√n at 4096 samples); unoccluded plate bakes to **exactly 1.0**; strictly monotone 0.508301 → 0.928467; asset values in [0,1] with spread 1.0; **`BYTE_COLOR` is sRGB-encoded 8-bit, not linear** (0.735 → 0.7379107, peak round-trip error 3.782e-03, matching an independent encode/quantise/decode model to 3.189e-07) while `FLOAT_COLOR` is exact; both survive depsgraph evaluation at deviation 0.0; `bmesh.ops.bevel` offsets along **cached** face normals and flips outward past 90° of staleness (12 mm below ground on a ring of boxes, fixed by `normal_update()`); point-domain AO needs subdivision or the crevice gradient never reaches the attribute; `color_attributes` enumeration order differs between 4.5.11 and 5.1.2 — look up by name; byte-identical on 4.5.11 and 5.1.2 (6966 verts)
141+
- ~~High-to-low tangent normal bake~~ **SHIPPED** as `examples/bake-normal-high-to-low/` — Cycles CPU `type='NORMAL'` cage bake onto a `DECIMATE COLLAPSE` hatch; statistical gates (detail frac 0.7211 / MAD 0.09356 vs flat 0.0000 / 0.00277); `--flat-source` exits 5; RNA identical on 4.5.11, 5.1.2, 5.2.1; byte-identity is not the contract
139142
- 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)
140143
- ~~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`
141144
- 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`)
38.1 KB
Loading

0 commit comments

Comments
 (0)