Skip to content

Commit 5a56f5e

Browse files
TMHSDigitalclaude
andcommitted
feat(distribution): rules and skills for Codex, Copilot, Windsurf and Gemini CLI
README said any agent that reads files could use the pack, with nothing behind it. scripts/build_claude_rules.py now also writes, from rules/*.mdc: - agents/AGENTS.md, for agents that read AGENTS.md (Codex, Windsurf, Gemini CLI, Copilot's coding agent); - agents/copilot/blender-python.instructions.md, a Copilot path-scoped instructions file with applyTo "**/*.py" (the rules' own scope). Both ship in plugin-dist (DIRS gains agents/), and the dist build fails if either is missing. Validate's existing --check covers staleness. README Quick start gains an Other agents entry: clone plugin-dist, copy skills into .agents/skills/, append the AGENTS.md export, and drop in the Copilot file. Ran against a fresh dist build: 20 skills copied, 11 rule sections appended, frontmatter intact. The landing install step matches. The blender-rules skill description also names the two newest rules. Closes #376 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: TM Hospitality Strategies <TMHospitalityStrategies@gmail.com>
1 parent ed48f75 commit 5a56f5e

8 files changed

Lines changed: 688 additions & 509 deletions

File tree

‎.github/workflows/validate.yml‎

Lines changed: 497 additions & 497 deletions
Large diffs are not rendered by default.

‎README.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,19 @@ Neither agent needs a clone of `main`: both install the slim `plugin-dist` build
7878
`@plugin-dist` is a generated branch holding only what the plugin loads (about 0.3 MB, rebuilt on every release), so the add does not clone the gallery renders, showcase props and history on `main`. Adding the repo without `@plugin-dist` still works; the plugin itself installs from `plugin-dist` either way.
7979

8080
Claude Code does not read Cursor `.mdc` rules, so the plugin ships them as the `blender-rules` skill (generated from `rules/`), which Claude loads when it writes or reviews bpy code. No second clone is needed. To keep the rules in context all the time instead, add `@/path/to/Blender-Developer-Tools/claude/blender-rules.md` to your project's `CLAUDE.md` from a checkout. Without the plugin, copy `skills/*` and `claude/skills/*` into your project's `.claude/skills/`.
81+
- **Other agents (Codex, GitHub Copilot, Windsurf, Gemini CLI)** — clone the same slim build once, then copy into each project:
82+
83+
```bash
84+
git clone --branch plugin-dist --single-branch https://github.com/TMHSDigital/Blender-Developer-Tools.git bdt
85+
# Skills: agents that read .agents/skills/ (Codex, Cursor project skills)
86+
mkdir -p .agents/skills && cp -r bdt/skills/* .agents/skills/
87+
# Rules: agents that read AGENTS.md (Codex, Windsurf, Gemini CLI, Copilot's coding agent)
88+
cat bdt/agents/AGENTS.md >> AGENTS.md
89+
# Rules: GitHub Copilot path-scoped instructions (applies to **/*.py)
90+
mkdir -p .github/instructions && cp bdt/agents/copilot/blender-python.instructions.md .github/instructions/
91+
```
92+
93+
`agents/AGENTS.md` and the Copilot file are generated from `rules/` by `scripts/build_claude_rules.py`, and CI fails if they fall out of date. The plugin-dist build fails if either file is missing. The layouts follow each agent's documented conventions; CI does not run the agents themselves.
8194

8295
### Live Blender session (MCP)
8396

‎agents/AGENTS.md‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Blender Developer Tools rules
2+
3+
<!-- Generated by scripts/build_claude_rules.py from rules/*.mdc. Do not edit. -->
4+
5+
Apply these when writing or reviewing Blender Python. Each rule links to the
6+
full version with Wrong/Right examples.
7+
8+
## always-free-bmesh
9+
10+
Flag bmesh.new() calls without a paired bm.free() in a try/finally block. Blender frees an unreferenced BMesh only when its Python wrapper dies, which a traceback, global, closure or modal operator can delay for the whole session; an explicit free() in finally releases it deterministically.
11+
12+
Applies to: `**/*.py`. Full rule: [`rules/always-free-bmesh.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/always-free-bmesh.mdc).
13+
14+
## no-bgl
15+
16+
Flag `import bgl` and any `bgl.` call. The `bgl` OpenGL wrapper was deprecated in 3.x and is gone in Blender 5.x (`importlib.util.find_spec('bgl')` is `None` on 5.0, 5.1 and 5.2), so an add-on that imports it fails to register. Draw with the `gpu` module, `gpu.state` and `gpu_extras.batch` instead; see the gpu-drawing-and-overlays skill.
17+
18+
Applies to: `**/*.py`. Full rule: [`rules/no-bgl.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/no-bgl.mdc).
19+
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+
26+
## no-unapplied-modifiers-on-export
27+
28+
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.
29+
30+
Applies to: `**/*.py`. Full rule: [`rules/no-unapplied-modifiers-on-export.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/no-unapplied-modifiers-on-export.mdc).
31+
32+
## prefer-data-over-ops-in-loops
33+
34+
Flag bpy.ops.* calls inside iteration over many objects, meshes, or frames. Each bpy.ops call triggers a full depsgraph evaluation and UI redraw; loops slow down by orders of magnitude. Use bpy.data.* and bmesh instead.
35+
36+
Applies to: `**/*.py`. Full rule: [`rules/prefer-data-over-ops-in-loops.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/prefer-data-over-ops-in-loops.mdc).
37+
38+
## prefer-temp-override-over-context-copy
39+
40+
Flag uses of `bpy.context.copy()` to override context for an operator call. Passing a context dict to an operator was deprecated in Blender 3.2 and removed in 4.0, so it already fails on the 4.5 LTS fallback. Use `bpy.context.temp_override(**overrides)` as a context manager instead.
41+
42+
Applies to: `**/*.py`. Full rule: [`rules/prefer-temp-override-over-context-copy.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/prefer-temp-override-over-context-copy.mdc).
43+
44+
## target-extensions-platform-format
45+
46+
Flag new Blender add-ons that ship only a legacy bl_info dict without a blender_manifest.toml. New add-ons must use the Extensions Platform format. bl_info may appear alongside as a fallback for backward compatibility, but the manifest is the source of truth.
47+
48+
Applies to: `**/__init__.py`, `**/blender_manifest.toml`. Full rule: [`rules/target-extensions-platform-format.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/target-extensions-platform-format.mdc).
49+
50+
## type-annotate-props-and-defend-context
51+
52+
Flag two related anti-patterns. (1) bpy.props defined as class-level assignments instead of type annotations (deprecated since 2.8). (2) Code that touches bpy.context.active_object without guarding for None, or that assumes the active object is selected.
53+
54+
Applies to: `**/*.py`. Full rule: [`rules/type-annotate-props-and-defend-context.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/type-annotate-props-and-defend-context.mdc).
55+
56+
## use-correct-axis-rna-per-exporter
57+
58+
Flag export_scene.gltf calls that pass FBX axis_forward or axis_up, and export_scene.fbx calls that pass glTF export_yup. The two exporters do not share axis RNA.
59+
60+
Applies to: `**/*.py`. Full rule: [`rules/use-correct-axis-rna-per-exporter.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/use-correct-axis-rna-per-exporter.mdc).
61+
62+
## use-foreach-set-for-bulk-data
63+
64+
Flag Python loops that set vertex coordinates, normals, UVs, or other bulk per-element data one element at a time. For meshes of more than a few thousand elements, this is 100x to 1000x slower than `mesh.vertices.foreach_set("co", flat_array)`, which writes through to C-level storage in one pass.
65+
66+
Applies to: `**/*.py`. Full rule: [`rules/use-foreach-set-for-bulk-data.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/use-foreach-set-for-bulk-data.mdc).
67+
68+
## validate-imported-mesh-scale
69+
70+
Flag a glTF or FBX import followed by mesh operations with no transform_apply and no unit-scale check. Generated files often arrive with non-identity object scale or a non-meter scene scale; mesh edits then bake the wrong size.
71+
72+
Applies to: `**/*.py`. Full rule: [`rules/validate-imported-mesh-scale.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/validate-imported-mesh-scale.mdc).
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
applyTo: "**/*.py"
3+
---
4+
5+
# Blender Developer Tools rules
6+
7+
<!-- Generated by scripts/build_claude_rules.py from rules/*.mdc. Do not edit. -->
8+
9+
Apply these when writing or reviewing Blender Python. Each rule links to the
10+
full version with Wrong/Right examples.
11+
12+
## always-free-bmesh
13+
14+
Flag bmesh.new() calls without a paired bm.free() in a try/finally block. Blender frees an unreferenced BMesh only when its Python wrapper dies, which a traceback, global, closure or modal operator can delay for the whole session; an explicit free() in finally releases it deterministically.
15+
16+
Applies to: `**/*.py`. Full rule: [`rules/always-free-bmesh.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/always-free-bmesh.mdc).
17+
18+
## no-bgl
19+
20+
Flag `import bgl` and any `bgl.` call. The `bgl` OpenGL wrapper was deprecated in 3.x and is gone in Blender 5.x (`importlib.util.find_spec('bgl')` is `None` on 5.0, 5.1 and 5.2), so an add-on that imports it fails to register. Draw with the `gpu` module, `gpu.state` and `gpu_extras.batch` instead; see the gpu-drawing-and-overlays skill.
21+
22+
Applies to: `**/*.py`. Full rule: [`rules/no-bgl.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/no-bgl.mdc).
23+
24+
## no-removed-normals-api
25+
26+
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.
27+
28+
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).
29+
30+
## no-unapplied-modifiers-on-export
31+
32+
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.
33+
34+
Applies to: `**/*.py`. Full rule: [`rules/no-unapplied-modifiers-on-export.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/no-unapplied-modifiers-on-export.mdc).
35+
36+
## prefer-data-over-ops-in-loops
37+
38+
Flag bpy.ops.* calls inside iteration over many objects, meshes, or frames. Each bpy.ops call triggers a full depsgraph evaluation and UI redraw; loops slow down by orders of magnitude. Use bpy.data.* and bmesh instead.
39+
40+
Applies to: `**/*.py`. Full rule: [`rules/prefer-data-over-ops-in-loops.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/prefer-data-over-ops-in-loops.mdc).
41+
42+
## prefer-temp-override-over-context-copy
43+
44+
Flag uses of `bpy.context.copy()` to override context for an operator call. Passing a context dict to an operator was deprecated in Blender 3.2 and removed in 4.0, so it already fails on the 4.5 LTS fallback. Use `bpy.context.temp_override(**overrides)` as a context manager instead.
45+
46+
Applies to: `**/*.py`. Full rule: [`rules/prefer-temp-override-over-context-copy.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/prefer-temp-override-over-context-copy.mdc).
47+
48+
## target-extensions-platform-format
49+
50+
Flag new Blender add-ons that ship only a legacy bl_info dict without a blender_manifest.toml. New add-ons must use the Extensions Platform format. bl_info may appear alongside as a fallback for backward compatibility, but the manifest is the source of truth.
51+
52+
Applies to: `**/__init__.py`, `**/blender_manifest.toml`. Full rule: [`rules/target-extensions-platform-format.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/target-extensions-platform-format.mdc).
53+
54+
## type-annotate-props-and-defend-context
55+
56+
Flag two related anti-patterns. (1) bpy.props defined as class-level assignments instead of type annotations (deprecated since 2.8). (2) Code that touches bpy.context.active_object without guarding for None, or that assumes the active object is selected.
57+
58+
Applies to: `**/*.py`. Full rule: [`rules/type-annotate-props-and-defend-context.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/type-annotate-props-and-defend-context.mdc).
59+
60+
## use-correct-axis-rna-per-exporter
61+
62+
Flag export_scene.gltf calls that pass FBX axis_forward or axis_up, and export_scene.fbx calls that pass glTF export_yup. The two exporters do not share axis RNA.
63+
64+
Applies to: `**/*.py`. Full rule: [`rules/use-correct-axis-rna-per-exporter.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/use-correct-axis-rna-per-exporter.mdc).
65+
66+
## use-foreach-set-for-bulk-data
67+
68+
Flag Python loops that set vertex coordinates, normals, UVs, or other bulk per-element data one element at a time. For meshes of more than a few thousand elements, this is 100x to 1000x slower than `mesh.vertices.foreach_set("co", flat_array)`, which writes through to C-level storage in one pass.
69+
70+
Applies to: `**/*.py`. Full rule: [`rules/use-foreach-set-for-bulk-data.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/use-foreach-set-for-bulk-data.mdc).
71+
72+
## validate-imported-mesh-scale
73+
74+
Flag a glTF or FBX import followed by mesh operations with no transform_apply and no unit-scale check. Generated files often arrive with non-identity object scale or a non-meter scene scale; mesh edits then bake the wrong size.
75+
76+
Applies to: `**/*.py`. Full rule: [`rules/validate-imported-mesh-scale.mdc`](https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/validate-imported-mesh-scale.mdc).

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: blender-rules
3-
description: Blender Python anti-pattern rules (bmesh leaks, bpy.ops in loops, deprecated context-copy overrides, bulk mesh writes, prop annotations and context guards, Extensions manifest, import scale, export modifiers, glTF/FBX axis RNA). Use when writing, reviewing or fixing any bpy / Blender add-on or script code.
3+
description: Blender Python anti-pattern rules (bmesh leaks, bpy.ops in loops, deprecated context-copy overrides, bulk mesh writes, prop annotations and context guards, Extensions manifest, import scale, export modifiers, glTF/FBX axis RNA, the removed bgl module, the normals API removed in 4.1). Use when writing, reviewing or fixing any bpy / Blender add-on or script code.
44
---
55

66
# Blender Developer Tools rules (Claude Code)

‎scripts/build_claude_rules.py‎

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
#!/usr/bin/env python3
2-
"""Generate the Claude Code copies of rules/*.mdc.
2+
"""Generate the agent-readable copies of rules/*.mdc.
33
4-
Claude Code does not read Cursor .mdc rules. This writes the same plain-markdown
5-
summary (one section per rule: description, load scope, link to the full rule
6-
with its Wrong/Right examples) to two places:
4+
Only Cursor reads .mdc rules. This writes the same plain-markdown summary (one
5+
section per rule: description, load scope, link to the full rule with its
6+
Wrong/Right examples) to four places:
77
88
- claude/skills/blender-rules/SKILL.md: a skill the plugin ships
99
(.claude-plugin/plugin.json "skills"), so plugin users get the rules with no
1010
second clone; Claude loads it when writing or reviewing bpy code.
1111
- claude/blender-rules.md: for always-on use, imported from a project's
1212
CLAUDE.md with ``@path/to/Blender-Developer-Tools/claude/blender-rules.md``.
13+
- agents/AGENTS.md: for agents that read AGENTS.md (Codex, Windsurf, Gemini
14+
CLI, Copilot's coding agent); append it to a project's AGENTS.md (#376).
15+
- agents/copilot/blender-python.instructions.md: a GitHub Copilot path-scoped
16+
instructions file (``applyTo: "**/*.py"``, the rules' own scope) for a
17+
project's .github/instructions/.
1318
1419
Links are absolute GitHub URLs so they open from an installed plugin, a
1520
checkout, or a CLAUDE.md import alike.
@@ -27,10 +32,17 @@
2732
REPO = Path(__file__).resolve().parent.parent
2833
OUT = REPO / "claude" / "blender-rules.md"
2934
SKILL_OUT = REPO / "claude" / "skills" / "blender-rules" / "SKILL.md"
35+
AGENTS_OUT = REPO / "agents" / "AGENTS.md"
36+
COPILOT_OUT = REPO / "agents" / "copilot" / "blender-python.instructions.md"
37+
COPILOT_FRONT = """---
38+
applyTo: "**/*.py"
39+
---
40+
41+
"""
3042
RULE_URL = "https://github.com/TMHSDigital/Blender-Developer-Tools/blob/main/rules/"
3143
SKILL_FRONT = """---
3244
name: blender-rules
33-
description: Blender Python anti-pattern rules (bmesh leaks, bpy.ops in loops, deprecated context-copy overrides, bulk mesh writes, prop annotations and context guards, Extensions manifest, import scale, export modifiers, glTF/FBX axis RNA). Use when writing, reviewing or fixing any bpy / Blender add-on or script code.
45+
description: Blender Python anti-pattern rules (bmesh leaks, bpy.ops in loops, deprecated context-copy overrides, bulk mesh writes, prop annotations and context guards, Extensions manifest, import scale, export modifiers, glTF/FBX axis RNA, the removed bgl module, the normals API removed in 4.1). Use when writing, reviewing or fixing any bpy / Blender add-on or script code.
3446
---
3547
3648
"""
@@ -49,9 +61,9 @@ def parse(path: Path) -> tuple[str, list[str]]:
4961
return desc.group(1).strip(), globs
5062

5163

52-
def render() -> str:
64+
def render(title: str = "Blender Developer Tools rules (Claude Code)") -> str:
5365
lines = [
54-
"# Blender Developer Tools rules (Claude Code)",
66+
f"# {title}",
5567
"",
5668
"<!-- Generated by scripts/build_claude_rules.py from rules/*.mdc. Do not edit. -->",
5769
"",
@@ -74,7 +86,9 @@ def render() -> str:
7486

7587
def outputs() -> dict[Path, str]:
7688
body = render()
77-
return {OUT: body, SKILL_OUT: SKILL_FRONT + body}
89+
generic = render("Blender Developer Tools rules")
90+
return {OUT: body, SKILL_OUT: SKILL_FRONT + body, AGENTS_OUT: generic,
91+
COPILOT_OUT: COPILOT_FRONT + generic}
7892

7993

8094
def main() -> int:
@@ -88,7 +102,7 @@ def main() -> int:
88102
print(f"{', '.join(stale)} stale; run python scripts/build_claude_rules.py",
89103
file=sys.stderr)
90104
return 1
91-
print("claude/blender-rules.md and claude/skills/blender-rules/SKILL.md are up to date")
105+
print("rules exports (claude/, agents/AGENTS.md, agents/copilot/) are up to date")
92106
return 0
93107
for path, new in outputs().items():
94108
path.parent.mkdir(parents=True, exist_ok=True)

‎scripts/build_plugin_dist.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
# the Blender Lab MCP server (launched with uvx from a pinned upstream commit;
3636
# GPL, so referenced, never vendored) and its skill. It is opt-in, so the core
3737
# plugin starts no server (#483).
38-
DIRS = ("skills", "claude", "rules", "snippets", "templates", "live-session")
38+
DIRS = ("skills", "claude", "agents", "rules", "snippets", "templates", "live-session")
3939
# The Cursor manifest ships too, so Cursor users can install the same slim
4040
# tree as a local plugin (~/.cursor/plugins/local/) instead of the full repo.
4141
# assets/favicon.svg is the Cursor manifest's "logo".
@@ -168,6 +168,10 @@ def verify(out: Path, size: int) -> list[str]:
168168
errors.append(f"{d.relative_to(out)} has no SKILL.md")
169169
if not (out / "claude" / "skills" / "blender-rules" / "SKILL.md").is_file():
170170
errors.append("blender-rules skill missing")
171+
# The README's "Other agents" commands copy these (#376).
172+
for rel in ("agents/AGENTS.md", "agents/copilot/blender-python.instructions.md"):
173+
if not (out / rel).is_file():
174+
errors.append(f"{rel} missing (the README's other-agents commands copy it)")
171175
cursor = json.loads((out / ".cursor-plugin" / "plugin.json").read_text(encoding="utf-8"))
172176
if "mcpServers" in plugin or "mcpServers" in cursor:
173177
errors.append("the core plugin must not start an MCP server; that is "

0 commit comments

Comments
 (0)