Difference between revisions of "IC Python API:Plugin Window"
Chuck (RL) (Talk | contribs) m (→Initializing the Plugin) |
Chuck (RL) (Talk | contribs) m |
||
Line 1: | Line 1: | ||
{{TOC}} | {{TOC}} | ||
{{Parent|IC_Python_API:RL_Python_Samples|RL Python Samples}} | {{Parent|IC_Python_API:RL_Python_Samples|RL Python Samples}} | ||
− | |||
− | |||
If you are interested in creating plugins and tools for the iClone environment, then you'll eventually want to add the access to the scripts to iClone upon every start-up. This way you can execute your scripts from the '''Plugins''' menu instead of reading it from file each and every time. | If you are interested in creating plugins and tools for the iClone environment, then you'll eventually want to add the access to the scripts to iClone upon every start-up. This way you can execute your scripts from the '''Plugins''' menu instead of reading it from file each and every time. | ||
− | |||
=== Window Creation Workflow === | === Window Creation Workflow === | ||
+ | |||
+ | [[File:Ic_python_api_plugin_window_01.png|frame|left]] | ||
The standard start-up boilerplate for a plugin is the following: | The standard start-up boilerplate for a plugin is the following: | ||
Line 19: | Line 18: | ||
*If the window is hidden then show it. | *If the window is hidden then show it. | ||
*If the window is visible then show a message notifying the user of its existence. | *If the window is visible then show a message notifying the user of its existence. | ||
+ | {{clear}} | ||
== Necessary Modules and Global Variables == | == Necessary Modules and Global Variables == |
Latest revision as of 04:07, 18 February 2020
- Main article: RL Python Samples.
If you are interested in creating plugins and tools for the iClone environment, then you'll eventually want to add the access to the scripts to iClone upon every start-up. This way you can execute your scripts from the Plugins menu instead of reading it from file each and every time.
Window Creation Workflow
The standard start-up boilerplate for a plugin is the following:
- iClone automatically calls the run_script command.
- The plugin should initialize the process of adding itself into the plugin menu.
- The plugin menu action should call to the function for showing the window.
- The show window function should decide which action to take depending on the situation:
- If the window instance does not exist, then create a new window.
- If the window is hidden then show it.
- If the window is visible then show a message notifying the user of its existence.
Necessary Modules and Global Variables
Besides the rudimentary Reallusion Python module, we'll also need Pyside2 to build the user interface. And we'll need a global variable to house a reference to our window elements.
import RLPy
from PySide2 import *
from PySide2.QtCore import Qt
from PySide2.shiboken2 import wrapInstance
ui = {}
Dialog Event Callback
Next, we'll need a dialog event callback for the dialog window that will be created. This custom class inherits from RLPy.RDialogCallback. It is crucial to clear the global variable when the dialog window is closed; in order to compare to it when the same window initialization process is called. This will prevent the creation of duplicate windows.
class DialogEventCallback(RLPy.RDialogCallback):
def __init__(self):
RLPy.RDialogCallback.__init__(self)
def OnDialogClose(self):
global ui
ui.clear() # Clear global variables
return True
Notice how OnDialogClose returns True in order to notify iClone that the window is to be disposed of.
Create the Window
Let's create the actual plugin window interface. We'll also add a hide button to make it convenient to call the hide command on this window.
def create_window():
global ui
ui["window"] = RLPy.RUi.CreateRDialog()
ui["window"].SetWindowTitle("Plugin Window")
# Create Pyside layout for RDialog
qt_dialog = wrapInstance(int(ui["window"].GetWindow()), QtWidgets.QDialog)
qt_dialog.setFixedWidth(400)
qt_dialog.setFixedHeight(280)
# Create a label for previous status
ui["pre-state"] = QtWidgets.QLabel("This window was just created.")
ui["pre-state"].setAlignment(Qt.AlignCenter)
ui["pre-state"].setStyleSheet("color:#ff7c7c; font-weight:bold")
qt_dialog.layout().addWidget(ui["pre-state"])
# Create a description label
label = QtWidgets.QLabel(
'''<p>This is a dialog window created by the {0}Plugin Window{1} Python script.</p>
<p>Window check works by registering the window to a global variable where it can be compared.
This only works if the script is executed from the {0}Plugins{1} menu
because global variable don't share the same scope when loaded via the {0}Script{1} menu.</p>
<p>If the variable does not exist, then a new window is created.
If the variable does exist and the window is hidden then the it will be shown again.
If the variable does exist and the window is visible,
then a message will be issued to notify the user of its existence.</p>'''.format(
'''<span style="color:#a2ec13; font-weight:bold">''', '''</span>'''))
label.setWordWrap(True)
label.setTextFormat(Qt.RichText)
qt_dialog.layout().addWidget(label)
qt_dialog.layout().addStretch()
hide_button = QtWidgets.QPushButton("Hide Window")
hide_button.clicked.connect(lambda: ui["window"].Hide())
hide_button.setFixedHeight(28)
qt_dialog.layout().addWidget(hide_button)
ui["window"].Show()
Showing the Window
Here comes the crucial part of deciding which action to take depending on the state of the plugin window.
def show_window():
global ui
if "window" in ui:
if ui["window"].IsVisible():
RLPy.RUi.ShowMessageBox(
"Plugin Window",
"The current Plugin Window session is still running. You must first close the window to start another session.",
RLPy.EMsgButton_Ok
)
ui["pre-state"].setText("This window is still visible.")
else:
ui["window"].Show()
ui["pre-state"].setText("This window was hidden.")
else:
create_window()
ui["dialog_events"] = DialogEventCallback()
ui["window"].RegisterEventCallback(ui["dialog_events"])
Initializing the Plugin
Global variable only share the same scope if they are initialized from the Plugin menu. Therefore we must add the initialization command to the Plugin menu.
def initialize_plugin():
# Create Pyside interface with iClone main window
ic_dlg = wrapInstance(int(RLPy.RUi.GetMainWindow()), QtWidgets.QMainWindow)
plugin_menu = ic_dlg.menuBar().findChild(QtWidgets.QMenu, "potm_fundamentals") # Check if the menu item exists
if plugin_menu is None:
# Create Pyside layout for QMenu named "POTM Fundamentals" and attach it to the Plugins menu
plugin_menu = wrapInstance(int(RLPy.RUi.AddMenu("POTM Fundamentals", RLPy.EMenu_Plugins)), QtWidgets.QMenu)
plugin_menu.setObjectName("potm_fundamentals") # Setting an object name for the menu is equivalent to giving it an ID
# Check if the menu action already exists
menu_actions = plugin_menu.actions()
for i in range(len(menu_actions)):
if menu_actions[i].text() == "Plugin Window":
plugin_menu.removeAction(menu_actions[i]) # Remove duplicate actions
# Set up the menu action
menu_action = plugin_menu.addAction("Plugin Window")
menu_action.triggered.connect(show_window)
Notice that there are two crucial checks that must be performed:
- Checking if the plugin menu already exists.
- Checking if the action already exists under the plugin sub-menu.
This is to prevent the existence of duplicate plugin menus and actions.
Running the Script
You can run this script via Script > Load and it will be accessible via Plugins > POTM Fundamentals > Plugin Window command; with the help of the following code snippet:
def run_script():
initialize_plugin()
RLPy.RUi.ShowMessageBox(
"Plugin Window",
"You can run the Plugin Window script from the menu bar under <b>Plugins > POTM Fundamentals</b>.",
RLPy.EMsgButton_Ok
)
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
from PySide2 import *
from PySide2.QtCore import Qt
from PySide2.shiboken2 import wrapInstance
ui = {}
class DialogEventCallback(RLPy.RDialogCallback):
def __init__(self):
RLPy.RDialogCallback.__init__(self)
def OnDialogClose(self):
global ui
ui.clear() # Clear global variables
return True
def create_window():
global ui
ui["window"] = RLPy.RUi.CreateRDialog()
ui["window"].SetWindowTitle("Plugin Window")
# Create Pyside layout for RDialog
qt_dialog = wrapInstance(int(ui["window"].GetWindow()), QtWidgets.QDialog)
qt_dialog.setFixedWidth(400)
qt_dialog.setFixedHeight(280)
# Create a label for previous status
ui["pre-state"] = QtWidgets.QLabel("This window was just created.")
ui["pre-state"].setAlignment(Qt.AlignCenter)
ui["pre-state"].setStyleSheet("color:#ff7c7c; font-weight:bold")
qt_dialog.layout().addWidget(ui["pre-state"])
# Create a description label
label = QtWidgets.QLabel(
'''<p>This is a dialog window created by the {0}Plugin Window{1} Python script.</p>
<p>Window check works by registering the window to a global variable where it can be compared.
This only works if the script is executed from the {0}Plugins{1} menu
because global variable don't share the same scope when loaded via the {0}Script{1} menu.</p>
<p>If the variable does not exist, then a new window is created.
If the variable does exist and the window is hidden then the it will be shown again.
If the variable does exist and the window is visible,
then a message will be issued to notify the user of its existence.</p>'''.format(
'''<span style="color:#a2ec13; font-weight:bold">''', '''</span>'''))
label.setWordWrap(True)
label.setTextFormat(Qt.RichText)
qt_dialog.layout().addWidget(label)
qt_dialog.layout().addStretch()
hide_button = QtWidgets.QPushButton("Hide Window")
hide_button.clicked.connect(lambda: ui["window"].Hide())
hide_button.setFixedHeight(28)
qt_dialog.layout().addWidget(hide_button)
ui["window"].Show()
def show_window():
global ui
if "window" in ui:
if ui["window"].IsVisible():
RLPy.RUi.ShowMessageBox(
"Plugin Window",
"The current Plugin Window session is still running. You must first close the window to start another session.",
RLPy.EMsgButton_Ok
)
ui["pre-state"].setText("This window is still visible.")
else:
ui["window"].Show()
ui["pre-state"].setText("This window was hidden.")
else:
create_window()
ui["dialog_events"] = DialogEventCallback()
ui["window"].RegisterEventCallback(ui["dialog_events"])
def initialize_plugin():
# Create Pyside interface with iClone main window
ic_dlg = wrapInstance(int(RLPy.RUi.GetMainWindow()), QtWidgets.QMainWindow)
plugin_menu = ic_dlg.menuBar().findChild(QtWidgets.QMenu, "potm_fundamentals") # Check if the menu item exists
if plugin_menu is None:
# Create Pyside layout for QMenu named "POTM Fundamentals" and attach it to the Plugins menu
plugin_menu = wrapInstance(int(RLPy.RUi.AddMenu("POTM Fundamentals", RLPy.EMenu_Plugins)), QtWidgets.QMenu)
plugin_menu.setObjectName("potm_fundamentals") # Setting an object name for the menu is equivalent to giving it an ID
# Check if the menu action already exists
menu_actions = plugin_menu.actions()
for i in range(len(menu_actions)):
if menu_actions[i].text() == "Plugin Window":
plugin_menu.removeAction(menu_actions[i]) # Remove duplicate actions
# Set up the menu action
menu_action = plugin_menu.addAction("Plugin Window")
menu_action.triggered.connect(show_window)
def run_script():
initialize_plugin()
RLPy.RUi.ShowMessageBox(
"Plugin Window",
"You can run the Plugin Window script from the menu bar under <b>Plugins > POTM Fundamentals</b>.",
RLPy.EMsgButton_Ok
)
APIs Used
You can research the following references for the APIs deployed in this code.