Thanks for helping improve this repository. This document describes how to set up locally, extend skills, rules, snippets, templates, examples, and showcase pieces, and submit changes.
The fastest useful contributions are small and need no Blender run:
- Fix a skill or rule. If a skill tells an agent something that is wrong
for Blender 5.2 or 4.5, edit
skills/<name>/SKILL.mdorrules/<name>.mdc, say in the PR how you know (a docs link, or a shortblender --backgroundscript and its output), and run the checks below. - Improve a snippet.
snippets/*.pyare short standalone patterns (MIT). - Pick up a starter issue: issues labelled
good first issue.
Then: fork, branch, pip install -r requirements-dev.txt,
python tests/run_all.py, and open a PR with a DCO sign-off (git commit -s;
see the end of this file). Examples and showcase pieces have more gates
(falsifiers, exit-code tables, renders); read their sections first.
-
Fork the repository on GitHub.
-
Clone your fork:
git clone https://github.com/<your-username>/Blender-Developer-Tools.git cd Blender-Developer-Tools
-
Create a branch for your work:
git checkout -b your-feature-name
-
Run the checks before you open a pull request. One command runs every Validate check that does not need Blender (the frontmatter, catalog, exit-code, gallery, link and packaging gates, plus the tooling tests):
pip install -r requirements-dev.txt python tests/run_all.py # or: python tests/run_all.py -k catalogBlender smoke runs in CI on 5.2 LTS and 4.5 LTS; to run an example locally, see
tests/smoke/run_example.py.
This repo is a content collection (skills, rules, snippets, templates, examples, and showcase pieces) for Blender Python development. There is no runtime and no MCP server. Headless checks run through tests/smoke/run_example.py; CI validates frontmatter, syntax, and aggregate counts.
skills/
<skill-name-kebab>/
SKILL.md
rules/
<rule-name>.mdc
snippets/
<snippet-name>.py
templates/
<template-name>/
blender_manifest.toml
__init__.py
README.md
examples/
gallery.json
<example-name>/
README.md
showcase/
README.md
gallery.json
<piece-name>/
README.md
skills/- one directory per skill, each containingSKILL.mdwith YAML frontmatter (name,description,standards-version).rules/- Cursor-style rules as.mdcfiles with YAML frontmatter (description,alwaysApply,globs,standards-version).snippets/- small standalone.pyfiles (5 to 75 lines) demonstrating a single canonical pattern.templates/- copy-paste starting points; one directory per template.showcase/- budget-conformance props, sibling ofexamples/. Not API contracts. Conventions:showcase/README.md.
-
Add a kebab-case directory under
skills/, e.g.skills/procedural-materials-and-shaders/. -
Create
SKILL.mdwith YAML frontmatter:--- name: procedural-materials-and-shaders description: "What it covers. Use when the user mentions X, does Y, or hits error Z." standards-version: <current meta-repo STANDARDS_VERSION> ---
-
Aim for 150 to 350 lines covering the canonical pattern, common AI mistakes, version-correctness notes, and one or two worked code examples. Cite Blender API doc URLs where relevant. Avoid encyclopedic API tours.
-
The skill
namein frontmatter must match the directory name exactly (CI enforces this).
-
Add a
.mdcfile underrules/, e.g.rules/use-foreach-set-for-bulk-data.mdc. -
Start with YAML frontmatter:
--- description: One-line summary for humans and tooling. alwaysApply: false globs: - "**/*.py" standards-version: <current meta-repo STANDARDS_VERSION> ---
alwaysApply: falseis deliberate and is what makesglobsload-bearing: the rule enters context only when a file it matches does.alwaysApply: truewould apply the rule in every context and makeglobsdecorative, so the two must never both be set as if they compose. Pick globs that cover every file the rule is meant to guard before setting this — a glob that is too narrow means the rule silently stops firing, which is worse than over-applying. Record the scope in theCLAUDE.mdrules table to match. -
Write 30 to 80 lines: the anti-pattern, a code example showing it wrong, a code example showing it right, and a short "Why it matters" section.
- Add a
.pyfile undersnippets/, e.g.snippets/depsgraph-evaluated-mesh.py. - Keep it 5 to 75 lines, fully working code, with a header comment naming the snippet and citing the relevant Blender doc URL or research section.
- Snippets are validated for Python syntax in CI.
- Add a directory under
templates/, e.g.templates/headless-batch-script-template/. - Include all files needed for an immediate copy-paste starting point. For add-on templates, include
blender_manifest.toml,__init__.py, and a briefREADME.md.
Read showcase/README.md first. Showcase asserts
budget conformance, never an API contract.
- Add
showcase/<kebab-name>/with a script, a README that includes an exit-code table, and a falsifier that breaks one pipeline stage so a named budget fails. - Add a
tests/smoke/catalog.jsonrow with at least one entry infalsifiers({"args": ["--your-flag"], "expect_exit": N}, matching the README falsifier table). The runner takes opaque script paths. - Add a
showcase/gallery.jsonpieces[]entry, including analt: one sentence saying what the still shows (the generator fails without it). Render a still, and runpython scripts/build_gallery.py. Do not hand-editdocs/gallery/HTML. - Update the README showcase-piece count.
validate-countschecks it separately from the example total.
Content targets Blender 5.2 LTS as primary, Blender 5.1 as prior stable, and Blender 4.5 LTS as fallback. When the API differs, branch on bpy.app.version and document both paths. Example:
if bpy.app.version >= (5, 0, 0):
# 5.x path
...
else:
# 4.5 LTS path
...Default PR smoke is Blender 5.2 and 4.5 (.github/workflows/blender-smoke.yml).
5.1 is not in that matrix.
- Apply the
needs-5.1label when the change can diverge on 5.1 (bake, UV RNA, version-branched API). That starts a 5.1 smoke job. Auto-label will not apply this; it is opt-in. - Run any series on demand: Actions > Blender Smoke Test > Run workflow,
pick the branch and the
seriesinput. - Monday 07:00 UTC cron still runs 5.2, 5.1, and 4.5. Do not treat cron as PR evidence.
blender-smoke.ymlalso runs onpushtomain, with the samepaths-ignoreas pull requests. It exists so a direct push tomainis smoke-tested beforerelease.ymlreleases it: the release gate waits for that run. For a merged PR the gate reads the PR's own checks, so the PR-head 5.2 and 4.5 jobs remain the merge evidence.pages.ymlis path-filtered. A workflow-or-docs-only merge does not deploy Pages. Observed on13ea521(ci:#137): Validate, drift-check, and Release ran; Pages did not. Intentional, not a failed job.
What main enforces today, why required status checks cannot be added on a
user-owned repository, and what changes if that becomes possible: see
AGENTS.md § Branch protection on main.
Contributors only need to know that PRs merge on green CI.
Every falsifier declares the budget it aims at, and it must fail that budget. A falsifier that exits non-zero on some earlier check proves nothing about its target: the run is red for the wrong reason, and the budget it was built for was never reached.
Fix a collision by changing the model or the falsifier. Never widen a band so an ill-aimed falsifier lands.
Worked example. showcase/stone-archway's arch falsifier began as
--flat-arch, laying the voussoirs as a flat lintel. A lintel is 0.62 m
shorter than the arch, so it tripped the bounding-box budget (exit 8) and
never reached the intrados circle fit (exit 19) it existed to break.
Widening the bbox tolerance to let it through would have destroyed a real
budget to rescue a bad falsifier. It was replaced by --off-circle, which
keeps the angles, joints, materials, triangle count and envelope identical
and wanders only the intrados radius. Four other falsifiers in the same run
needed the same treatment: a stray vertex moved inside the silhouette, a
--short-skids that floats one runner of three instead of all of them, a
--same-seed split into design and placement RNG streams, and a
--sink-keystone that no longer changes the Y envelope.
tests/check_falsifier_targets.py enforces the declaration. Its default
static mode reads each showcase piece's falsifier table and asserts the
flags are real argparse flags, the declared exit codes appear in that
piece's exit-code table, and every falsifier names a target budget. Its
--run BLENDER mode executes each falsifier and asserts the observed
exit equals the declared one — the mode that catches an ill-aimed
falsifier. Runtime costs one Blender launch per falsifier, measured at
276 s for the whole showcase tree on one version, so it is an authoring
and cron tool rather than a per-PR smoke step.
Three roles, not one global table. Do not copy a code from one script into
another and assume it means the same thing. 9 is a valid sequential-check
code; there is no rule against it.
Per-script exits (examples and headless templates). 0 success. 2
argument or usage error, matching argparse. 3 and above for that script's
own sequential check failures, in the order the checks run. These codes are
file-local and are not portable. no-mesh is 2 in
templates/headless-batch-script-template/ and 5 in
templates/ai-asset-pipeline-template/; both are correct. Copy a template's
own table from that template, not from this paragraph.
FATAL wrapper. sys.exit(1) on an uncaught exception in the __main__
guard. Uniform across the examples. 1 means crashed, never a named check.
Smoke protocol. Owned by tests/ and the runner, not by product check
tables. 0 pass, 1 fail (tests/smoke/run_example.py,
tests/check_import_export_rules.py), 77 skip (tests/smoke/canary_skip.py,
and the product scripts that self-skip: examples/gn-bundle-roundtrip/,
examples/exit-pre-sidecar/). A script under test prints SMOKE_SKIP: and
exits 77; the runner records SKIP and returns 0 so the YAML step stays green.
Version-gated falsifiers. A falsifier for a cross-version removal or
rename is version-gated by nature. It exits its documented code on versions
where the API changed and exits 0 on versions where the old API still works.
That is the correct witness: the naive script is still valid on the older
binaries. It differs from every other falsifier in the tree (for example
--same-axis), which is red on all three versions.
Do not "fix" these flags to fail unconditionally. The examples that behave this way:
examples/vse-linear-modifiers/(--assume-present)examples/gn-socket-rename/(--legacy-ids)examples/eval-mesh-datablock-name/(--assume-distinct-names)examples/mesh-automasking-settings/(--assume-brush-attrs)
Files that participate in ecosystem drift checking must carry a standards-version marker matching the current meta-repo STANDARDS_VERSION (which is decoupled from this repo's VERSION):
AGENTS.md,CLAUDE.md,ROADMAP.md: HTML comment first line, e.g.<!-- standards-version: <STANDARDS_VERSION> -->.skills/*/SKILL.md,rules/*.mdc: YAML frontmatter fieldstandards-version: <STANDARDS_VERSION>.
The drift-check workflow enforces these on every push and PR.
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.
- Update docs if you change skill or rule lists, content counts, or versioning (
README.md,CLAUDE.md,ROADMAP.mdas appropriate). The release workflow rewritesCHANGELOG.md,CLAUDE.md**Version:**line, andROADMAP.md**Current:**line automatically when afeat:orfix:commit lands onmain, so only edit those files for content beyond the version markers. - Open a PR against
mainwith a clear title and summary of changes. - Use Conventional Commits for the PR title (and the squash-merge subject, which is what the release workflow scans). Prefixes:
feat:(minor bump),fix:(patch bump),feat!:/fix!:/BREAKING CHANGE(major bump),chore:/docs:/ci:/refactor:(no release — the workflow runs and exits without tagging). A mixed range still releases if any commit since the last tag is afeat:/fix:.[skip ci]remains an optional override and is no longer required to avoid a release for non-release commits. - Respond to review feedback; CI must pass before merge. Documentation-only and chore changes do not trigger a release.
This project uses CC-BY-NC-ND-4.0 as its outbound license, which forbids derivatives. Every pull request is a derivative. Contributions are accepted inbound under a broader grant via the Developer Certificate of Origin (DCO), which resolves the conflict so the project can accept and redistribute contributions.
By submitting a contribution to this repository, you certify that you have the right to do so under the Developer Certificate of Origin (DCO) 1.1, and you grant TMHSDigital a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license to use, reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute your contribution under the license that governs the part of the tree you contribute to (CC-BY-NC-ND-4.0 at the repository root, MIT for snippets/ and templates/) or any successor license chosen by the project.
Every commit in a pull request must have a Signed-off-by: trailer matching the commit author:
Signed-off-by: Jane Developer <jane@example.com>
Signing is done at commit time:
git commit -s -m "feat: add new skill"The GitHub DCO App enforces this on every PR.
For the full inbound/outbound model and rationale, see standards/licensing.md in the Developer-Tools-Directory meta-repo.
This project follows the guidelines in CODE_OF_CONDUCT.md. By participating, you agree to uphold them.