3
$\begingroup$

It looks like the best way to report a user-error to the user is by using self.report() of Operator types. However, what about when the operator calls a global function that needs to report a specific error? Is there any way to reach back to the calling operator and have it throw an error with a specific message?

The classic method to handle something like this would be to have the global function return some type of error index, then have the operator send a string that relates to the error type. But this creates a lot of relationship links between the operator and the functions it calls. If I later change the function, I have to also modify the operator.

Is there a simple way to pull this off? Either to access the "active" operator or to throw a global user error?

$\endgroup$
5
  • $\begingroup$ try except and raise? $\endgroup$
    – lemon
    Commented Sep 9, 2019 at 14:18
  • $\begingroup$ Why would returning an index "create a lot of relationship links between the operator and the functions it calls"? If the function is an independent unit, it should simply report back the index specific to the error it encountered. The caller will do a lookup to find the string mapping (again global) corresponding to the error index. One thing to be wary about while writing scripts and add-ons is over-engineering. Just remember scripts have a way too limited scope and lifetime in comparison with an application. $\endgroup$ Commented Sep 9, 2019 at 14:22
  • $\begingroup$ @lemon: I was doing it that way, but I don't like how it shows exception messages and confusing callback traces to the user. I think a lot of people would be confused by these. $\endgroup$
    – Robert
    Commented Sep 9, 2019 at 17:34
  • $\begingroup$ @Blender Dadaist: You're right. That would be the smart and less lazy way to handle it. I'm just too lazy to create all of those lookup tables and global strings. $\endgroup$
    – Robert
    Commented Sep 9, 2019 at 17:36
  • $\begingroup$ Please see my answer. I don't think it would require a lot of effort to maintain a global error message repository :) $\endgroup$ Commented Sep 10, 2019 at 4:43

4 Answers 4

2
$\begingroup$

I agree with @lemon that the 'calculations should not be caller dependent'.

Here is my take... (this could be refined further, but maybe, that would be an overkill for a script):

import bpy, random

class ErrorInfo:
    SUCCESS = 0
    ERR_1 = 1
    ERR_2 = 2    

    ERROR_LOOKUP = {
        ERR_1: ['Forbidden Odd', 'Unerlaubte ungerade Zahl'],
        ERR_2: ['Forbidden Even', 'Unerlaubte gerade Zahl']
    }

    def getErrMsg(errNo, msgTypeIdx = 0):
        return ErrorInfo.ERROR_LOOKUP.get(errNo)[msgTypeIdx]

def a_function(context):
    errorVals1 = {1, 5, 3}
    errorVals2 = {2, 8}
    x = random.randint(1, 10)

    if(x in errorVals1):
        return ErrorInfo.ERR_1, x
    elif(x in errorVals2):
        return ErrorInfo.ERR_2, x
    else:
        return ErrorInfo.SUCCESS, x


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

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

    def execute(self, context):
        errorCode, val = a_function( context )
        if(errorCode != ErrorInfo.SUCCESS):
            errMsg = ErrorInfo.getErrMsg(errorCode)
            self.report({'ERROR'}, (errMsg + ': ' + str(val)))
        else:
            self.report({'INFO'}, ('Value: ' + str(val)))
        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()
$\endgroup$
2
  • $\begingroup$ Yep, this is a good way to handle it. But I'm not sure I follow what you guys mean by caller dependent? Are you just referring to the concept of sending the operator to the function? If so, then yeah, I'm not a fan of that either. What about turning the function into an operator and calling one operator from another? Is that possible in Blender? Btw, I really like this setup. I think I may try it out. $\endgroup$
    – Robert
    Commented Sep 10, 2019 at 12:01
  • $\begingroup$ Caller independence means simply put: the function does not care who is calling it. If, let's say, I write a function to calculate tangent of an angle; then this function does not need to know whether it's being called from a UI application or a command line or some other type. Nothing in it should make it refer back to the caller. All it should do is throw an exception if the argument is 90 degrees. So it acts as an independent unit. It's up to the caller to decide how it wants to handle this exception. $\endgroup$ Commented Sep 10, 2019 at 14:48
4
$\begingroup$

You can pass what you want and even self, this way it is easy determine where the call is coming from. Operator based on Templates > Python > Operator Simple:

import bpy


def main(operator, context):
    print (operator, context)
    operator.report({'INFO'}, 'Call is comming from: {}'.format(operator))
    #return operator


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

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

    def execute(self, context):
        main(self, 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()
$\endgroup$
2
  • $\begingroup$ I don't know why I didn't try this. Thanks! $\endgroup$
    – Robert
    Commented Sep 9, 2019 at 17:39
  • $\begingroup$ Glad I could help. BTW: If you want to display an error directly to the user, replace 'INFO' with 'ERROR' and it shows a little popup. Another (prefered) option is using the invoke() method of an operator which allows to display whatever you want or to confirm any operation, see How to call a confirmation dialog box? @Robert $\endgroup$
    – brockmann
    Commented Sep 10, 2019 at 10:26
2
$\begingroup$

I think calculations should not be 'caller dependent', because if so, you'll need to pass many additional arguments to every function called in the process.

So one way to inform a calculation has failed is simply to raise an exception. Like so:

import bpy

def a_function(context):
    raise Exception( "An error occured in 'a_function'" )

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

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

    def execute(self, context):
        try:
            a_function( context )
        except Exception as e:
            raise Exception( *e.args, "from SimpleOperator" )

        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()
$\endgroup$
2
  • $\begingroup$ Is there any way to instruct the exception handler to generate a user-friendly message box, similar to the one created by the report function? Primarily to hide the "traceback" list? I have a feeling most users will assume this type of message is a bug or crash and close it without trying to locate the actual message buried in it. The messages I'm wanting to show the user are related to mistakes they have made, such as forgetting to save their project before running a tool. $\endgroup$
    – Robert
    Commented Sep 9, 2019 at 21:23
  • $\begingroup$ @Robert, I agree. In the example I give here, have raise again an exception. But e.args can include what you want (for instance error codes as shown in the answer by Blender Dadaist), from that in the except part you can use args the way you want (and for instance not raise again if you don't want to). Though, I don't think Blender has some 'message box' api (to be checked). Practically speaking, you can also define the exception classes you want (not only 'Exception'). All this should be a choice when writing the function and the operator part should know what kind of exceptions are expected $\endgroup$
    – lemon
    Commented Sep 10, 2019 at 5:44
0
$\begingroup$

If you want to determine if the call is from code, use [self.options.is_invoke == True] is button call

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .