Adding Presets to Global Context Variables
Global context variables can be useful for ensuring a context variable is always accessible and set for the entire script. Unless it is overridden by a ContextVariables or ContextVariablesTweaks node in the node graph, all nodes will have access to the same global context variable. There are some global context variables added by Gaffer by default when creating a new script. You can see these, and add your own, from the Variables
tab in the Settings
dialog accessed from the File.Settings
menu item.
The default user interface for a global context variable can be useful for many situations, but like all plugs in Gaffer, it is possible to customize how the plugs for global context variables are presented to the user. One such customization is adding presets for the user to choose from. This ensures only valid values can be assigned to the context variable. This guide will describe how to add presets to the widget controlling a global context variable.
Adding Presets to the Settings
Dialog
Presets can be added to plugs in the Settings
dialog by assigning metadata to the value plug for a global context variable. There are three steps to adding the needed metadata.
Identify the target plug
Before we assign metadata, we need to identify which plug represents the context variable you want to add presets to. Global context variables are stored in the variables
child of the script node. In the Python editor, these are accessed by root["variables"]
.
The entries in root["variables"]
are of the type NameValuePlug
. These plugs themselves have three child plugs: name
, enabled
and value
. The name
plug sets the name of the context variable and the value
sets the value of the variable. The enabled
plug is used to enable or disable the variable. Note that the value of name
may be different from the name of the actual NameValuePlug
.
To determine which child of root["variables"]
you want to add metadata to, loop through all the children of root["variables"]
checking the value of the name
plug until you find the variable. The corresponding value
child plug will be the plug we add metadata to.
Set the widget type
Next we need to tell Gaffer to use a presets dropdown widget for our value plug. That is done by setting plugValueWidget:type
to "GafferUI.PresetsPlugValueWidget"
:
Gaffer.Metadata.registerValue( variablePlug, "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget" )
Set the preset names and values
Now we add the presets themselves. This can be done in one of two ways. You can add a metadata entry starting with preset:
followed by the display string of your preset set to the preset value. For example, Gaffer.Metadata.registerValue( variablePlug, "preset:Shot 1004", "shot1004")
will create a preset showing in the UI as Shot 1004
. When the user chooses that preset, the actual value of the context variable will be shot1004
.
The second method sets all of the preset names and all of the values in one step each. This can be done by setting the presetNames
metadata entry to a Cortex string vector (IECore.StringVectorData
)holding all of the preset names. Setting presetValues
to a Cortex vector of some type, such as IECoreStringVectorData
or IECoreFloatVectorData
.
Gaffer.Metadata.registerValue( variablePlug, "presetNames", IECore.StringVectorData( presetNames ) )
Gaffer.Metadata.registerValue( variablePlug, "presetValues", IECore.StringVectorData( presetValues ) )
Resetting the Settings
dialog
Note that the Settings
UI is built once and then reused for subsequent showings. This means after adding presets, you need to force Gaffer to refresh the Settings
dialog. You can force Gaffer to rebuild the Settings
dialog by running the following code in the Python editor:
sw = GafferUI.ScriptWindow.acquire(root)
for window in sw.childWindows():
if hasattr( window, "_settingsEditor" ) :
sw.removeChild(window)
Once you have registered the metadata for a plug in this way, it will be saved with the script so there is no need to register the metadata again.
Example Script
The following script is an example of all of the above steps condensed into a reusable function for adding presets for a global context variable.
# Sets the preset names and values for a global context variable.
# `scriptNode` is the script node which you want to set global context variables for.
# `variableName` is the name of the variable to set presets for.
# `presetNames` is a list of strings to set the preset names to.
# `presetValues` is an `IECore.*VectorData` holding a list of preset values
# for each corresponding item in `presetNames`
def globalContextPresets( scriptNode, variableName, presetNames, presetValues ) :
if len( presetNames ) != len( presetValues ) :
raise ValueError( "`presetNames` and `presetValues` must be the same length" )
# Find the value plug for `variableName` global context variable.
variablePlug = None
for p in scriptNode["variables"] :
if p["name"].getValue() == variableName :
variablePlug = p["value"]
break
if variablePlug is None :
raise RuntimeError( "\"{}\" not found in global context variables".format( variableName ) )
# Set the widget to a presets dropdown.
Gaffer.Metadata.registerValue( variablePlug, "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget" )
# Register the names and values.
Gaffer.Metadata.registerValue( variablePlug, "presetNames", IECore.StringVectorData( presetNames ) )
Gaffer.Metadata.registerValue( variablePlug, "presetValues", presetValues )
# Set the value to the first preset.
variablePlug.setValue( presetValues[0] )
# Tell Gaffer to rebuild the `Settings` dialog.
sw = GafferUI.ScriptWindow.acquire( scriptNode )
for window in sw.childWindows():
if hasattr( window, "_settingsEditor" ) :
sw.removeChild(window)
For example, to set a few presets for a global context variable named shot
run the script above in the Python editor, then run the line below.
globalContextPresets( root, "shot", ["shot A", "shot B", "shot C"], IECore.StringVectorData( [ "shotA", "shotB", "shotC" ] ) )
Adding Presets to a Menu
Global context variables can also be controlled from a customized menu in Gaffer's main application menu. In a script in your ~/gaffer/startup/gui
directory add the following code. It will add new menu item to Gaffer called Shot
. Each time the user opens it, it will be rebuilt by the method __shotMenu()
, which you can customize to populate the shot menu with shot names from any source accessible to Python such as a node in the node graph or an external database.
import functools
import IECore
import GafferUI
# This will be called each time the user makes a selection from the `Shot` menu.
def __setShot( shotPlug, shot, checked ) :
if shotPlug is not None :
shotPlug.setValue( shot )
# This will be called for each menu item when showing the menu. It should return `True`
# if it should be checked and `False` if it should not be.
def __activeShot( shotPlug, shot ) :
if shotPlug is not None :
return shotPlug.getValue() == shot
# This will be called each time the user opens the `Shot` menu. It should return an
# `IECore.MenuDefinition` object representing the menu.
def __shotMenu( menu ) :
globalContextVariable = "shot"
# Populate the menu with placeholder item if no plug is found for `globalContextVariable`
defaultItems = [ "None" ]
scriptWindow = menu.ancestor(GafferUI.ScriptWindow)
currentScript = scriptWindow.scriptNode()
# Find the plug for the value of the global context variable.
shotPlug = None
for p in currentScript["variables"] :
if p["name"].getValue() == globalContextVariable :
shotPlug = p["value"]
result = IECore.MenuDefinition()
if shotPlug is None :
shotList = defaultItems
else :
# Hardcoded shot list
shotList = ["shotA", "shotB", "shotC"]
# Shot list from the enabled rows of a spreadsheet named "shots"
# shotList = currentScript["shots"]["enabledRowNames"].getValue()
# Add a shot for each item in `shotList`. When selecting the item, the `__setShot()`
# function will be called with the the `shotPlug`, the `shot` value from the loop
# and a `checked` variable the Gaffer menu system adds.
# When determining if the item should have a checkmark next to it, `__activeShot()` will
# be called with the `shotPlug` and `shot` value from the loop.
for shot in shotList :
result.append(
"/" + shot,
{
"command": functools.partial( __setShot, shotPlug, shot ) ,
"checkBox": functools.partial( __activeShot, shotPlug, shot ),
}
)
return result
# Called once at the start of a Gaffer session. Note that this will not be called
# on subsequent loading of a script, since the newly created window will be in the same session.
scriptWindowMenu = GafferUI.ScriptWindow.menuDefinition( application )
scriptWindowMenu.append( "/Shot", { "subMenu": __shotMenu } )