IC Python API:Stopwatch

From Reallusion Wiki!
Jump to: navigation, search
Main article: Python of the Month.

This article will demonstrate the usage of the timer class and handling the timer callback events by creating a stopwatch inside iClone.

Required Modules

Besides the fundamental Reallusion Python module, we'll also need Pyside2 to build the user interface, os to read a qt UI file, and datetime to handle the time format.

import RLPy
import os
import datetime
from PySide2 import *
from PySide2.shiboken2 import wrapInstance

Timer Callback Class

In order to create a timer callback event, we have to inherit from the RLPy.RPyTimerCallback class and modify it -specifically the Timeout function that triggers on each interval.

class TimerCallback(RLPy.RPyTimerCallback):
    def __init__(self):
        RLPy.RPyTimerCallback.__init__(self)

    def Timeout(self):
        global stop_watch
        if stop_watch["milliseconds"] > 5949999:  # Restart the timer at 99:99:99 mark
            stop_watch["milliseconds"] = 0
        stop_watch["milliseconds"] += 10
        time = datetime.datetime.min + datetime.timedelta(milliseconds=stop_watch["milliseconds"])
        widget.lcdNumber.display(time.strftime('%M:%S:%f')[:-4])
        widget.dial.setValue(stop_watch["milliseconds"])

Dialog Event Callback

A timer is a very powerful feature in iClone scripting but is easily prone to abuse due to its tendency to proliferate the session with background calculations that are invisible to the user once initiated. Therefore, it is highly recommended to also handle the events clean up process with a interface dependent callback, specifically in the form of a dialog event callback. By doing so, we tie the timer events that were fired off with the presence of a user interface, and once that interface ceases to exist (for instance, when closed) then the timer can be recalled and all related global variables can be sanitized.

class DialogEventCallback(RLPy.RDialogCallback):
    def __init__(self):
        RLPy.RDialogCallback.__init__(self)

    def OnDialogHide(self):
        global stop_watch

        stop_watch["timer"].UnregisterPyTimerCallback()
        del stop_watch

Timer Event Globals

All events in iClone must be declared as global variables. This means that event variables lie outside the scope of class and functions and must be so in order for iClone to properly handle callback requests. There is no one size fits all for this scenario, some people prefer to create separate global variables, but for this simple exercise, we'll just create a dictionary that'll house all of the global variables needed for this script and populate it.

# Global variables
stop_watch = {}
stop_watch["activated"] = False
stop_watch["milliseconds"] = 0
stop_watch["timer"] = RLPy.RPyTimer()
stop_watch["timer"].SetInterval(10)
stop_watch["callback"] = TimerCallback()
stop_watch["timer"].RegisterPyTimerCallback(stop_watch["callback"])

Notice that the timer's interval is set to 10 which is equivalent to 10 milliseconds.

Stopwatch Start/Stop Button

The stopwatch start/stop button is simply a toggle function that inverts the state of the watch from running to stopped and vice versa.

def toggle():
    global stop_watch

    stop_watch["activated"] = not stop_watch["activated"]

    if stop_watch["activated"]:
        widget.toggle.setText("Stop")
        stop_watch["timer"].Start()
    else:
        widget.toggle.setText("Start")
        stop_watch["timer"].Stop()

Stopwatch Reset Button

As a fancy feature, we'll add the function for stopping the watch and resetting the counter back to 00:00:00.

def reset():
    global stop_watch

    stop_watch["milliseconds"] = 0
    widget.dial.setValue(0)
    widget.lcdNumber.display("00:00:00")
    stop_watch["activated"] = True
    toggle()

Creating the Interface

Ic python api stop watch 02.png

Let's load the configured QT UI file. You can download Stop_Watch.ui here -make sure this UI file is placed in the same script directory.

# Create the dialog window in iClone
window = RLPy.RUi.CreateRDialog()
window.SetWindowTitle("Stop Watch")

# Empower the window with Python QT functionalities
dialog = wrapInstance(int(window.GetWindow()), QtWidgets.QDialog)
dialog.setFixedHeight(230)
dialog.setFixedWidth(202)

# Read the QT UI file from location and deploy as widget
ui = QtCore.QFile(os.path.dirname(__file__) + "/Stop_Watch.ui")
ui.open(QtCore.QFile.ReadOnly)
widget = QtUiTools.QUiLoader().load(ui)
ui.close()
dialog.layout().addWidget(widget)

# Configure and connect functions to the stopwatch widget
widget.lcdNumber.display("00:00:00")
widget.toggle.clicked.connect(toggle)
widget.reset.clicked.connect(reset)

# Dialog event callback
dialogCallback = DialogEventCallback()
window.RegisterEventCallback(dialogCallback)

window.Show()

Everything Put Together

Ic python api stopwatch 01.gif

You can copy and paste the following code into a PY file and load it into iClone via Script > Load Python.

import RLPy
import os
import datetime
from PySide2 import *
from PySide2.shiboken2 import wrapInstance


class TimerCallback(RLPy.RPyTimerCallback):
    def __init__(self):
        RLPy.RPyTimerCallback.__init__(self)

    def Timeout(self):
        global stop_watch
        if stop_watch["milliseconds"] > 5949999:  # Restart the timer at 99:99:99 mark
            stop_watch["milliseconds"] = 0
        stop_watch["milliseconds"] += 10
        time = datetime.datetime.min + datetime.timedelta(milliseconds=stop_watch["milliseconds"])
        widget.lcdNumber.display(time.strftime('%M:%S:%f')[:-4])
        widget.dial.setValue(stop_watch["milliseconds"])


class DialogEventCallback(RLPy.RDialogCallback):
    def __init__(self):
        RLPy.RDialogCallback.__init__(self)

    def OnDialogHide(self):
        global stop_watch

        stop_watch["timer"].UnregisterPyTimerCallback()
        del stop_watch


# Global variables
stop_watch = {}
stop_watch["activated"] = False
stop_watch["milliseconds"] = 0
stop_watch["timer"] = RLPy.RPyTimer()
stop_watch["timer"].SetInterval(10)
stop_watch["callback"] = TimerCallback()
stop_watch["timer"].RegisterPyTimerCallback(stop_watch["callback"])


def toggle():
    global stop_watch

    stop_watch["activated"] = not stop_watch["activated"]

    if stop_watch["activated"]:
        widget.toggle.setText("Stop")
        stop_watch["timer"].Start()
    else:
        widget.toggle.setText("Start")
        stop_watch["timer"].Stop()


def reset():
    global stop_watch

    stop_watch["milliseconds"] = 0
    widget.dial.setValue(0)
    widget.lcdNumber.display("00:00:00")
    stop_watch["activated"] = True
    toggle()


# Create the dialog window in iClone
window = RLPy.RUi.CreateRDialog()
window.SetWindowTitle("Stop Watch")

# Empower the window with Python QT functionalities
dialog = wrapInstance(int(window.GetWindow()), QtWidgets.QDialog)
dialog.setFixedHeight(230)
dialog.setFixedWidth(202)

# Read the QT UI file from location and deploy as widget
ui = QtCore.QFile(os.path.dirname(__file__) + "/Stop_Watch.ui")
ui.open(QtCore.QFile.ReadOnly)
widget = QtUiTools.QUiLoader().load(ui)
ui.close()
dialog.layout().addWidget(widget)

# Configure and connect functions to the stopwatch widget
widget.lcdNumber.display("00:00:00")
widget.toggle.clicked.connect(toggle)
widget.reset.clicked.connect(reset)

# Dialog event callback
dialogCallback = DialogEventCallback()
window.RegisterEventCallback(dialogCallback)

window.Show()

APIs Used

You can research the following references for the APIs deployed in this code.