Skip to content

Commit c906438

Browse files
TMHSDigitalclaude
andauthored
feat(skills): timers-modal-and-threading skill, snippet and example (#446)
Long-running add-on work had no coverage. New skill timers-modal-and-threading covers: - bpy.app.timers return values and persistent=True - modal operators driven by event_timer_add - worker threads that hand results to the main thread through a queue.Queue - why none of this runs in --background Every claim is measured on 4.5.11, 5.1.2 and 5.2.1: - In --background a registered timer never runs. - A None timer runs once; a float timer re-runs; a 0.0 timer keeps re-running. - first_interval is keyword-only; register(fn, 0.5) raises TypeError. - A file load keeps persistent timers and drops plain ones. - bpy.types.Event has no `timer` attribute, so event.timer in modal() raises AttributeError. - A modal operator ticks to FINISHED and removes its timer. New snippet snippets/thread-queue-timer.py. New check-only example examples/timers-modal-threading runs a --background child and a windowed child that quits itself, with falsifiers --return-zero-once (exit 5) and --non-persistent (exit 8). The windowed child needs a display; smoke runs under xvfb. Counts move to 18 skills, 29 snippets and 66 examples (10 check-only); the example maps to this skill and operators. Closes #381 Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 75d3b30 commit c906438

16 files changed

Lines changed: 627 additions & 25 deletions

File tree

‎.cursor-plugin/plugin.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"skills/bake-high-to-low/SKILL.md",
2525
"skills/engine-export-presets/SKILL.md",
2626
"skills/extension-runtime-and-packaging/SKILL.md",
27+
"skills/timers-modal-and-threading/SKILL.md",
2728
"skills/operators/SKILL.md",
2829
"skills/ui-panels/SKILL.md",
2930
"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 17 skills, 9 rules, 3 templates, 28 snippets, 65
23+
The content base is 18 skills, 9 rules, 3 templates, 29 snippets, 66
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 # 17 skill files
35+
skills/<skill-name>/SKILL.md # 18 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: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ The **Blender Developer Tools** repository is at **v0.146.0**. It packages skill
1717
## Repository Architecture
1818

1919
```
20-
skills/<skill-name>/SKILL.md - AI workflow definitions, 17 total
20+
skills/<skill-name>/SKILL.md - AI workflow definitions, 18 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, 28 total
24-
examples/<name>/ - Runnable smoke-gated examples, 65 total (+ gallery.json)
23+
snippets/<snippet-name>.py - Standalone code patterns, 29 total
24+
examples/<name>/ - Runnable smoke-gated examples, 66 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 (17)
33+
## Skills (18)
3434

3535
| Skill | Purpose |
3636
| --- | --- |
@@ -51,6 +51,7 @@ VERSION - Source of truth for the repo version
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 |
5353
| extension-runtime-and-packaging | Bundled wheels, `extension_path_user` data that survives upgrades, `online_access`, `bl_ext` names, `--command extension` build in CI |
54+
| 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` |
5455

5556
## Rules (9)
5657

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

99-
## Snippets (28)
100+
## Snippets (29)
100101

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

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

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

109-
Extensions runtime: `extension-user-data.py` (`extension_path_user` settings file and an `online_access` guard).
110+
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).
110111

111-
## Examples (65)
112+
## Examples (66)
112113

113114
Runnable scripts at `examples/<name>/`, each asserting a real API contract with
114115
deterministic checks (exit non-zero on failure) and optionally rendering a still via
115116
`--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);
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
117+
**56 of the 66 ship a render in the site gallery** at `docs/gallery/`. The other
118+
ten are **check-only**: they carry no `--output` path, no gallery entry, and no
118119
hero asset. The criterion is whether the contract is expressible in pixels. An
119120
example is check-only when its witness is a data or state fact that no scene
120121
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. "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.
279+
`README.md` declares aggregate counts (e.g. "18 skills, 9 rules, 3 templates, 29 snippets, 66 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>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>
28+
<strong>18 skills</strong> &nbsp;&bull;&nbsp; <strong>9 rules</strong> &nbsp;&bull;&nbsp; <strong>3 templates</strong> &nbsp;&bull;&nbsp; <strong>29 snippets</strong> &nbsp;&bull;&nbsp; <strong>66 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 **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.
47+
This repository ships **18 skills, 9 rules, 3 templates, 29 snippets, 66 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 17 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 18 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 17 skills plus `blender-rules`:
73+
- **Claude Code** — install as a plugin, then run `/skills` to see all 18 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 65 examples carries a **falsifier**: a flag that changes the
108+
Every one of the 66 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-
**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).
136+
**66 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 - 17 skill files, YAML frontmatter, one canonical pattern each
154+
skills/<name>/SKILL.md - 18 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 - 28 standalone Python snippets, 5 to 75 lines each
158-
examples/<name>/ - 65 example directories: script, README with exit codes and falsifier
157+
snippets/<name>.py - 29 standalone Python snippets, 5 to 75 lines each
158+
examples/<name>/ - 66 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. Nine of the 65 examples currently qualify, and `CLAUDE.md`
62+
renders that are hard. Ten of the 66 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.

‎examples/index.json‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1901,6 +1901,44 @@
19011901
"13": "`--output`: bundled `datafiles/fonts/Inter.woff2` not found"
19021902
}
19031903
},
1904+
{
1905+
"name": "timers-modal-threading",
1906+
"path": "examples/timers-modal-threading/",
1907+
"script": "examples/timers-modal-threading/timers_modal_threading.py",
1908+
"skills": [
1909+
"operators",
1910+
"timers-modal-and-threading"
1911+
],
1912+
"render": false,
1913+
"min_version": "4.5",
1914+
"run": "blender --background --python examples/timers-modal-threading/timers_modal_threading.py",
1915+
"summary": "Proves the event-loop contracts behind long-running add-on work.",
1916+
"falsifiers": [
1917+
{
1918+
"args": [
1919+
"--return-zero-once"
1920+
],
1921+
"expect_exit": 5
1922+
},
1923+
{
1924+
"args": [
1925+
"--non-persistent"
1926+
],
1927+
"expect_exit": 8
1928+
}
1929+
],
1930+
"exit_codes": {
1931+
"0": "Success",
1932+
"1": "Uncaught exception (FATAL wrapper)",
1933+
"2": "argparse / usage",
1934+
"3": "`--background` child failed, or its timer ran",
1935+
"4": "Windowed child wrote no result",
1936+
"5": "Timer return values not honoured (`--return-zero-once` lands here)",
1937+
"6": "Worker result not applied once on the main thread",
1938+
"7": "Modal operator did not finish and remove its timer",
1939+
"8": "File load did not keep only the persistent timer (`--non-persistent` lands here)"
1940+
}
1941+
},
19041942
{
19051943
"name": "triangulate-tangents",
19061944
"path": "examples/triangulate-tangents/",

‎examples/skills.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"solidify-even-thickness": ["depsgraph-and-evaluated-data", "mesh-editing-and-bmesh"],
5454
"swatch-grid": ["procedural-materials-and-shaders"],
5555
"temp-override-join": ["headless-batch-scripting", "operators"],
56+
"timers-modal-threading": ["operators", "timers-modal-and-threading"],
5657
"text-version-stamp": [],
5758
"triangulate-tangents": ["mesh-editing-and-bmesh"],
5859
"turntable": ["slotted-actions-animation"],

0 commit comments

Comments
 (0)