IC Python API:Bounding Info

From Reallusion Wiki!
Jump to: navigation, search

Main article: RL Python Samples.

Bounding boxes are the farthest extents of the vertices in world-space coordinates.

This article will go over the handling of bounding area data in iClone and how to derive useful bounding box size information from it. Bounds of an object are the farthest extent of its vertices in world-space coordinates, therefore, they are subject to transformational changes. This can be counter-intuitive when it comes to changing rotations where one would expect the bounding box to stay the same. However, this is not the case and it more useful to think of the bounding box as a fixed orientation where the points represented do not rotate in the object's transform space.

Required Modules and Global Variables

Besides the fundamental Reallusion Python module, we'll also need Pyside2 and os to read the QT UI file and build the user interface. We'll also need a global variable to house our UI and callback events that we'll need to link the custom user controls with those of iClone.

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

bi_events = {}  # Global dict for events and callbacks

Event Callback

In order to sync the custom user controls with the data model, we'll need to receive event triggers on object data and selection change and tie it to an update function. We do this by inheriting and configuring the RLPy.REventCallback class.

class BoundingInfoEventCallback(RLPy.REventCallback):
    def __init__(self):
        RLPy.REventCallback.__init__(self)

    def OnObjectDataChanged(self):
        get_bounding_info()

    def OnObjectSelectionChanged(self):
        get_bounding_info()

Dialog Event Callback

Event callbacks are global values, therefore they tend to persist even when the user is not longer using the script. This can quickly pollute the environment with wasteful scripted calculations that can chew up memory and CPU cycles. Therefore, we'll need a dialog event callback to discard current event callbacks created by this script and clean up the global variables.

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

    def OnDialogClose(self):
        global bi_events

        RLPy.REventHandler.UnregisterCallback(bi_events["callback_id"])
        bi_events.clear
        return True

Creating the UI

Ic python api bounding info 02.png

We'll need to load the configured QT UI file. You can download Bounding_Info.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("Bounding Box")

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

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

window.Show()

Get Bounding Info

Reallusion Python's GetBounds is central to this function, however, three world-space points that define the object's bounds isn't much use. Therefore, we'll need to calculate the bounding box size by subtracting the max point from the center point. This can also be calculated by subtracting min point from the center point.

def get_bounding_info():

    items = RLPy.RScene.GetSelectedObjects()

    if len(items) > 0:
        window.SetWindowTitle("Bounding Box ({})".format(items[0].GetName()))

        maxPoint = RLPy.RVector3()
        cenPoint = RLPy.RVector3()
        minPoint = RLPy.RVector3()

        status = items[0].GetBounds(maxPoint, cenPoint, minPoint)
        bounding = maxPoint - cenPoint  # Can also be: cenPoint - minPoint

        if status == RLPy.RStatus.Success:

            # Populate the coordinate fields with bounding information
            for field in [[widget.max_x, maxPoint.x], [widget.max_y, maxPoint.y], [widget.max_z, maxPoint.z],
                          [widget.cen_x, cenPoint.x], [widget.cen_y, cenPoint.y], [widget.cen_z, cenPoint.z],
                          [widget.min_x, minPoint.x], [widget.min_y, minPoint.y], [widget.min_z, minPoint.z],
                          [widget.bbs_x, bounding.x], [widget.bbs_y, bounding.y], [widget.bbs_z, bounding.z]]:
                field[0].setText(str(round(field[1], 2)))
            return

        if status == RLPy.RStatus.Failure:
            pass

    window.SetWindowTitle("Bounding Box (--)")

    # Reset the coordinate fields
    for field in [widget.max_x, widget.max_y, widget.max_z, widget.cen_x, widget.cen_y, widget.cen_z,
                  widget.min_x, widget.min_y, widget.min_z, widget.bbs_x, widget.bbs_y, widget.bbs_z]:
        field.setText("--")

Registering Callbacks

Now we'll need to connect the events to call the function to retrieve the bounding information. We'll also need to tie the event to the window so that when it is closed, the event callbacks are removed and the global variable is cleaned.

# Register dialog event callbacks
bi_events["dialog_callbacks"] = DialogEventCallback()
window.RegisterEventCallback(bi_events["dialog_callbacks"])

# Register callback events
bi_events["callback"] = BoundingInfoEventCallback()
bi_events["callback_id"] = RLPy.REventHandler.RegisterCallback(bi_events["callback"])

get_bounding_info()  # Retrieve the bounding info on start up

Everything Put Together

Ic python api bounding info 04.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
from PySide2 import *
from PySide2.shiboken2 import wrapInstance

bi_events = {}  # Global dict for events and callbacks


class BoundingInfoEventCallback(RLPy.REventCallback):
    def __init__(self):
        RLPy.REventCallback.__init__(self)

    def OnObjectDataChanged(self):
        get_bounding_box()

    def OnObjectSelectionChanged(self):
        get_bounding_box()


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

    def OnDialogClose(self):
        global bi_events

        RLPy.REventHandler.UnregisterCallback(bi_events["callback_id"])
        bi_events.clear
        return True


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

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

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

window.Show()


def get_bounding_info():

    items = RLPy.RScene.GetSelectedObjects()

    if len(items) > 0:
        window.SetWindowTitle("Bounding Box ({})".format(items[0].GetName()))

        maxPoint = RLPy.RVector3()
        cenPoint = RLPy.RVector3()
        minPoint = RLPy.RVector3()

        status = items[0].GetBounds(maxPoint, cenPoint, minPoint)
        bounding = maxPoint - cenPoint  # Can also be: cenPoint - minPoint

        if status == RLPy.RStatus.Success:

            # Populate the coordinate fields with bounding information
            for field in [[widget.max_x, maxPoint.x], [widget.max_y, maxPoint.y], [widget.max_z, maxPoint.z],
                          [widget.cen_x, cenPoint.x], [widget.cen_y, cenPoint.y], [widget.cen_z, cenPoint.z],
                          [widget.min_x, minPoint.x], [widget.min_y, minPoint.y], [widget.min_z, minPoint.z],
                          [widget.bbs_x, bounding.x], [widget.bbs_y, bounding.y], [widget.bbs_z, bounding.z]]:
                field[0].setText(str(round(field[1], 2)))
            return

        if status == RLPy.RStatus.Failure:
            pass

    window.SetWindowTitle("Bounding Box (--)")

    # Reset the coordinate fields
    for field in [widget.max_x, widget.max_y, widget.max_z, widget.cen_x, widget.cen_y, widget.cen_z,
                  widget.min_x, widget.min_y, widget.min_z, widget.bbs_x, widget.bbs_y, widget.bbs_z]:
        field.setText("--")


# Register dialog event callbacks
bi_events["dialog_callbacks"] = DialogEventCallback()
window.RegisterEventCallback(bi_events["dialog_callbacks"])

# Register callback events
bi_events["callback"] = BoundingInfoEventCallback()
bi_events["callback_id"] = RLPy.REventHandler.RegisterCallback(bi_events["callback"])

get_bounding_info()  # Retrieve the bounding info on start up

APIs Used

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