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