[Maya][PyQt] Send exceptions from Maya to PyQt dialog

I created a custom PyQt progress bar dialog for monitoring operations in Maya. As long as everything runs smoothly with the operation being monitored, then the progress dialog works fine. However, if the operation does something bad and Maya shows an exception, I would like to be able to get that information back to my dialog. (It could be something as simple as just knowing that an exception happened.) I’m not sure of the best way to go about it.

I tried just calling “raise” in my progress dialog, but that kept giving me the error message “exceptions must be classes or instances, not NoneType”. I think it is because the Maya exception was never active in the scope of the progrogress dialog, but I’m not sure.

I also thought about maybe connecting some sort of Maya error signal to a slot in my progress dialog. But I don’t know if any such signal exists. Does anyone know if it does? Or if not, how I could make Maya send such a signal when an exception happens?

wouldn’t try and except allow you to do exactly that? you could even pipe the exception using something like;

except, Exception as e:
dialog.showStatus(e)

Is the maya function being run in the main thread using ExecuteDeferred or ExecuteInMainThreadwithResult? You may find it easier to handle debugging if your in-scene operations are exception-safe: ie, they’re all wrapped in a try-catch and they return a success code or an exception object so the calling code in the dialog is guaranteed to ‘work’ and the exception handling is done on the pure-maya side:

 
    import traceback
    
    def do_something_in_maya (*args):
        try: 
            # do stuff
            return None
        except:
            return traceback.format_exc()
   
    # in the dialog, the calling command would do something like:
    
    result = maya.utils.executeInMainThreadWithResult (do_something_in_maya)  # if you needed args you could pass this as a functools.partial
    if result # this would indicate an exception and print the traceback as a warning...
       cmds.warning(result)

For the case of a progress bar, you might replace the returns with yields – though I honestly don’t know if that works properly across threads.

Thanks for replies. Using try and except is what I will probably have to do. I mean, we normally use try and except to catch errors. But occasionally we miss a case and don’t know it until Maya shows an unhandled exception. I was just hoping there was a smoother way to detect those unhandled exceptions.

All the operations are being run in the main thread. (I’m not using ExecuteDeferred or ExecuteInMainThreadWithResult.)

I finally figured out something that seems to work. I created a class derived from QObject, that contains a custom signal. Within that class, I hijack Maya’s formatGuiException function, and make it emit the new signal right before it prints out the unhandled exception to the Script Editor.

class MayaExceptionMonitor(QtCore.QObject):
    # Custom signals
    unhandledException = QtCore.pyqtSignal(type, BaseException)

    # Other class variables
    _instance = None
    
    def __new__(cls, *args, **kwargs):
        """ Class instance creation. Make this class a singleton. """
        if not isinstance(cls._instance, cls):
            cls._instance = QtCore.QObject.__new__(cls, *args, **kwargs)
        return cls._instance
    
    def __init__(self):
        super(QtCore.QObject, self).__init__()
        
        # Maya has a function to print unhandled exceptions to the Script
        # Editor.  Hijack that function and emit our custom "unhandledException"
        # signal before returning to the normal Maya control.
        # 
        # This method is approved by Maya, and can be seen in the documentation
        # for maya.utils
        def myExceptCB(etype, value, tb, detail=2):
            self.unhandledException.emit(etype, value)
            return maya.utils._formatGuiException(etype, value, tb, detail)
        
        maya.utils.formatGuiException = myExceptCB

Then in the code for my dialog, I create an instance of the new class. That way I can connect the custom signal to a slot function that I define.

class MyProgressBarDialog(QtGui.QDialog):
    
    def __init__(self, parent=rsui.rsp_getMayaMainWindow()):
        super(QtGui.QDialog, self).__init__(parent)

        self.setWindowModality(QtCore.Qt.WindowModal)
        # The progress dialog is a modal window that returns control to the main
        # program as soon as it is created.  For that reason, it has no way of
        # knowing if the main program encountered an unhandled exception.  
        # Create a MayaExceptionMonitor and connect the "unhandledException"
        # signal to a custom slot that will handle this event.
        excMon = rserr.MayaExceptionMonitor()
        excMon.unhandledException.connect(self.handleUnexpectedError)

        # The rest of the setup code goes here...

    def handleUnexpectedError(self):
        """ If an unhandled exception occurs in the main program, do stuff. """
        print 'There was an unhandled exception!'