I have two dynamic lists. One is for strings and another one for objects. When the string changes I need the other list to show the objects assigned to the string in the first list. So I would need something like a "global 2d array", but the only way I know of in Blender is assigning properties to ex. the scene. But I'm not familiar with ways to deal with stuff like: ["BOB", ("Camera", "Light", "Cube")] using properties. How would you guys solve this? Here'a a gif of the UI: enter image description here

The code is far to big to paste here, but is based on p2or's UIList code: https://www.codepile.net/pile/0J6E3WxB


1 Answer 1


It's indeed tricky getting all the details sorted but it is possible to make it work properly:

enter image description here

(Minimal) Example: https://gist.github.com/p2or/4635887cfe6ed9e652eadc8aa7585a87

Data Structure

I'd suggest create a nested collection for your objects as part of the main collection (users), register e.g. per scene to get your data structure working and then test using the Console in the first place:

class CUSTOM_ObjectProps(PropertyGroup):
    obj_ref: PointerProperty(

class CUSTOM_NameProps(PropertyGroup):
    object_collection: CollectionProperty(
        name = "Object Collection",
        type = CUSTOM_ObjectProps)

class CUSTOM_SceneProps(PropertyGroup):
    user_collection: CollectionProperty(
        name = "Name Collection",
        type = CUSTOM_NameProps)

def register():
    bpy.types.Scene.ctm = PointerProperty(type=CUSTOM_SceneProps)

Proof using the Console

# Add User
>>> user = C.scene.ctm.user_collection.add()
>>> user.name = "Bob"
>>> user

>>> user_obj = C.scene.ctm.user_collection[0].object_collection.add()
>>> user_obj.obj_ref = C.object
>>> user_obj.name = "My Object"
>>> user_obj

>>> user_obj.obj_ref

To speed up testing, operators for adding or printing the values might become handy:

class CUSTOM_OT_PrintOperator(bpy.types.Operator):
    bl_idname = "scene.ctm_print_operator"
    bl_label = "Simple Print Operator"

    def execute(self, context):
        scn = context.scene
        custom = scn.ctm
        lookup_dict = {}

        for uc, user in enumerate(custom.user_collection):
            print ("User Index: {}, Name: {}, Ref: {}".format(uc, user.name, user))

            for oc, obj in enumerate(user.object_collection):
                print ("-> Object {}, Ref {}".format(oc, obj))
                lookup_dict.setdefault(user, []).append(obj.obj_ref)

        # In case, dicts are nice
        print ("\nLookup:", lookup_dict)
        return {'FINISHED'}

Proof using the Console

>>> bpy.ops.scene.ctm_print_operator()
User Index: 0, Name: Bob, Ref: <bpy_struct, CUSTOM_NameProps("Bob")>
-> Object 0, Ref <bpy_struct, CUSTOM_ObjectProps("Bob_Mball")>
-> Object 1, Ref <bpy_struct, CUSTOM_ObjectProps("Bob_Icosphere")>

Lookup: {bpy.data.scenes['Scene'].ctm.user_collection[0]: [bpy.data.objects['Mball'], bpy.data.objects['Icosphere']]}


Hardest part is getting both lists to draw properly. Since the actual names (from the first list) don't necessarily have to be unique, we obviously can not rely on any name value. One approach is introducing a custom index property to get track of the changes and keep it in sync (required for template_list anyway).

enter image description here

class CUSTOM_NameProps(PropertyGroup):
    active_obj_index: IntProperty()
    object_collection: CollectionProperty(
        name = "Object Collection",
        type = CUSTOM_ObjectProps)

user = custom.user_collection[custom.active_user_index]


    listtype_name = "CUSTOM_UL_Sub",
    list_id = "", 
    dataptr = user,
    propname = "object_collection",
    active_dataptr = user,
    active_propname = "active_obj_index",
    rows = min_rows

Notice, when using the wrong index value pointing to any non existing item will raise exceptions.


You must log in to answer this question.

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