| name | blender-core-runtime |
| description | Use when dealing with Blender Python runtime behavior -- mathutils, threading, handlers, timers, or crashes after undo/redo. Prevents the #1 runtime crash: using threading for bpy operations instead of bpy.app.timers, or caching bpy.data references across undo boundaries. Covers Vector/Matrix/Quaternion, KDTree, BVHTree, @persistent handlers, bpy.msgbus subscriptions, and background mode limitations. Keywords: mathutils, threading, undo, ReferenceError, bpy.app.timers, @persistent, bpy.msgbus, KDTree, BVHTree, background mode, Blender crash, startup script, auto-run, run on open, Blender freezes.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires Blender 3.x/4.x/5.x with Python. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
blender-core-runtime
Quick Reference
Critical Warnings
NEVER call ANY bpy API from a background thread ā Blender's C/C++ core is NOT thread-safe. Crashes are immediate and unrecoverable.
NEVER store bpy.data references (objects, meshes, materials) across undo/redo boundaries ā undo rebuilds the entire data model, invalidating ALL Python pointers.
NEVER omit @persistent on handlers in addons ā without it, handlers are silently removed on file load.
NEVER do heavy computation inside bpy.app.timers callbacks ā they run on the main thread and freeze the UI.
NEVER use matrix1 * matrix2 for matrix multiplication ā the * operator is element-wise. ALWAYS use @ operator.
NEVER use bgl module in Blender 5.0+ ā fully removed. Use gpu module.
ALWAYS call kd.balance() after inserting all points into a KDTree ā queries return incorrect results without it.
ALWAYS pass depsgraph to BVHTree.FromObject() ā missing depsgraph skips modifiers and evaluated mesh.
ALWAYS remove handlers in your addon's unregister() function ā handlers accumulate on addon reload.
Decision Tree: Thread-Safe Execution
Need to run code from a worker thread?
āāā Is it a bpy.data read/write? āāāāāāāā YES ā Queue it via bpy.app.timers
āāā Is it pure Python / mathutils? āāāāāā YES ā Safe to run in thread
āāā Does it call bpy.ops.*? āāāāāāāāāāāāā YES ā Queue it via bpy.app.timers
āāā Does it use bpy.context? āāāāāāāāāāāā YES ā Queue it via bpy.app.timers
Decision Tree: Choosing a Spatial Query Structure
What do you need?
āāā Nearest points (vertex/position queries) āāāā Use KDTree
ā āāā find() ā single nearest
ā āāā find_n() ā N nearest
ā āāā find_range() ā all within radius
āāā Ray casting / surface intersection āāāāāāāāāāā Use BVHTree
ā āāā ray_cast() ā ray-surface intersection
ā āāā find_nearest() ā closest point on surface
ā āāā overlap() ā two-mesh intersection
āāā Simple distance between two points āāāāāāāāāāā Use Vector math
āāā (v1 - v2).length_squared for comparisons
Decision Tree: Event Subscription
What kind of event?
āāā Property changed on specific object āāāā bpy.msgbus.subscribe_rna()
āāā File load/save āāāāāāāāāāāāāāāāāāāāāāāāā bpy.app.handlers.load_post / save_pre
āāā Undo/Redo āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā bpy.app.handlers.undo_post / redo_post
āāā Frame change (animation) āāāāāāāāāāāāāāā bpy.app.handlers.frame_change_post
āāā Depsgraph update āāāāāāāāāāāāāāāāāāāāāāā bpy.app.handlers.depsgraph_update_post
āāā Deferred one-shot execution āāāāāāāāāāāā bpy.app.timers (return None)
āāā Periodic polling āāāāāāāāāāāāāāāāāāāāāāā bpy.app.timers (return float)
Essential Patterns
Pattern 1: mathutils Core Types
All types available in ALL versions (3.x/4.x/5.x). Import from mathutils.
from mathutils import Vector, Matrix, Quaternion, Euler
| Type | Purpose | Key Operations |
|---|
Vector | 2D/3D/4D point/direction | +, -, * (scalar), @ (matrix), .dot(), .cross(), .normalized(), .lerp() |
Matrix | 4x4 transformation | @ (multiply), .decompose(), .inverted(), .Translation(), .Rotation() |
Quaternion | Rotation (no gimbal lock) | @ (combine), .slerp(), .to_euler(), .to_matrix() |
Euler | Rotation with order (XYZ, etc.) | .to_quaternion(), .to_matrix(), .make_compatible() |
Matrix multiplication order (right-to-left application):
transform = translation_matrix @ rotation_matrix @ scale_matrix
point_transformed = transform @ point_vector
Pattern 2: Thread-Safe bpy Access
import threading, queue, bpy
execution_queue = queue.Queue()
def run_in_main_thread(fn):
execution_queue.put(fn)
def _process_queue():
while not execution_queue.empty():
execution_queue.get()()
return 0.1
bpy.app.timers.register(_process_queue)
def worker():
import time
time.sleep(2)
run_in_main_thread(lambda: setattr(
bpy.data.objects["Cube"].location, 'x', 5.0))
threading.Thread(target=worker, daemon=True).start()
Pattern 3: Safe Reference Handling (Undo-Proof)
obj_name = bpy.context.active_object.name
obj = bpy.data.objects.get(obj_name)
if obj is None:
raise RuntimeError(f"Object '{obj_name}' was deleted or renamed")
Pattern 4: Application Handlers with @persistent
from bpy.app.handlers import persistent
import bpy
@persistent
def on_file_loaded(dummy):
print(f"File loaded: {bpy.data.filepath}")
bpy.app.handlers.load_post.append(on_file_loaded)
def unregister():
if on_file_loaded in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.remove(on_file_loaded)
Pattern 5: Timers (One-Shot and Repeating)
import bpy
def delayed_action():
print("Executed once after 2 seconds")
return None
bpy.app.timers.register(delayed_action, first_interval=2.0)
def periodic_check():
print(f"Frame: {bpy.context.scene.frame_current}")
return 0.5
bpy.app.timers.register(periodic_check)
bpy.app.timers.register(periodic_check, persistent=True)
if bpy.app.timers.is_registered(periodic_check):
bpy.app.timers.unregister(periodic_check)
Pattern 6: Message Bus Subscriptions
import bpy
owner = object()
def on_location_changed(*args):
print(f"Location changed: {args}")
bpy.msgbus.subscribe_rna(
key=bpy.context.object.location,
owner=owner,
args=(),
notify=on_location_changed,
options=set(),
)
bpy.msgbus.subscribe_rna(
key=(bpy.types.Object, "location"),
owner=owner,
args=(),
notify=on_location_changed,
)
bpy.msgbus.clear_by_owner(owner)
Key differences from property update callbacks:
- msgbus callbacks are POSTPONED until all operators finish
- msgbus fires ONCE per update cycle, even if property changed multiple times
- Subscriptions cleared on file load unless
PERSISTENT option is set
Spatial Query Structures
KDTree: Nearest-Neighbor Queries
from mathutils.kdtree import KDTree
mesh = bpy.context.active_object.data
kd = KDTree(len(mesh.vertices))
for i, v in enumerate(mesh.vertices):
kd.insert(v.co, i)
kd.balance()
co, index, dist = kd.find((5.0, 3.0, 0.0))
results = kd.find_n((5.0, 3.0, 0.0), 10)
results = kd.find_range((5.0, 3.0, 0.0), 2.0)
BVHTree: Ray Casting and Surface Queries
from mathutils.bvhtree import BVHTree
from mathutils import Vector
depsgraph = bpy.context.evaluated_depsgraph_get()
obj_eval = obj.evaluated_get(depsgraph)
bvh = BVHTree.FromObject(obj_eval, depsgraph)
loc, normal, idx, dist = bvh.ray_cast(
Vector((0, 0, 10)), Vector((0, 0, -1)))
loc, normal, idx, dist = bvh.find_nearest(Vector((5, 3, 0.5)))
overlap_pairs = bvh1.overlap(bvh2)
Background Mode
blender --background scene.blend --python script.py
blender -b scene.blend -P script.py -- --custom-arg value
if bpy.app.background:
pass
Version-Specific Changes
| Feature | Blender 3.x/4.x | Blender 5.0+ |
|---|
bgl module | Available (deprecated 3.5+) | REMOVED ā use gpu module |
del obj["prop"] on RNA props | Works | REMOVED ā use obj.property_unset("prop") |
scene["cycles"] dict access | Works | REMOVED ā use scene.cycles attribute |
img.bindcode | Available | REMOVED ā use gpu.texture.from_image(img) |
img.gl_load() | Available | REMOVED |
bl_math Utility Functions
import bl_math
bl_math.lerp(0.0, 10.0, 0.25)
bl_math.clamp(15.0, 0.0, 10.0)
bl_math.clamp(-5.0)
bl_math.smoothstep(0.0, 1.0, 0.5)
Available Handlers Reference
| Handler | Trigger | Signature |
|---|
load_pre / load_post | File load | (filepath) |
save_pre / save_post | File save | (filepath) |
undo_pre / undo_post | Undo | (scene) |
redo_pre / redo_post | Redo | (scene) |
depsgraph_update_pre/post | Depsgraph eval | (scene, depsgraph) |
frame_change_pre/post | Frame change | (scene) / (scene, depsgraph) |
render_pre / render_post | Render start/end | (scene) |
render_init | Engine init | (engine) |
render_complete / render_cancel | Render finish | (scene) |
Reference Links
Official Sources