From cf2640783090dcbf06046f82c28c8c1d218d833f Mon Sep 17 00:00:00 2001 From: fOuttaMyPaint Date: Sat, 13 Jun 2026 13:53:01 -0400 Subject: [PATCH] fix: correctness pass on Blender skills, snippets, and docs Code bugs the AI serves to users: - operators: import Vector from mathutils (not bpy.types) in the offset example and column-normalize the basis so object scale no longer distorts the local-axis direction. - cross-version-property-delete: property_unset resets RNA props to default, it does not remove a custom ID property. Use del on all versions and drop the bogus version branch; note the property_unset distinction. - version-branch-skeleton: ID-property deletion is not version-dependent; clear_property now uses del unconditionally. EEVEE engine id stays as the genuine divergence example. - custom-properties: stop crediting property_unset for unbinding a type PointerProperty or removing an ID property; both are del on all versions. - shader-node-group: modernize ShaderNodeMixRGB to ShaderNodeMix with data_type='RGBA', wiring RGBA sockets by index to avoid the node's duplicate socket names. Doc drift: - README: drop the static version-0.2.0 shields badge that drifts every release; the dynamic github/v/release badge already covers it. - ROADMAP: v0.2.0 row -> Shipped, drop the per-row Current marker so the action-owned Current: prose line is the single source of truth. - SECURITY: Supported Versions -> 0.2.x; scope text -> two templates. - CONTRIBUTING: snippet length 5 to 50 (was 5 to 30); standards-version wording points at meta-repo STANDARDS_VERSION, not VERSION. - CLAUDE: relabel the 7 materials/drivers/migration snippets v0.2.1 -> v0.2.0 to match ROADMAP/CHANGELOG/README. Signed-off-by: fOuttaMyPaint Co-authored-by: Cursor --- CLAUDE.md | 2 +- CONTRIBUTING.md | 14 +++++++------- README.md | 1 - ROADMAP.md | 2 +- SECURITY.md | 6 +++--- skills/custom-properties/SKILL.md | 11 ++++++++--- skills/operators/SKILL.md | 6 +++++- snippets/cross-version-property-delete.py | 17 ++++++++--------- snippets/shader-node-group.py | 13 +++++++++---- snippets/version-branch-skeleton.py | 13 ++++++------- 10 files changed, 48 insertions(+), 37 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d4317d2..b29262a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,7 +74,7 @@ Small standalone `.py` files at `snippets/.py`, each 5 to 50 lines. v0.1.0: canonical object creation and deletion, depsgraph evaluated mesh, bmesh load-edit-free, temp_override context, foreach_set vertex bulk write, register_classes_factory, PointerProperty binding, cross-version property delete, and the `action_ensure_channelbag_for_slot` slotted-actions bridge. -v0.2.1: Principled BSDF material, driver-with-custom-function via `driver_namespace`, application handler registration, shader node group with cross-version `interface` API, `foreach_get` bulk vertex read, version-branch skeleton, and USD export with `evaluation_mode='RENDER'`. +v0.2.0: Principled BSDF material, driver-with-custom-function via `driver_namespace`, application handler registration, shader node group with cross-version `interface` API, `foreach_get` bulk vertex read, version-branch skeleton, and USD export with `evaluation_mode='RENDER'`. ## Development Workflow diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f9a1e34..d798007 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,7 +39,7 @@ templates/ - **`skills/`** - one directory per skill, each containing `SKILL.md` with YAML frontmatter (`name`, `description`, `standards-version`). - **`rules/`** - Cursor-style rules as `.mdc` files with YAML frontmatter (`description`, `alwaysApply`, `globs`, `standards-version`). -- **`snippets/`** - small standalone `.py` files (5 to 30 lines) demonstrating a single canonical pattern. +- **`snippets/`** - small standalone `.py` files (5 to 50 lines) demonstrating a single canonical pattern. - **`templates/`** - copy-paste starting points; one directory per template. ## Adding a Skill @@ -51,7 +51,7 @@ templates/ --- name: procedural-materials description: One-line description, under 200 chars. - standards-version: + standards-version: --- ``` @@ -69,7 +69,7 @@ templates/ alwaysApply: true globs: - "**/*.py" - standards-version: + standards-version: --- ``` @@ -78,7 +78,7 @@ templates/ ## Adding a Snippet 1. Add a `.py` file under `snippets/`, e.g. `snippets/depsgraph-evaluated-mesh.py`. -2. Keep it 5 to 30 lines, fully working code, with a header comment naming the snippet and citing the relevant Blender doc URL or research section. +2. Keep it 5 to 50 lines, fully working code, with a header comment naming the snippet and citing the relevant Blender doc URL or research section. 3. Snippets are validated for Python syntax in CI. ## Adding a Template @@ -101,10 +101,10 @@ else: ## Standards-version Markers -Files that participate in ecosystem drift checking must carry a `standards-version` marker matching the current meta-repo `VERSION`: +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. ``. -- `skills/*/SKILL.md`, `rules/*.mdc`: YAML frontmatter field `standards-version: 1.9.1`. +- `AGENTS.md`, `CLAUDE.md`, `ROADMAP.md`: HTML comment first line, e.g. ``. +- `skills/*/SKILL.md`, `rules/*.mdc`: YAML frontmatter field `standards-version: `. The drift-check workflow enforces these on every push and PR. diff --git a/README.md b/README.md index 1d372b7..6bb7f28 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@

- Version Release License

diff --git a/ROADMAP.md b/ROADMAP.md index a0a16d3..80076e4 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7,7 +7,7 @@ | Version | Theme | Skills | Rules | Templates | Snippets | Status | | --- | --- | --- | --- | --- | --- | --- | | v0.1.0 | Foundation | 8 | 4 | 1 | 10 | Shipped | -| v0.2.0 | Materials, drivers, migration | 12 | 6 | 2 | 17 | **Current** | +| v0.2.0 | Materials, drivers, migration | 12 | 6 | 2 | 17 | Shipped | | v0.3.0 | 5.2 LTS sweep, modal operators, USD | TBD | TBD | TBD | TBD | Planned | | v1.0.0 | Stable | TBD | TBD | TBD | TBD | Planned | diff --git a/SECURITY.md b/SECURITY.md index 09b62d7..47b0e45 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -15,7 +15,7 @@ Please include: ## Scope -This repository ships Markdown skill files, MDC rule files, Python snippets, and one Blender extension add-on template. The primary security concerns are: +This repository ships Markdown skill files, MDC rule files, Python snippets, and two starter templates (a Blender extension add-on and a headless batch script). The primary security concerns are: - **Snippets or templates demonstrating insecure patterns** (executing arbitrary code from `.blend` files, loading remote scripts without validation, leaking filesystem paths into logs). - **The extension-addon template declaring over-broad permissions** in `blender_manifest.toml` (e.g. `network`, `files`, `clipboard`, `camera`) without a documented justification. @@ -28,8 +28,8 @@ Issues with the Blender Python API itself (`bpy`, `bmesh`, `bpy_extras`) belong | Version | Supported | |---------|-----------| -| 0.1.x | Yes | -| < 0.1.0 | No | +| 0.2.x | Yes | +| < 0.2.0 | No | ## Response Timeline diff --git a/skills/custom-properties/SKILL.md b/skills/custom-properties/SKILL.md index f500dc7..d3fa98f 100644 --- a/skills/custom-properties/SKILL.md +++ b/skills/custom-properties/SKILL.md @@ -133,11 +133,11 @@ The order matters. The bound `PointerProperty` references the `PropertyGroup` cl ```python def unregister(): - del bpy.types.Scene.my_addon # or property_unset() on 5.0+ + del bpy.types.Scene.my_addon bpy.utils.unregister_class(MY_ADDON_PG_settings) ``` -See the snippet `cross-version-property-delete.py` for the 5.0+ `property_unset` shape and the 4.5 LTS `del` shape. +Unbinding a type-level property is `del bpy.types.Scene.my_addon` on all Blender versions. See the snippet `cross-version-property-delete.py` for removing a custom ID property (also `del`, version-stable). ## CollectionProperty (lists) @@ -231,7 +231,12 @@ This is a frequent AI mistake when generating quick scripts. Always wrap state i ## Compatibility paths (4.5 LTS vs 5.0+) -In Blender 5.0+, `bpy.types.Scene.my_addon.property_unset(...)` is the preferred unbind for individual props. In 4.5 LTS, `del bpy.types.Scene.my_addon` is still required for `PointerProperty` bindings. The snippet `cross-version-property-delete.py` shows both. +Two distinct operations, both version-stable via `del`: + +- **Unbind a type-level property** (e.g. a `PointerProperty` bound to `bpy.types.Scene`): `del bpy.types.Scene.my_addon`. Same on 4.5 LTS and 5.0+. +- **Remove a custom ID property** (the dict-style `obj["key"]`): `del obj["key"]`. Same on 4.5 LTS and 5.0+. The snippet `cross-version-property-delete.py` shows this. + +Do not reach for `property_unset()` here. It resets a registered RNA property to its default, which is neither unbinding a type property nor removing an ID property. ## Related diff --git a/skills/operators/SKILL.md b/skills/operators/SKILL.md index 81d822c..602d484 100644 --- a/skills/operators/SKILL.md +++ b/skills/operators/SKILL.md @@ -129,6 +129,7 @@ Pair `report({'ERROR'}, ...)` with `return {'CANCELLED'}` so the operator does n ```python import bpy +from mathutils import Vector class OBJECT_OT_offset_active(bpy.types.Operator): @@ -157,7 +158,10 @@ class OBJECT_OT_offset_active(bpy.types.Operator): offset = (self.offset_x, self.offset_y, self.offset_z) if self.use_local: - obj.location += obj.matrix_world.to_3x3() @ bpy.types.Vector(offset) + # Column-normalize the basis so object scale does not distort the + # local-axis direction; to_3x3() alone carries scale into the result. + basis = obj.matrix_world.to_3x3().normalized() + obj.location += basis @ Vector(offset) else: obj.location.x += self.offset_x obj.location.y += self.offset_y diff --git a/snippets/cross-version-property-delete.py b/snippets/cross-version-property-delete.py index 67414ee..c3d10d3 100644 --- a/snippets/cross-version-property-delete.py +++ b/snippets/cross-version-property-delete.py @@ -1,7 +1,10 @@ -# Cross-version property deletion: property_unset (5.0+) vs del (4.5 LTS). -# Custom ID properties (the dict-style obj["my_key"] = value) are removed -# differently across Blender versions. property_unset is the cleaner -# 5.x-and-up form; del obj["key"] still works on 4.5 LTS. +# Removing a custom ID property (the dict-style obj["my_key"] = value). +# ID-property removal is version-stable: `del id_block[key]` works on all +# Blender versions (4.5 LTS and 5.x alike). There is no version branch here. +# +# Do NOT use property_unset() for this. property_unset() RESETS a registered +# RNA property to its default value; it does not remove a custom ID property. +# That is a different operation. # # Reference: # https://docs.blender.org/api/current/bpy.types.bpy_struct.html @@ -13,11 +16,7 @@ def remove_custom_property(id_block, key): if key not in id_block.keys(): return False - if bpy.app.version >= (5, 0, 0): - id_block.property_unset(key) - else: - del id_block[key] - + del id_block[key] return True diff --git a/snippets/shader-node-group.py b/snippets/shader-node-group.py index d1630bc..019b044 100644 --- a/snippets/shader-node-group.py +++ b/snippets/shader-node-group.py @@ -23,10 +23,15 @@ def make_tint_group(name="Tint"): group_in = nodes.new('NodeGroupInput') group_out = nodes.new('NodeGroupOutput') - mix = nodes.new('ShaderNodeMixRGB') + mix = nodes.new('ShaderNodeMix') + mix.data_type = 'RGBA' mix.blend_type = 'MULTIPLY' - links.new(group_in.outputs['Color'], mix.inputs['Color1']) - links.new(group_in.outputs['Strength'], mix.inputs['Fac']) - links.new(mix.outputs['Color'], group_out.inputs['Result']) + # ShaderNodeMix shares socket names (Factor/A/B/Result) across every + # data_type, so name lookup is ambiguous. Wire the RGBA sockets by index: + # inputs[0]=Factor (Float), inputs[6]=A (Color), inputs[7]=B (Color) + # outputs[2]=Result (Color) + links.new(group_in.outputs['Strength'], mix.inputs[0]) + links.new(group_in.outputs['Color'], mix.inputs[6]) + links.new(mix.outputs[2], group_out.inputs['Result']) return group diff --git a/snippets/version-branch-skeleton.py b/snippets/version-branch-skeleton.py index 29c1913..0af8cc3 100644 --- a/snippets/version-branch-skeleton.py +++ b/snippets/version-branch-skeleton.py @@ -6,18 +6,17 @@ def clear_property(obj, key): - """Delete a custom property cross-version. + """Delete a custom ID property. - Blender 5.0 introduced property_unset() as the supported path; on 4.5 LTS - fall back to del obj[key]. Either form raises if key is absent, so check first. + NOT version-dependent: del obj[key] works on every Blender version + (4.5 LTS and 5.x). del raises if key is absent, so check membership first. + See get_eevee_engine_id() below for the file's genuine version-divergence + example. """ if key not in obj: return False - if bpy.app.version >= (5, 0, 0): - obj.property_unset(f'["{key}"]') - else: - del obj[key] + del obj[key] return True