Difference between revisions of "IC Python API:Stopwatch"

From Reallusion Wiki!
Jump to: navigation, search
(Created page with "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 f...")
 
m (Creating the Interface)
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
 +
{{TOC}}
 +
{{Parent|IC_Python_API:RL_Python_Samples|RL Python Samples}}
 +
 
This article will demonstrate the usage of the timer class and handling the timer callback events by creating a stopwatch inside iClone.
 
This article will demonstrate the usage of the timer class and handling the timer callback events by creating a stopwatch inside iClone.
  
Line 12: Line 15:
 
from PySide2.shiboken2 import wrapInstance
 
from PySide2.shiboken2 import wrapInstance
 
</syntaxhighlight>
 
</syntaxhighlight>
 
 
== 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.
 
 
<syntaxhighlight lang="Python">
 
stop_watch = {}  # Globals
 
</syntaxhighlight>
 
 
  
 
== Timer Callback Class ==
 
== Timer Callback Class ==
Line 41: Line 34:
 
         widget.dial.setValue(stop_watch["milliseconds"])
 
         widget.dial.setValue(stop_watch["milliseconds"])
 
</syntaxhighlight>
 
</syntaxhighlight>
 
  
 
== Dialog Event Callback ==
 
== Dialog Event Callback ==
Line 59: Line 51:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
== 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.
 +
 +
<syntaxhighlight lang="Python">
 +
# 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"])
 +
</syntaxhighlight>
  
 
== Stopwatch Start/Stop Button ==
 
== Stopwatch Start/Stop Button ==
Line 77: Line 83:
 
         stop_watch["timer"].Stop()
 
         stop_watch["timer"].Stop()
 
</syntaxhighlight>
 
</syntaxhighlight>
 
  
 
== Stopwatch Reset Button ==
 
== Stopwatch Reset Button ==
Line 93: Line 98:
 
     toggle()
 
     toggle()
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
== Creating the Interface ==
 +
 +
Let's load the configured QT UI file.
 +
 +
<syntaxhighlight lang="python">
 +
# 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()
 +
</syntaxhighlight>
 +
 +
== Everything Put Together ==
 +
 +
You can copy and paste the following code into a PY file and load it into iClone via Script > Load Python.
 +
 +
<syntaxhighlight lang="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()
 +
</syntaxhighlight>
 +
 +
 +
== APIs Used ==
 +
 +
You can research the following references for the APIs deployed in this code.
 +
 +
=== Stop_Watch.py ===
 +
<div style="column-count:4; -moz-column-count:4; -webkit-column-count:4">
 +
* [[ IC_Python_API:RLPy_RPyTimerCallback#__init__ | RLPy.RPyTimerCallback.__init__() ]]
 +
* [[ IC_Python_API:RLPy_RDialogCallback#__init__ | RLPy.RDialogCallback.__init__() ]]
 +
* [[ IC_Python_API:RLPy_RPyTimer | RLPy.RPyTimer() ]]
 +
* [[ IC_Python_API:RLPy_RUi#CreateRDialog | RLPy.RUi.CreateRDialog() ]]
 +
</div>

Revision as of 21:41, 13 August 2019

Main article: RL Python Samples.

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"])

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

Let's load the configured QT UI file.

# 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

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.

Stop_Watch.py