# Building Blender extensions in this repository

This guide targets Blender 5.x, with Blender 5.2 as the current user target.
Blender calls installable Python plug-ins **add-on extensions**. Use that format
instead of the legacy single-file add-on format.

## 1. Understand the package boundary

Every extension is an independent directory under `blender/`:

```text
blender/
  example_tool/
    blender_manifest.toml
    __init__.py
    operators.py        # optional
    properties.py       # optional
    ui.py               # optional
    wheels/             # optional bundled dependencies
```

The build command turns the contents of `example_tool/` into a ZIP whose root
contains the manifest and `__init__.py`. The directory containing all extensions
is not itself a Python package.

Start with one `__init__.py` while a tool is small. Split it when operators,
panels, and data helpers become difficult to navigate. Multi-file extensions
must use relative imports:

```python
from . import operators, properties, ui
```

Do not import the extension by a hard-coded installed name. Blender namespaces
extensions by repository, for example `bl_ext.user_default.example_tool`, and
that prefix changes when the package is installed elsewhere.

## 2. Write the manifest

A minimal repository-compatible manifest looks like this:

```toml
schema_version = "1.0.0"

id = "example_tool"
version = "0.1.0"
name = "Example Tool"
tagline = "Perform one useful Blender workflow"
maintainer = "Your Name"
type = "add-on"

blender_version_min = "5.0.0"

license = [
  "SPDX:GPL-3.0-or-later",
]
```

Manifest rules worth catching early:

- `id` is the stable package identity. Do not change it merely to rename the UI.
- `version` follows semantic versioning. Bump it when distributing an update.
- `tagline` is short, has no trailing punctuation, and describes an action.
- `blender_version_min` is the oldest version actually supported, not simply the
  developer's newest installed version.
- Omit unused optional fields rather than setting them to empty strings/lists.
- Use SPDX-prefixed license identifiers.

### Permissions

Declare capabilities that reach outside normal Blender data:

```toml
[permissions]
files = "Export processed textures selected by the user"
network = "Synchronize assets with the configured server"
```

Only declare what the extension uses. Permission explanations must be short and
must not end in punctuation. Network permission does not override Blender's
online-access preference; check it before connecting:

```python
if not bpy.app.online_access:
    self.report({"ERROR"}, "Enable Online Access in Blender preferences")
    return {"CANCELLED"}
```

## 3. Implement registration, an operator, and UI

This is a small but complete extension entry point:

```python
import bpy
from bpy.props import BoolProperty, PointerProperty
from bpy.types import Operator, Panel, PropertyGroup


class EXAMPLE_PG_Settings(PropertyGroup):
    affect_selected: BoolProperty(
        name="Affect Selected",
        default=True,
    )


class EXAMPLE_OT_Run(Operator):
    bl_idname = "object.example_run"
    bl_label = "Run Example"
    bl_description = "Perform the example operation on the active mesh"
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context):
        return bool(
            context.mode == "OBJECT"
            and context.active_object
            and context.active_object.type == "MESH"
        )

    def execute(self, context):
        obj = context.active_object
        if obj is None:
            self.report({"ERROR"}, "Select a mesh object")
            return {"CANCELLED"}

        # Prefer direct data API changes here. Use context, not bpy.context.
        obj["example_was_run"] = True
        self.report({"INFO"}, f"Processed {obj.name}")
        return {"FINISHED"}


class EXAMPLE_PT_Panel(Panel):
    bl_label = "Example Tool"
    bl_idname = "EXAMPLE_PT_main"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Example"

    def draw(self, context):
        layout = self.layout
        settings = context.scene.example_tool
        layout.prop(settings, "affect_selected")
        layout.operator("object.example_run")


CLASSES = (
    EXAMPLE_PG_Settings,
    EXAMPLE_OT_Run,
    EXAMPLE_PT_Panel,
)


def register():
    for cls in CLASSES:
        bpy.utils.register_class(cls)
    bpy.types.Scene.example_tool = PointerProperty(type=EXAMPLE_PG_Settings)


def unregister():
    del bpy.types.Scene.example_tool
    for cls in reversed(CLASSES):
        bpy.utils.unregister_class(cls)
```

Important details:

- Register `PropertyGroup` classes before creating pointer/collection properties
  that reference them.
- Unregister in exact reverse dependency order.
- Prefix class names and Blender identifiers to avoid collisions.
- A panel's `draw()` executes frequently. It should be cheap and should not
  mutate scene data.
- Use `poll()` both to disable invalid UI actions and to document required context.
- `bl_options = {"REGISTER", "UNDO"}` is appropriate for scene-data changes,
  but Undo does not reverse files, HTTP calls, or other external side effects.

## 4. Design safe Blender operations

Blender data often has multiple users. Editing `obj.data` can change every
object sharing that mesh, and editing a material can change every object using
it. When the feature should be non-destructive, copy first:

```python
result_object = source.copy()
result_object.data = source.data.copy()
source.users_collection[0].objects.link(result_object)
```

When modifiers must be applied without touching the source:

```python
depsgraph = context.evaluated_depsgraph_get()
evaluated = source.evaluated_get(depsgraph)
result_mesh = bpy.data.meshes.new_from_object(
    evaluated,
    preserve_all_data_layers=True,
    depsgraph=depsgraph,
)
```

Stateful operations should follow a transaction-like structure:

```python
active_before = context.view_layer.objects.active
selected_before = list(context.selected_objects)
temporary_object = None
success = False

try:
    # Create copies and perform the operation.
    success = True
finally:
    # Remove temporary data and restore selection/settings.
    if temporary_object is not None:
        bpy.data.objects.remove(temporary_object, do_unlink=True)
```

Track every temporary data-block you create. Removing an object does not
automatically remove its orphaned mesh, material, or image. Check `users == 0`
before removing a data-block that might legitimately be shared.

Use `bpy.ops` only when Blender exposes no suitable data API. Operators depend
on context: editor area, mode, active object, selected objects, active material
node, render engine, or view layer. A headless test is the quickest way to find
hidden context assumptions.

## 5. Store files and settings correctly

An installed extension may live in a read-only system repository, and upgrades
replace its package directory. Never store user data beside `__init__.py`.

Use the per-extension storage API:

```python
storage_dir = bpy.utils.extension_path_user(
    __package__,
    path="cache",
    create=True,
)
```

Use Blender properties for settings that should live in a `.blend` file, and an
`AddonPreferences` subclass for user preferences that should apply across
projects. Access preferences through `__package__`, not a literal module name:

```python
preferences = bpy.context.preferences.addons[__package__].preferences
```

Use `bpy.path.abspath()` for Blender paths such as `//textures/output.png`.
Validate empty paths and file formats before starting an expensive operation.

## 6. Bundle dependencies

Extensions must be self-contained. For a third-party Python dependency:

1. Download/build wheels for every supported OS and architecture.
2. Put them under the extension, conventionally `wheels/`.
3. List each relative wheel path in the manifest's `wheels` array.
4. Build and test the installed ZIP on every advertised platform.

Do not import from the developer's system Python, mutate Blender's bundled
Python, or invoke `pip` during registration. Pure-Python code may be vendored,
but keep its license and avoid top-level package-name collisions.

## 7. Build and install

Build all repository extensions:

```bash
./build-all.sh
```

Build, reinstall, and enable them for the current Blender user:

```bash
./install-all.sh
```

Windows Command Prompt:

```bat
install-all.bat
```

Select a non-default Blender executable with `BLENDER_BIN`. Close running
Blender instances before reinstalling; an open process retains already imported
Python modules.

For the full validation and release sequence, continue with
[Testing and releasing](testing-and-releasing.md).

## Official references

- [Creating Blender extensions](https://docs.blender.org/manual/en/dev/advanced/extensions/getting_started.html)
- [Extension add-ons, namespaces, storage, and online access](https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html)
- [Bundling Python wheels](https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html)
- [Extension command-line arguments](https://docs.blender.org/manual/en/dev/advanced/command_line/extension_arguments.html)
- [Current Blender Python API](https://docs.blender.org/api/current/)
- [Blender extension add-on guidelines](https://developer.blender.org/docs/handbook/extensions/addon_guidelines/)

