Can I dynamically generate boolean options on a custom Blender operator?

1.2k Views Asked by At

I'm trying to write a custom operator for Blender that:

  1. Gets the objects currently in the scene (may be lots)
  2. Filters them based on some criteria
  3. Prompts the user to select/deselect any of the filtered objects (using checkboxes or similar)
  4. Does something with the final selection.

I'm stuck on number 3. I'd like to show the user a window with checkboxes beside each of the filtered options, but to do that I'd have to be able to generate the properties dynamically.

The closest thing I've found so far is a bpy.props.EnumProperty, which takes callable to set its items. But it only supports 1 selection, whereas I need the user to be able to select multiple options.

Example:

def filter_objects(self, context):
    return [obj for obj in bpy.data.objects if obj.name.startswith('A')]

class TurnObjectsBlue(bpy.types.Operator):
    'TurnObjectsBlue'
    bl_idname = 'object.turnobjectsblue'
    bl_label = 'TurnObjectsBlue'
    bl_options = {'REGISTER'}

    # MultiSelectCheckboxes doesn't exist :(
    chosen_objects: bpy.props.MultiSelectCheckboxes(
        name='Select Objects',
    )

    def execute(self, context):
        from coolmodule import turn_blue
        for obj in self.user_selected_objects:
            turn_blue(obj)

        return {'FINISHED'}

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

There are 1 best solutions below

0
On

It looks like you want a CollectionProperty. These are basically native blender lists that can store blender properties, and can have as many items as you like.

To set one up, start by creating a new class that will represent one of the items in the CollectionProperty. It should inherit from bpy.types.PropertyGroup, and in it you can define any properties that you want to be able to access (also make sure to register that class):

class MyCollectionItem(bpy.types.PropertyGroup):

    # This will tell us which object this item is for
    object: bpy.props.PointerProperty(type=bpy.types.Object)

    # This is whether this item (and by extension the object) is enabled/disabled
    is_item_selected: bpy.props.BoolProperty()

def register():
    # Don't forget to register it!
    bpy.utils.register_class(MyCollectionItem)

Then you can rewrite your operator to do something like this:

class TurnObjectsBlue(bpy.types.Operator):
    'TurnObjectsBlue'
    bl_idname = 'object.turnobjectsblue'
    bl_label = 'TurnObjectsBlue'
    bl_options = {'REGISTER'}

    # This acts as a list, where each item is an instance of MyCollectionItem
    chosen_objects: bpy.props.CollectionProperty(
        type=MyCollectionItem,
    )

    def execute(self, context):
        from coolmodule import turn_blue
        # Loop through all items in the CollectionProperty, and if they are selected, do something
        for item in self.chosen_objects:
            if item.is_item_selected:
                turn_blue(item.object)

        return {'FINISHED'}

    def invoke(self, context, event):
        objects = filter_objects(context)
        # For each object, add a new item to the CollectionProperty, and set it's object property
        for object in objects:
            # Note how items are created with CollectionProperty.add()
            obj_item = self.chosen_objects.add()
            obj_item.object = object

        return context.window_manager.invoke_props_dialog(self)
    
    def draw(self, context):
        layout = self.layout
        # Loop through all of the CollectionProperty items and draw the "is_item_selected" property
        for item in self.chosen_objects:
            layout.prop(item, "is_item_selected")

While collection properties can act like Blender lists, they work fairly differently to python lists, so I'd suggest reading a bit about them to try and understand how they work: https://docs.blender.org/api/current/bpy.props.html#collection-example

Hope that helps!