0

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

pekkuskär
  • 307
  • 2
  • 11

1 Answers1

2

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)

pyCod3R
  • 1,926
  • 4
  • 15