FunctionProperty for Operators? Invoking an operator with a callback as parameter.

Hello everybody,

For my activities it would be very convenient to invoke operators specifying, as parameter, a function that has to be called back by the operator itself.
There exist a PointerProperty, but after a test I realised that it is not fulfilling my needs.

Put in code, that WOULD look something like:

import bpy
from bpy.props import * # for properties

class TestCallback(bpy.types.Operator):
    bl_idname = "wm.test_callback"
    bl_label = "Test callback"
    bl_description = "..."

    int_prop = IntProperty(name="iProp", default=0)    
    bool_prop = BoolProperty(name="bProp", default=False)    
    callback = FunctionProperty(name="my_callback", default=None)   # HYPOTHESIS!!!

    def execute(self, context):        
        print("Int property is " + str(self.int_prop))
        print("Bool property is " + str(self.bool_prop))
        print("callback is " + str(self.callback))
        print("Invoking callback...")
        self.callback("yooo")                                          # INVOKING THE CALLBACK!!!
        return {'FINISHED'}
        
bpy.utils.register_class(TestCallback)

def my_callback(message):
    print("Hello callback: " + message)

bpy.ops.wm.test_callback(int_prop=5, bool_prop=True, callback=my_callback)

Alternatively, a generic InstanceProperty, where I can store the reference to an object instance would be enough.

class TestCallback(bpy.types.Operator):
    ...
    instance = InstanceProperty(name="my_instance", default=None)   # HYPOTHESIS!!!! 
    def execute(self, context):
        self.instance.callback("yooo")
...

class MyCallBackHolder:
    def callback(message):
        print("Hello callback: " + message)
...
cb_instance = MyCallBackHolder()
bpy.ops.wm.test_callback(int_prop=5, bool_prop=True, instance=cb_instance)

Is there any way to simulate this behaviour?
Any plan to add a FunctionProperty, or InstanceProperty, to the Blender code base?
Any theoretical limitations?

Thanks and best regards,
Fabrizio Nunnari

specifying, as parameter, a function that has to be called back

If you limit the number of possible functions that can be called back you could construct a simple way to call a function upon operator termination.


def iAmAnOperator(passedExitType):
    # Do work.

    if passedExitType == 0:
        # Activate calback to this function.
        myFuncType0 ()
    if passedExitType == 1:
        # Activate calback to this function.
        myFuncType1 ()

Not really elegant or fancy but if you just need to get the job done to support a few call backs it might work.

I wonder why you need some kind of function pointer…

You can achieve this with a StringProperty:

import bpy


def main(context):
    for ob in context.scene.objects:
        print(ob)
        
def my_callback_1(*args):
    print("Arg:", args[0])

def my_callback_2(*args):
    print("Arg1:", args[0], " - Args2:", args[1])
    

class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"

    callback = bpy.props.StringProperty()

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

    def execute(self, context):
        
        #print(globals())
        
        try:
            func = globals()[self.callback]
            args = (self.bl_label, self.callback)
            func(*args)
            
        except KeyError:
            # fallback
            main(context)
            
        return {'FINISHED'}


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


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


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.object.simple_operator(callback="my_callback_2")

But make sure to test this well, it might break the undo stack.

Thanks for the suggestion CoDEmanX.
Of course this solution would work only with global level-functions.
I would prefer to directly pass the function symbol, instead on relying on strings, but you proposal might match my need. I’ll try it soon.

Why I need it?
Because I coded a pretty huge and complex modal operator.
Now I want to freeze its code and yet keep it general purpose.
So, according to the specific needs, at the moment of invocation I want to specify some extra code to be executed and the end of the modal() execution.
Is it understandable?

thanks and best regards,
Fabrizio Nunnari

The operator system doesn’t support functions as arguments, as well as any other python objects. Only bpy.props are supported. See here for the reason:

Operator arguments - Identification if object names are not unique?

Another robust and sneaky way to pass function (class or whatever) names in blender is to use the driver namespace.


import bpy


def foo(x):
    print(x)
    
bpy.app.driver_namespace["my_name_for_foo"] = foo


#test call


goob = bpy.app.driver_namespace["my_name_for_foo"]


goob("hello")

in the console


>>> goob = bpy.app.driver_namespace["my_name_for_foo"]
>>> 
>>> goob("hello")
hello