# 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)
