0
$\begingroup$

I have a script (from Populate UIlist with Certain Collections) that creates new collections into the scene and populate the UIlist with them which can be selected via the ui. Everything is working properly until I rename one of the already created collection, then that new renamed collection cannot be selected anymore.

After I rename a collection and try to selecte it via ui I get this message in the console:

File "\Text", line 34, in update_func TypeError: bpy_struct: item.attr = val: ViewLayer.active_layer_collection does not support a 'None' assignment LayerCollection type File "\Text", line 18, in update_func

Here is the script:

import bpy

from bpy.props import (IntProperty,
                       BoolProperty,
                       StringProperty,
                       CollectionProperty,
                       PointerProperty)

from bpy.types import (Operator,
                       Panel,
                       PropertyGroup,
                       UIList)

# -------------------------------------------------------------------
#   Functions
# -----------------------------------------------------------------

def update_func(self, context):
    scn = context.scene
    idx = scn.custom_index
    item = scn.custom[idx]

    def recurLayerCollection(layerColl, collName):
        found = None
        if (layerColl.name == collName):
            return layerColl
        for layer in layerColl.children:
            found = recurLayerCollection(layer, collName)
            if found:
                return found
    
    layer_collection = bpy.context.view_layer.layer_collection
    layerColl = recurLayerCollection(layer_collection, item.name)
    bpy.context.view_layer.active_layer_collection = layerColl

# -------------------------------------------------------------------
#   Operators
# -------------------------------------------------------------------

class CUSTOM_OT_actions(Operator):
    """Move items up and down, add and remove"""
    bl_idname = "custom.list_action"
    bl_label = "List Actions"
    bl_description = "Move items up and down, add and remove"
    bl_options = {'REGISTER'}

    action: bpy.props.EnumProperty(
        items=(
            ('REMOVE', "Remove", ""),
            ('ADD', "Add", "")))

    def invoke(self, context, event):
        scn = context.scene
        idx = scn.custom_index


        def find_layer_collection_recursive(find, col):
            for c in col.children:
                if c.collection == find:
                    return c
            return None


        # List of object references
        objs = bpy.context.selected_objects

        # create the collection
        my_collection = bpy.data.collections.new("MY CUSTOM NAME")
        # link it to the scene
        bpy.context.scene.collection.children.link(my_collection)

        # now find the matching layer collection for the collection you created
        found = find_layer_collection_recursive(my_collection, bpy.context.view_layer.layer_collection)
        if found:
            # once it's found, set the active layer collection to the one we found
            bpy.context.view_layer.active_layer_collection = found


        # Loop through all objects
        for ob in objs:
            my_collection.objects.link(ob)
         
        act_coll = context.view_layer.active_layer_collection.collection
        item = scn.custom.add()
        item.coll_ptr = act_coll
        item.name = item.coll_ptr.name
        scn.custom_index = (len(scn.custom)-1)
        info = '%s added to list' % (item.name)
        self.report({'INFO'}, info)
        return {"FINISHED"}

class CUSTOM_OT_clearList(Operator):
    """Clear all items of the list"""
    bl_idname = "custom.clear_list"
    bl_label = "Clear List"
    bl_description = "Clear all items of the list"
    bl_options = {'INTERNAL'}

    @classmethod
    def poll(cls, context):
        return bool(context.scene.custom)

    def invoke(self, context, event):
        return context.window_manager.invoke_confirm(self, event)

    def execute(self, context):
        if bool(context.scene.custom):
            context.scene.custom.clear()
            self.report({'INFO'}, "All items removed")
        else:
            self.report({'INFO'}, "Nothing to remove")
        return{'FINISHED'}

# -------------------------------------------------------------------
#   Drawing
# -------------------------------------------------------------------

class CUSTOM_UL_items(UIList):
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
        split = layout.split(factor=0.3)
        split.label(text="Index: %d" % (index))
        split.prop(item.coll_ptr, "name", text="", emboss=False, icon="COLLECTION_NEW")

    def invoke(self, context, event):
        pass   

class CUSTOM_PT_objectList(Panel):
    """Adds a custom panel to the TEXT_EDITOR"""
    bl_idname = 'TEXT_PT_my_panel'
    bl_space_type = "TEXT_EDITOR"
    bl_region_type = "UI"
    bl_label = "Custom Object List Demo"

    def draw(self, context):
        layout = self.layout
        scn = bpy.context.scene

        rows = 2
        row = layout.row()
        row.template_list("CUSTOM_UL_items", "", scn, "custom", scn, "custom_index", rows=rows)

        col = row.column(align=True)
        col.operator("custom.list_action", icon='ZOOM_IN', text="").action = 'ADD'

        row = layout.row()
        col = row.column(align=True)
        row = col.row(align=True)
        row.operator("custom.clear_list", icon="X")


# -------------------------------------------------------------------
#   Collection
# -------------------------------------------------------------------

class CUSTOM_objectCollection(PropertyGroup):
    coll_ptr: PointerProperty(
        name="Collection",
        type=bpy.types.Collection,
        update=update_func
    )

# -------------------------------------------------------------------
#   Register & Unregister
# -------------------------------------------------------------------

classes = (
    CUSTOM_OT_actions,
    CUSTOM_OT_clearList,
    CUSTOM_UL_items,
    CUSTOM_PT_objectList,
    CUSTOM_objectCollection,
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

    # Custom scene properties
    bpy.types.Scene.custom = CollectionProperty(type=CUSTOM_objectCollection)
    bpy.types.Scene.custom_index = IntProperty(update=update_func)


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

    del bpy.types.Scene.custom
    del bpy.types.Scene.custom_index


if __name__ == "__main__":
    register()
$\endgroup$

1 Answer 1

2
$\begingroup$

Remove the update function from coll_ptr.

class CUSTOM_objectCollection(PropertyGroup):
    coll_ptr: PointerProperty(
        name="Collection",
        type=bpy.types.Collection
    )

To set the selected collection of the list as active collection in the Outliner, you can use a callback for custom_index property

EDIT ... and use coll_ptr.name in your update callback to get the name of the collection.

def update_func(self, context):
    idx = context.scene.custom_index
    item = context.scene.custom[idx]
    
    def find_layer_collection(layer_coll, coll_name):
        c = None
        if (layer_coll.name == coll_name):
            return layer_coll
        for layer in layer_coll.children:
            c = find_layer_collection(layer, coll_name)
            if c:
                return c
    
    vl = context.view_layer
    vl.active_layer_collection = find_layer_collection(vl.layer_collection, item.coll_ptr.name)
$\endgroup$
0

You must log in to answer this question.

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