How to set a property for an operator dynamically?

Normally one specifies operator properties in the definition of the operator class


class MyOperator(bpy.types.Operator):
	bl_label = "My Operator"
	
	myProperty = bpy.props.FloatProperty(name = "My Property")

I need to add properties dynamically after the first operator invocation, so the properties are accessible via the operator panel.
I tried to accomplish this in the operator code:


self.myProperty2 = bpy.props.FloatProperty(name = "My Property 2")

or


MyOperator.myProperty2 = bpy.props.FloatProperty(name = "My Property 2")

Both approaches didn’t work. Is it possible to set a property for an operator dynamically?

No, you need to declare all properties in advance. In the moment you register the class, the property declarations are processed.

You can however control what is drawn in the redo panel:

import bpy


def main(context):
    for ob in context.scene.objects:
        print(ob)


class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"
    bl_options = {'REGISTER', 'UNDO'}

    val1 = bpy.props.BoolProperty()
    val2 = bpy.props.BoolProperty()
    val3 = bpy.props.BoolProperty()

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        main(context)
        return {'FINISHED'}
    
    def draw(self, context):
        layout = self.layout
        layout.prop(self, "val1")
        
        if self.val1:
            box = layout.box()
            box.prop(self, "val2")
            box.prop(self, "val3")


def register():
    bpy.utils.register_class(SimpleOperator)


def unregister():
    bpy.utils.unregister_class(SimpleOperator)


if __name__ == "__main__":
    register()


That is what I was afraid of :frowning:

The problem is that I know the properties to be displayed in the operator redo panel only after the operator invocation. Those properties are read from a file inside operator’s invoke method.

If you know a hack how that could be done, please let me know.

I tried to add properties to bpy.types.Object and to bpy.types.Scene dynamically and then display the properties in the operator’s redo panel. However it’s not possible to change context.scene and context.active_object properties in the operator’s redo panel.

If all properties are of the same type, you can use a CollectionProperty. The collection is typed on a PropertyGroup, and you can add new properties with the .add() function on the collection. An example is shown at here: CollectionExample.

Those properties are read from a file inside operator’s invoke method.

Sounds like a misconception to me, you shouldn’t do such things with operators. It it easy to corrupt the undo system.

Could you please describe a scenario, how it could corrupt the undo system?

stante and CoDEmanX: thank you for your help.

I followed stante’s advice and ended up with the following solution


class CustomFloatProperty(bpy.types.PropertyGroup):
    """A bpy.types.PropertyGroup descendant for bpy.props.CollectionProperty"""
    # it's not possible to set the name dynamically, so keept it empty
    value = bpy.props.FloatProperty(name="")

class MyOperator(bpy.types.Operator):
    # attributes definition is skipped
    collection = bpy.props.CollectionProperty(type=CustomFloatProperty)
    
    def invoke(self, context, event):
        # only relevant code is shown
        # clear self.collection
        self.collection.clear()
        # self.attrs is a list of objects representing application specific and dynamically set attributes
        for attr in self.attrs:
            # create a new item in self.collection
            collectionItem = self.collection.add()
            collectionItem.value = attr.value
            attr.collectionItem = collectionItem

    def draw(self, context):
        layout = self.layout
        for attr in self.attrs:
            attrName = attr[0]
            row = self.layout.split()
            # it's not possible to set the name for an item in self.collection,
            # however it's possible to set a label dynamically
            row.label(attr.name)
            # the following property refers to the attribute <value> of the item in self.collection
            # remember the attribute <value> was defined in the class CustomFloatProperty
            row.prop(attr.collectionItem, "value")





Is there a reason why you use the self.attrs collection at all? Or in the other way around, why don’t you use exclusively self.collection and bother with copying from one collection to another?

Here is a short description what I am developing. I hope to start a separate thread for that project soon.

I’d like to create 3D models of buildings procedurally. A simple program (a set of rules) in Python with specific syntax is applied to the selected 2D building outline to produce a full fledged 3D model. An example set of rules can be seen here. The program may contain some attributes marked as visible for a user.
My addon reads those attributes from the selected Python module and displays them to the user. The user can change the default values of the attributes. The changes are reflected on the generated 3D model in the real time.

So those self.attrs are actually read from the Python program containing a set of rules to generate a 3D model from a 2D building outline. self.attrs are both readable and writable. That’s why I need both self.attrs and seld.collection.

Here is a shot video (1 minute) illustrating the description above:

Это интересно.