6.9 KiB
Blender Python API notes for extension authors
These notes collect the failure modes most likely to matter in this repository. They are not a replacement for the current Blender Python API.
Context, modes, and operators
bpy.context is global ambient state. Operator and panel callbacks receive a
context argument that may be overridden, so use that argument inside the
callback.
An operator that works when clicked can still fail from a script or in background mode. Common implicit requirements include:
- active object and selected objects;
- Object/Edit/Sculpt mode;
- active view layer and collection visibility;
- active image texture node in every material;
- current editor area/region;
- render engine and bake settings.
Put cheap requirements in poll() and validate again in execute() when a
specific error message helps. Prefer direct data manipulation over synthesizing
UI context overrides.
Data-block ownership
Objects reference meshes; material slots live primarily on meshes but can be overridden per object; nodes reference images. Copy the level whose ownership must become independent.
object_copy = source.copy() # independent object, shared mesh
object_copy.data = source.data.copy() # independent object and mesh
collection.clear() and similar operations may normalize dependent indices.
For example, clearing mesh material slots can reset polygon material indices.
Snapshot indices before clearing and restore them after rebuilding the slots.
Blender data collections do not behave like ordinary Python ownership. Clean up temporary data explicitly:
bpy.data.objects.remove(temp_object, do_unlink=True)
if temp_mesh.users == 0:
bpy.data.meshes.remove(temp_mesh)
Do not remove a data-block with active users merely to avoid an orphan.
Registration lifecycle
Keep a deterministic tuple of registered classes. Dependencies go first:
PropertyGroupandAddonPreferences;- operators;
- panels, menus, UI lists, and gizmos.
Unregister in reverse. Any side effect outside class registration needs its own inverse operation:
bpy.types.SomeMenu.append()→.remove();bpy.app.handlers.*.append()→.remove();bpy.app.timers.register()→ unregister or make the callback stop;- created keymaps → remove their keymap items;
- preview collections → close/remove them;
bpy.types.Scene.some_property = ...→del bpy.types.Scene.some_property.
Reloading exposes incomplete cleanup quickly. A package that only works after restarting Blender has a lifecycle bug.
Properties and persistence
Blender properties are RNA definitions, not normal instance attributes. Declare
them on registered Blender classes with bpy.props annotations. Register a
PropertyGroup before using it as a PointerProperty or CollectionProperty.
Choose storage based on intended lifetime:
- operator property: one invocation and redo panel;
Scene/Objectproperty: saved in the.blend;AddonPreferences: current Blender user profile;- module global: only the current enabled Python module lifetime;
extension_path_user(): persistent files owned by the extension.
Property update callbacks can run in surprising contexts. Keep them small, avoid expensive operators, and guard against recursion.
Meshes, modifiers, and UVs
source.data.copy() copies the base mesh. It does not apply modifiers.
bpy.data.meshes.new_from_object(source.evaluated_get(depsgraph), ...) captures
evaluated geometry. Decide explicitly which topology the result should use.
Evaluated geometry can add/remove UV layers or material slots. After evaluation:
- confirm a UV layer still exists;
- choose and set the intended active/render UV layer;
- compute the required material-slot count from both slots and polygon indices;
- do not assume source and evaluated polygon counts match.
Materials, nodes, and images
For materials used by common exporters, a Principled BSDF with an Image Texture connected to Base Color is more portable than a custom shader tree. For a bake target, every material involved in the bake needs an active Image Texture node that points at the destination image.
Use Non-Color for ID maps, masks, normal maps, roughness, metallic, and packed
data textures. Color-space configuration affects both values returned to Python
and values saved to disk.
Byte images quantize floats. A value recorded as 0.1 in metadata may become
byte 25 or 26 depending on the conversion path. If another tool consumes an ID
map, prefer exact byte matching with a narrowly bounded fallback, and reject
overlapping color masks.
Generated images are Blender data-blocks. Decide whether they should be saved, packed, or intentionally remain generated data before the user closes the file.
Baking
Image baking is context-sensitive. A robust bake typically controls and restores:
- active/selected bake object;
- Object mode;
scene.render.engine(Cycles owns the image-bake pipeline);- Cycles samples (one sample is sufficient for a deterministic flat emission bake);
scene.render.bake.target, margin, clear behavior, and selected-to-active;- active target Image Texture nodes;
- temporary materials and material indices.
Use a flat emission shader for material-ID colors so lights, normals, and
sampling do not alter the values. Always restore the previous render engine and
bake settings in finally.
Files, paths, and external effects
Blender accepts // paths relative to the current .blend. Resolve them with
bpy.path.abspath(). Do not assume the .blend has been saved, and do not
silently write into the extension package directory.
UNDO only covers Blender's undo-aware data changes. It does not remove a PNG,
undo a JSON/GLB write, retract a network request, or restore another program's
state. Validate first and order external effects late.
Compatibility checks
Blender's Python API changes between releases. Avoid relying on memory for enum names, node socket names, or RNA properties. Query the target build when needed:
blender --factory-startup --background --python-expr \
"import bpy; print(bpy.app.version_string); print(bpy.types.BakeSettings.bl_rna.properties.keys())"
Feature-detect optional properties with hasattr or guarded assignment, but do
not use broad exception handling to hide genuine failures. When support differs
materially by version, set an honest manifest minimum or branch on
bpy.app.version and test both branches.
Useful API entry points: