Confusing, Unclear Extension System Requirements

Original discussion here, with more links/info because this forum prohibits me from adding more links/images.

I’m trying to add extension support for my pre-existing addon, “Mesh Align Plus”. Mesh Align Plus is a full Python package, and the instructions for structuring my zip file do not work. What I want:

  • To make my addon an extension, that loads into Blender 4.2 successfully (this is the top priority)
  • I would PREFER the zip structure to have my package folder + the toml file from the extensions guide page…
    • That structure hasn’t worked
    • Neither has including the toml inside the package itself
    • Neither has putting the package’s folder contents plus with the toml directly inside the zip file
  • Question: Will an extension zip install via the extensions menu AND the addons menu in Blender settings? I want it to work in both places (hopefully the devs have considered this and it just works)

Here’s the error:

Here’s the TOML file:

schema_version = "1.0.0"

# Example of manifest file for a Blender extension
# Change the values according to your extension
id = "mesh_mesh_align_plus"
version = "1.0.0"
name = "Mesh Align Plus"
tagline = "Precisely align, move, and measure+match objects and mesh parts in your 3D scenes."
maintainer = "Eric Gentry <[email protected]>"
# Supported types: "add-on", "theme"
type = "add-on"

# Optional link to documentation, support, source files, etc
# website = "https://extensions.blender.org/add-ons/my-example-package/"
website = "https://github.com/egtwobits/mesh_mesh_align_plus/wiki"

# Optional list defined by Blender and server, see:
# https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html
tags = ["3D View", "Mesh", "Modeling", "Object", "User Interface"]

blender_version_min = "2.8.0"
# # Optional: Blender version that the extension does not support, earlier versions are supported.
# # This can be omitted and defined later on the extensions platform if an issue is found.
# blender_version_max = "5.1.0"

# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix)
# https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html
license = [
  "SPDX:GPL-2.0-or-later",
]
# Optional: required by some licenses.
copyright = [
  "2002-2024 Eric Gentry"
]

# Optional list of supported platforms. If omitted, the extension will be available in all operating systems.
# platforms = ["windows-x64", "macos-arm64", "linux-x64"]
# Other supported platforms: "windows-arm64", "macos-x64"

# Optional: bundle 3rd party Python modules.
# https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html
# wheels = [
#   "./wheels/hexdump-3.3-py3-none-any.whl",
#   "./wheels/jsmin-3.0.1-py3-none-any.whl",
# ]

# # Optional: add-ons can list which resources they will require:
# # * files (for access of any filesystem operations)
# # * network (for internet access)
# # * clipboard (to read and/or write the system clipboard)
# # * camera (to capture photos and videos)
# # * microphone (to capture audio)
# #
# # If using network, remember to also check `bpy.app.online_access`
# # https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access
# #
# # For each permission it is important to also specify the reason why it is required.
# # Keep this a single short sentence without a period (.) at the end.
# # For longer explanations use the documentation or detail page.
#
# [permissions]
# network = "Need to sync motion-capture data to server"
# files = "Import/export FBX from/to disk"
# clipboard = "Copy and paste bone transforms"

# Optional: build settings.
# https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build
# [build]
# paths_exclude_pattern = [
#   "__pycache__/",
#   "/.git/",
#   "/*.zip",
# ]

Can someone clarify the extension spec for me? I’ve read both “How to Create Extensions” and “Converting a Legacy Add-on into an Extension”.

My addon “Mesh Align Plus” is a vanilla Python package. It seems absurd that a standard Python package structure isn’t supported. Inside my extension zip file root I’ve tried:

  • my_zip.zip structure A (package folder and manifest side by side)
    • mesh_mesh_align_plus (Standard Python package folder)
    • blender_manifest.toml
  • my_zip.zip structure B (package folder inside root, manifest inside the package folder)
    • mesh_mesh_align_plus (package folder)
      • __init__.py
      • blender_manifest.toml
      • utils (subpackage)
      • advanced_tools.py
      • … (other package internals)
  • my_zip.zip structure C (dump the package internals into the zip root, which as a Python devloper is nonsensical)
    • __init__.py
    • blender_manifest.toml
    • utils (subpackage)
    • advanced_tools.py (submodule)
    • … (other package internals)

I’ve also tried these structures with the edits from “Converting a Legacy Add-on into an Extension” applied. Some statements in that document are nonsensical for a Python package, and seem to assume your addon is a single module. What does the Blender extension system actually expect? Again, not supporting a standard package structure seems ridiculous.

It is essential to explain what the system does and how it works, instead of being vague and cryptic in the docs in ways that do not make expectations and assumptions clear.

I’m a Python developer, and the extension system does not seem to follow typical Python conventions (and uses its own magic behind the scenes that is not explained, not standard, and thus impossible to guess or reason about).

Can anyone provide clarity? Thanks for your help. (Blenderartists thread has some additional discussion)

Okay so, the key to getting my package working was the “use relative imports” step in the “Converting a legacy addon into an extension”. That bullet, and some other sections, should be rewritten to make sense for legacy addon devs using proper package structures (maybe I’ll PR a docs fix for that).

All of my package imports were absolute, which was the root cause of my problems (and adding an __init__.py file next to the package was nonsensical because it affects name resolution in those cases so that should be made more clear).

Hopefully this will help others needing clarification.

2 Likes

That would be much appreciated, thanks

@dfelinto I’m guessing the system takes the internals and wraps them into a new dynamically generated package, is that correct? Explaining HOW and WHY the system needs what it does helps users reason about how to fix their files, and demystifies what is as of now a black box. I can include a short explanation in a PR.

2 Likes

@BitByte from reading your posts it seems like you want to know the how & why of some decisions behind how extensions work. While that’s OK, I think it makes more sense to include this in an developer-architecture document - than user level docs.


I’m guessing the system takes the internals and wraps them into a new dynamically generated package, is that correct?

No, at least it depends how you define a “dynamically generated package” but I wouldn’t describe it this way.

Your extension is a package that is imported as a sub-module.
The __package__ of your extensions __init__.py file represents:

Root Extension Module -> Repository Sub-Module -> Extension Package

Internally the link between Root Extension Module -> Repository Sub-Module uses re-direction (like a symbolic-link) so the repository doesn’t have to be located in a sub-directory on the file system, also needed so repositories can be stored in a custom directory.
Besides this non-standard package redirection, an extension is loaded as a “typical” sub-module of the repositories module.


Explaining HOW and WHY the system needs what it does helps users reason about how to fix their files, and demystifies what is as of now a black box. I can include a short explanation in a PR.

For user level documentation a statement like this might help:

Internally, extensions are loaded as a sub-module
of the repositories module. Since there can be any number
of repositories and their module name is user defined:
It's necessary to use relative imports within your extension
to ensure your extension works in any repository and doesn't
make assumptions about the users preferences.

Another point to make, although it’s getting more verbose:

Therefor in the same way an extension most  make
assumptions about its file-system location on the users system,
you also can't make assumptions about its module name.
This is why it's important to use `__package__` as an identifier
in various places (the preferences ID for example),
as it's ensured to be globally unique within Blender's Python
run-time.

Comparing with legacy add-ons.

Legacy add-ons use Python's module name-space,
meaning add-ons shared a name-space with Python
modules and could collide with the many modules
available on https://pypi.org
1 Like