Creating multifile add-on for Blender

In the development of complex add-ons with large code volume storing all the code in a single file is inappropriate. When a single file contains logically unrelated classes, functions, and datasets, it is difficult to read, debug, find the necessary code pieces, reuse code. Such code layout is considered as very bad programming tone.

Blender Python supports modular system that allows subdividing logical code parts of the add-on into different files, and then connect them to use. Even if you have never thought about modules, creating scripts or add-ons, you have already used them – any code stored in the *.py file is a separate independent module. Just your addon consists of only one module. Complex add-ons may consist of several tens of modules.

Multifile add-on
Multifile add-on

So the module is the separate file with the *.py extension, contains Python executable code.

In order to make the whole add-on from several modules, they are combined to package. A package is just a directory (folder), which collected the necessary add-ons modules. The hallmark of a package is the presence of a file named __init__.py inside it. To correctly install such add-on to Blender, entire package (the directory, not only files from it) needs to be archived to *.zip and during installation – Install From File – this archive needs to be specified.

Let’s consider a simple creating multifile add-on example:

  1. Create a package:
    1. In your favorite file manager create a directory with sample name. For example – d:/Python/TestMultifile/
  2. It is more convenient to use an external IDE for coding, but you can write code with the built-in Blender Text Editor.
    1. Create a new PyCharm project.
    2. Specify the created package as the working directory.
  3. Create the first module:
    1. Create new file.
    2. Name it addCube.py

Module (file) names should not be exactly the same as the package (directory) name, not to make troubles with their further importing within the add-on.

Let’s write the simplest operator code which adds to the scene the default cube, nothing without it ).

This creates a custom operator, by wrapping the system operator bpy.ops.mesh.primitive_cube_add (), that adds a cube to the scene, to user-defined class.

Another difference from a single file add-on – setting the add-on description dictionary bl_info in each module is not required, only executable code.

  1. Create the second module:
    1. Create new file.
    2. Name it addCubePanel.py

In this module let’s create an interface to call the operator from the first module. Now we follow the MVC (Model-View-Controller) concept, which prescribes the separation of the executable code (model) and the code responsible for the interface creation (view).

Let’s create a simple “Add Cube” tab in the T-bar with a single button to call the operator from the first module.

  1. And now let’s create an index file for the combining modules from the package:
    1. Create new file.
    2. Name it __init__.py

Only this file from the whole package is executed at the time of the add-on activation. Therefore, add-on description dictionary bl_info should be placed here:

And it needs to make the import of all other modules exactly in this file.

Index file __init__.py is used only for modules import and registration and therefore it should not contain any executable code related to a specific addon. This file should be universal as much as possible.

Append the names of all the modules that must be imported to the list:

Module name – is the file name without the extension “.py”.

This string is the one changeable string in the file. To make an index file for any other add-on the above code can be used, only with specifying the necessary module names in the modulesNames list.

Modules, imported from the package, in Python environment are available by the ID: package_name.module_name. Let’s extend module names in the list to full format:

Now the modulesFullNames dictionary contains the full module names used in our add-on.

Let’s import all the modules from this dictionary with their full names using the importlib utility:

All imported modules are stored in sys.modules. Reloading already imported module (importlib.reload) is required when the user presses the refresh button f8 in add-ons installing window (User Preferences – Add-ons).

To speed up the Blender add-ons performance, modules compiled and then stored in the add-ons cache directory, from which they are taken for execution. If the module code changes, the Blender will still use the old, already compiled version of the module, as long as the add-on will not be reinstalled or reloaded by pressing f8 key. Module reloading required to recompile the changed code and make it available for use.

importlib.import_module doesn’t create a global variable pointed to imported module. To further refer imported module by name it needs to put in the globals() that variable.

Attribute “modulesNames” creates in every imported module to have an ability to access all other imported modules. Now from any imported module we can access another imported module by its name. For example to access addCube module from addCubePanel module we need to use the instruction:

After importing all of the necessary modules we must register their classes. The register() function is executed automatically at the time of add-on activation only in the index file. So we need to call the register functions for all add-on modules inside it.

The same needs to be done with unregistering functions.

Completed __init__.py code:

This is the finished (release) version of the index __init__.py file. It can be used for any multifile addon. It is only necessary to specify the desired module names in modulesNames list.

  1. Our TestMultifile directory now contains three files:
    1. __init__.py
    2. addCube.py
    3. addCubePanel.py

It remains to pack it (the directory, not only files) to *.zip archive, and install a test multifile add-on to Blender.

Sample multifile add-on
Sample multifile add-on

Bonus: debugging multifile add-on from Blender internal Text Editor

If develop multifile add-on with external IDE, existing startup script in Blender internal editor don’t work. With existing script Blender can’t understand what is called – the package index file or the single file add-on. To debug a multifile add-on we need to change the startup Blender script.

First of all, we need to specify the location of our add-on – the path to the directory d:/Python/TestMultifile/. Python search paths to the imported modules in the sys.path list. Let’s append our package directory path to sys.path. The name of the index file let’s store to the separate variable for convenient use.

Debug add-on launch is different from running add-on already installed in Blender. In order to understand what type of execution is required in __init__.py, add to the list of input parameters sys.argv additional parameter “DEBUG_MODE”.

Remove this parameter after script finish.

Full script code that runs the multifile add-on from internal Blender Text Editor in debug mode now looks like this:

This script code is universal. For the development of other multifile add-ons, you only need to change the modules location path in the filesDir variable.

Executing from the Blender Text Editor by pressing Run Script button __init__.py file considered a separate module, not the package index file. Therefore, all add-on modules should not be imported as package parts. They must be imported as the separate individual modules. So the full module name will contain only the name of the module, omitting the package name.

Use the “DEBUG_MODE” input parameter to modify __init__.py file. Make the condition for the debugging run that allows correctly modules importing. To do this we just need to correctly form full modules names in the modulesFullNames dictionary:

Finished __init__.py file for running in the debug and release modes:

To publish add-on release you can use both the __init__.py file, above, and modified.