4
$\begingroup$

How would I approach editing/reloading/printing to console a multi-file addon?

There's a (someone else's) multi-file addon with a complex structure of libraries and classes with a bug in a particular class and I'd like to try to fix it. However I never made an addon with several separated files and I don't understand how to reload it properly. At the moment I edit the original .py file and restart Blender which seems like a terrible workflow.

$\endgroup$
8
  • $\begingroup$ I really am not sure but you can try to reload the module with the 'importlib' module. Use "import importlib" and then "importlib.reload(module)" $\endgroup$ – Gorgious Mar 4 '20 at 10:00
  • 2
    $\begingroup$ Does F3 > Reload Scripts help? You can also bind the operator to any hotkey... $\endgroup$ – brockmann Mar 4 '20 at 10:57
  • $\begingroup$ @Gorgious I'll try that, thank you $\endgroup$ – Sergey Kritskiy Mar 4 '20 at 11:50
  • $\begingroup$ @brockmann this doesn't seem to update the code $\endgroup$ – Sergey Kritskiy Mar 4 '20 at 11:51
  • $\begingroup$ Does it for me. Is the addon enabled? Also see: blender.stackexchange.com/a/28505/31447 $\endgroup$ – brockmann Mar 4 '20 at 12:04
2
$\begingroup$

Not sure why Reload Scripts didn't work at first for me, maybe I didn't save the correct file or whatnot, anyway, that's a possible way. My issues were however with this method is that

  • I have to edit files in Blender addons folder, not in my project folder;
  • this reloads all the scripts, on my machine with a lot of addons installed it takes about four seconds and plus after that some addons reset their settings;

In the end I followed this article for creating and debugging multifile addons. Author suggests using a specific setup for the __init__.py file and then provides a Blender script in the second part of the article to update only a specific addon. I've assigned this script to a hotkey and it works much faster than reloading all the scripts.

Here are the final scripts from the article. __init__.py:

bl_info = {
    'name': 'Test Multifile Addon',
    'category': 'All',
    'version': (0, 0, 1),
    'blender': (2, 80, 0)
}

modulesNames = ['addCube', 'addCubePanel'] # a list of imported module names

import sys
import importlib

modulesFullNames = {}
for currentModuleName in modulesNames:
    if 'DEBUG_MODE' in sys.argv:
        modulesFullNames[currentModuleName] = ('{}'.format(currentModuleName))
    else:
        modulesFullNames[currentModuleName] = ('{}.{}'.format(__name__, currentModuleName))

for currentModuleFullName in modulesFullNames.values():
    if currentModuleFullName in sys.modules:
        importlib.reload(sys.modules[currentModuleFullName])
    else:
        globals()[currentModuleFullName] = importlib.import_module(currentModuleFullName)
        setattr(globals()[currentModuleFullName], 'modulesNames', modulesFullNames)

def register():
    for currentModuleName in modulesFullNames.values():
        if currentModuleName in sys.modules:
            if hasattr(sys.modules[currentModuleName], 'register'):
                sys.modules[currentModuleName].register()

def unregister():
    for currentModuleName in modulesFullNames.values():
        if currentModuleName in sys.modules:
            if hasattr(sys.modules[currentModuleName], 'unregister'):
                sys.modules[currentModuleName].unregister()

if __name__ == "__main__":
    register()

Blender script to reload the addon:

import os
import sys

filesDir = "d:/Python/TestMultifile"

initFile = "__init__.py"

if filesDir not in sys.path:
    sys.path.append(filesDir)

file = os.path.join(filesDir, initFile)

if 'DEBUG_MODE' not in sys.argv:
    sys.argv.append('DEBUG_MODE')

exec(compile(open(file).read(), initFile, 'exec'))

if 'DEBUG_MODE' in sys.argv:
    sys.argv.remove('DEBUG_MODE')
$\endgroup$
1
0
$\begingroup$

I have found an easiest method. In this example the addon has two modules - main and operator

__init__.py:

bl_info = {
    # Your regular bl_info goes here.
}

# This is essential part
if "bpy" in locals():
    import importlib
    importlib.reload(main)
    importlib.reload(operator)
else:
    import bpy
    from . import main
    from . import operator

classes = (
    main.AddonProperties,
    operator.OBJECT_OT_skt_my_operator,
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
$\endgroup$
1
  • $\begingroup$ To keep code "modular" I would recommend each module with classes to register having a register method as above.. eg modules = [main, operator] then in the register method loop the modules and mod.register() IMO making adding another module easier to test and integrate into addon. $\endgroup$ – batFINGER Jun 9 '20 at 1:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.