1
$\begingroup$

I've been developing add-ons for Blender for some time, creating shared modules among them. Each add-on has a similar internal structure, which makes it easy to update dependencies when necessary.

Example:

my_addon/
   ├── __init__.py
   └── libs/
        ├── module1
        └── module2

To access a dependency within the add-on codebase, I would use an import like this:

from my_addon.libs import module1

This method has always worked perfectly. I prefer this approach over forcing an installation via pip into Blender's Python environment because it allows me to control the versioning of each module individually for each add-on. For example, one add-on might use version 1.0 of a module, while another uses version 1.1, and I can update each add-on as needed.

The Problem with Blender 4.2:

With the introduction of the extensions system in Blender 4.2, I decided to convert my add-ons accordingly, as demonstrated on this page: Blender Extensions Guide.

After completing the conversion, I encountered the following error when attempting to install the extension:

No module named my_addon

Upon analyzing the traceback, the problem lies precisely at the line where I import the external modules located in my_addon/libs:

from my_addon.libs import module1

In the same version of Blender, installing the add-on the legacy way works perfectly.

From what I understand, this import method works in the legacy installation because:

AppData\Roaming\Blender Foundation\Blender\4.2\scripts\addons

is in the PYTHONPATH, so when installing the add-on the legacy way, the import works.

However, it seems that the installation path for extensions is not in the PYTHONPATH:

AppData\Roaming\Blender Foundation\Blender\4.2\extensions\user_default

My Question:

Is there a way to maintain the import structure for external modules as I did before Blender 4.1?

I can think of possible solutions like forcing the extensions path into the PYTHONPATH, but I would like to know if there is a more elegant solution, similar to how it worked in Blender 4.1.


Context:

  • Developing add-ons for Blender.
  • Shared modules among add-ons.
  • Need to maintain control over versioning of shared modules.
  • Transitioning from legacy add-on system to new extensions system in Blender 4.2.

Any insights or suggestions on how to resolve this issue would be greatly appreciated!

$\endgroup$
3
  • $\begingroup$ Cf projects.blender.org/blender/blender/issues/125582 $\endgroup$
    – scurest
    Commented Aug 1 at 13:42
  • $\begingroup$ I think this is a very bad move from the developpers. Today there is next to no incentive for non-core addon developpers to jump to extensions. but TLDR from the devtalk link try this >>> sys.path.append('/home/carbon/.config/blender/4.2/extensions/user_default') >>> import some_extension $\endgroup$
    – Gorgious
    Commented Aug 1 at 14:13
  • $\begingroup$ You could also try putting all of the shared modules into a separate package in the startup folder. That way those just get auto-imported before the other addons register. But that's also if the way the startup folder works not also disrupted by the new extension system, I haven't actually checked. $\endgroup$
    – Jakemoyo
    Commented Aug 1 at 21:13

1 Answer 1

0
$\begingroup$

According to the Blender 4.2 manual, extensions must be self-contained, meaning all dependencies need to be included within the extension itself.

Any attempt to access dependencies outside of this self-contained structure is considered a policy violation. Even if you forcibly add the path of extensions to sys.path, you will encounter a "Policy violation with top level module" warning in the add-on preferences for each import done this way. In my add-on's case, this resulted in more than 15 warnings, cluttering the add-on preferences.

Solution

The same page suggests that imports within the add-on module should now be done using relative imports. This approach resolved my problem since the dependencies are in the same module. (Previously, I avoided using relative imports as I found them confusing with the complexity of the internal modules.)

For other situations where dependencies are not in the same module, the page suggests these alternatives:

  1. Bundle with Python Wheels:

    • This is the recommended way to bundle dependencies.
  2. Bundle other add-ons together:

    • Recommended if an add-on depends on another add-on.
    • Ensure that both the individual and combined add-ons check for already registered types (Operators, Panels, etc.). This avoids duplication of operators and panels in the interface if the add-ons are installed as a bundle and individually.
  3. Bundle with Vendorize:

    • This method bundles pure Python dependencies as a sub-module.
    • It avoids version conflicts but requires some setup work for each package.
$\endgroup$
4
  • $\begingroup$ I have a similar experience: from the tests I did so far on my light add-ons when I used relative paths it fixed the import issues. But I have an bigger add-on with 400 (399 to be exact) python files. Updating it is a nightmare. And the import part of the files becomes clearly unreadable and hard to maintain with relative paths. 😡 Not to mention the "wheel packing" for the library dependencies. I rely on OpenTimelineIO, which install other libraries when being installed. Just impossible to pack. 😡😡 I am strongly considering keeping this add-on as a legacy one. $\endgroup$
    – Werwack
    Commented Aug 7 at 23:39
  • $\begingroup$ @werwack were you able to find a solution with OpenTimelineIO? I'm also attempting to use this package. I seem to have the exact same problem as you. $\endgroup$
    – Grant
    Commented Oct 20 at 23:12
  • $\begingroup$ @Grant Yep, I found one. I didn't convert my add-on to an extension, and I will not. 😁 It's just not possible. Relative paths for imports makes development too tricky: on a large scale add-on it's far to confusing not to know exactly where and which are the packages imported in a file. On the wheel side: creating a wheel is very technical, often too much for scripters, it's time consuming, you cannot test them on every platform, many referenced libs require other libs (Fpdf2 requires PIL, OpenTImelineIO requires the whole world), and they keep upgrading (at least for Python version)... $\endgroup$
    – Werwack
    Commented Oct 22 at 7:08
  • $\begingroup$ I'm annoyed with all that, because the way extensions were introduced was like: "this is the only way to go now". Add-ons that didn't / couldn't be converted are seen as "stuff from the past". Even the "Install from disk" looks like a "last possibility when all previous attempts failed", whereas it should still have been presented as "the other supported way". That's the same on extension platforms and websites. I have the feeling Blender developers don't understand the amount of work required just to maintain an add-on from one version to another and how scripting is not as low level as code $\endgroup$
    – Werwack
    Commented Oct 22 at 7:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .