initial approach
One possible solution which I successfully used in a different context is to use Pythons type() function to dynamically instantiate a class.
This allows you to register Blender classes, like UI_Lists, Popovers, etc dynamically, and if you combine this with the concept of inheritance (mix-in classes), you should be able to re-use code efficiently. The beauty of this method is that it not only works from registration code as I did it here. In my own example I dynamically register UI_Lists from within an Operator as well, and this so far works beautifully.
I have commented the steps in the demo Add-on below. Just add random texts to the texts list as you wish. Of course you could also rework this using Python dictionaries etc. etc., but from here only you know what you want to achieve actually.
Complete Add-on example with comments below:
'''
Created on 25.07.2022
@author: r.trummer
'''
import bpy
from bpy.types import Panel
bl_info = {
"name": "Popover Demo",
"author": "Rainer Trummer",
"version": (0, 1, 0),
"blender": (3, 1, 0),
"description": "draws variable popovers in the UI",
"category": "Interface"
}
holder variable for our dynamic classes which each will be a popup
popoverClasses = []
the texts you want to pass on to each class
texts = ['alpha', 'beta', 'gamma', 'delta', 'epsilon']
#===============================================================================
the TEST_PT_popover class is a mix in class which we will later on
combine with the Panel class using inheritance. In here we add our own
ui_text Propoerty, which in the dynamic instantiation we can fill with
our desired content. Of course you can define as many properties as you
like. Even lists will work
#===============================================================================
class TEST_PT_popover():
bl_category = ""
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
# note the bl_options has to be set to INSTANCED, otherwise the Panels will
# draw twice in the UI. You'll see what I mean if you comment it out
bl_options = {'INSTANCED'}
# hold the text info the user dynamically wants to change
ui_text = None
def draw(self, context):
# put your to be shared draw code in here
self.layout.label(text = self.ui_text)
#===============================================================================
this class draws our main Panel, where the Popovers will live in
#===============================================================================
class TEST_PT_popover_holder(Panel):
bl_idname = "TEST_PT_popover_holder"
bl_label = "sucker"
bl_category = ""
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
def draw(self, context):
layout = self.layout
layout.label(text = 'HOLDER')
# iterate over all given texts and construct a unique name for the Panel
for i, t in enumerate(texts):
print(f'drawing popover myPanel_PT_{i}')
layout.popover(panel = f'myPanel_PT_{i}', text = t)
classes = (
TEST_PT_popover_holder,
)
def register():
# standard base class registration first
# do NOT yet register our dynamic class
for c in classes:
bpy.utils.register_class(c)
# now iterate over all texts...
for i, t in enumerate(texts):
# ...and using the type function create a panel class dynamically
# this class gets a unique name by adding an index, and inherits from
# TEST_PT_popover and Panel classes at the same time
panelClass = type(f'myPanel_PT_{i}', (TEST_PT_popover, Panel), {})
# now give the new class a label
panelClass.bl_label = t
# and finally, this is what you're here for, set the custom ui_text
panelClass.ui_text = f'{t} is my favorite number {i+1}'
# appending the new class to a static variable helps us to unregister
# the new class afterwards when we exit Blender or reload Add-ons
print(f'appending popover {panelClass} to class holder variable')
popoverClasses.append(panelClass)
# now finally register the dynamic class
print(f'registering class {panelClass}')
bpy.utils.register_class(panelClass)
def unregister():
# iterate over all dynamic classes that we created and unregister them
for poc in popoverClasses:
print(f'unregistering class {poc}')
bpy.utils.unregister_class(poc)
# clear the variable too
popoverClasses.clear()
# standard unregistration code
for c in reversed(classes):
bpy.utils.unregister_class(c)
if name == 'main':
register()
review after additional comment
After reviewing the additional question from the comment, I understand that the request is to create multiple Panels with multiple Popups each, and pass on Strings to them. I have updated the example above with the following approach, where instead of a text list I use a dictionary. Dictionaries are very flexible and can also hold lists as data, while using strings as keys. Only requirement is that the keys are unique. Also the Panel and Popup names need to be unique, so that's something to watch out for.
By constructing a dictionary, where the keys() are the Panel header names, and the items() are the lists of strings that shall become popups, then using dynamic registration twice, this becomes rather flexible. Just change the texts dictionary to whatever you require:
'''
Created on 25.07.2022
@author: r.trummer
'''
import bpy
from bpy.types import Panel
bl_info = {
"name": "Popover Demo",
"author": "Rainer Trummer",
"version": (0, 1, 5),
"blender": (3, 1, 0),
"description": "draws variable popovers in the UI",
"category": "Interface"
}
holder variable for our dynamic classes which each will be a popup
popoverClasses = []
the texts you want to pass on to each class
texts = {'Panel A': ['alpha', 'beta', 'gamma', 'delta', 'epsilon'],
'Panel B': ['not', 'a', 'fan', 'of', 'Greek', 'nomenclature'],
'Some Dude At The End': ['latin', 'rocks']}
#===============================================================================
the TEST_PT_popover class is a mix in class which we will later on
combine with the Panel class using inheritance. In here we add our own
ui_text Propoerty, which in the dynamic instantiation we can fill with
our desired content. Of course you can define as many properties as you
like. Even lists will work
#===============================================================================
class TEST_PT_popover():
bl_category = ""
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
# note the bl_options has to be set to INSTANCED, otherwise the Panels will
# draw twice in the UI. You'll see what I mean if you comment it out
bl_options = {'INSTANCED'}
# hold the text info the user dynamically wants to change
ui_text = None
def draw(self, context):
# put your to be shared draw code in here
self.layout.label(text = self.ui_text)
def generate_panel_name(number, caption):
return f'myPanel_PT_{caption.replace(" ", "")}{number}'
#===============================================================================
this class draws our main Panel, where the Popovers will live in
#===============================================================================
class TEST_PT_popover_holder(Panel):
bl_idname = "TEST_PT_popover_holder"
bl_label = "Main Panel"
bl_category = ""
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
def draw(self, context):
layout = self.layout
layout.label(text = 'HOLDER')
#===============================================================================
this class will also be dynamically instantiated, and create the Panels
themselves, as childs of the main manel
#===============================================================================
class TEST_PT_popover_panel_item(Panel):
bl_label = "subitem"
bl_category = ""
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
# make this Panel a child of the holder Panel
bl_parent_id = TEST_PT_popover_holder.bl_idname
bl_options = {'INSTANCED'}
dictionaryIndex = None
customCaption = None
def draw(self, context):
layout = self.layout
layout.label(text = self.customCaption)
# now iterate, for the passed on dictionary element, over the given list of tests
# by doing this, we pick a certain text list from the dictionary to be displayed
# in this specific Panel
for i, t in enumerate(texts[self.dictionaryIndex]):
print(f'drawing popover myPanel_PT_{i} called {self.dictionaryIndex}')
layout.popover(panel = generate_panel_name(i, self.dictionaryIndex), text = t)
classes = (
TEST_PT_popover_holder,
)
def register():
# standard base class registration first
# do NOT yet register our dynamic class
for c in classes:
bpy.utils.register_class(c)
# construct unique names for Panel classes
for k, p in enumerate(texts):
# k is the index in the Dictionary
# p is the Panel name
# register a sub-Panel class first to hold the elements
panelClassName = generate_panel_name(k, f'{p}_panel')
print(f'creating Panel Class {panelClassName}')
panelClass = type(panelClassName, (TEST_PT_popover_panel_item, Panel), {})
# pass the dictionary index so the subpanel finds the right text list from the dictionary
panelClass.dictionaryIndex = p
# also change the Panel label while we're at it
panelClass.customCaption = f'this is {p}'
# and if you like, the header too
panelClass.bl_label = f'{p.upper()}'
# register the Panel now
bpy.utils.register_class(panelClass)
popoverClasses.append(panelClass)
# now iterate, for each dictionary element, over the given list of tests
for i, t in enumerate(texts[p]):
# i is the index of the string list picked from the dictionary
# t is the text string itself
pName = generate_panel_name(i, p)
print(f'constructing popover Panel called {pName}')
# ...and using the type function create a panel class dynamically
# this class gets a unique name by adding an index, and inherits from
# TEST_PT_popover and Panel classes at the same time
popoverClass = type(pName, (TEST_PT_popover, Panel), {})
# now give the new class a label
popoverClass.bl_label = f'label of this class: {t}'
# and finally, this is what you're here for, set the custom ui_text
popoverClass.ui_text = f'{t} belongs to Panel class {p}'
# appending the new class to a static variable helps us to unregister
# the new class afterwards when we exit Blender or reload Add-ons
print(f'appending popover {popoverClass} to class holder variable')
popoverClasses.append(popoverClass)
# now finally register the dynamic class
print(f'registering class {popoverClass}')
bpy.utils.register_class(popoverClass)
def unregister():
# iterate over all dynamic classes that we created and unregister them
for poc in popoverClasses:
print(f'unregistering class {poc}')
bpy.utils.unregister_class(poc)
# clear the variable too
popoverClasses.clear()
# standard unregistration code
for c in reversed(classes):
bpy.utils.unregister_class(c)
if name == 'main':
register()