Make an add-on panel appear in a category set in preferences at blender startup

Hello. I’m having a hard time trying to make the interface panel of my add-on appear in a category of the N-Panel set in the add-on preferences.
Basically, my panel appears in a default category but that can be changed in the preferences of the add-on. When I change that preference, the panel correctly gets moved to that category, but no matter what I try it doesn’t stay there when I restart Blender. It returns to the default position every time.

I searched online and I found this links that talk about this, but I still can’t get it to work.

This one link by @mano-wii

I’ll share a cleaned-up version of my code.

In my add on I have a Debug button that when pressed fires the UpdatePanel class and the panel gets correctly moved to the category set in the preferences. I works fine even after that preference changes, but at restart the panel appears again in the default category.

bl_info = {
    "name": "MyAddon",
    #Data
}

import bpy
#import more props

#________________________PREFERENCES CLASSES_________________________
        

class UpdatePanel(Operator):
    bl_idname = "object.update_panel"
    bl_label = "Updates the Interface"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        # Updates the panel and redraws it by unregistering and registering it again
        # This function triggers when the add-on preferences change
        
        # Gets the preferences
        addon_prefs = bpy.context.preferences.addons[__name__].preferences
        
        #Tries to unregister (delete) the panel
        try:
            bpy.utils.unregister_class(UIPANEL_PT_Panel)
        except:
            pass
        
        # Changes the panel category
        UIPANEL_PT_Panel.bl_category = addon_prefs.category
        # Registers the panel for it to appear in the new category
        bpy.utils.register_class(UIPANEL_PT_Panel)
        return {'FINISHED'}


class AddonPreferences(bpy.types.AddonPreferences):
    # The preferences of the add-on and their layout
    
    # This must match the addon name
    bl_idname = __name__

    # List of all the preferences
    category: StringProperty(
        name = "Category",
        description="Choose a name for the category of the panel",
        default = "MyAddonCategory",
        update = UpdatePanel.execute
    )

    # Layout of the preferences
    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.label(text="Category:")
        row.prop(self, "category", text="")



#______________________USER INTERFACE_________________________

class UIPANEL_PT_Panel(bpy.types.Panel):  #Interface of the add-on
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_label = "MyAddon Interface"
    bl_idname = "UIPANEL_PT_Panel"
    bl_context = "objectmode"
    bl_category = "MyAddon Category"


    def draw(self, context):
    # Draw the interface


#_______________CLASSES REGISTRATION__________________________________

classes = (
    # More classes
    UIPANEL_PT_Panel,
    UpdatePanel,
    AddonPreferences,
)


def register():
    from bpy.utils import register_class
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.settings_group = PointerProperty(type=SettingsClass)


def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)
    del bpy.types.Scene.settings_group


if __name__ == "__main__":
    register()

You might want to study application handlers, especially load_post to fire your logic there on file load and I highly suggest you read and use the part about @persistent decorator that doesn’t throw away the callback on file load.

what is bpy.types.PointerProperty(type=SettingsClass) pointing to and where is SettingsClass registered?

Several items in your pasted code show errors for not having specific imports.

class UpdatePanel(Operator):

class UpdatePanel(bpy.types.Operator):

if all your other code is actually working you may be able to get away with just calling your update operator as the last line of your registration (post registering your properties)

I deleted many parts of the code and left only those that I thought were relevant.

It doesn’t work

I got it to work, thanks. This is the cleaned up code. It simply calls the UpdatePanel class function at the end of the load so that the panel gets redrawn in the correct category

bl_info = {
    "name": "MyAddon",
    #Data
}

import bpy
#import more props

#________________________LOAD_POST CLASSES_________________________
# These commands get executed at the end of Blender loading

@persistent
def load_handler(dummy):
    bpy.ops.object.update_panel() # Calls a panel update
    
bpy.app.handlers.load_post.append(load_handler) # Sets the handler



#________________________PREFERENCES CLASSES_________________________
        

class UpdatePanel(bpy.types.Operator):
    bl_idname = "object.update_panel"
    bl_label = "Updates the Interface"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        # Updates the panel and redraws it by unregistering and registering it again        
        addon_prefs = bpy.context.preferences.addons[__name__].preferences # Gets the preferences
        #print(addon_prefs.category)
        
        try: #Tries to unregister (delete) the panel
            bpy.utils.unregister_class(UIPANEL_PT_Panel)
        except:
            pass
        
        UIPANEL_PT_Panel.bl_category = addon_prefs.category # Changes the panel category
        bpy.utils.register_class(UIPANEL_PT_Panel) # Registers the panel for it to appear in the new category


class AddonPreferences(bpy.types.AddonPreferences):
    # The preferences of the add-on and their layout
    
    # This must match the addon name
    bl_idname = __name__

    # List of all the preferences
    category: StringProperty(
        name = "Category",
        description="Choose a name for the category of the panel",
        default = "MyAddonCategory",
        update = UpdatePanel.execute
    )

    # Layout of the preferences
    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.label(text="Category:")
        row.prop(self, "category", text="")



#______________________USER INTERFACE_________________________

class UIPANEL_PT_Panel(bpy.types.Panel):  #Interface of the add-on
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_label = "MyAddon Interface"
    bl_idname = "UIPANEL_PT_Panel"
    bl_context = "objectmode"
    bl_category = "MyAddon Category"


    def draw(self, context):
    # Draw the interface


#_______________CLASSES REGISTRATION__________________________________

classes = (
    # More classes
    UIPANEL_PT_Panel,
    UpdatePanel,
    AddonPreferences,
)


def register():
    from bpy.utils import register_class
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.settings_group = PointerProperty(type=SettingsClass)


def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)
    del bpy.types.Scene.settings_group


if __name__ == "__main__":
    register()
1 Like