Skip to content

Commit c8aa929

Browse files
TMHSDigitalclaude
andcommitted
feat(skills): extension-runtime-and-packaging skill, snippet and example
Nothing covered what breaks an extension after its first run. New skill extension-runtime-and-packaging covers: - bundled wheels instead of pip, matched to Python 3.11 (4.5) and 3.13 (5.2) - user data in bpy.utils.extension_path_user, never beside __file__ - the network permission plus bpy.app.online_access - bl_ext package names and relative imports - blender --command extension validate/build/server-generate in CI Every claim is measured on 4.5.11, 5.1.2 and 5.2.1 in a throwaway BLENDER_USER_RESOURCES: - validate passes a manifest whose wheel file is missing (also an unknown platform name and schema_version 9.9.9); only build fails it. - An upgrade replaces the install tree but keeps extensions/.user/...; extension remove deletes it. - extension_path_user raises ValueError for a non-bl_ext package. - Wheels land in extensions/.local/lib/pythonX.Y/site-packages, which is already on sys.path. New snippet snippets/extension-user-data.py, and new check-only example examples/extension-package-lifecycle. The example builds, lists, installs, upgrades and removes the repo's extension template, with falsifiers --validate-only (exit 4) and --data-next-to-file (exit 7). Counts move to 17 skills, 28 snippets and 65 examples (9 check-only). The example maps to the new skill and headless-batch-scripting in examples/skills.json. build_examples_index.py now drops the backticks from a stripped link whose text is a path, so a README lead such as [`templates/...`](...) no longer leaves a bare repo path in a skill. Closes #380 Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f80411b commit c8aa929

17 files changed

Lines changed: 639 additions & 25 deletions

File tree

‎.cursor-plugin/plugin.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"skills/ai-mesh-cleanup/SKILL.md",
2424
"skills/bake-high-to-low/SKILL.md",
2525
"skills/engine-export-presets/SKILL.md",
26+
"skills/extension-runtime-and-packaging/SKILL.md",
2627
"skills/operators/SKILL.md",
2728
"skills/ui-panels/SKILL.md",
2829
"skills/custom-properties/SKILL.md",

‎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 16 skills, 9 rules, 3 templates, 27 snippets, 64
23+
The content base is 17 skills, 9 rules, 3 templates, 28 snippets, 65
2424
examples, and 76 showcase pieces (counts are CI-enforced against README.md)
2525
and the manifest). The full inventory tables and per-item purposes live in
2626
`CLAUDE.md`. Example anatomy and authoring rules: copy `examples/bmesh-gear/`;
@@ -32,7 +32,7 @@ in `docs/VISUAL-STYLE.md`; the canonical run prompt is
3232

3333
```
3434
Blender-Developer-Tools/
35-
skills/<skill-name>/SKILL.md # 16 skill files
35+
skills/<skill-name>/SKILL.md # 17 skill files
3636
rules/<rule-name>.mdc # 9 rule files
3737
templates/<template-name>/ # 3 starter templates
3838
snippets/<snippet-name>.py # 27 standalone Python snippets

‎CLAUDE.md‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ The **Blender Developer Tools** repository is at **v0.144.3**. It packages skill
1717
## Repository Architecture
1818

1919
```
20-
skills/<skill-name>/SKILL.md - AI workflow definitions, 16 total
20+
skills/<skill-name>/SKILL.md - AI workflow definitions, 17 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, 27 total
24-
examples/<name>/ - Runnable smoke-gated examples, 64 total (+ gallery.json)
23+
snippets/<snippet-name>.py - Standalone code patterns, 28 total
24+
examples/<name>/ - Runnable smoke-gated examples, 65 total (+ gallery.json)
2525
showcase/<name>/ - Budget-conformance props, 76 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
@@ -30,7 +30,7 @@ docs/gallery/ - Committed generated gallery pages + hero render
3030
VERSION - Source of truth for the repo version
3131
```
3232

33-
## Skills (16)
33+
## Skills (17)
3434

3535
| Skill | Purpose |
3636
| --- | --- |
@@ -50,6 +50,7 @@ VERSION - Source of truth for the repo version
5050
| drivers-and-app-handlers | Driver expressions, `driver_namespace`, application handlers including the new 5.1 `exit_pre` |
5151
| bl-info-migration | Three-step migration from legacy `bl_info` to Extensions Platform, dual-format pattern |
5252
| vse-python | VSE timeline from Python: `.strips` vs `.sequences`, `new_effect` kwargs, 5.2 COLOR `width`/`height` bake |
53+
| extension-runtime-and-packaging | Bundled wheels, `extension_path_user` data that survives upgrades, `online_access`, `bl_ext` names, `--command extension` build in CI |
5354

5455
## Rules (9)
5556

@@ -95,7 +96,7 @@ matching its `globs` does. Changing a glob changes when the rule fires.
9596
- Unity / Godot / Unreal glTF export via the engine-export-presets contract
9697
- Explicit exit codes matching `headless-batch-script-template` (0, then 2+)
9798

98-
## Snippets (27)
99+
## Snippets (28)
99100

100101
Small standalone `.py` files at `snippets/<name>.py`, each 5 to 75 lines.
101102

@@ -105,13 +106,15 @@ v0.2.0: Principled BSDF material, driver-with-custom-function via `driver_namesp
105106

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

108-
## Examples (64)
109+
Extensions runtime: `extension-user-data.py` (`extension_path_user` settings file and an `online_access` guard).
110+
111+
## Examples (65)
109112

110113
Runnable scripts at `examples/<name>/`, each asserting a real API contract with
111114
deterministic checks (exit non-zero on failure) and optionally rendering a still via
112115
`--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);
113-
**56 of the 64 ship a render in the site gallery** at `docs/gallery/`. The other
114-
eight are **check-only**: they carry no `--output` path, no gallery entry, and no
116+
**56 of the 65 ship a render in the site gallery** at `docs/gallery/`. The other
117+
nine are **check-only**: they carry no `--output` path, no gallery entry, and no
115118
hero asset. The criterion is whether the contract is expressible in pixels. An
116119
example is check-only when its witness is a data or state fact that no scene
117120
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
@@ -276,7 +276,7 @@ The drift-check workflow enforces these on every push and PR.
276276

277277
## Aggregate Counts
278278

279-
`README.md` declares aggregate counts (e.g. "16 skills, 9 rules, 3 templates, 27 snippets, 64 examples, and 76 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.
279+
`README.md` declares aggregate counts (e.g. "17 skills, 9 rules, 3 templates, 28 snippets, 65 examples, and 76 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.
280280

281281
## Pull Request Process
282282

‎README.md‎

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

2727
<p align="center">
28-
<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>64 examples</strong> &nbsp;&bull;&nbsp; <strong>76 showcase pieces</strong>
28+
<strong>17 skills</strong> &nbsp;&bull;&nbsp; <strong>9 rules</strong> &nbsp;&bull;&nbsp; <strong>3 templates</strong> &nbsp;&bull;&nbsp; <strong>28 snippets</strong> &nbsp;&bull;&nbsp; <strong>65 examples</strong> &nbsp;&bull;&nbsp; <strong>76 showcase pieces</strong>
2929
</p>
3030

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

4545
## Overview
4646

47-
This repository ships **16 skills, 9 rules, 3 templates, 27 snippets, 64 examples, and 76 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 **17 skills, 9 rules, 3 templates, 28 snippets, 65 examples, and 76 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 reading these files directly from a checkout — **there is no MCP server in this repository, and none is required**. Both agents 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

@@ -63,14 +63,14 @@ The content is consumed by AI coding agents reading these files directly from a
6363
git clone https://github.com/TMHSDigital/Blender-Developer-Tools.git
6464
```
6565

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

6868
```bash
6969
git clone --branch plugin-dist --single-branch https://github.com/TMHSDigital/Blender-Developer-Tools.git ~/.cursor/plugins/local/blender-developer-tools
7070
```
7171

7272
(On Windows the folder is `%USERPROFILE%\.cursor\plugins\local\`. Cursor skips symlinks that point outside that folder, so clone or copy rather than link.) Teams can import the repository as a team marketplace instead; it carries `.cursor-plugin/marketplace.json`. For one project only, copy `skills/*` into the project's `.cursor/skills/` and `rules/*.mdc` into `.cursor/rules/` from a checkout. Copying only the rules gives you no skills.
73-
- **Claude Code** — install as a plugin, then run `/skills` to see all 16 skills plus `blender-rules`:
73+
- **Claude Code** — install as a plugin, then run `/skills` to see all 17 skills plus `blender-rules`:
7474

7575
```text
7676
/plugin marketplace add TMHSDigital/Blender-Developer-Tools@plugin-dist
@@ -105,7 +105,7 @@ Releases ship often; [CHANGELOG.md](CHANGELOG.md) lists what each one changed.
105105

106106
## Falsifiers
107107

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

134134
## Examples and showcase
135135

136-
**64 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. **76 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).
136+
**65 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. **76 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).
137137

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

@@ -151,11 +151,11 @@ Browse both, with filters, full-size renders and each script's README, in the **
151151
## How content is organized
152152

153153
```
154-
skills/<name>/SKILL.md - 16 skill files, YAML frontmatter, one canonical pattern each
154+
skills/<name>/SKILL.md - 17 skill files, YAML frontmatter, one canonical pattern each
155155
rules/<name>.mdc - 9 rule files, anti-pattern + correction
156156
templates/<name>/ - 3 template directories (extension-addon-template, headless-batch-script-template, ai-asset-pipeline-template)
157-
snippets/<name>.py - 27 standalone Python snippets, 5 to 75 lines each
158-
examples/<name>/ - 64 example directories: script, README with exit codes and falsifier
157+
snippets/<name>.py - 28 standalone Python snippets, 5 to 75 lines each
158+
examples/<name>/ - 65 example directories: script, README with exit codes and falsifier
159159
showcase/<name>/ - 76 showcase pieces: budget-gated game props, script and README
160160
claude/ - generated Claude Code copies of the rules (blender-rules skill + import file)
161161
```

‎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. Eight of the 64 examples currently qualify, and `CLAUDE.md`
62+
renders that are hard. Nine of the 65 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.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Extension package lifecycle
2+
3+
Takes [`templates/extension-addon-template`](../../templates/extension-addon-template/)
4+
through what an extension meets after its first run: validate, build, a
5+
repository listing, install, an upgrade over the top, removal, and the
6+
online-access gate. Every step runs a child Blender against a throwaway user
7+
directory (`BLENDER_USER_RESOURCES`), so the real install is never touched.
8+
Check-only: the witnesses are exit codes, zip members and paths, so a render
9+
would look the same whether the contract held or broke.
10+
11+
Follows [`extension-runtime-and-packaging`](../../skills/extension-runtime-and-packaging/SKILL.md),
12+
whose snippet is [`extension-user-data.py`](../../snippets/extension-user-data.py).
13+
Scaffolding matches [`eval-mesh-datablock-name`](../eval-mesh-datablock-name/)
14+
(`check()` steps return codes, argparse falsifier flags, FATAL wrapper).
15+
16+
**What it witnesses:**
17+
18+
- `blender --command extension validate` and `build` accept the template, and
19+
the zip carries `blender_manifest.toml` and `__init__.py` at its root.
20+
- **`validate` is not a release gate.** With `wheels = ["./wheels/not_shipped-1.0-py3-none-any.whl"]`
21+
appended and no such file, `validate` exits 0 and `build` exits 1 with no
22+
zip. Only the build opens the files the manifest names.
23+
- `extension server-generate --repo-dir` writes `index.json` listing the
24+
package with `archive_url` `./example_addon-0.1.0.zip`.
25+
- Installed with `extension install-file -r user_default`, the add-on imports
26+
as `bl_ext.user_default.example_addon`, and
27+
`bpy.utils.extension_path_user(__package__)` names
28+
`extensions/.user/user_default/example_addon`, beside the install tree
29+
`extensions/user_default/example_addon`, not inside it.
30+
- Installing 0.2.0 over 0.1.0 replaces the install tree (a file written
31+
beside `__file__` is gone) and keeps the user directory (`settings.json`
32+
survives). `extension remove` deletes the user directory too.
33+
- `bpy.app.online_access` is `False` in a factory-startup child and `True`
34+
under `--online-mode`.
35+
36+
**What failure each check would catch:**
37+
38+
- exit 3 — the template stopped validating or building, or the zip lost its manifest
39+
- exit 4 — a package whose wheel does not exist passed the release gate
40+
(`--validate-only` lands here: it gates on `validate` alone); also exit 4 if
41+
`validate` starts rejecting the missing wheel, because the trap this example
42+
names would then be gone
43+
- exit 5 — `server-generate` wrote no listing, or the listing does not name the package
44+
- exit 6 — install, import, upgrade or remove failed, or `__package__` is not
45+
`bl_ext.user_default.example_addon`
46+
- exit 7 — user data lives inside the install tree, did not survive the
47+
upgrade, or survived removal (`--data-next-to-file` lands here: it stores
48+
data beside `__file__`)
49+
- exit 8 — `online_access` is not `False` by default and `True` under `--online-mode`
50+
51+
## Re-verified
52+
53+
| Measurement | 4.5.11 | 5.1.2 | 5.2.1 |
54+
| --- | --- | --- | --- |
55+
| validate / build, template | 0 / 0 | 0 / 0 | 0 / 0 |
56+
| validate / build, missing wheel | 0 / 1 | 0 / 1 | 0 / 1 |
57+
| `__package__` | `bl_ext.user_default.example_addon` | same | same |
58+
| user data kept by 0.2.0 upgrade | yes | yes | yes |
59+
| file beside `__file__` kept by upgrade | no | no | no |
60+
| user data after `remove` | deleted | deleted | deleted |
61+
| `online_access` default / `--online-mode` | False / True | False / True | False / True |
62+
| default exit | 0 | 0 | 0 |
63+
| `--validate-only` / `--data-next-to-file` exit | 4 / 7 | 4 / 7 | 4 / 7 |
64+
65+
## API reference
66+
67+
- [`bpy.utils.extension_path_user`](https://docs.blender.org/api/current/bpy.utils.html#bpy.utils.extension_path_user)
68+
([4.5 LTS](https://docs.blender.org/api/4.5/bpy.utils.html#bpy.utils.extension_path_user))
69+
- [`bpy.app.online_access`](https://docs.blender.org/api/current/bpy.app.html#bpy.app.online_access)
70+
- [Extensions command line arguments](https://docs.blender.org/manual/en/latest/advanced/command_line/extension_arguments.html)
71+
72+
## Run
73+
74+
```bash
75+
blender --background --python extension_package_lifecycle.py --
76+
blender --background --python extension_package_lifecycle.py -- --validate-only
77+
blender --background --python extension_package_lifecycle.py -- --data-next-to-file
78+
```
79+
80+
The script starts about a dozen child Blender processes, so it takes longer
81+
than most examples.
82+
83+
## Exit codes
84+
85+
| Code | Meaning |
86+
| --- | --- |
87+
| 0 | Success |
88+
| 1 | Uncaught exception (FATAL wrapper) |
89+
| 2 | argparse / usage |
90+
| 3 | Template does not validate or build, or the zip lacks its manifest |
91+
| 4 | Missing-wheel package passed the release gate (`--validate-only` lands here) |
92+
| 5 | `server-generate` listing missing or wrong |
93+
| 6 | Install, import, upgrade or remove failed, or `__package__` wrong |
94+
| 7 | User data inside the install tree, lost on upgrade, or kept after removal (`--data-next-to-file` lands here) |
95+
| 8 | `online_access` default or `--online-mode` value wrong |
96+
97+
The `blender-smoke` workflow runs the check on Blender 5.2 LTS and 4.5 LTS
98+
(5.1 on the weekly cron, the `needs-5.1` PR label, or manual dispatch).
99+
Smoke passes no extra flags on the happy path. Its catalog falsifiers are
100+
`--validate-only` (expects exit 4) and `--data-next-to-file` (expects exit 7).

0 commit comments

Comments
 (0)