3
$\begingroup$

My add-on edits Blender's UI scripts.

To apply changes to UI - edited scripts should be reloaded.

For this purpose I use bpy.ops.script.reload()

It does the job, but it works kind of slow.

So I wonder: is there any way to reload one specific UI script ?


I thought importlib could do the thing, but appears it cannot...

An illustration:

As a simple example let's edit the script which is responsible for [File] menu tab.

It's space_topbar.py, which is located in :

Blender\4.1\scripts\startup\bl_ui\space_topbar.py

For demonstration purposes let's get rid of a [Revert] menu option.

This option is being drawn inside the class TOPBAR_MT_file(Menu) *in the line 282:

layout.operator("wm.revert_mainfile")

so let's just comment this line :

# layout.operator("wm.revert_mainfile")

*(After saving "space_topbar.py") I'm trying to reload the changed space_topbar module:

1st try:

import sys,importlib
importlib.reload(sys.modules['bl_ui.space_topbar'])

• no effect: img_01


2nd try: *let's also reload the module's __init__ :

import sys,importlib
importlib.reload(sys.modules['bl_ui.space_topbar'])
importlib.reload(sys.modules['bl_ui'])

• no effect: img_02


3rd try: *screw it – let's reload ALL modules which can be reloaded:

import sys,importlib
for m in list(sys.modules):
    try:
        importlib.reload(sys.modules[m])
    except: pass

• no effect: img_03


Ok, let's use Reload Scripts:

import bpy
bpy.ops.script.reload()

• In this case the work is done: img_04



• But "Reload Scripts" is SLOW.

So I wonder: is there any way to cherry-pick the UI script I would like to reload?

I thought Reload Scripts just do bulk importlib.reload, hence I could have use importlib – but as we saw in the examples above – importlib.reload is useless for reloading UI scripts.

Is it possible to reload one specific UI-script without using Reload Scripts ?

?

$\endgroup$

2 Answers 2

2
+100
$\begingroup$
import importlib
import bpy
import bl_ui

bpy.utils.unregister_class(bl_ui.space_topbar.TOPBAR_MT_file)

importlib.reload(bl_ui.space_topbar)

bpy.utils.register_class(bl_ui.space_topbar.TOPBAR_MT_file)

enter image description here

bl_ui.space_topbar has the list classes with all the menu and panel classes.

import importlib
import bpy
import bl_ui


for cls in bl_ui.space_topbar.classes:
    if cls.is_registered:
        bpy.utils.unregister_class(cls)

importlib.reload(bl_ui.space_topbar)

for cls in bl_ui.space_topbar.classes:
    bpy.utils.register_class(cls)

If you importlib.reload bl_ui it reloads all the sub-modules. Also it has its own register and unregister. But this is a bit slow as well.

import importlib
import bl_ui

bl_ui.unregister()

importlib.reload(bl_ui)

bl_ui.register()

Also keep in mind that user might not be able to edit files in the Blender installation folder because of a lack of permissions.

$\endgroup$
0
1
$\begingroup$

I actually have had this exact issue before, and this is how I worked around it. It's janky but it did the trick. Feel free to use all or only a portion of this code, I haven't used it in a long time.

It adds a panel in the text editor to select your script and reload it.

It should be mentioned that this won't work if you're trying to only reload a specific python file within a full package e.g. trying to reload space_topbar.py. The script has to have it's own __init__ file essentially that will run.

bl_info = {
    "name": "Refresh Single Addon",
    "description": "Refresh one addon with builtin register function",
    "author": "Jakemoyo",
    "version": (1, 0),
    "blender": (3, 0, 0),
    "location": "Text Editor > N Panel",
    "category": "Text Editor",
}

import bpy


class RefreshAddonProps(bpy.types.PropertyGroup):
    refresh_addon_filepath: bpy.props.StringProperty(
        name="File Path",
        default="",
        subtype="FILE_PATH",
    )
    refresh_addon_module_name: bpy.props.StringProperty(name="Module Name")


class PREFS_OT_refresh_single_addon(bpy.types.Operator):
    """Refresh single addon with built-in register function."""

    bl_idname = "preferences.refresh_single_addon"
    bl_label = "Refresh Single Addon"

    @classmethod
    def poll(cls, context):
        return True

    def execute(self, context):
        props = context.window_manager.refresh_props
        bpy.ops.preferences.addon_remove(module=props.refresh_addon_module_name)
        bpy.ops.preferences.addon_install(filepath=props.refresh_addon_filepath)
        bpy.ops.preferences.addon_enable(module=props.refresh_addon_module_name)
        return {"FINISHED"}


class TEXT_PT_RefreshSingleAddonPanel(bpy.types.Panel):
    bl_space_type = "TEXT_EDITOR"
    bl_region_type = "UI"
    bl_label = "Refresh Single Addon"
    bl_category = "Text"
    bl_options = {"DEFAULT_CLOSED"}

    def draw(self, context):
        layout = self.layout
        row = layout.row()
        props = context.window_manager.refresh_props
        layout.prop(props, "refresh_addon_filepath")
        layout.prop(props, "refresh_addon_module_name")
        op = layout.operator("preferences.refresh_single_addon")


def register():
    bpy.utils.register_class(RefreshAddonProps)
    bpy.types.WindowManager.refresh_props = bpy.props.PointerProperty(
        type=RefreshAddonProps
    )
    bpy.utils.register_class(PREFS_OT_refresh_single_addon)
    bpy.utils.register_class(TEXT_PT_RefreshSingleAddonPanel)


def unregister():
    bpy.utils.unregister_class(PREFS_OT_refresh_single_addon)
    bpy.utils.unregister_class(TEXT_PT_RefreshSingleAddonPanel)
    bpy.utils.unregister_class(RefreshAddonProps)
    del bpy.types.WindowManager.refresh_props


if __name__ == "__main__":
    register()
$\endgroup$
8
  • $\begingroup$ Your add-on is meant to re-install custom add-ons, so it has ZERO-relevance to the Blender's UI and to the problem I've described in my question. $\endgroup$
    – Yaroslav
    Commented Jun 15 at 10:25
  • 1
    $\begingroup$ @Yaroslav I think it's relevant considering some people wrap their scripts in addons. $\endgroup$ Commented Jun 15 at 10:47
  • $\begingroup$ @Markus von Broady — It COULD be relevant to some other questions, but not to the one which says: "reload one particular UI SCRIPT", because it just automatically re-installs CUSTOM add-ons, but do not update Blender's UI at all. $\endgroup$
    – Yaroslav
    Commented Jun 15 at 11:09
  • $\begingroup$ @Yaroslav Hello, flags should not be used to indicate technical inaccuracies, or an altogether wrong answer $\endgroup$
    – Harry McKenzie
    Commented Jun 15 at 11:15
  • 1
    $\begingroup$ @MarkusvonBroady This script uses addon_remove, addon_install, addon_enable. bl_ui is the native UI code files that are not meant to be removed or installed. The method will not work for the addon's sub-modules. It only reloads the top level file. So if the __init__.py does not reload sub-modules they won't be updated. You would need a zip files to install a multi-file addon and update the zip every time you make a change. If you are not testing the installation itself it does not make much sense as internally it all boils down to importlib.reload or del sys.modules['my_module']. $\endgroup$
    – unwave
    Commented Jun 15 at 15:21

You must log in to answer this question.

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