Skip to content

Commit

Permalink
Setting/clearing UFE metadata in "SessionLayer-*" groups can be editR…
Browse files Browse the repository at this point in the history
…outed.
  • Loading branch information
jufrantz committed Sep 23, 2024
1 parent 91199ad commit 5d0718d
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 37 deletions.
59 changes: 40 additions & 19 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 @@ -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:
Expand All @@ -724,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 @@ -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()
Expand Down
7 changes: 6 additions & 1 deletion lib/usdUfe/python/wrapEditRouter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,5 +202,10 @@ void wrapEditRouter()
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&>())
.def(init<
const PXR_NS::UsdPrim&,
const PXR_NS::TfToken&,
const PXR_NS::TfToken&,
const PXR_NS::SdfLayerHandle&>());
}
12 changes: 9 additions & 3 deletions lib/usdUfe/ufe/UsdUndoClearSceneItemMetadataCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 9 additions & 3 deletions lib/usdUfe/ufe/UsdUndoSetSceneItemMetadataCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
16 changes: 10 additions & 6 deletions lib/usdUfe/utils/editRouterContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
}

Expand Down
12 changes: 8 additions & 4 deletions lib/usdUfe/utils/editRouterContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
41 changes: 40 additions & 1 deletion test/lib/ufe/testPrimMetadataEditRouting.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())),
Expand All @@ -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):
Expand Down Expand Up @@ -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)

0 comments on commit 5d0718d

Please sign in to comment.