Difference between revisions of "IC Python API:Linked Controls"
Chuck (RL) (Talk | contribs) m |
Chuck (RL) (Talk | contribs) m (→Event Callback) |
||
Line 40: | Line 40: | ||
def OnObjectSelectionChanged(self): | def OnObjectSelectionChanged(self): | ||
update_interface() | update_interface() | ||
− | </ | + | </syntaxhighlight> |
== Dialog Event Callback == | == Dialog Event Callback == | ||
Line 58: | Line 58: | ||
events.clear() | events.clear() | ||
return True | return True | ||
− | </ | + | </syntaxhighlight> |
== Update Prop (Data Model) == | == Update Prop (Data Model) == | ||
Line 77: | Line 77: | ||
RLPy.RGlobal.SetTime(RLPy.RGlobal.GetTime() + RLPy.RTime(1)) | RLPy.RGlobal.SetTime(RLPy.RGlobal.GetTime() + RLPy.RTime(1)) | ||
RLPy.RGlobal.SetTime(RLPy.RGlobal.GetTime() - RLPy.RTime(1)) | RLPy.RGlobal.SetTime(RLPy.RGlobal.GetTime() - RLPy.RTime(1)) | ||
− | </ | + | </syntaxhighlight> |
== Update Custom User Controls == | == Update Custom User Controls == | ||
Line 118: | Line 118: | ||
ui["widget"].scale_z.setValue(0) | ui["widget"].scale_z.setValue(0) | ||
ui["widget"].widget.setEnabled(False) | ui["widget"].widget.setEnabled(False) | ||
− | </ | + | </syntaxhighlight> |
== Creating the UI == | == Creating the UI == | ||
Line 159: | Line 159: | ||
ui["widget"].scale_z.setValue(0) | ui["widget"].scale_z.setValue(0) | ||
ui["widget"].widget.setEnabled(False) | ui["widget"].widget.setEnabled(False) | ||
− | </ | + | </syntaxhighlight> |
== Everything Put Together == | == Everything Put Together == | ||
Line 288: | Line 288: | ||
update_interface() | update_interface() | ||
− | </ | + | </syntaxhighlight> |
Revision as of 23:00, 15 August 2019
- Main article: RL Python Samples.
Making a custom interface to drive common object properties such as transform values is a commonplace operation. Problems arise however, when custom interfaces come in conflict with iClone's native controls implementation. To resolve this conflict of multiple operators, the custom controls much be in sync with the native controls and vice versa via a scripted link.
Linked interactions happen in 3 ways:
- Commands issued from the script in the form of custom user controls first updates the data model in the scene (such as prop transformations) and iClone handles the rest by updating the native UI parameters with no knowledge of the script's existence. This form of interaction is the simplest and most straightforward of the three.
- Changes to the data model are reflected back onto native UI parameters as well as any receiving linked custom user controls instantiated by scripts. Since data updates are written to the custom user controls which are, in turn, linked to the data model; the signals back to the data model must be blocked to prevent a double update to the data model and the native controls.
- Changes to the native controls drive changes to the data model which, in turn, drive the linked custom user controls instantiated by scripts. Similar to the mechanism mentioned above, the returning signal must be blocked to prevent a double update to the data model and native controls.
One can derive a unified premise for the aforementioned scenarios into one simple rule: Returning signals on all data model updates to the script must be blocked to prevent double updates to the data model.
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 some global variables to house our UI and callback events 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
ui = {} # User interface globals
events = {} # Globals for events and callbacks
Event Callback
class EventCallback(RLPy.REventCallback):
def __init__(self):
RLPy.REventCallback.__init__(self)
def OnObjectDataChanged(self):
update_interface()
def OnObjectSelectionChanged(self):
update_interface()
Dialog Event Callback
class DialogEventCallback(RLPy.RDialogCallback):
def __init__(self):
RLPy.RDialogCallback.__init__(self)
def OnDialogClose(self):
global events
RLPy.REventHandler.UnregisterCallback(events["event_callback_id"])
# Clear all global variables.
ui.clear()
events.clear()
return True
Update Prop (Data Model)
def update_prop(parameter, value):
items = RLPy.RScene.GetSelectedObjects()
if len(items) > 0:
object_type = items[0].GetType()
if object_type == RLPy.EObjectType_Prop:
control = items[0].GetControl("Transform")
db = control.GetDataBlock()
db.SetData(parameter, RLPy.RGlobal.GetTime(), RLPy.RVariant(value))
# Force update on the Modify panel
RLPy.RGlobal.SetTime(RLPy.RGlobal.GetTime() + RLPy.RTime(1))
RLPy.RGlobal.SetTime(RLPy.RGlobal.GetTime() - RLPy.RTime(1))
Update Custom User Controls
def update_interface():
global ui
items = RLPy.RScene.GetSelectedObjects()
if len(items) > 0:
object_type = items[0].GetType()
if object_type == RLPy.EObjectType_Prop:
ui["dialog_window"].SetWindowTitle(items[0].GetName())
ct = RLPy.RGlobal.GetTime()
control = items[0].GetControl("Transform")
db = control.GetDataBlock()
ui["widget"].move_x.setValue(db.GetData("Position/PositionX", ct).ToFloat())
ui["widget"].move_y.setValue(db.GetData("Position/PositionY", ct).ToFloat())
ui["widget"].move_z.setValue(db.GetData("Position/PositionZ", ct).ToFloat())
ui["widget"].rotate_x.setValue(db.GetData("Rotation/RotationX", ct).ToFloat() * RLPy.RMath.CONST_RAD_TO_DEG)
ui["widget"].rotate_y.setValue(db.GetData("Rotation/RotationY", ct).ToFloat() * RLPy.RMath.CONST_RAD_TO_DEG)
ui["widget"].rotate_z.setValue(db.GetData("Rotation/RotationZ", ct).ToFloat() * RLPy.RMath.CONST_RAD_TO_DEG)
ui["widget"].scale_x.setValue(db.GetData("Scale/ScaleX", ct).ToFloat() * 100)
ui["widget"].scale_y.setValue(db.GetData("Scale/ScaleY", ct).ToFloat() * 100)
ui["widget"].scale_z.setValue(db.GetData("Scale/ScaleZ", ct).ToFloat() * 100)
ui["widget"].widget.setEnabled(True)
return
ui["dialog_window"].SetWindowTitle("No Prop Selected")
ui["widget"].move_x.setValue(0)
ui["widget"].move_y.setValue(0)
ui["widget"].move_z.setValue(0)
ui["widget"].rotate_x.setValue(0)
ui["widget"].rotate_y.setValue(0)
ui["widget"].rotate_z.setValue(0)
ui["widget"].scale_x.setValue(0)
ui["widget"].scale_y.setValue(0)
ui["widget"].scale_z.setValue(0)
ui["widget"].widget.setEnabled(False)
Creating the UI
def update_interface():
global ui
items = RLPy.RScene.GetSelectedObjects()
if len(items) > 0:
object_type = items[0].GetType()
if object_type == RLPy.EObjectType_Prop:
ui["dialog_window"].SetWindowTitle(items[0].GetName())
ct = RLPy.RGlobal.GetTime()
control = items[0].GetControl("Transform")
db = control.GetDataBlock()
ui["widget"].move_x.setValue(db.GetData("Position/PositionX", ct).ToFloat())
ui["widget"].move_y.setValue(db.GetData("Position/PositionY", ct).ToFloat())
ui["widget"].move_z.setValue(db.GetData("Position/PositionZ", ct).ToFloat())
ui["widget"].rotate_x.setValue(db.GetData("Rotation/RotationX", ct).ToFloat() * RLPy.RMath.CONST_RAD_TO_DEG)
ui["widget"].rotate_y.setValue(db.GetData("Rotation/RotationY", ct).ToFloat() * RLPy.RMath.CONST_RAD_TO_DEG)
ui["widget"].rotate_z.setValue(db.GetData("Rotation/RotationZ", ct).ToFloat() * RLPy.RMath.CONST_RAD_TO_DEG)
ui["widget"].scale_x.setValue(db.GetData("Scale/ScaleX", ct).ToFloat() * 100)
ui["widget"].scale_y.setValue(db.GetData("Scale/ScaleY", ct).ToFloat() * 100)
ui["widget"].scale_z.setValue(db.GetData("Scale/ScaleZ", ct).ToFloat() * 100)
ui["widget"].widget.setEnabled(True)
return
ui["dialog_window"].SetWindowTitle("No Prop Selected")
ui["widget"].move_x.setValue(0)
ui["widget"].move_y.setValue(0)
ui["widget"].move_z.setValue(0)
ui["widget"].rotate_x.setValue(0)
ui["widget"].rotate_y.setValue(0)
ui["widget"].rotate_z.setValue(0)
ui["widget"].scale_x.setValue(0)
ui["widget"].scale_y.setValue(0)
ui["widget"].scale_z.setValue(0)
ui["widget"].widget.setEnabled(False)
Everything Put Together
import RLPy
import os
from PySide2 import *
from PySide2.shiboken2 import wrapInstance
ui = {} # User interface globals
events = {} # Globals for events and callbacks
class EventCallback(RLPy.REventCallback):
def __init__(self):
RLPy.REventCallback.__init__(self)
def OnObjectDataChanged(self):
update_interface()
def OnObjectSelectionChanged(self):
update_interface()
class DialogEventCallback(RLPy.RDialogCallback):
def __init__(self):
RLPy.RDialogCallback.__init__(self)
def OnDialogClose(self):
global events
RLPy.REventHandler.UnregisterCallback(events["event_callback_id"])
# Clear all global variables.
ui.clear()
events.clear()
return True
def update_prop(parameter, value):
items = RLPy.RScene.GetSelectedObjects()
if len(items) > 0:
object_type = items[0].GetType()
if object_type == RLPy.EObjectType_Prop:
control = items[0].GetControl("Transform")
db = control.GetDataBlock()
db.SetData(parameter, RLPy.RGlobal.GetTime(), RLPy.RVariant(value))
# Force update on the Modify panel
RLPy.RGlobal.SetTime(RLPy.RGlobal.GetTime() + RLPy.RTime(1))
RLPy.RGlobal.SetTime(RLPy.RGlobal.GetTime() - RLPy.RTime(1))
def update_interface():
global ui
items = RLPy.RScene.GetSelectedObjects()
if len(items) > 0:
object_type = items[0].GetType()
if object_type == RLPy.EObjectType_Prop:
ui["dialog_window"].SetWindowTitle(items[0].GetName())
ct = RLPy.RGlobal.GetTime()
control = items[0].GetControl("Transform")
db = control.GetDataBlock()
ui["widget"].move_x.setValue(db.GetData("Position/PositionX", ct).ToFloat())
ui["widget"].move_y.setValue(db.GetData("Position/PositionY", ct).ToFloat())
ui["widget"].move_z.setValue(db.GetData("Position/PositionZ", ct).ToFloat())
ui["widget"].rotate_x.setValue(db.GetData("Rotation/RotationX", ct).ToFloat() * RLPy.RMath.CONST_RAD_TO_DEG)
ui["widget"].rotate_y.setValue(db.GetData("Rotation/RotationY", ct).ToFloat() * RLPy.RMath.CONST_RAD_TO_DEG)
ui["widget"].rotate_z.setValue(db.GetData("Rotation/RotationZ", ct).ToFloat() * RLPy.RMath.CONST_RAD_TO_DEG)
ui["widget"].scale_x.setValue(db.GetData("Scale/ScaleX", ct).ToFloat() * 100)
ui["widget"].scale_y.setValue(db.GetData("Scale/ScaleY", ct).ToFloat() * 100)
ui["widget"].scale_z.setValue(db.GetData("Scale/ScaleZ", ct).ToFloat() * 100)
ui["widget"].widget.setEnabled(True)
return
ui["dialog_window"].SetWindowTitle("No Prop Selected")
ui["widget"].move_x.setValue(0)
ui["widget"].move_y.setValue(0)
ui["widget"].move_z.setValue(0)
ui["widget"].rotate_x.setValue(0)
ui["widget"].rotate_y.setValue(0)
ui["widget"].rotate_z.setValue(0)
ui["widget"].scale_x.setValue(0)
ui["widget"].scale_y.setValue(0)
ui["widget"].scale_z.setValue(0)
ui["widget"].widget.setEnabled(False)
def run_script():
global ui, events
ui["dialog_window"] = RLPy.RUi.CreateRDialog()
ui["dialog_window"].SetWindowTitle("No Prop Selected")
events["dialog_event_callback"] = DialogEventCallback()
ui["dialog_window"].RegisterEventCallback(events["dialog_event_callback"])
dialog = wrapInstance(int(ui["dialog_window"].GetWindow()), QtWidgets.QDialog)
dialog.setFixedWidth(350)
qt_ui_file = QtCore.QFile(os.path.dirname(__file__) + "/Linked_Controls.ui")
qt_ui_file.open(QtCore.QFile.ReadOnly)
ui["widget"] = QtUiTools.QUiLoader().load(qt_ui_file)
qt_ui_file.close()
dialog.layout().addWidget(ui["widget"])
ui["dialog_window"].Show()
events["event_callback"] = EventCallback()
events["event_callback_id"] = RLPy.REventHandler.RegisterCallback(events["event_callback"])
ui["widget"].move_x.valueChanged.connect(lambda x: update_prop("Position/PositionX", x))
ui["widget"].move_y.valueChanged.connect(lambda x: update_prop("Position/PositionY", x))
ui["widget"].move_z.valueChanged.connect(lambda x: update_prop("Position/PositionZ", x))
ui["widget"].rotate_x.valueChanged.connect(lambda x: update_prop("Rotation/RotationX", x * RLPy.RMath.CONST_DEG_TO_RAD))
ui["widget"].rotate_y.valueChanged.connect(lambda x: update_prop("Rotation/RotationY", x * RLPy.RMath.CONST_DEG_TO_RAD))
ui["widget"].rotate_z.valueChanged.connect(lambda x: update_prop("Rotation/RotationZ", x * RLPy.RMath.CONST_DEG_TO_RAD))
ui["widget"].scale_x.valueChanged.connect(lambda x: update_prop("Scale/ScaleX", x * 0.01))
ui["widget"].scale_y.valueChanged.connect(lambda x: update_prop("Scale/ScaleY", x * 0.01))
ui["widget"].scale_z.valueChanged.connect(lambda x: update_prop("Scale/ScaleZ", x * 0.01))
update_interface()