Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Small standalone `.py` files at `snippets/<name>.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

Expand Down
14 changes: 7 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,7 +51,7 @@ templates/
---
name: procedural-materials
description: One-line description, under 200 chars.
standards-version: <current meta-repo VERSION>
standards-version: <current meta-repo STANDARDS_VERSION>
---
```

Expand All @@ -69,7 +69,7 @@ templates/
alwaysApply: true
globs:
- "**/*.py"
standards-version: <current meta-repo VERSION>
standards-version: <current meta-repo STANDARDS_VERSION>
---
```

Expand All @@ -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
Expand All @@ -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. `<!-- standards-version: 1.9.1 -->`.
- `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. `<!-- standards-version: <STANDARDS_VERSION> -->`.
- `skills/*/SKILL.md`, `rules/*.mdc`: YAML frontmatter field `standards-version: <STANDARDS_VERSION>`.

The drift-check workflow enforces these on every push and PR.

Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
</p>

<p align="center">
<a href="https://github.com/TMHSDigital/Blender-Developer-Tools/releases"><img src="https://img.shields.io/badge/version-0.2.0-e87d0d?style=flat-square" alt="Version" /></a>
<a href="https://github.com/TMHSDigital/Blender-Developer-Tools/releases"><img src="https://img.shields.io/github/v/release/TMHSDigital/Blender-Developer-Tools?style=flat-square&color=e87d0d&label=release" alt="Release" /></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-CC--BY--NC--ND--4.0-384d54?style=flat-square" alt="License" /></a>
</p>
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down
6 changes: 3 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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

Expand Down
11 changes: 8 additions & 3 deletions skills/custom-properties/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand Down
6 changes: 5 additions & 1 deletion skills/operators/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
17 changes: 8 additions & 9 deletions snippets/cross-version-property-delete.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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


Expand Down
13 changes: 9 additions & 4 deletions snippets/shader-node-group.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 6 additions & 7 deletions snippets/version-branch-skeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
Loading