| name | blender-errors-data |
| description | Use when debugging Blender ReferenceError from removed objects, undo-invalidated bpy.data references, or stale ID pointers. Prevents the common crash of caching bpy.data references across undo/redo operations (references become invalid). Covers safe data access patterns, ID reference caching pitfalls, data lifecycle management, and bpy.data collections. Keywords: ReferenceError, undo, stale reference, bpy.data, ID pointer, data invalidation, removed object, data lifecycle, StructRNA, Blender crash, object disappeared after undo, StructRNA has been removed.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires Python 3.x. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
blender-errors-data
Quick Reference
Critical Rules
NEVER store direct bpy.data references (objects, meshes, materials) across undo/redo, file load, or file revert operations — ALL Python pointers to Blender data are invalidated.
NEVER access a data block after calling bpy.data.<collection>.remove() on it — the StructRNA is freed and access raises ReferenceError.
NEVER assume bpy.data.<collection>.new(name) returns the exact name you passed — Blender appends .001, .002, etc. when a name already exists.
NEVER keep references to CollectionProperty items across .add() or .remove() calls — the underlying C array is re-allocated, invalidating all prior element pointers.
NEVER use del obj["prop"] to reset RNA-defined properties in Blender 5.0+ — use obj.property_unset("prop") instead.
NEVER use obj["cycles"] dict-like access for system properties in Blender 5.0+ — system properties are separated from custom properties.
ALWAYS store object/data block names (strings) or stable identifiers instead of direct bpy references when the reference must survive across operator execution, undo, or file operations.
ALWAYS re-fetch references from bpy.data.<collection> using .get(name) before accessing data that may have been invalidated.
ALWAYS store the return value of bpy.data.<collection>.new() directly in a variable — do not look up by name afterward.
ALWAYS use bpy.data.objects.get(name) (returns None) instead of bpy.data.objects[name] (raises KeyError) when the data block may not exist.
Error Identification Table
| Error Message | Root Cause | Section |
|---|
ReferenceError: StructRNA of type X has been removed | Accessing data block after remove() call | Removed Data Access |
ReferenceError after Ctrl+Z | Undo invalidated all bpy references | Undo Invalidation |
KeyError: 'bpy_prop_collection[key] not found' | Name collision — Blender renamed the data block | Name Collision |
Crash / segfault after CollectionProperty.add() | Stale pointer to re-allocated array element | Stale Collection References |
ReferenceError after file load/revert | File operation rebuilt entire data model | File Operation Invalidation |
KeyError with del obj["prop"] in 5.0 | IDProperty separation — dict access removed for system props | 5.0 IDProperty Changes |
Decision Tree: Safe Data Access
Need to reference a Blender data block?
├── Will you use it immediately (same function, no operators called)?
│ └── YES → Direct reference is safe: obj = bpy.data.objects["Cube"]
├── Will an operator with undo run between store and access?
│ └── YES → Store NAME, re-fetch later: name = obj.name
├── Will undo/redo possibly occur (modal operator, interactive use)?
│ └── YES → Store NAME, re-fetch on every access
├── Will file load/revert occur?
│ └── YES → Store NAME, re-fetch after load_post handler fires
└── Will CollectionProperty.add()/remove() be called?
└── YES → Re-fetch ALL element references after modification
Decision Tree: Diagnosing ReferenceError
Got ReferenceError: StructRNA of type X has been removed?
├── Did you call bpy.data.<collection>.remove() on this data?
│ └── YES → Do not access the variable after remove()
├── Did undo/redo happen since you obtained the reference?
│ └── YES → Re-fetch by name from bpy.data
├── Did a file load/revert happen?
│ └── YES → Re-fetch by name from bpy.data
├── Is this inside a modal operator?
│ └── YES → Store name in operator property, re-fetch each modal() call
└── Did you switch modes (Edit → Object)?
└── YES → Re-fetch mesh data references after mode switch
Error 1: ReferenceError from Removed Data
When a data block is removed via bpy.data.<collection>.remove(), ANY Python variable still pointing to that data raises ReferenceError on access.
mesh = bpy.data.meshes.new("TempMesh")
bpy.data.meshes.remove(mesh)
print(mesh.name)
mesh = bpy.data.meshes.new("TempMesh")
mesh_name = mesh.name
bpy.data.meshes.remove(mesh)
mesh = None
Cascading removal: Removing an object does NOT automatically remove its data. Removing a mesh does NOT automatically remove objects using it. Track dependencies manually.
obj = bpy.data.objects.get("MyObject")
if obj:
mesh = obj.data
bpy.data.objects.remove(obj)
if mesh and mesh.users == 0:
bpy.data.meshes.remove(mesh)
Error 2: Undo Invalidates ALL References
When undo or redo occurs, Blender rebuilds the entire data model from its undo history. ALL Python references to bpy.types.ID subclasses (objects, meshes, materials, scenes, etc.) become invalid. This is the single most dangerous data access pitfall.
When invalidation occurs:
bpy.ops.ed.undo() / bpy.ops.ed.redo()
- Interactive Ctrl+Z / Ctrl+Shift+Z by the user
bpy.ops.wm.open_mainfile() (file load)
bpy.ops.wm.revert_mainfile() (file revert)
- Any operator with built-in undo support that the user subsequently undoes
obj = bpy.data.objects["Cube"]
bpy.ops.ed.undo()
obj.location.x = 1.0
obj_name = bpy.data.objects["Cube"].name
bpy.ops.ed.undo()
obj = bpy.data.objects.get(obj_name)
if obj is not None:
obj.location.x = 1.0
See references/examples.md for the SafeObjectRef wrapper pattern.
Error 3: Name Collision on Data Creation
Blender enforces unique names within each bpy.data collection. When you create a data block with a name that already exists, Blender silently appends .001, .002, etc.
bpy.data.meshes.new(name="MyMesh")
mesh = bpy.data.meshes["MyMesh"]
mesh = bpy.data.meshes.new(name="MyMesh")
print(mesh.name)
Renaming also triggers this: If you set obj.name = "Cube" but "Cube" already exists, Blender renames the OTHER object to "Cube.001". Code storing the old name of the other object will break.
Error 4: Stale CollectionProperty References
CollectionProperty elements are stored in a contiguous C array. Calling .add() or .remove() may re-allocate the entire array, invalidating ALL Python references to existing elements.
items = bpy.context.scene.my_collection
first_item = items.add()
first_item.name = "First"
for i in range(100):
items.add()
first_item.name = "Updated"
items = bpy.context.scene.my_collection
items.add()
items[len(items) - 1].name = "First"
for i in range(100):
items.add()
items[0].name = "Updated"
Error 5: File Operations Invalidate References
File load and revert rebuild the entire data model, identical to undo invalidation. References obtained before the file operation are invalid.
obj = bpy.data.objects["Wall"]
bpy.ops.wm.open_mainfile(filepath="/path/to/other.blend")
print(obj.name)
from bpy.app.handlers import persistent
@persistent
def on_file_loaded(filepath):
obj = bpy.data.objects.get("Wall")
if obj:
print(f"Found Wall in new file: {obj.name}")
bpy.app.handlers.load_post.append(on_file_loaded)
Error 6: Blender 5.0 IDProperty Separation
Blender 5.0 separates system-defined properties (RNA properties defined via bpy.props) from user-defined custom properties. Dict-like access to system properties is removed.
value = bpy.context.scene["cycles"]
del obj["my_rna_prop"]
value = bpy.context.scene["cycles"]
del obj["my_rna_prop"]
value = bpy.context.scene.cycles
obj.property_unset("my_rna_prop")
obj["my_custom_prop"] = 42
value = obj["my_custom_prop"]
del obj["my_custom_prop"]
Migration for legacy files: Blender 5.0 auto-duplicates old IDProperties into system storage when opening pre-5.0 files. Clean up with:
for ob in bpy.data.objects:
sys_props = ob.bl_system_properties_get()
Mode Switching Invalidates Mesh Data
Switching between Edit Mode and Object Mode re-allocates mesh data arrays. References to mesh.vertices, mesh.polygons, mesh.edges, and UV layers obtained before the switch are invalid.
verts = bpy.context.active_object.data.vertices
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')
print(verts[0].co)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')
verts = bpy.context.active_object.data.vertices
print(verts[0].co)
Safe Data Access Patterns Summary
| Pattern | When to Use | Implementation |
|---|
| Store name, re-fetch | Modal operators, handlers, any long-lived reference | name = obj.name → bpy.data.objects.get(name) |
Use .get() not [] | Any lookup where data may not exist | bpy.data.objects.get("X") returns None |
| Capture return value | After .new() calls | mesh = bpy.data.meshes.new("X") |
| Re-fetch after collection mutation | After .add() / .remove() on CollectionProperty | Access by index, not stored reference |
| Null-check after re-fetch | After undo, file load, or any invalidation point | if obj is not None: |
Use @persistent on handlers | Any handler in an addon | Without it, handler is removed on file load |
Reference Links
Official Sources