You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b612f3a
Browse filesBrowse the repository at this point in the historyBrowse files
fix(skills): document that registered props are not ID properties on 5.0+ (#420)
On 4.x a registered bpy.props value was stored as an ID property, so
scene["my_prop"], "my_prop" in scene.keys(), del scene["my_prop"] and a
'["my_prop"]' driver path all reached it. On 5.0+ it lives in a separate
system-property group and every one of those raises KeyError/ValueError
or silently misses it (measured on 4.5.11, 5.1.2, 5.2.1). Legacy add-on
code and AI-written code break here without a clear error, and the
custom-properties skill called its operations "version-stable".
custom-properties now has the measured table and the branch-free recipe
(attribute access, property_unset, is_property_set, plain data path),
plus a common-mistakes entry; drivers-and-app-handlers distinguishes
'["custom"]' from a registered prop's plain path; the delete snippet
warns that del id["registered"] raises on 5.0+.
examples/cross-version-property-delete proves it in CI: a new
registered-prop check (exit 8) runs on every version, and a new
--subscript-registered falsifier (min_version 5.0) reads the prop the 4.x
way and must exit 8.
Closes#354
Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/gallery/cross-version-property-delete/index.html
+6-3Lines changed: 6 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -64,8 +64,11 @@ <h1>Cross Version Property Delete</h1>
64
64
<p>A pair of stage lamps that witnesses the custom <strong>ID-property</strong> delete contract from <ahref="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/snippets/cross-version-property-delete.py"><code>cross-version-property-delete.py</code></a> and <ahref="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/skills/custom-properties/SKILL.md"><code>custom-properties</code></a> — not the snippet's <code>__main__</code>, which keys off <code>context.active_object</code> and prints nothing in <code>--background</code>.</p>
65
65
<p>The IDs are two stage-lamp housings built with <code>bpy.data.objects.new</code>. The Keep lamp still carries <code>["accession"] = 42</code>; the Clear lamp had <code>del obj["accession"]</code>. Same <code>del</code> on 4.5 LTS and 5.x; there is no version branch.</p>
66
66
<p><strong>What it witnesses:</strong><code>property_unset</code> is a TypeError on a custom ID key and does <strong>not</strong> remove it. <code>del id_block[key]</code> does. After factory-empty, there is no <code>active_object</code>.</p>
67
+
<p>It also witnesses the other half of the split: a <strong>registered</strong><code>bpy.props</code> value (<code>Scene.bdt_counter = IntProperty()</code>) is not an ID property on 5.0+. Measured on 4.5.11, 5.1.2 and 5.2.1:</p>
68
+
<divclass="table-wrap"><table><thead><tr><thscope="col">On a registered prop</th><thscope="col">4.5 LTS</th><thscope="col">5.0+</th></tr></thead><tbody><tr><td><code>scene.bdt_counter</code></td><td>5</td><td>5</td></tr><tr><td><code>scene["bdt_counter"]</code></td><td>5</td><td><code>KeyError</code></td></tr><tr><td><code>"bdt_counter" in scene.keys()</code></td><td>True</td><td>False</td></tr><tr><td><code>del scene["bdt_counter"]</code></td><td>resets it</td><td><code>KeyError</code></td></tr><tr><td><code>path_resolve('["bdt_counter"]')</code> (driver path)</td><td>5</td><td><code>ValueError</code></td></tr><tr><td><code>path_resolve("bdt_counter")</code>, <code>property_unset</code>, <code>is_property_set</code></td><td>work</td><td>work</td></tr></tbody></table></div>
69
+
<p>On 5.0+ the raw storage is <code>scene.bl_system_properties_get()</code>. A value saved by 4.5 still loads as the attribute on 5.2.</p>
67
70
<p><strong>What failure each check would catch:</strong></p>
68
-
<ul><li>exit 2 — someone set an active object; the snippet <code>__main__</code> would have been the only path</li><li>exit 3 — the ID property never landed</li><li>exit 4 — <code>property_unset</code> removed the key or failed to raise</li><li>exit 7 — <code>del</code> did not run (<code>--skip-delete</code> / <code>--unset-instead</code> falsify this: measured <code>clear_has=True</code>)</li></ul>
71
+
<ul><li>exit 2 — someone set an active object; the snippet <code>__main__</code> would have been the only path</li><li>exit 3 — the ID property never landed</li><li>exit 4 — <code>property_unset</code> removed the key or failed to raise</li><li>exit 7 — <code>del</code> did not run (<code>--skip-delete</code> / <code>--unset-instead</code> falsify this: measured <code>clear_has=True</code>)</li><li>exit 8 — a registered prop was read or reset the 4.x way, or sits on the wrong side of the ID-property split for this version (<code>--subscript-registered</code> falsifies this on 5.0+)</li></ul>
69
72
<h2id="staging">Staging<aclass="anchor" href="#staging" aria-label="Link to this section">#</a></h2>
70
73
<p>Both lamps hang in U-yokes from one T-stand. The render path reads each housing's <code>keys()</code> after the check has run, so the still is driven by the checked IDs, not staged from the expected outcome. A lamp that still carries the property gets an emissive lens and a real SPOT light along its beam axis, which pools warm light on the placard below it. A lamp without the property keeps a dark lens and casts nothing. Each placard is set in the bundled DejaVu Sans Mono and shows the line of code that produced its lamp's state. Only render-side geometry (stand, yokes, lenses, placards) is added in the render path; the check reads ID properties, never geometry.</p>
71
74
<h2id="run">Run<aclass="anchor" href="#run" aria-label="Link to this section">#</a></h2>
@@ -75,8 +78,8 @@ <h2 id="run">Run<a class="anchor" href="#run" aria-label="Link to this section">
75
78
<p>The <code>--output</code> render path measures framing against the Layer 1 band via <code>examples/gallery_framing.py</code> (exit 10 on violation) before writing the still.</p>
76
79
<h2id="exit-codes">Exit codes<aclass="anchor" href="#exit-codes" aria-label="Link to this section">#</a></h2>
77
80
<p>Per-script sequential checks. <code>9</code> is a valid check code; there is no rule against it. <code>10</code> is the shared framing helper.</p>
78
-
<divclass="table-wrap"><table><thead><tr><thscope="col">Code</th><thscope="col">Meaning</th></tr></thead><tbody><tr><td>0</td><td>Success</td></tr><tr><td>1</td><td>Uncaught exception (FATAL wrapper)</td></tr><tr><td>2</td><td>argparse / usage; also an active object existed after the data-API build</td></tr><tr><td>3</td><td>Custom ID property did not land</td></tr><tr><td>4</td><td><code>property_unset</code> on a custom ID key did not TypeError, or it deleted the key</td></tr><tr><td>5</td><td><code>del</code> did not report removal</td></tr><tr><td>6</td><td>Keep lamp lost the ID property</td></tr><tr><td>7</td><td>Clear lamp still has the ID property (<code>--skip-delete</code> / <code>--unset-instead</code> land here)</td></tr><tr><td>10</td><td>Gallery framing violation</td></tr><tr><td>12</td><td><code>--output</code> produced no file</td></tr></tbody></table></div>
79
-
<p>The <code>blender-smoke</code> workflow runs the check on Blender 5.2 LTS and 4.5 LTS (5.1 on the weekly cron, the <code>needs-5.1</code> PR label, or manual dispatch). Smoke does not pass <code>--output</code> or <code>--unset-instead</code>. Its catalog falsifier is<code>--skip-delete</code> (expects exit 7).</p>
81
+
<divclass="table-wrap"><table><thead><tr><thscope="col">Code</th><thscope="col">Meaning</th></tr></thead><tbody><tr><td>0</td><td>Success</td></tr><tr><td>1</td><td>Uncaught exception (FATAL wrapper)</td></tr><tr><td>2</td><td>argparse / usage; also an active object existed after the data-API build</td></tr><tr><td>3</td><td>Custom ID property did not land</td></tr><tr><td>4</td><td><code>property_unset</code> on a custom ID key did not TypeError, or it deleted the key</td></tr><tr><td>5</td><td><code>del</code> did not report removal</td></tr><tr><td>6</td><td>Keep lamp lost the ID property</td></tr><tr><td>7</td><td>Clear lamp still has the ID property (<code>--skip-delete</code> / <code>--unset-instead</code> land here)</td></tr><tr><td>8</td><td>Registered-prop contract broke: attribute/<code>property_unset</code> failed, the prop sits on the wrong side of the 5.0 ID-property split, or <code>scene["name"]</code> raised <code>KeyError</code> (<code>--subscript-registered</code> on 5.0+; exits 0 on 4.5, where subscript access still works)</td></tr><tr><td>10</td><td>Gallery framing violation</td></tr><tr><td>12</td><td><code>--output</code> produced no file</td></tr></tbody></table></div>
82
+
<p>The <code>blender-smoke</code> workflow runs the check on Blender 5.2 LTS and 4.5 LTS (5.1 on the weekly cron, the <code>needs-5.1</code> PR label, or manual dispatch). Smoke does not pass <code>--output</code> or <code>--unset-instead</code>. Its catalog falsifiers are<code>--skip-delete</code> (expects exit 7); <code>--subscript-registered</code> (expects exit 8; 5.0+ only; SKIP on older Blender).</p>
|`path_resolve("bdt_counter")`, `property_unset`, `is_property_set`| work | work |
31
+
32
+
On 5.0+ the raw storage is `scene.bl_system_properties_get()`. A value saved
33
+
by 4.5 still loads as the attribute on 5.2.
34
+
19
35
**What failure each check would catch:**
20
36
21
37
- exit 2 — someone set an active object; the snippet `__main__` would have
@@ -24,6 +40,9 @@ no `active_object`.
24
40
- exit 4 — `property_unset` removed the key or failed to raise
25
41
- exit 7 — `del` did not run (`--skip-delete` / `--unset-instead` falsify
26
42
this: measured `clear_has=True`)
43
+
- exit 8 — a registered prop was read or reset the 4.x way, or sits on the
44
+
wrong side of the ID-property split for this version
45
+
(`--subscript-registered` falsifies this on 5.0+)
27
46
28
47
## Staging
29
48
@@ -65,9 +84,10 @@ against it. `10` is the shared framing helper.
65
84
| 5 |`del` did not report removal |
66
85
| 6 | Keep lamp lost the ID property |
67
86
| 7 | Clear lamp still has the ID property (`--skip-delete` / `--unset-instead` land here) |
87
+
| 8 | Registered-prop contract broke: attribute/`property_unset` failed, the prop sits on the wrong side of the 5.0 ID-property split, or `scene["name"]` raised `KeyError` (`--subscript-registered` on 5.0+; exits 0 on 4.5, where subscript access still works) |
68
88
| 10 | Gallery framing violation |
69
89
| 12 |`--output` produced no file |
70
90
71
91
The `blender-smoke` workflow runs the check on Blender 5.2 LTS and 4.5 LTS
72
92
(5.1 on the weekly cron, the `needs-5.1` PR label, or manual dispatch).
73
-
Smoke does not pass `--output` or `--unset-instead`. Its catalog falsifier is`--skip-delete` (expects exit 7).
93
+
Smoke does not pass `--output` or `--unset-instead`. Its catalog falsifiers are`--skip-delete` (expects exit 7); `--subscript-registered` (expects exit 8; 5.0+ only; SKIP on older Blender).
Copy file name to clipboardExpand all lines: skills/custom-properties/SKILL.md
+22-1Lines changed: 22 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -231,14 +231,35 @@ This is a frequent AI mistake when generating quick scripts. Always wrap state i
231
231
232
232
6.**Confusing `subtype='FILE_PATH'` and `'DIR_PATH'`**. `'FILE_PATH'` lets the user pick a file, `'DIR_PATH'` a directory.
233
233
234
+
7.**Reading a registered property as an ID property on 5.x**: `scene["my_prop"]`, `"my_prop" in scene.keys()`, `del scene["my_prop"]`, or a driver path of `'["my_prop"]'`. These worked on 4.x and raise `KeyError` / `ValueError` (or silently skip the prop) on 5.0+. Use `scene.my_prop`, `scene.property_unset("my_prop")`, `scene.is_property_set("my_prop")` and the plain `"my_prop"` path. See the compatibility section below.
235
+
234
236
## Compatibility paths (4.5 LTS vs 5.0+)
235
237
238
+
### Registered properties are not ID properties on 5.0+
239
+
240
+
On 4.x a value assigned to a registered `bpy.props` property was stored as an ID property, so dict-style access reached it. On 5.0+ it lives in a separate system-property group. Measured with `bpy.types.Scene.my_prop = IntProperty(default=1)` and `scene.my_prop = 5`:
241
+
242
+
| Access | 4.5 LTS | 5.0+ (5.1.2, 5.2.1) |
243
+
| --- | --- | --- |
244
+
|`scene.my_prop`| 5 | 5 |
245
+
|`scene["my_prop"]`| 5 |`KeyError`|
246
+
|`scene.get("my_prop")`| 5 |`None`|
247
+
|`"my_prop" in scene.keys()`| True | False |
248
+
|`del scene["my_prop"]`| resets to default |`KeyError`|
|`scene.property_unset("my_prop")`, `scene.is_property_set("my_prop")`| work | work |
252
+
253
+
So the cross-version code needs no branch: use attribute access to read and write, `property_unset()` to reset, `is_property_set()` to test, and the plain `"my_prop"` path for drivers and keyframes. `keys()` lists only user custom properties on 5.0+; the raw storage is `id.bl_system_properties_get()` (5.0+ only), which you should rarely need. A value saved by 4.5 still loads as the attribute on 5.2. Witness: [`examples/cross-version-property-delete/`](https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/cross-version-property-delete) (exit 8, falsifier `--subscript-registered`).
254
+
255
+
### Deleting
256
+
236
257
Two distinct operations, both version-stable via `del`:
237
258
238
259
-**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+.
239
260
-**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.
240
261
241
-
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.
262
+
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. Conversely, `del id["name"]` is not a way to reset a *registered* property: it raises `KeyError` on 5.0+.
-`data_path` is the RNA path of the property, like `"location"` or `"scale"` or `'["my_custom_prop"]'`.
48
+
-`data_path` is the RNA path of the property, like `"location"` or `"scale"`. A user custom (ID) property set with `obj["my_custom_prop"] = 1.0` is `'["my_custom_prop"]'`; a registered `bpy.props` property is its plain name, `"my_prop"`. On 5.0+ `'["my_prop"]'` no longer resolves a registered property (`ValueError`), though it did on 4.x.
49
49
-`index` is the array index for vector or color properties (0 = X, 1 = Y, 2 = Z), or `-1` for scalar properties.
50
50
- Returns the `FCurve` whose `.driver` is now active.
0 commit comments