By default, when a Maya user modifies USD data, the modifications are written to the current edit target. That is, to the current targeted layer. The current layer is selected in the Layer Editor window in Maya.
Edit routing is a mechanism to select which USD layer will receive edits. When a routable edit is about to happen, the Maya USD plugin can temporarily change the targeted layer so that the modifications are written to a specific layer, instead of the current target layer.
The mechanism allows users to write code (scripts or plugins) to handle the routing. The script receives information about which USD prim is about to be change and the name of the operation that is about to modify that prim. In the case of attribute changes, the name of the attribute is also provided.
Given these informations, the script can choose a specific layer among the available layers. It returns the layer it wants to be targeted. If the script does not wish to select a specific layer, it just returns nothing and the current targeted layer will be used.
Currently, edit routing is divided in two categories: commands and attributes.
For commands, when the edit routing is called it receives an operation name that corresponds to the specific command to be routed. Only a subset of commands can be routed, but this subset is expected to grow. The operations that can be routed are named:
- duplicate
- parent (including parenting and grouping)
- visibility
- mayaReferencePush
For attributes, any modifications to the attribute value, including metadata, can be routed. The edit routing is called with an 'attribute' operation name and receives the name of the attribute that is about to be modified.
An edit router can be written in C++ or Python. The principle is the same in both cases, so we will describe the Python version. The C++ version is similar.
The edit router is a function that receives two arguments. The arguments are both dictionaries (dict in Python, VtDictionary in C++). Each is filled with data indexed by USD tokens (TfToken):
- Context: the input context of the routing.
- Routing: the output data of the routing.
In theory, each edit routing operation could fill the context differently and expect different data in the output dictionary. In practice many operations share the same inputs and outputs. Currently, the operations can be divided in three categories:
- Simple commands
- Attributes
- Maya references
The following sections describe the input and output of each category. Each input or output is listed with its token name and the data that it contains.
Inputs:
- prim: the USD prim (UsdPrim) that is being affected.
- operation: the operation name (TfToken). Either visibility, duplicate or parent.
Outputs:
- layer: the desired layer ID (text string) or layer handle (SdfLayerHandle).
On return, if the layer entry is empty, no routing is done and the current edit target is used. Here is an example of a simple edit router:
def routeToSessionLayer(context, routingData):
'''
Edit router implementation for that routes to the session layer
of the stage that contains the prim.
'''
prim = context.get('prim')
if prim is None:
print('Prim not in context')
return
routingData['layer'] = prim.GetStage().GetSessionLayer().identifier
Inputs:
- prim: the USD prim (UsdPrim) that is being affected.
- operation: the operation name (TfToken). Always attribute.
- attribute: the attribute name, including its namespace, if any (TfToken).
Outputs:
- layer: the desired layer ID (text string) or layer handle (SdfLayerHandle).
On return, if the layer entry is empty, no routing is done and the current edit target is used. Here is an example of an attribute edit router:
def routeAttrToSessionLayer(context, routingData):
'''
Edit router implementation for 'attribute' operations that routes
to the session layer of the stage that contains the prim.
'''
prim = context.get('prim')
if prim is None:
print('Prim not in context')
return
attrName = context.get('attribute')
if attrName != "visibility":
return
routingData['layer'] = prim.GetStage().GetSessionLayer().identifier
The maya reference edit routing is more complex than the other ones. It is described in the following documentation: Maya Reference Edit Router.
The Maya USD plugin provides C++ and Python functions to register edit routers
and to remove them. The function to register an edit router is called
registerEditRouter
and takes as arguments the name of the operation to be
routed, as a USD token (TfToken) and the function that will do the routing.
For example, the following Python script routes the visibility
operation
using a function called routeToSessionLayer
:
import mayaUsd.lib
mayaUsd.lib.registerEditRouter('visibility', routeToSessionLayer)
Routing attributes that are affected by certain commands is still necessary, as
routing a command might not route changes on the attribute itself.
When Routing Attributes, using the routeAttrToSessionLayer
example above,
the function used to register would be similar - only using the attribute
argument:
import mayaUsd.lib
mayaUsd.lib.registerEditRouter('attribute', routeAttrToSessionLayer)
The function to turn off an edit router for a given operation is called
restoreDefaultEditRouter
. It restores the default edit router for that
operation. For example, to turn off the edit router for visibility
:
import mayaUsd.lib
mayaUsd.lib.restoreDefaultEditRouter('visibility')
There is also a function to turn off all edit routing for all operations,
called restoreAllDefaultEditRouters
. It takes no argument. For example:
import mayaUsd.lib
mayaUsd.lib.restoreAllDefaultEditRouters()
There is another way to route edits designed to make routing faster for the
common simple case where a command should always be routed to the same layer.
This is called fast edit routing. It is used by calling a different function
to register the route: registerStageLayerEditRouter
. This function takes
the name of the operation to route, a stage and a layer and will route that
operation to the given layer.
The advantage is that it avoids creating temporary dictionaries to ask where to route the edits, is pure C++ code and thus is optimized even when used from Python. If you want to always route a given operation to the same layer for a given stage, that is the recommended way of doing it.
Here is an example of routing the transform commands in Python using the fast edit routing:
import mayaUsd.lib
def getStage():
'''
Here we hard-code the stage, but a more sphoisticated script
would select the stage using some mechanism, like UI or
based on notifications when a stage is created.
'''
psPathStr = "|stage1|stageShape1"
return mayaUsd.lib.GetPrim(psPathStr).GetStage()
def getLayer(stage):
'''Here we route to the session layer, but any known layer could be used.'''
return stage.GetSessionLayer()
stage = getStage()
layer = getLayer(stage)
mayaUsd.lib.registerStageLayerEditRouter('transform', stage, layer)
It is possible to prevent a command from executing instead of simply routing to a layer. This is done by raising an exception in the edit router. The command handles the exception and does not execute. This is how an end-user (or studio) can block certain types of edits. For example, to prevent artists from modifying things they should not touch. For instance, a lighting specialist might not be allowed to move props in a scene.
For example, to prevent all opertions for which it is registered, one could use the following Python edit router:
def preventCommandEditRouter(context, routingData):
'''
Edit router that prevents an operation from happening.
'''
opName = context.get('operation') or 'unknown operation'
raise Exception('Sorry, %s is not permitted' % opName)
When writing composite commands, that is a command made-up of sub-commands, it is possible to route the whole command. This will route all sub-commands to the same destination layer and ignore individual routing of each individual sub-command.
To do this, one must choose a new and unique operation name for the composite command. We suggest that you use some unique prefix for your operation names to avoid conflict with future operation the Maya team might add in the future.
Then one use the OperationEditRouterContext
object in the composite command
implementation to automatically do the routing. It is important to use the
router countext in all functions so that the sub-commands are routed to the
correct layer. Here is an example of a composite command written in Python:
import mayaUsd.lib
import ufe
class CustomCompositeCmd(ufe.CompositeUndoableCommand):
'''
Custom composite command that can be routed.
'''
customOpName = 'my studio name: custom operation name'
def __init__(self, prim, sceneItem):
super().__init__()
self._prim = prim
ctx = mayaUsd.lib.OperationEditRouterContext(self.customOpName, self._prim)
o3d = ufe.Object3d.object3d(sceneItem)
self.append(o3d.setVisibleCmd(False))
def execute(self):
ctx = mayaUsd.lib.OperationEditRouterContext(self.customOpName, self._prim)
super().execute()
def undo(self):
ctx = mayaUsd.lib.OperationEditRouterContext(self.customOpName, self._prim)
super().undo()
def redo(self):
ctx = mayaUsd.lib.OperationEditRouterContext(self.customOpName, self._prim)
super().redo()
Afterward, to route the new custom command, one would register an edit router with the custom operation name, like any other simple command. For example, in Python, it would look like this:
import mayaUsd.lib
def routeCmdToRootLayer(context, routingData):
'''
Route the command to the root layer.
'''
prim = context.get('prim')
if prim:
routingData['layer'] = prim.GetStage().GetRootLayer().identifier
mayaUsd.lib.registerEditRouter('my studio name: custom operation name', routeCmdToRootLayer)
Edit routers must be registered with the MayaUSD plugin each time Maya is
launched. This can be automated via the standard Maya startup scripts.
The user startup script is called userSetup.mel
. It is located in the
scripts
folder in the yearly Maya release folder in the user's Documents
folder. For example: Documents\maya\2024\scripts\userSetup.mel
.
This is a MEL script, so any logic necessary to register a given set of edit routers can be performed. For example, one can detect that the Maya USD plugin is loaded and register the custom edit routers like this:
global proc registerUserEditRouters() {
if (`pluginInfo -q -loaded mayaUsdPlugin`) {
python("import userEditRouters; userEditRouters.registerEditRouters()");
} else {
print("*** Missing Maya USD plugin!!! ***");
}
}
scriptJob -permanent -event "NewSceneOpened" "registerUserEditRouters";
This requires the Python script that does the registration of edit routers
to exists in the user site-packages
, located next to the user scripts in
this folder: Documents\maya\2024\scripts\site-packages
.
For example, to continue the example given above, the following Python script could be used:
import mayaUsd.lib
sessionAttributes = set(['visibility', 'radius'])
def routeToSessionLayer(context, routingData):
'''
Edit router implementation for that routes to the session layer
of the stage that contains the prim.
'''
prim = context.get('prim')
if prim is None:
print('Prim not in context')
return
routingData['layer'] = prim.GetStage().GetSessionLayer().identifier
def routeAttrToSessionLayer(context, routingData):
'''
Edit router implementation for 'attribute' operations that routes
to the session layer of the stage that contains the prim.
'''
prim = context.get('prim')
if prim is None:
print('Prim not in context')
return
attrName = context.get('attribute')
if attrName not in sessionAttributes:
return
routingData['layer'] = prim.GetStage().GetSessionLayer().identifier
def registerAttributeEditRouter():
'''
Register an edit router for the 'attribute' operation that routes to
the session layer.
'''
mayaUsd.lib.registerEditRouter('attribute', routeAttrToSessionLayer)
def registerVisibilityEditRouter():
'''
Register an edit router for the 'visibility' operation that routes to
the session layer.
'''
mayaUsd.lib.registerEditRouter('visibility', routeToSessionLayer)
def registerEditRouters():
registerAttributeEditRouter()
registerVisibilityEditRouter()