blender-tools/docs/blender-api-notes.md

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:

  1. PropertyGroup and AddonPreferences;
  2. operators;
  3. 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/Object property: 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: