Skip to content

Commit f62501d

Browse files
TMHSDigitalclaude
andcommitted
feat(rules): no-removed-normals-api rule and mesh-normals-after-41 example
Pre-4.1 auto smooth code (use_auto_smooth, auto_smooth_angle, calc_normals_split, free/create_normals_split) is among the most common errors in generated Blender code, and it raises AttributeError on 4.5 LTS and 5.x. Nothing in the pack mentioned it. - Rule no-removed-normals-api flags those calls, with a replacement table. - mesh-editing-and-bmesh gains a Normals after 4.1 section and a mistakes entry. - examples/mesh-normals-after-41 (check-only) proves, on 4.5.14 and 5.2.1: - the removed attributes are absent, and the pre-4.1 code raises; - set_sharp_from_angle(30 deg) marks the 32 cap rims of a 16-sided cylinder and also smooths all 18 faces (undocumented in the old pattern); - corner_normals needs no calc call: a rim vertex has 2 normals, and 3 after flattening. Falsifiers: --legacy-api 3, --sharp-angle 10 4, --flatten-after 5, --skip-set-sharp 6. - Also measured: shade_auto_smooth adds a Smooth by Angle modifier on 5.2.1 but returns CANCELLED on 4.5.14 headless. The rule and skill say so. Counts move to 11 rules and 68 examples (12 check-only). Closes #379 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
1 parent 83b09cf commit f62501d

19 files changed

Lines changed: 463 additions & 23 deletions

File tree

‎.cursor-plugin/plugin.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"rules/validate-imported-mesh-scale.mdc",
5151
"rules/no-unapplied-modifiers-on-export.mdc",
5252
"rules/use-correct-axis-rna-per-exporter.mdc",
53-
"rules/no-bgl.mdc"
53+
"rules/no-bgl.mdc",
54+
"rules/no-removed-normals-api.mdc"
5455
]
5556
}

‎AGENTS.md‎

Lines changed: 3 additions & 3 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 19 skills, 10 rules, 3 templates, 29 snippets, 66
23+
The content base is 19 skills, 11 rules, 3 templates, 29 snippets, 66
2424
examples, and 80 showcase pieces (counts are CI-enforced across README.md, the agent docs, ROADMAP.md,
2525
`site.json` and the site templates). The full inventory tables and per-item purposes live in
2626
`CLAUDE.md`. Example anatomy and authoring rules: copy `examples/bmesh-gear/`;
@@ -33,10 +33,10 @@ in `docs/VISUAL-STYLE.md`; the canonical run prompt is
3333
```
3434
Blender-Developer-Tools/
3535
skills/<skill-name>/SKILL.md # 19 skill files
36-
rules/<rule-name>.mdc # 10 rule files
36+
rules/<rule-name>.mdc # 11 rule files
3737
templates/<template-name>/ # 3 starter templates
3838
snippets/<snippet-name>.py # 29 standalone Python snippets
39-
examples/<name>/ # 67 runnable smoke-gated examples (+ gallery.json)
39+
examples/<name>/ # 68 runnable smoke-gated examples (+ gallery.json)
4040
examples/gallery_framing.py # shared Layer 1 framing measurement (render path only)
4141
showcase/<name>/ # budget-conformance props (sibling of examples/)
4242
showcase/gallery.json # this tree's gallery index; merged into docs/gallery/

‎CLAUDE.md‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ The **Blender Developer Tools** repository is at **v0.154.0**. It packages skill
1818

1919
```
2020
skills/<skill-name>/SKILL.md - AI workflow definitions, 19 total
21-
rules/<rule-name>.mdc - Anti-pattern rules, 10 total
21+
rules/<rule-name>.mdc - Anti-pattern rules, 11 total
2222
templates/<template-name>/ - Starter projects, 3 total
2323
snippets/<snippet-name>.py - Standalone code patterns, 29 total
24-
examples/<name>/ - Runnable smoke-gated examples, 67 total (+ gallery.json)
24+
examples/<name>/ - Runnable smoke-gated examples, 68 total (+ gallery.json)
2525
showcase/<name>/ - Budget-conformance props, 80 pieces, each with a gallery `category` (sibling of examples/; see showcase/README.md § Categories)
2626
scripts/build_gallery.py - Regenerates docs/gallery/ from examples/gallery.json + showcase/gallery.json
2727
scripts/site/ - Vendored landing-page build (Jinja2); tokens.css is the shared palette
@@ -54,7 +54,7 @@ VERSION - Source of truth for the repo version
5454
| timers-modal-and-threading | `bpy.app.timers` return values and `persistent`, modal operators on `event_timer_add`, worker thread + queue + main-thread drain, no timers in `--background` |
5555
| gpu-drawing-and-overlays | `gpu` instead of the removed `bgl`: `draw_handler_add`/`remove`, builtin shader names, `gpu.state` blending and depth, `POLYLINE_*` line width, `blf` text, `GPUOffScreen` with 5.2's `gpu.init()` headless |
5656

57-
## Rules (10)
57+
## Rules (11)
5858

5959
Every rule sets `alwaysApply: false`, so the Scope column below is the rule's
6060
actual load condition, not a description: a rule enters context only when a file
@@ -72,6 +72,7 @@ matching its `globs` does. Changing a glob changes when the rule fires.
7272
| no-unapplied-modifiers-on-export | `*.py` | Export with live modifiers when the export does not request evaluated geometry |
7373
| use-correct-axis-rna-per-exporter | `*.py` | `export_scene.gltf` with FBX `axis_forward`/`axis_up`, or `export_scene.fbx` with glTF `export_yup` |
7474
| no-bgl | `*.py` | `import bgl` or any `bgl.` call (removed in 5.x; the add-on fails to register) |
75+
| no-removed-normals-api | `*.py` | `use_auto_smooth`, `auto_smooth_angle`, `calc_normals_split()` and the other normals calls removed in 4.1 |
7576

7677
## Templates (3)
7778

@@ -111,13 +112,13 @@ AI asset pipeline track: `decimate_to_budget.py`, `convex_hull_collider.py`, `lo
111112

112113
Extensions runtime: `extension-user-data.py` (`extension_path_user` settings file and an `online_access` guard). Long-running work: `thread-queue-timer.py` (worker thread, `queue.Queue`, main-thread drain timer).
113114

114-
## Examples (67)
115+
## Examples (68)
115116

116117
Runnable scripts at `examples/<name>/`, each asserting a real API contract with
117118
deterministic checks (exit non-zero on failure) and optionally rendering a still via
118119
`--output`. All of them run headless on Blender 5.2 LTS and 4.5 LTS in `blender-smoke.yml` (5.1 on the weekly cron, the `needs-5.1` PR label, or manual dispatch);
119-
**56 of the 67 ship a render in the site gallery** at `docs/gallery/`. The other
120-
eleven are **check-only**: they carry no `--output` path, no gallery entry, and no
120+
**56 of the 68 ship a render in the site gallery** at `docs/gallery/`. The other
121+
twelve are **check-only**: they carry no `--output` path, no gallery entry, and no
121122
hero asset. The criterion is whether the contract is expressible in pixels. An
122123
example is check-only when its witness is a data or state fact that no scene
123124
redesign can make visible — a datablock name, an RNA attribute's presence, a

‎CONTRIBUTING.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ The drift-check workflow enforces these on every push and PR.
288288

289289
## Aggregate Counts
290290

291-
`README.md` declares aggregate counts (e.g. "19 skills, 10 rules, 3 templates, 29 snippets, 67 examples, and 80 showcase pieces"). The `validate-counts` job in `.github/workflows/validate.yml` enforces these substrings against the filesystem on every push and PR. Showcase pieces are counted separately from examples. When you add or remove content, update the README counts in the same commit.
291+
`README.md` declares aggregate counts (e.g. "19 skills, 11 rules, 3 templates, 29 snippets, 68 examples, and 80 showcase pieces"). The `validate-counts` job in `.github/workflows/validate.yml` enforces these substrings against the filesystem on every push and PR. Showcase pieces are counted separately from examples. When you add or remove content, update the README counts in the same commit.
292292

293293
## Pull Request Process
294294

‎README.md‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
</p>
2626

2727
<p align="center">
28-
<strong>19 skills</strong> &nbsp;&bull;&nbsp; <strong>10 rules</strong> &nbsp;&bull;&nbsp; <strong>3 templates</strong> &nbsp;&bull;&nbsp; <strong>29 snippets</strong> &nbsp;&bull;&nbsp; <strong>67 examples</strong> &nbsp;&bull;&nbsp; <strong>80 showcase pieces</strong>
28+
<strong>19 skills</strong> &nbsp;&bull;&nbsp; <strong>11 rules</strong> &nbsp;&bull;&nbsp; <strong>3 templates</strong> &nbsp;&bull;&nbsp; <strong>29 snippets</strong> &nbsp;&bull;&nbsp; <strong>68 examples</strong> &nbsp;&bull;&nbsp; <strong>80 showcase pieces</strong>
2929
</p>
3030

3131
<p align="center">
@@ -44,7 +44,7 @@
4444

4545
## Overview
4646

47-
This repository ships **19 skills, 10 rules, 3 templates, 29 snippets, 67 examples, and 80 showcase pieces** for Blender Python development targeting Blender 5.2 LTS (current stable) with Blender 4.5 LTS fallback support. Blender 5.1 is prior stable.
47+
This repository ships **19 skills, 11 rules, 3 templates, 29 snippets, 68 examples, and 80 showcase pieces** for Blender Python development targeting Blender 5.2 LTS (current stable) with Blender 4.5 LTS fallback support. Blender 5.1 is prior stable.
4848

4949
The content is consumed by AI coding agents, installed as a plugin or read from a checkout. An optional second plugin registers the official [Blender Lab MCP server](https://www.blender.org/lab/mcp-server/) for live sessions (see [Live Blender session](#live-blender-session-mcp)); the server is referenced from upstream, not part of this repository, and nothing else depends on it. Cursor and Claude Code both install it as a plugin (see [Quick start](#quick-start)): Cursor loads the skills and applies `rules/*.mdc` wherever their scope globs match; Claude Code loads the skills and gets the rules through a generated `blender-rules` skill, since it does not read Cursor `.mdc` files. An agent picks a skill up when its description matches the task. Any agent that can read files in a workspace can use it the same way. There is no build step for the content — edit the Markdown and Python files directly.
5050

@@ -61,7 +61,7 @@ The content is consumed by AI coding agents, installed as a plugin or read from
6161

6262
Neither agent needs a clone of `main`: both install the slim `plugin-dist` build. Clone the full repository only to [run the examples or contribute](#run-the-examples-or-contribute).
6363

64-
- **Cursor** — install it as a local plugin from the slim `plugin-dist` branch, then run **Developer: Reload Window**. **Customize** lists the 19 skills and 10 rules; the rules apply by glob scope and the agent loads a skill when its description matches the task.
64+
- **Cursor** — install it as a local plugin from the slim `plugin-dist` branch, then run **Developer: Reload Window**. **Customize** lists the 19 skills and 11 rules; the rules apply by glob scope and the agent loads a skill when its description matches the task.
6565

6666
```bash
6767
git clone --branch plugin-dist --single-branch https://github.com/TMHSDigital/Blender-Developer-Tools.git ~/.cursor/plugins/local/blender-developer-tools
@@ -129,7 +129,7 @@ Releases ship often; [CHANGELOG.md](CHANGELOG.md) lists what each one changed.
129129

130130
## Falsifiers
131131

132-
Every one of the 67 examples carries a **falsifier**: a flag that changes the
132+
Every one of the 68 examples carries a **falsifier**: a flag that changes the
133133
input so a real assertion fails. It never disables the assertion, skips the
134134
check, or short-circuits to an error — it feeds the script something the
135135
contract says must not pass, and the same check that guards the happy path
@@ -157,7 +157,7 @@ per-script exit-code model, are in
157157

158158
## Examples and showcase
159159

160-
**67 examples** in [`examples/`](examples/) are runnable, self-checking scripts: each asserts one API contract, carries a falsifier, and runs headless on Blender 5.2 LTS and 4.5 LTS in the `blender-smoke` workflow (5.1 on the weekly cron or by manual dispatch). Those whose contract is visible also render a still. **80 showcase pieces** in [`showcase/`](showcase/) exist to show the skills working together at production scale: each is a single headless script that models a game prop, then runs it through the same cleanup, bake, LOD, collider and export steps the skills teach, and fails if a measured budget (triangle counts, colliders, materials, real-world size) is missed. They are not API examples. Conventions: [`showcase/README.md`](showcase/README.md).
160+
**68 examples** in [`examples/`](examples/) are runnable, self-checking scripts: each asserts one API contract, carries a falsifier, and runs headless on Blender 5.2 LTS and 4.5 LTS in the `blender-smoke` workflow (5.1 on the weekly cron or by manual dispatch). Those whose contract is visible also render a still. **80 showcase pieces** in [`showcase/`](showcase/) exist to show the skills working together at production scale: each is a single headless script that models a game prop, then runs it through the same cleanup, bake, LOD, collider and export steps the skills teach, and fails if a measured budget (triangle counts, colliders, materials, real-world size) is missed. They are not API examples. Conventions: [`showcase/README.md`](showcase/README.md).
161161

162162
Browse both, with filters, full-size renders and each script's README, in the **[gallery](https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/)**.
163163

@@ -176,10 +176,10 @@ Browse both, with filters, full-size renders and each script's README, in the **
176176

177177
```
178178
skills/<name>/SKILL.md - 19 skill files, YAML frontmatter, one canonical pattern each
179-
rules/<name>.mdc - 10 rule files, anti-pattern + correction
179+
rules/<name>.mdc - 11 rule files, anti-pattern + correction
180180
templates/<name>/ - 3 template directories (extension-addon-template, headless-batch-script-template, ai-asset-pipeline-template)
181181
snippets/<name>.py - 29 standalone Python snippets, 5 to 75 lines each
182-
examples/<name>/ - 67 example directories: script, README with exit codes and falsifier
182+
examples/<name>/ - 68 example directories: script, README with exit codes and falsifier
183183
showcase/<name>/ - 80 showcase pieces: budget-gated game props, script and README
184184
claude/ - generated Claude Code copies of the rules (blender-rules skill + import file)
185185
```

‎ROADMAP.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ showcase pool further down. Each item is an open issue; nothing here is pinned t
1111

1212
- **New skills:** `porting-to-blender-5`, one index of every 4.x to 5.x break the pack covers ([#466](https://github.com/TMHSDigital/Blender-Developer-Tools/issues/466)); teach the API contracts that shipped examples prove but no skill covers yet, such as Grease Pencil v3, the 5.x compositor, constraints and image pixels ([#456](https://github.com/TMHSDigital/Blender-Developer-Tools/issues/456))
1313
- **Skill corrections:** the full Principled BSDF v2 socket rename table in `procedural-materials-and-shaders` ([#382](https://github.com/TMHSDigital/Blender-Developer-Tools/issues/382))
14-
- **New rules:** flag the mesh-normals APIs removed in 4.1 (`use_auto_smooth`, `calc_normals_split`, `free_normals_split`) ([#379](https://github.com/TMHSDigital/Blender-Developer-Tools/issues/379))
1514
- **Examples for skills that have none:** check-only examples for `operators`, `ui-panels`, `addon-scaffolding` and `bl-info-migration` ([#378](https://github.com/TMHSDigital/Blender-Developer-Tools/issues/378)); a skill-to-example index and one machine-readable examples manifest with version support ([#377](https://github.com/TMHSDigital/Blender-Developer-Tools/issues/377))
1615
- **Verification:** run the shipped snippets and SKILL.md code blocks in CI instead of frozen copies ([#447](https://github.com/TMHSDigital/Blender-Developer-Tools/issues/447)); a weekly, non-gating smoke run against Blender daily builds to catch API breaks before a release ([#457](https://github.com/TMHSDigital/Blender-Developer-Tools/issues/457))
1716
- **More agents:** first-class support for Codex, Copilot, Windsurf and Gemini through `.agents/skills` and an AGENTS.md rules export ([#376](https://github.com/TMHSDigital/Blender-Developer-Tools/issues/376))
@@ -39,6 +38,7 @@ derives the actual version from conventional-commit types.
3938
| Extension runtime and packaging | 17 | 9 | 3 | 28 | Shipped (v0.146.0) |
4039
| Timers, modal operators and threading | 18 | 9 | 3 | 29 | Shipped (v0.147.0) |
4140
| GPU drawing without `bgl` | 19 | 10 | 3 | 29 | Shipped |
41+
| Removed normals API rule (pre-4.1 auto smooth) | 19 | 11 | 3 | 29 | Shipped |
4242
| Live-session bridge (opt-in `blender-live-session` plugin, upstream Blender Lab MCP) | - | - | - | - | Shipped |
4343
| Stable (1.0) | — | — | — | — | Upcoming; criteria below |
4444

‎claude/blender-rules.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ Flag `import bgl` and any `bgl.` call. The `bgl` OpenGL wrapper was deprecated i
1717

1818
Applies to: `**/*.py`. Full rule: [`rules/no-bgl.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/no-bgl.mdc).
1919

20+
## no-removed-normals-api
21+
22+
Flag the mesh normals API removed in Blender 4.1 — `use_auto_smooth`, `auto_smooth_angle`, `calc_normals_split()`, `free_normals_split()`, `create_normals_split()`. Each raises AttributeError on 4.5 LTS and 5.x. Use `Mesh.set_sharp_from_angle`, `object.shade_auto_smooth`, and `Mesh.corner_normals` (always current) instead.
23+
24+
Applies to: `**/*.py`. Full rule: [`rules/no-removed-normals-api.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/no-removed-normals-api.mdc).
25+
2026
## no-unapplied-modifiers-on-export
2127

2228
Flag an export call on objects that still carry unapplied modifiers when the export arguments do not request evaluated geometry. The engine then receives the authored cage, not the modifier result.

‎claude/skills/blender-rules/SKILL.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ Flag `import bgl` and any `bgl.` call. The `bgl` OpenGL wrapper was deprecated i
2222

2323
Applies to: `**/*.py`. Full rule: [`rules/no-bgl.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/no-bgl.mdc).
2424

25+
## no-removed-normals-api
26+
27+
Flag the mesh normals API removed in Blender 4.1 — `use_auto_smooth`, `auto_smooth_angle`, `calc_normals_split()`, `free_normals_split()`, `create_normals_split()`. Each raises AttributeError on 4.5 LTS and 5.x. Use `Mesh.set_sharp_from_angle`, `object.shade_auto_smooth`, and `Mesh.corner_normals` (always current) instead.
28+
29+
Applies to: `**/*.py`. Full rule: [`rules/no-removed-normals-api.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/no-removed-normals-api.mdc).
30+
2531
## no-unapplied-modifiers-on-export
2632

2733
Flag an export call on objects that still carry unapplied modifiers when the export arguments do not request evaluated geometry. The engine then receives the authored cage, not the modifier result.

‎docs/gallery/index.html‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
<h1>Examples and Showcase</h1>
6363
<p>Runnable Blender Python examples, each run headless on Blender 5.2 LTS and 4.5 LTS in CI, so every render reflects code that actually runs — shown beside budget-conformance showcase props.</p>
6464
<details class="check-only" id="check-only">
65-
<summary>56 rendered &middot; 11 check-only examples (data contracts with nothing to see)</summary>
65+
<summary>56 rendered &middot; 12 check-only examples (data contracts with nothing to see)</summary>
6666
<div>
6767
A check-only example asserts a fact no render can show, such as a datablock name,
6868
an RNA attribute, a post-exit sidecar or a topology count. It runs in the same
@@ -75,6 +75,7 @@ <h1>Examples and Showcase</h1>
7575
<li><a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/gn-bundle-roundtrip/">gn-bundle-roundtrip</a>: Combine Bundle / Separate Bundle packing a cube plus Scale, Offset, and Mark — witnessing <code>geometry-nodes-python</code> item-name round-trip, not tree structure.</li>
7676
<li><a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/gpu-offscreen-draw/">gpu-offscreen-draw</a>: Check-only witness for drawing with the <code>gpu</code> module, the replacement for the <code>bgl</code> module that 5.x removed.</li>
7777
<li><a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/mesh-automasking-settings/">mesh-automasking-settings</a>: Pathology witness for the 5.2 move of sculpt automasking RNA off <code>Brush</code> into <code>MeshAutomaskingSettings</code>.</li>
78+
<li><a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/mesh-normals-after-41/">mesh-normals-after-41</a>: Check-only witness for the normals API that replaced auto smooth in Blender 4.1, on 4.5 LTS and 5.x alike.</li>
7879
<li><a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/ngon-triangulate/">ngon-triangulate</a>: Synthesizes one hexagon by dissolving a cube edge, then asserts the hygiene / tangent contracts on that defective mesh.</li>
7980
<li><a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/timers-modal-threading/">timers-modal-threading</a>: Proves the event-loop contracts behind long-running add-on work.</li>
8081
<li><a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/unapplied-scale-gltf/">unapplied-scale-gltf</a>: Synthesizes unapplied non-uniform object scale on a unit cube, then asserts the glTF exporter contract.</li>

‎docs/new-example-prompt.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ metadata — so the render would be identical whether the API held or broke. The
5959
"redesign the scene until failure would be visible" instruction above is the test:
6060
attempt it first, and only when it cannot succeed in principle does the example
6161
become check-only. The exception is for contracts that are invisible, not for
62-
renders that are hard. Ten of the 67 examples currently qualify, and `CLAUDE.md`
62+
renders that are hard. Ten of the 68 examples currently qualify, and `CLAUDE.md`
6363
carries the same rule. A check-only example is otherwise a full example: it still
6464
asserts a real contract, still exits non-zero on failure, still carries a falsifier,
6565
and still takes a `tests/smoke/catalog.json` row so it runs on every PR.

0 commit comments

Comments
 (0)