Skip to content

feat(skills): timers-modal-and-threading skill, snippet and example - #446

Merged
TMHSDigital merged 1 commit into
mainfrom
feat/timers-modal-threading-skill
Oct 5, 2026
Merged

TMHSDigital merged 1 commit into
mainfrom
feat/timers-modal-threading-skill

Conversation

@TMHSDigital

@TMHSDigital TMHSDigital commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Closes #381. (Rebased onto main after #445 merged.)

What ships

  • Skill timers-modal-and-threading:
    • 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 it runs in --background.
  • Snippet snippets/thread-queue-timer.py.
  • Check-only example examples/timers-modal-threading:
    • runs a --background child, and a windowed child that quits itself from a timer and reports JSON;
    • falsifiers --return-zero-once (exit 5) and --non-persistent (exit 8).
  • Wiring: catalog row; examples/skills.json (→ this skill and operators); Cursor manifest; counts (18 skills, 29 snippets, 66 examples, 10 check-only).

Measured facts the skill relies on (4.5.11, 5.1.2, 5.2.1)

Fact Result
Timer in a --background child is_registered True, never runs
None timer / timer returning 0.05 until its 3rd call runs 1× / 3×, then unregistered
Run-once timer returning 0.0 re-runs (5× on 5.2.1 before a file load removed it)
register(fn, 0.5) TypeError: register() takes exactly 1 positional argument (2 given) on all three. 5.2's docstring shows the *; 4.5's doesn't, but the behaviour is the same
wm.read_factory_settings from a timer the persistent timer stays registered, the plain one is dropped
Queue drained by a timer runs on the main thread; mesh created from the worker's result
Modal operator via INVOKE_DEFAULT under temp_override(window=…) RUNNING_MODAL, 3 TIMER events, FINISHED, timer removed
'timer' in bpy.types.Event.bl_rna.properties False (4.5.11 and 5.2.1). event.timer raised AttributeError in a 5.2.1 modal, which first hung my probe

Found while building: the first windowed run hung, because the timer meant to quit Blender was itself dropped by the file load the example performs. That is the contract under test. The quit timer is now persistent=True, and the skill says so.

Evidence

Live run: tests/smoke/run_catalog.py over the new row on local Windows binaries; all PASS.

Binary reports Happy path --return-zero-once --non-persistent
Blender 4.5.11 LTS PASS PASS (exit 5) PASS (exit 8)
Blender 5.1.2 PASS PASS (exit 5) PASS (exit 8)
Blender 5.2.1 LTS PASS PASS (exit 5) PASS (exit 8)

Local checks: tests/run_all.py 31 passed, 0 failed.

Not yet proven: the windowed child on Linux under xvfb. This PR's smoke legs are the first such run. If the child cannot open a window there, the row fails with exit 4 rather than passing silently.

🤖 Generated with Claude Code

@github-actions github-actions Bot added skills snippets examples Runnable smoke-gated examples under examples/ documentation Improvements or additions to documentation labels Oct 5, 2026
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>
@TMHSDigital
TMHSDigital force-pushed the feat/timers-modal-threading-skill branch from 7e29598 to 525191b Compare October 5, 2026 03:15
@TMHSDigital

Copy link
Copy Markdown
Owner Author

CI evidence before merge: all checks pass ( 13 pass ). Blender Smoke on PR head: Blender 5.2.2 LTS / Blender 4.5.14 LTS. Summaries (happy path; falsifiers) per leg: 145 passed, 1 skipped, 0 failed;166 passed, 0 skipped, 0 failed;143 passed, 3 skipped, 0 failed;151 passed, 15 skipped, 0 failed;

@TMHSDigital
TMHSDigital merged commit c906438 into main Oct 5, 2026
13 checks passed
@TMHSDigital
TMHSDigital deleted the feat/timers-modal-threading-skill branch October 5, 2026 03:33
@TMHSDigital

Copy link
Copy Markdown
Owner Author

Linux evidence (run 37258749968, head 525191b): the windowed child ran under xvfb, [PASS] timers-modal-threading plus both falsifiers. Under --return-zero-once the run-once timer ran 4x on Linux (5x locally on Windows); any count other than 1 trips exit 5, so the timing difference does not affect the check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation examples Runnable smoke-gated examples under examples/ skills snippets

Projects

None yet

Development

Successfully merging this pull request may close these issues.

skills: new timers-modal-and-threading skill (bpy.app.timers, modal operators, bpy is not thread-safe)

1 participant