diff --git a/lib/mayaUsd/resources/ae/usdschemabase/ae_template.py b/lib/mayaUsd/resources/ae/usdschemabase/ae_template.py index c0cb6149e4..813344a013 100644 --- a/lib/mayaUsd/resources/ae/usdschemabase/ae_template.py +++ b/lib/mayaUsd/resources/ae/usdschemabase/ae_template.py @@ -20,6 +20,7 @@ from .material_custom_control import MaterialCustomControl import collections +import contextlib import fnmatch import re import ufe @@ -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): @@ -713,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: @@ -724,6 +740,8 @@ 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"): @@ -731,29 +749,35 @@ def refresh(self): 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]) @@ -765,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() diff --git a/lib/usdUfe/python/wrapEditRouter.cpp b/lib/usdUfe/python/wrapEditRouter.cpp index 5f180ec049..acc50c4a24 100644 --- a/lib/usdUfe/python/wrapEditRouter.cpp +++ b/lib/usdUfe/python/wrapEditRouter.cpp @@ -202,5 +202,10 @@ void wrapEditRouter() using PrimMdThis = UsdUfe::PrimMetadataEditRouterContext; class_("PrimMetadataEditRouterContext", no_init) .def(init()) - .def(init()); + .def(init()) + .def(init< + const PXR_NS::UsdPrim&, + const PXR_NS::TfToken&, + const PXR_NS::TfToken&, + const PXR_NS::SdfLayerHandle&>()); } diff --git a/lib/usdUfe/ufe/UsdUndoClearSceneItemMetadataCommand.cpp b/lib/usdUfe/ufe/UsdUndoClearSceneItemMetadataCommand.cpp index d27b0ed75e..415c872416 100644 --- a/lib/usdUfe/ufe/UsdUndoClearSceneItemMetadataCommand.cpp +++ b/lib/usdUfe/ufe/UsdUndoClearSceneItemMetadataCommand.cpp @@ -59,11 +59,17 @@ void ClearSceneItemMetadataCommand::executeImplementation() prim.ClearCustomDataByKey(key); } else { // When the group name starts with "SessionLayer-", remove that prefix - // and clear in the session layer. + // and clear in the session layer if the operation is not editRouted. std::string prefixlessGroupName; if (isSessionLayerGroupMetadata(_group, &prefixlessGroupName)) { - PXR_NS::UsdEditContext editCtx(_stage, _stage->GetSessionLayer()); - PXR_NS::TfToken fullKey(prefixlessGroupName + std::string(":") + _key); + PXR_NS::TfToken fullKey(prefixlessGroupName + std::string(":") + _key); + + PrimMetadataEditRouterContext ctx( + prim, + PXR_NS::SdfFieldKeys->CustomData, + fullKey, + /*fallbackLayer=*/_stage->GetSessionLayer()); + prim.ClearCustomDataByKey(fullKey); } else { PXR_NS::TfToken fullKey(_group + std::string(":") + _key); diff --git a/lib/usdUfe/ufe/UsdUndoSetSceneItemMetadataCommand.cpp b/lib/usdUfe/ufe/UsdUndoSetSceneItemMetadataCommand.cpp index 1610ee6c38..625e3adabb 100644 --- a/lib/usdUfe/ufe/UsdUndoSetSceneItemMetadataCommand.cpp +++ b/lib/usdUfe/ufe/UsdUndoSetSceneItemMetadataCommand.cpp @@ -77,11 +77,17 @@ void SetSceneItemMetadataCommand::setGroupMetadata() const PXR_NS::UsdPrim prim = _stage->GetPrimAtPath(_primPath); // When the group name starts with "SessionLayer-", remove that prefix - // and write in the session layer. + // and write in the session layer if the operation is not editRouted. std::string prefixlessGroupName; if (isSessionLayerGroupMetadata(_group, &prefixlessGroupName)) { - PXR_NS::UsdEditContext editCtx(_stage, _stage->GetSessionLayer()); - PXR_NS::TfToken fullKey(prefixlessGroupName + std::string(":") + _key); + PXR_NS::TfToken fullKey(prefixlessGroupName + std::string(":") + _key); + + PrimMetadataEditRouterContext ctx( + prim, + PXR_NS::SdfFieldKeys->CustomData, + fullKey, + /*fallbackLayer=*/_stage->GetSessionLayer()); + prim.SetCustomDataByKey(fullKey, ufeValueToVtValue(_value)); } else { PXR_NS::TfToken fullKey(_group + std::string(":") + _key); diff --git a/lib/usdUfe/utils/editRouterContext.cpp b/lib/usdUfe/utils/editRouterContext.cpp index 5453595ac5..60d5447cb9 100644 --- a/lib/usdUfe/utils/editRouterContext.cpp +++ b/lib/usdUfe/utils/editRouterContext.cpp @@ -132,23 +132,27 @@ AttributeEditRouterContext::AttributeEditRouterContext( } PXR_NS::SdfLayerHandle PrimMetadataEditRouterContext::getPrimMetadataLayer( - const PXR_NS::UsdPrim& prim, - const PXR_NS::TfToken& metadataName, - const PXR_NS::TfToken& metadataKeyPath) + const PXR_NS::UsdPrim& prim, + const PXR_NS::TfToken& metadataName, + const PXR_NS::TfToken& metadataKeyPath, + const PXR_NS::SdfLayerHandle& fallbackLayer) { if (isTargetAlreadySet()) return nullptr; - return getPrimMetadataEditRouterLayer(prim, metadataName, metadataKeyPath); + auto routerLayer = getPrimMetadataEditRouterLayer(prim, metadataName, metadataKeyPath); + + return routerLayer ? routerLayer : fallbackLayer; } PrimMetadataEditRouterContext::PrimMetadataEditRouterContext( const PXR_NS::UsdPrim& prim, const PXR_NS::TfToken& metadataName, - const PXR_NS::TfToken& metadataKeyPath) + const PXR_NS::TfToken& metadataKeyPath, + const PXR_NS::SdfLayerHandle& fallbackLayer) : StackedEditRouterContext( prim.GetStage(), - getPrimMetadataLayer(prim, metadataName, metadataKeyPath)) + getPrimMetadataLayer(prim, metadataName, metadataKeyPath, fallbackLayer)) { } diff --git a/lib/usdUfe/utils/editRouterContext.h b/lib/usdUfe/utils/editRouterContext.h index d46a2527f7..cf54c5401f 100644 --- a/lib/usdUfe/utils/editRouterContext.h +++ b/lib/usdUfe/utils/editRouterContext.h @@ -142,11 +142,14 @@ class USDUFE_PUBLIC PrimMetadataEditRouterContext : public StackedEditRouterCont { public: /*! \brief Route a metadata operation on a prim for the given metadata / metadataKeyPath. + * If there is no editRouting for this metadata and a non-null fallbackLayer is given, + * it will be used as edit target. */ PrimMetadataEditRouterContext( - const PXR_NS::UsdPrim& prim, - const pxr::TfToken& metadataName, - const pxr::TfToken& metadataKeyPath = pxr::TfToken {}); + const PXR_NS::UsdPrim& prim, + const pxr::TfToken& metadataName, + const pxr::TfToken& metadataKeyPath = pxr::TfToken {}, + const PXR_NS::SdfLayerHandle& fallbackLayer = PXR_NS::SdfLayerHandle {}); /*! \brief Route to the given stage and layer. * Should be used in undo to ensure the same target is used as in the initial execution. @@ -159,7 +162,8 @@ class USDUFE_PUBLIC PrimMetadataEditRouterContext : public StackedEditRouterCont PXR_NS::SdfLayerHandle getPrimMetadataLayer( const PXR_NS::UsdPrim& prim, const PXR_NS::TfToken& metadataName, - const PXR_NS::TfToken& metadataKeyPath); + const PXR_NS::TfToken& metadataKeyPath, + const PXR_NS::SdfLayerHandle& fallbackLayer); }; } // namespace USDUFE_NS_DEF diff --git a/test/lib/ufe/testPrimMetadataEditRouting.py b/test/lib/ufe/testPrimMetadataEditRouting.py index 3c3613f6ee..9a3fcdafba 100644 --- a/test/lib/ufe/testPrimMetadataEditRouting.py +++ b/test/lib/ufe/testPrimMetadataEditRouting.py @@ -64,6 +64,12 @@ def createSimpleSceneItem(): stage = mayaUsd.lib.GetPrim(psPathStr).GetStage() prim = stage.DefinePrim('/Xf', 'Xform') + # Add a subLayer that will be used as editRouting destination by all tests. + # Note: We do not use session layer as some tests verify that we route + # edits that are authored in session by default. + layer = Sdf.Layer.CreateAnonymous("metadataLayerTag") + stage.GetRootLayer().subLayerPaths.append(layer.identifier) + return ufe.Hierarchy.createItem(ufe.Path([ ufe.PathString.path(psPathStr).segments[0], usdUtils.createUfePathSegment(str(prim.GetPath())), @@ -74,7 +80,15 @@ def getMetadataDestinationLayer(prim): ''' Returns the editRouting destination layer used by the following tests. ''' - return prim.GetStage().GetSessionLayer() + # Note: We do not use session layer here as some tests verify that we route + # edits that are authored in session by default. + for layer in prim.GetStage().GetLayerStack(False): + if layer.anonymous and layer.identifier.endswith('metadataLayerTag'): + destLayer = layer + break + else: + raise Exception("Could not find expected anonymous layer in layer stack") + return destLayer def varSelectionCmd(sceneItem, variantSetName, variantName): @@ -445,5 +459,30 @@ def testEditRouterForClearSceneItemGroupMetadata(self): lambda spec: self.assertNotIn('Group', spec.customData), ) + @unittest.skipUnless(ufeMetadataSupported, 'Available only if UFE supports metadata.') + def testEditRouterForSetSceneItemSessionMetadata(self): + ''' + Test edit router functionality for setting Ufe sceneItem metadata with 'SessionLayer-' + group prefix. They are authored in session layer by default, verify that we can + route them to another layer. + ''' + self._verifyEditRoutingForSetMetadata( + lambda item: item.setGroupMetadataCmd('SessionLayer-Autodesk', 'Key', 'Edited'), + lambda spec: self.assertEqual(spec.customData.get('Autodesk'), {'Key': 'Edited'}), + ) + + @unittest.skipUnless(ufeMetadataSupported, 'Available only if UFE supports metadata.') + def testEditRouterForClearSceneItemSessionMetadata(self): + ''' + Test edit router functionality for clearing Ufe sceneItem metadata with 'SessionLayer-' + group prefix. They are authored in session layer by default, verify that we can + route them to another layer. + ''' + self._verifyEditRoutingForClearMetadata( + lambda prim: prim.SetCustomDataByKey('Autodesk:Key', 'Edited'), + lambda item: item.clearGroupMetadataCmd('SessionLayer-Autodesk', 'Key'), + lambda spec: self.assertNotIn('Autodesk', spec.customData), + ) + if __name__ == '__main__': unittest.main(verbosity=2)