Prop_search with pointer for modifier's collection property

I have an object that has a cloth modifier which of course has the collision collection property. I want to use prop_search for that property but only show results of collections which are children of a collection called “Collision”. I have done this type of pointer prop_search “filter” before for objects but never for collections.

I keep running into an error:

uiItemPointerR_prop: search collection property is not a collection type: PropGroup.collision_collection

This error sounds very self explanatory but I am not getting any search hits for it and I can’t figure out where exactly I am going wrong. Any help would be appreciated.

Test file: collision_collecton_filter2.blend (788.4 KB)

Code:

import bpy
from bpy.props import *
from bpy.types import Operator, AddonPreferences, Panel, PropertyGroup

def filter_collision_collections(self, collection):    
    
    list = []    
    for coll in bpy.data.collections["Collision"].children:
        list.append(coll.name)

    if collection.name in list:
        return collection.name

class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Test"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'    

    def draw(self, context):        

        obj = bpy.context.active_object
        
        layout = self.layout        
        col = layout.column()              
        col.prop_search(obj.modifiers["Cloth"].collision_settings, "collection", context.scene.PropGroup, "collision_collection", text="")

class PropGroup(PropertyGroup):

    collision_collection : PointerProperty(
        type=bpy.types.Collection,
        poll=filter_collision_collections
        )        

classes = (
HelloWorldPanel,
PropGroup
)

def register(): 
    
    for cls in classes:
        bpy.utils.register_class(cls)       
    
    bpy.types.Scene.PropGroup = PointerProperty(type=PropGroup)

def unregister():
    
    for cls in classes:
        bpy.utils.unregister_class(cls)
    
    del bpy.types.Scene.PropGroup

if __name__ == "__main__":
    register()

I think you don’t need the PropertyGroup ?

col.prop_search(obj.modifiers["Cloth"].collision_settings, "collection", bpy.data.collections["Collision"], "children", text="")

1 Like

Well…! That sure does work perfectly, thanks Gorgious.

Though I am curious, is the method I was originally using even possible? Because it could be useful in other situations.

You can use the pointerproperty’s update callback and draw the custom property instead of the modifier’s property :

def filter_collision_collections(self, collection):
    return collection.name in bpy.data.collections["Collision"].children
    
def update_collision_collection(self, context):
    context.object.modifiers["Cloth"].collision_settings.collection = self.collision_collection


class HelloWorldPanel(bpy.types.Panel):
    bl_label = "Test"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'    

    def draw(self, context):
        obj = bpy.context.active_object        
        layout = self.layout        
        col = layout.column()
        col.prop(context.scene.PropGroup, "collision_collection", text="")

class PropGroup(PropertyGroup):
    collision_collection : PointerProperty(
        type=bpy.types.Collection,
        poll=filter_collision_collections,
        update=update_collision_collection,
        )        
1 Like

I initially did it that way (with poll and update) but then the issue was if you were working with multiple context objects (which all have cloth modifiers), the prop in the panel of course does not update when you selected a different object which has a different collision collection. That is what led me to try the pointer with prop_search instead, but yeah that didn’t work. Is it maybe just not possible with collections?

Yeah that’s a limitation. You can subscribe to the active object changing with msgbus. Something like that :

def register():
    # ...
    def active_object_change(*args):
        bpy.context.scene.PropGroup.collision_collection = bpy.context.object.modifiers["Cloth"].collision_settings.collection
    subscribe_to = bpy.types.LayerObjects, "active"
    bpy.msgbus.subscribe_rna(
        key=subscribe_to,
        owner=bpy.context.scene,
        args=(),
        notify=active_object_change,
    )

Of course this will throw an error if the object doesn’t have a cloth modifier but you can add straigthforward if/else or try/except clauses

1 Like

Nice, I had not known about msgbus. Thanks again for your help – it’s greatly appreciated.