10
$\begingroup$

I have two scripts. One that runs the main logic and another that holds a set of functions that I want to call. However when I make a change to the functions script it is not updated. I have saved the function script as a file and I can see that the changes have been applied to it but these changes are not reflected when I click run on my main script.

I have tried click run script on my functions script and if I modify it so it contains an error it will tell me but the main script doesn't seem to notice or care. The only way to get Blender to take notice of any changes in my scripts is to close and reopen it! Why is this happening?

Edit

It appears I've not understood how script works in Blender when you're not running the game engine. So my question effectively becomes:

How can I run a script that calls functions in other scripts by clicking a button in Blender?

I understand this could be considered too broad or too different to my original question so I am considering closing this and starting again.

$\endgroup$

4 Answers 4

24
$\begingroup$

Once a Python module has been imported, it will never be reloaded by the interpreter (that is, not automatically) when source file changes.

Blender has an operator to reload all of its py scripts, triggered by the F8 shortcut (in default keymap).

Now, that will only reload registered addons (and startup/UI scripts), if your addon contains several sub-modules you need to handle their reload yourself, using that kind of construct in your __init__.py addon’s file:

# When bpy is already in local, we know this is not the initial import...
if "bpy" in locals():
    # ...so we need to reload our submodule(s) using importlib
    import importlib
    if "my_submodule" in locals():
        importlib.reload(my_submodule)


# This is only relevant on first run, on later reloads those modules
# are already in locals() and those statements do not do anything.
import bpy
import my_submodule
$\endgroup$
4
  • $\begingroup$ Hi mont29, thank you for your answer. I think I understand what you're trying to explain but I didn't realise how different to the game engine this was going to be. I haven't set up an add on or a module, I just assumed I could call a script whenever I needed to. How come this works out of the box with bge but not with bpy? $\endgroup$
    – sydan
    Commented Apr 15, 2015 at 11:17
  • $\begingroup$ I don’t know much about BGE, it probably re-reads (reloads) scripts over and over? Anyway, if you are new to addons in Blender, I suggest you have a look around for tutorials, read some already existing ones, and more than everything official doc, which has some nice introductions. In a few words, you need to define an operator, which code will then do whatever you want. $\endgroup$
    – mont29
    Commented Apr 15, 2015 at 12:21
  • $\begingroup$ Thank you for the guidance! I will proceed to read up and learn how to write addons for Blender. $\endgroup$
    – sydan
    Commented Apr 15, 2015 at 12:30
  • $\begingroup$ Is it standard practice to use this conditional when developing a multi-file addon in Blender, or are there other ways around the problem? This seems like it would be a fairly common issue, yet this question has relatively few views and upvotes. $\endgroup$
    – splic
    Commented May 9, 2018 at 21:08
5
$\begingroup$

I just had the exactly the same problem as OP and mont29's answer helped me figure out the solution for that particular problem.

So this answer applies to running scripts from Text Editor, not from an addon.

To get a submodule script reload every time the main script is run, put this in the main script in place of the usual import statement for the submodule script:

# this really loads the "my_submodule.py" Text only the first time this script is run
import my_submodule 

# this re-loads the "my_submodule.py" Text every time
# (needs the "normal" import statement above to define the module name)
import importlib
importlib.reload(my_submodule)

Yes, that will make the submodule script get loaded twice the first time main script is run - but that is not important because the purpose of all this is to conveniently test scripts.

(Note that, when run from Text Editor, "my_submodule" will not be in locals() unless it is imported with import so testing for it cannot be used to skip reloading it.)

Finally, to answer sydan's updated question about how to run a script from a button, use this code for the execute of the button's operator:

def execute(self, context):
    text_main= bpy.data.texts['my_mainmodule.py']
    context_c = context.copy()
    context_c['edit_text'] = text_main
    bpy.ops.text.run_script(context_c)
    return {'FINISHED'}
$\endgroup$
4
$\begingroup$

Adding to existing answers, this snippet will reload all submodules in the current addon, without having to manually list your submodules:

import importlib
import sys

current_package_prefix = f"{__name__}."
for name, module in sys.modules.copy().items():
    if name.startswith(current_package_prefix):
        print(f"Reloading {name}")
        importlib.reload(module)

Update

The code above will throw an exception upon running Reload Scripts if you deleted/renamed a submodule; In that case, either wrap importlib.reload in a try block, restart Blender, or run

import sys
del sys.modules["addon_name.deleted_module_name"]

in Blender's Python console to "unload" it first.

$\endgroup$
3
  • $\begingroup$ Thanks, now if only I could figure out why I still have to run the Reload Scripts twice for all changes to take effect. $\endgroup$
    – TedMilker
    Commented Jan 13 at 4:02
  • $\begingroup$ @TedMilker I'm no python expert, but I think you should declare imports (of your submodules, not bpy) after reloading submodules $\endgroup$ Commented Jan 13 at 8:08
  • $\begingroup$ Yeah me neither. I already had your code at the top of my init.py with a check whether bpy was in locals. Took the check out and moved it below the import of the Blender/Python modules, before mine and that did it. Thanks. $\endgroup$
    – TedMilker
    Commented Jan 13 at 13:11
0
$\begingroup$

for relative imports

import sys
import importlib

from . import my_submodule

if "bpy" in locals():
    prefix = __package__ + '.'
    for name, module in sys.modules.copy().items():
        if name.startswith(prefix):
            basename = name.removeprefix(prefix)
            globals()[basename] = importlib.reload(module)

import bpy

if your addon uses the new Extensions platform for Blender 4.2+, then you are forced to use relative imports, because Blender places the addon in some parent package, which makes a simple execution of importlib.reload(module) not do much by itself.

but then the relative import like from . import my_submodule is equivalent to:

import parent.my_addon.my_submodule as my_submodule

or

import parent.my_addon.my_submodule
my_submodule = parent.my_addon.my_submodule

the issue with Extensions is that the parent is dynamic depending on the source of installation (local file / extensions.blender.org / other repositories / etc). for example, bl_ext.user_default is the local installation, but we are not supposed to even know it. that's why we are forced to use relative imports.

so the above code is just a combination of this trick to reassign the module variables and Todd J. York's answer to get their names dynamically (see for mode details).

$\endgroup$

You must log in to answer this question.

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