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.
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()