Skip to content

Commit bce71a9

Browse files
TMHSDigitalclaude
andcommitted
docs: fix version and API facts in CLAUDE.md, custom-properties and operators skills
- CLAUDE.md rules table: bpy.context.copy() passed to operators was "deprecated 4.x, removed 5.x"; it was deprecated in 3.2 and removed in 4.0 (what README and claude/blender-rules.md already say). - custom-properties: plain Python attributes on a datablock do not "silently" vanish on reload; `bpy.context.scene.my_intensity = 1.5` raises AttributeError immediately (verified on .scratch 4.5.11 and 5.2.1). Text now says so and notes ID properties (scene["key"]) as the untyped alternative. - operators: the anatomy example looped `for v in mesh.vertices: v.co += ...` right under "Operate on bpy.data", the pattern rule use-foreach-set-for-bulk-data flags, and was named "extrude" without extruding. Now MESH_OT_offset_along_normals with one foreach_get/foreach_set pair. Ran the extracted example on 5.2.1: {'FINISHED'}, cube corner (-1,-1,-1) -> (-1.289,-1.289,-1.289) at distance 0.5. - docs/technical-audit.md labelled historical (v0.5.0 snapshot), with the standing findings log called out as the maintained part. Closes #333 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 8680ca1 commit bce71a9

4 files changed

Lines changed: 25 additions & 15 deletions

File tree

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ matching its `globs` does. Changing a glob changes when the rule fires.
6363
| always-free-bmesh | `*.py` | `bmesh.new()` without paired `bm.free()` in a `try`/`finally` block |
6464
| target-extensions-platform-format | Add-on roots | Legacy `bl_info` only add-ons missing `blender_manifest.toml` |
6565
| type-annotate-props-and-defend-context | `*.py` | `bpy.props` defined as assignments, unguarded `context.active_object` |
66-
| prefer-temp-override-over-context-copy | `*.py` | `bpy.context.copy()` passed to operators (deprecated 4.x, removed 5.x) |
66+
| prefer-temp-override-over-context-copy | `*.py` | `bpy.context.copy()` passed to operators (deprecated 3.2, removed 4.0) |
6767
| use-foreach-set-for-bulk-data | `*.py` | Python loops over `mesh.vertices` setting bulk attributes one at a time |
6868
| validate-imported-mesh-scale | `*.py` | glTF/FBX import then mesh work with no `transform_apply` and no unit-scale check |
6969
| no-unapplied-modifiers-on-export | `*.py` | Export with live modifiers when the export does not request evaluated geometry |

‎docs/technical-audit.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
_Audit date: 2026-06-20 · Repo @ v0.5.0 · Auditor: Principal Eng review_
44

5+
> **Historical.** The audit sections describe the repository as of v0.5.0 and are kept as a record; counts and file references in them are not current. Only "Findings the release notes did not list" below is a standing, maintained log.
6+
57
## Findings the release notes did not list
68

79
_Standing log. Notes-derived 5.1→5.2 (and later) deltas are incomplete by default._

‎skills/custom-properties/SKILL.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,11 +196,11 @@ api_key = prefs.api_key
196196
## Why "just use Python attributes" does not work
197197

198198
```python
199-
# WRONG: this attribute does not survive save and load.
199+
# WRONG: raises AttributeError: 'Scene' object has no attribute 'my_intensity'
200200
bpy.context.scene.my_intensity = 1.5
201201
```
202202

203-
Properties attached as plain Python attributes to a Blender datablock are not serialized into the `.blend` file. The .blend file format only saves registered `bpy.props`. Reload the file and the value is gone.
203+
Blender datablocks do not accept arbitrary Python attributes: the assignment fails immediately, on 4.5 LTS and 5.x alike. Only registered `bpy.props` (or ID properties set with `scene["key"] = value`) exist on a datablock and are saved into the `.blend` file.
204204

205205
This is a frequent AI mistake when generating quick scripts. Always wrap state in a `PropertyGroup` bound via `PointerProperty` if it must persist.
206206

@@ -218,9 +218,11 @@ This is a frequent AI mistake when generating quick scripts. Always wrap state i
218218
2. **Storing complex state on Python attributes**:
219219

220220
```python
221-
scene.my_data = {"items": [...]} # gone after save and reload
221+
scene.my_data = {"items": [...]} # AttributeError: not a registered property
222222
```
223223

224+
Ad-hoc ID properties (`scene["my_data"] = ...`) do save, but they are untyped and invisible to the UI; use a `PropertyGroup` for anything an add-on owns.
225+
224226
3. **Wrong unregister order** (unregister class before deleting binding) -> crash on save.
225227

226228
4. **Binding individual properties directly to Scene** instead of grouping into a `PropertyGroup`. Hard to unregister cleanly, pollutes the namespace.

‎skills/operators/SKILL.md‎

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ Use this skill when the user:
2828
import bpy
2929

3030

31-
class MESH_OT_extrude_normal(bpy.types.Operator):
32-
bl_idname = "mesh.extrude_normal"
33-
bl_label = "Extrude Along Normal"
34-
bl_description = "Extrude active mesh by a fixed amount along its normal"
31+
class MESH_OT_offset_along_normals(bpy.types.Operator):
32+
bl_idname = "mesh.offset_along_normals"
33+
bl_label = "Offset Along Normals"
34+
bl_description = "Move every vertex of the active mesh a fixed distance along its normal"
3535
bl_options = {'REGISTER', 'UNDO'}
3636

3737
distance: bpy.props.FloatProperty(
3838
name="Distance",
39-
description="How far to extrude",
39+
description="How far to move each vertex",
4040
default=0.5,
4141
min=-100.0,
4242
max=100.0,
@@ -54,14 +54,20 @@ class MESH_OT_extrude_normal(bpy.types.Operator):
5454
self.report({'ERROR'}, "No active object")
5555
return {'CANCELLED'}
5656

57-
# Operate on bpy.data, not bpy.ops, in tight loops.
58-
# See the mesh-editing-and-bmesh skill for the full pattern.
57+
# Operate on bpy.data, not bpy.ops, and in bulk: one foreach_get /
58+
# foreach_set pair instead of a Python loop over mesh.vertices (rule
59+
# use-foreach-set-for-bulk-data). See the mesh-editing-and-bmesh skill.
5960
mesh = obj.data
60-
for v in mesh.vertices:
61-
v.co += v.normal * self.distance
61+
n = len(mesh.vertices)
62+
co = [0.0] * (n * 3)
63+
nor = [0.0] * (n * 3)
64+
mesh.vertices.foreach_get("co", co)
65+
mesh.vertices.foreach_get("normal", nor)
66+
d = self.distance
67+
mesh.vertices.foreach_set("co", [c + k * d for c, k in zip(co, nor)])
6268

6369
mesh.update()
64-
self.report({'INFO'}, f"Extruded {len(mesh.vertices)} vertices by {self.distance:.3f}")
70+
self.report({'INFO'}, f"Offset {n} vertices by {d:.3f}")
6571
return {'FINISHED'}
6672
```
6773

@@ -71,7 +77,7 @@ class MESH_OT_extrude_normal(bpy.types.Operator):
7177
| --- | --- | --- | --- |
7278
| `poll(cls, context)` | Optional | Every UI redraw to decide if the operator is enabled. Must be cheap. | `True` to enable, `False` or falsy to disable |
7379
| `invoke(self, context, event)` | Optional | The user triggers the operator interactively (button, menu, keymap). Default forwards to `execute`. | `{'FINISHED'}`, `{'CANCELLED'}`, `{'PASS_THROUGH'}`, `{'RUNNING_MODAL'}` |
74-
| `execute(self, context)` | Yes | The operator runs, either after `invoke` or directly via `bpy.ops.mesh.extrude_normal()` | `{'FINISHED'}` or `{'CANCELLED'}` |
80+
| `execute(self, context)` | Yes | The operator runs, either after `invoke` or directly via `bpy.ops.mesh.offset_along_normals()` | `{'FINISHED'}` or `{'CANCELLED'}` |
7581
| `modal(self, context, event)` | Optional | After `invoke` returns `{'RUNNING_MODAL'}`. Called for each event until you return `{'FINISHED'}` or `{'CANCELLED'}`. | Standard return set |
7682
| `draw(self, context)` | Optional | The redo panel after the operator runs. Defaults to drawing all properties. | None |
7783

0 commit comments

Comments
 (0)