171 lines
6.9 KiB
Markdown
171 lines
6.9 KiB
Markdown
# 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](https://docs.blender.org/api/current/).
|
|
|
|
## 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.
|
|
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
- [`bpy.types.Operator`](https://docs.blender.org/api/current/bpy.types.Operator.html)
|
|
- [`bpy.props`](https://docs.blender.org/api/current/bpy.props.html)
|
|
- [`bpy.utils`](https://docs.blender.org/api/current/bpy.utils.html)
|
|
- [`bpy.types.Image`](https://docs.blender.org/api/current/bpy.types.Image.html)
|
|
- [`bpy.types.Mesh`](https://docs.blender.org/api/current/bpy.types.Mesh.html)
|
|
- [`bpy.types.Depsgraph`](https://docs.blender.org/api/current/bpy.types.Depsgraph.html)
|