Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prim metadata editRouting #3790

Merged
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ab31d89
Prepare for primMetadata editRouting.
jufrantz May 28, 2024
8f53231
ToggleInstanceable command can be editRouted
jufrantz May 28, 2024
f033c96
ToggleActive command can be editRouted
jufrantz May 28, 2024
9e04811
SetKind command can be editRouted
jufrantz May 28, 2024
d07abbc
Operations on references/payload can be editRouted
jufrantz May 28, 2024
1da6452
SceneItemMetadata commands can be editRouted
jufrantz May 28, 2024
191c3ec
SetVariantSelection command can be editRouted
jufrantz May 28, 2024
62956c9
Merge branch 'dev' into issue3778/prim_metadata_edit_routing
jufrantz Sep 17, 2024
388a63c
Python bindings for usdUfe SetKindCommand
jufrantz Sep 23, 2024
5e60622
Setting kind metadata from prim AE template is now edit routed
jufrantz Sep 23, 2024
9fd66f1
Faster metadata per-stage layer routing
jufrantz Sep 23, 2024
85bcb08
Python overload to construct PrimMetadataEditRouterContext omitting m…
jufrantz Sep 23, 2024
fd1e2ed
Output errors raised when setting kind from AE such as editRouting pr…
jufrantz Sep 23, 2024
91199ad
Add test case for primMetadata editRouting
jufrantz Sep 23, 2024
5d0718d
Setting/clearing UFE metadata in "SessionLayer-*" groups can be editR…
jufrantz Sep 23, 2024
0c783b4
Add documentation about primMetadata editRouting
jufrantz Sep 23, 2024
a2171ac
clang-format
jufrantz Sep 23, 2024
c0f8a94
Fix primMetadataEditRouting test for USD<23.11.
jufrantz Sep 24, 2024
b317f57
SetKind command enforces edit restrictions
jufrantz Sep 24, 2024
ca1ebb7
SetSceneItemMetadata command enforces edit restrictions
jufrantz Sep 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion doc/EditRouting.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ data indexed by USD tokens (TfToken):
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:
four categories:

- Simple commands
- Attributes
- Prim metadata
- Maya references

The following sections describe the input and output of each category. Each
Expand Down Expand Up @@ -121,6 +122,45 @@ def routeAttrToSessionLayer(context, routingData):
routingData['layer'] = prim.GetStage().GetSessionLayer().identifier
```

### Prim Metadata

Inputs:
- prim: the USD prim (UsdPrim) that is being affected.
- operation: the operation name (TfToken). Always 'primMetadata'.
- primMetadata: the metadata name (TfToken), e.g. "variantSelection"
- keyPath: the path of the edited key if the metadata is dict-valued (TfToken),
e.g. the variantSet name for "variantSelection" metadata, the key of a "customData".

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 primMetadata edit router:

```Python
def routeVariantSelectionToSessionLayer(context, routingData):
'''
Edit router implementation for 'primMetadata' operations that routes
variant selections within variantSets named 'mySessionVariant' to the
session layer of the stage that contains the prim.
'''
prim = context.get('prim')
if prim is None:
return

metadataName = context.get('primMetadata')
if metadataName != "variantSelection":
return

variantSetName = context.get('keyPath')
if variantSetName != "mySessionVariant":
return

routingData['layer'] = prim.GetStage().GetSessionLayer().identifier

mayaUsd.lib.registerEditRouter('primMetadata', routeVariantSelectionToSessionLayer)
```

### Maya references

The maya reference edit routing is more complex than the other ones. It is
Expand Down Expand Up @@ -325,6 +365,7 @@ could be used:
import mayaUsd.lib

sessionAttributes = set(['visibility', 'radius'])
sessionVariantSets = set(['rigVariants', 'proxyVariants'])

def routeToSessionLayer(context, routingData):
'''
Expand Down Expand Up @@ -354,6 +395,27 @@ def routeAttrToSessionLayer(context, routingData):

routingData['layer'] = prim.GetStage().GetSessionLayer().identifier

def routeVariantSelectionToSessionLayer(context, routingData):
'''
Edit router implementation for 'primMetadata' operations that routes
some variantSelection 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

metadataName = context.get('primMetadata')
if metadataName != 'variantSelection':
return

variantSetName = context.get('keyPath')
if variantSetName not in sessionVariantSets:
return

routingData['layer'] = prim.GetStage().GetSessionLayer().identifier

def registerAttributeEditRouter():
'''
Register an edit router for the 'attribute' operation that routes to
Expand All @@ -368,8 +430,16 @@ def registerVisibilityEditRouter():
'''
mayaUsd.lib.registerEditRouter('visibility', routeToSessionLayer)

def registerPrimMetadataEditRouter():
'''
Register an edit router for the 'primMetadata' operation that routes to
the session layer.
'''
mayaUsd.lib.registerEditRouter('primMetadata', routeVariantSelectionToSessionLayer)

def registerEditRouters():
registerAttributeEditRouter()
registerVisibilityEditRouter()
registerPrimMetadataEditRouter()

```
82 changes: 56 additions & 26 deletions lib/mayaUsd/resources/ae/usdschemabase/ae_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .material_custom_control import MaterialCustomControl

import collections
import contextlib
import fnmatch
import re
import ufe
Expand Down Expand Up @@ -136,6 +137,15 @@ def onReplace(self, *args):
# Nothing needed here since we don't create any UI.
pass

@contextlib.contextmanager
def PrimCustomDataEditRouting(prim, *args):
'''
A context manager that activates prim customData editRouting.
'''
# Note: the edit router context must be kept alive in a variable.
ctx = mayaUsdUfe.PrimMetadataEditRouterContext(prim, Sdf.PrimSpec.CustomDataKey, *args)
yield

class MetaDataCustomControl(object):
'''Custom control for all prim metadata we want to display.'''
def __init__(self, item, prim, useNiceName):
Expand Down Expand Up @@ -220,11 +230,7 @@ def onReplace(self, *args):
# that case we don't need to update our controls since none will change.
pass

def refresh(self):
# PrimPath
cmds.textFieldGrp(self.primPath, edit=True, text=str(self.prim.GetPath()))

# Kind
def _refreshKind(self):
model = Usd.ModelAPI(self.prim)
primKind = model.GetKind()
if not primKind:
Expand All @@ -233,6 +239,13 @@ def refresh(self):
else:
cmds.optionMenuGrp(self.kind, edit=True, value=primKind)

def refresh(self):
# PrimPath
cmds.textFieldGrp(self.primPath, edit=True, text=str(self.prim.GetPath()))

# Kind
self._refreshKind()

# Active
cmds.checkBoxGrp(self.active, edit=True, value1=self.prim.IsActive())

Expand All @@ -246,8 +259,14 @@ def refresh(self):

def _onKindChanged(self, value):
with mayaUsdLib.UsdUndoBlock():
model = Usd.ModelAPI(self.prim)
model.SetKind(value)
try:
usdUfe.SetKindCommand(self.prim, value).execute()
except Exception as ex:
# Note: the command might not work because there is a stronger
# opinion or an editRouting prevention so update the option menu
self._refreshKind()
cmds.error(str(ex))


def _onActiveChanged(self, value):
with mayaUsdLib.UsdUndoBlock():
Expand Down Expand Up @@ -704,9 +723,15 @@ def onCreate(self, *args):
def onReplace(self, *args):
pass

def clear(self):
def _clearUseOutlinerColor(self):
cmds.checkBoxGrp(self.useOutlinerColor, edit=True, v1=False)

def _clearOutlinerColor(self):
cmds.colorSliderGrp(self.outlinerColor, edit=True, rgb=(0,0,0))

def clear(self):
self._clearUseOutlinerColor()
self._clearOutlinerColor()

def refresh(self):
try:
Expand All @@ -715,36 +740,44 @@ def refresh(self):
useOutlinerColor = self.item.getGroupMetadata(self.GROUP, self.USE_OUTLINER_COLOR)
if not useOutlinerColor.empty() and (useOutlinerColor.typeName() == 'bool'):
cmds.checkBoxGrp(self.useOutlinerColor, edit=True, v1=bool(useOutlinerColor))
else:
self._clearUseOutlinerColor()

outlinerColor = self.item.getGroupMetadata(self.GROUP, self.OUTLINER_COLOR)
if not outlinerColor.empty() and (outlinerColor.typeName() == "ufe.Vector3d"):
# Color is stored as double3 USD custom data.
clr = ufe.Vector3d(outlinerColor)
cmds.colorSliderGrp(self.outlinerColor, edit=True,
rgb=(clr.x(), clr.y(), clr.z()))
else:
self._clearOutlinerColor()
else:
# Get the custom data directly from USD.
useOutlinerColor = self.prim.GetCustomDataByKey(self.USE_OUTLINER_COLOR)
if useOutlinerColor is not None and isinstance(useOutlinerColor, bool):
cmds.checkBoxGrp(self.useOutlinerColor, edit=True, v1=useOutlinerColor)
else:
self._clearUseOutlinerColor()

outlinerColor = self.prim.GetCustomDataByKey(self.OUTLINER_COLOR)
if outlinerColor is not None and isinstance(outlinerColor, Gf.Vec3d):
# Color is stored as double3 USD custom data.
cmds.colorSliderGrp(self.outlinerColor, edit=True,
rgb=(outlinerColor[0], outlinerColor[1], outlinerColor[2]))
else:
self._clearOutlinerColor()
except:
self.clear()

def _updateTextColorChanged(self):
'''Update the text color custom data for this prim based on the values
set in the two fields.'''
currEditTarget = None
# Get the value of "Use Outliner Color" checkbox.
useTextColor = cmds.checkBoxGrp(self.useOutlinerColor, query=True, v1=True)
# Get the value of "Outliner Color" color slider.
rgb = cmds.colorSliderGrp(self.outlinerColor, query=True, rgbValue=True)
try:
if self.useMetadata:
useTextColor = cmds.checkBoxGrp(self.useOutlinerColor, query=True, v1=True)
rgb = cmds.colorSliderGrp(self.outlinerColor, query=True, rgbValue=True)

# Get ufe commands for the two metadata.
cmd1 = self.item.setGroupMetadataCmd(self.GROUP, self.USE_OUTLINER_COLOR, useTextColor)
ufeVec = ufe.Vector3d(rgb[0], rgb[1], rgb[2])
Expand All @@ -756,20 +789,17 @@ def _updateTextColorChanged(self):
else:
with mayaUsdLib.UsdUndoBlock():
# As initially decided write out the color custom data to the session layer.
stage = self.prim.GetStage()
currEditTarget = stage.GetEditTarget()
stage.SetEditTarget(stage.GetSessionLayer())

# Get the value of "Use Outliner Color" checkbox and set in custom data.
useTextColor = cmds.checkBoxGrp(self.useOutlinerColor, query=True, v1=True)
self.prim.SetCustomDataByKey(self.USE_OUTLINER_COLOR, useTextColor)

# Get the value of "Outliner Color" color slider and set in custom data.
rgb = cmds.colorSliderGrp(self.outlinerColor, query=True, rgbValue=True)
self.prim.SetCustomDataByKey(self.OUTLINER_COLOR, Gf.Vec3d(rgb[0], rgb[1], rgb[2]))
finally:
if currEditTarget is not None:
stage.SetEditTarget(currEditTarget)
# It still can be edit-routed as a 'primMetadata' operation.
fallbackLayer = self.prim.GetStage().GetSessionLayer()
with PrimCustomDataEditRouting(self.prim, self.USE_OUTLINER_COLOR, fallbackLayer):
self.prim.SetCustomDataByKey(self.USE_OUTLINER_COLOR, useTextColor)
with PrimCustomDataEditRouting(self.prim, self.OUTLINER_COLOR, fallbackLayer):
self.prim.SetCustomDataByKey(self.OUTLINER_COLOR, Gf.Vec3d(rgb[0], rgb[1], rgb[2]))
except Exception as ex:
# Note: the command might not work because there is a stronger
# opinion or an editRouting prevention so update the metadata controls.
self.refresh()
cmds.error(str(ex))

def _onUseOutlinerColorChanged(self, value):
self._updateTextColorChanged()
Expand Down
3 changes: 3 additions & 0 deletions lib/usdUfe/base/tokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@ namespace USDUFE_NS_DEF {
/* Stage received in the context of some router */ \
((Stage, "stage")) \
((EditTarget, "editTarget")) \
/* Metadata key path received in the context */ \
((KeyPath, "keyPath")) \
jufrantz marked this conversation as resolved.
Show resolved Hide resolved
\
/* Routing operations */ \
\
((RouteParent, "parent")) \
((RouteDuplicate, "duplicate")) \
((RouteVisibility, "visibility")) \
((RouteAttribute, "attribute")) \
((RoutePrimMetadata, "primMetadata")) \
((RouteDelete, "delete")) \
((RouteTransform, "transform")) \
\
Expand Down
15 changes: 15 additions & 0 deletions lib/usdUfe/python/wrapCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <usdUfe/ufe/UsdUndoPayloadCommand.h>
#include <usdUfe/ufe/UsdUndoReloadRefCommand.h>
#include <usdUfe/ufe/UsdUndoSetDefaultPrimCommand.h>
#include <usdUfe/ufe/UsdUndoSetKindCommand.h>
#include <usdUfe/ufe/UsdUndoToggleActiveCommand.h>
#include <usdUfe/ufe/UsdUndoToggleInstanceableCommand.h>

Expand Down Expand Up @@ -67,6 +68,12 @@ UsdUfe::UsdUndoToggleInstanceableCommand* ToggleInstanceableCommandInit(const PX
return new UsdUfe::UsdUndoToggleInstanceableCommand(prim);
}

UsdUfe::UsdUndoSetKindCommand*
SetKindCommandInit(const PXR_NS::UsdPrim& prim, const PXR_NS::TfToken& kind)
{
return new UsdUfe::UsdUndoSetKindCommand(prim, kind);
}

UsdUfe::UsdUndoLoadPayloadCommand*
LoadPayloadCommandInit(const PXR_NS::UsdPrim& prim, PXR_NS::UsdLoadPolicy policy)
{
Expand Down Expand Up @@ -165,6 +172,14 @@ void wrapCommands()
.def("undo", &UsdUfe::UsdUndoToggleInstanceableCommand::undo)
.def("redo", &UsdUfe::UsdUndoToggleInstanceableCommand::redo);
}
{
using This = UsdUfe::UsdUndoSetKindCommand;
class_<This, boost::noncopyable>("SetKindCommand", no_init)
.def("__init__", make_constructor(SetKindCommandInit))
.def("execute", &UsdUfe::UsdUndoSetKindCommand::execute)
.def("undo", &UsdUfe::UsdUndoSetKindCommand::undo)
.def("redo", &UsdUfe::UsdUndoSetKindCommand::redo);
}
{
using This = UsdUfe::UsdUndoLoadPayloadCommand;
class_<This, boost::noncopyable>("LoadPayloadCommand", no_init)
Expand Down
10 changes: 10 additions & 0 deletions lib/usdUfe/python/wrapEditRouter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,14 @@ void wrapEditRouter()
using AttrThis = UsdUfe::AttributeEditRouterContext;
class_<AttrThis, boost::noncopyable>("AttributeEditRouterContext", no_init)
.def("__init__", make_constructor(AttributeEditRouterContextInit));

using PrimMdThis = UsdUfe::PrimMetadataEditRouterContext;
class_<PrimMdThis, boost::noncopyable>("PrimMetadataEditRouterContext", no_init)
.def(init<const PXR_NS::UsdPrim&, const PXR_NS::TfToken&>())
.def(init<const PXR_NS::UsdPrim&, const PXR_NS::TfToken&, const PXR_NS::TfToken&>())
.def(init<
const PXR_NS::UsdPrim&,
const PXR_NS::TfToken&,
const PXR_NS::TfToken&,
const PXR_NS::SdfLayerHandle&>());
}
16 changes: 12 additions & 4 deletions lib/usdUfe/ufe/SetVariantSelectionCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "SetVariantSelectionCommand.h"

#include <usdUfe/ufe/Utils.h>
#include <usdUfe/utils/editRouterContext.h>

#include <pxr/usd/usd/variantSets.h>

Expand Down Expand Up @@ -50,15 +51,20 @@ SetVariantSelectionCommand::SetVariantSelectionCommand(

void SetVariantSelectionCommand::redo()
{
const PXR_NS::TfToken metadataKeyPath(_varSet.GetName());

PrimMetadataEditRouterContext ctx(
_prim, PXR_NS::SdfFieldKeys->VariantSelection, metadataKeyPath);

std::string errMsg;
if (!UsdUfe::isPrimMetadataEditAllowed(
_prim,
PXR_NS::SdfFieldKeys->VariantSelection,
PXR_NS::TfToken(_varSet.GetName()),
&errMsg)) {
_prim, PXR_NS::SdfFieldKeys->VariantSelection, metadataKeyPath, &errMsg)) {
throw std::runtime_error(errMsg.c_str());
}

// Backup the destination layer for consistent undo.
_dstLayer = _prim.GetStage()->GetEditTarget().GetLayer();

// Make a copy of the global selection, to restore it on undo.
auto globalSn = Ufe::GlobalSelection::get();
_savedSn.replaceWith(*globalSn);
Expand All @@ -69,6 +75,8 @@ void SetVariantSelectionCommand::redo()

void SetVariantSelectionCommand::undo()
{
PrimMetadataEditRouterContext ctx(_prim.GetStage(), _dstLayer);

std::string errMsg;
if (!UsdUfe::isPrimMetadataEditAllowed(
_prim,
Expand Down
Loading