Difference between revisions of "IC Python API:Error Handling"

From Reallusion Wiki!
Jump to: navigation, search
m (Exceptions in Python)
m (Exceptions in Python)
Line 23: Line 23:
 
|The '''finally''' block lets you execute code, regardless of the result of the try and expect blocks.
 
|The '''finally''' block lets you execute code, regardless of the result of the try and expect blocks.
 
|}
 
|}
 +
 +
== Example Script (Error Included) ==
 +
 +
For this exercise we'll need a fully functional script that contains an error that will cause iClone to crash.
 +
 +
<syntaxhighlight lang="python">
 +
import RLPy
 +
from PySide2 import QtWidgets
 +
from PySide2.shiboken2 import wrapInstance
 +
 +
 +
count = 4
 +
timer = None
 +
timer_callback = None
 +
 +
 +
class MyPyTimerCallback(RLPy.RPyTimerCallback):
 +
    def __init__(self):
 +
        RLPy.RPyTimerCallback.__init__(self)
 +
        self.timeout_func = None
 +
 +
    def Timeout(self):
 +
        self.timeout_func()
 +
 +
    def register_timeout_func(self, func):
 +
        self.timeout_func = func
 +
 +
 +
def print_count():
 +
    global count
 +
    global timer
 +
 +
    if count < 0:
 +
        timer.stop()  # <-- Intentional attribute error
 +
        print("Lift off! We have lift off!")
 +
    else:
 +
        print('\t... {} ...'.format(count))
 +
 +
    count -= 1
 +
 +
 +
timer = RLPy.RPyTimer()
 +
timer.SetInterval(1000)  # call time out function every second
 +
 +
timer_callback = MyPyTimerCallback()
 +
timer_callback.register_timeout_func(print_count)
 +
 +
timer.RegisterPyTimerCallback(timer_callback)
 +
timer.Start()
 +
print("T minus 5 seconds and counting...")
 +
</syntaxhighlight>
 +
 +
This script is a simple countdown for a rocket launch starting from the 5 second mark.  An intentional error is embedded at '''line 29: timer.stop()''' which should read '''timer.Stop()''' instead.  If you run this script, iClone will crash.
 +
 +
== Error Handling ==
 +
 +
Let's add a reusable function specifically for error handling:
 +
 +
<syntaxhighlight lang="python">
 +
def try_except(try_func, exception_func):
 +
    try:
 +
        try_func()
 +
    except Exception as inst:
 +
        print('''EXCEPTION ERROR:
 +
        TYPE:\t{}
 +
        ARGS:\t{}
 +
        PRINT:\t{}'''.format(type(inst), inst.args, inst))
 +
        exception_func()
 +
</syntaxhighlight>
 +
 +
Let's also change the way we handle the timer callback '''Timeout''' function where the error is likely to occur.  Again, we can only take educated guesses at where the error occurred.  Knowing that error inside repeat calls can cause the program to crash, we can debug those areas first.  Since '''RPyTimerCallback''' has a repeat routine in its '''Timeout''' member function, the logical place to implement the error handling would be there:
 +
 +
<syntaxhighlight lang="python">
 +
class MyPyTimerCallback(RLPy.RPyTimerCallback):
 +
    def __init__(self):
 +
        RLPy.RPyTimerCallback.__init__(self)
 +
        self.timeout_func = None
 +
 +
    def Timeout(self):
 +
        global timer
 +
        try_except(self.timeout_func, timer.Stop)
 +
 +
    def register_timeout_func(self, func):
 +
        self.timeout_func = func
 +
</syntaxhighlight>
 +
 +
Noticed the error handling function is implemented inside the '''Timeout''' block.  Now, when we execute the same code, instead of the crashing, we get a nice print-out for the errors.
 +
 +
If the program still crashes without a print-out, then the error handling needs to be relocated to the next logical code block.  Or better yet, multiple error handling can be implemented across the script; With a reusable function in hand, this process should be trivial.
 +
 +
After we fix the error: Changing '''timer.stop()''' to '''timer.Stop()''', we have effectively debugged our script.

Revision as of 19:25, 17 February 2020

Main article: RL Python Samples.

iClone will attempt to parse scripts that are loaded into the environment. In most cases, it does a decent job at catching errors and raising issues within the the Console Log. However, in cases where the errors occur in a loop or sequence, a resulting stack overflow can cause the program to shutdown prematurely. With no chance for a print-out combined with a lack of session logs, the user can find it hard to pin-point the time and point of occurrence of the error. Developers are advised to pay special attention to areas of the code that rely on repeated calls to a set of functions. You can use Python's native Try and Except error handling to debug and help fool-proof exceptionally vulnerable segments of your code.

Exceptions in Python

Python has many built-in exceptions which forces your program to output an error when something in it goes wrong. When these exceptions occur, it causes the current process to stop and passes it to the calling process until it is handled. If not handled, our program will crash. For example, if function A calls function B which in turn calls function C and an exception occurs in function C. If it is not handled in C, the exception passes to B and then to A. If never handled, an error message is spit out and our program come to a sudden, unexpected halt. Having the program come to a halt and give details about the error is much more preferable to a program crash.

Python error handling can be broken down into code blocks:

try The try block lets you test block of code for errors.
except The except block lets you handle the error.
else The else block lets you execute code if no errors were raised.
finally The finally block lets you execute code, regardless of the result of the try and expect blocks.

Example Script (Error Included)

For this exercise we'll need a fully functional script that contains an error that will cause iClone to crash.

import RLPy
from PySide2 import QtWidgets
from PySide2.shiboken2 import wrapInstance


count = 4
timer = None
timer_callback = None


class MyPyTimerCallback(RLPy.RPyTimerCallback):
    def __init__(self):
        RLPy.RPyTimerCallback.__init__(self)
        self.timeout_func = None

    def Timeout(self):
        self.timeout_func()

    def register_timeout_func(self, func):
        self.timeout_func = func


def print_count():
    global count
    global timer

    if count < 0:
        timer.stop()  # <-- Intentional attribute error
        print("Lift off! We have lift off!")
    else:
        print('\t... {} ...'.format(count))

    count -= 1


timer = RLPy.RPyTimer()
timer.SetInterval(1000)  # call time out function every second

timer_callback = MyPyTimerCallback()
timer_callback.register_timeout_func(print_count)

timer.RegisterPyTimerCallback(timer_callback)
timer.Start()
print("T minus 5 seconds and counting...")

This script is a simple countdown for a rocket launch starting from the 5 second mark. An intentional error is embedded at line 29: timer.stop() which should read timer.Stop() instead. If you run this script, iClone will crash.

Error Handling

Let's add a reusable function specifically for error handling:

def try_except(try_func, exception_func):
    try:
        try_func()
    except Exception as inst:
        print('''EXCEPTION ERROR:
        TYPE:\t{}
        ARGS:\t{}
        PRINT:\t{}'''.format(type(inst), inst.args, inst))
        exception_func()

Let's also change the way we handle the timer callback Timeout function where the error is likely to occur. Again, we can only take educated guesses at where the error occurred. Knowing that error inside repeat calls can cause the program to crash, we can debug those areas first. Since RPyTimerCallback has a repeat routine in its Timeout member function, the logical place to implement the error handling would be there:

class MyPyTimerCallback(RLPy.RPyTimerCallback):
    def __init__(self):
        RLPy.RPyTimerCallback.__init__(self)
        self.timeout_func = None

    def Timeout(self):
        global timer
        try_except(self.timeout_func, timer.Stop)

    def register_timeout_func(self, func):
        self.timeout_func = func

Noticed the error handling function is implemented inside the Timeout block. Now, when we execute the same code, instead of the crashing, we get a nice print-out for the errors.

If the program still crashes without a print-out, then the error handling needs to be relocated to the next logical code block.  Or better yet, multiple error handling can be implemented across the script; With a reusable function in hand, this process should be trivial.

After we fix the error: Changing timer.stop() to timer.Stop(), we have effectively debugged our script.