Skip to content

Commit

Permalink
Merge pull request #3695 from AnimalLogic/NickWu/undoableDeleteCommand
Browse files Browse the repository at this point in the history
Support EditRouter in delete command
  • Loading branch information
seando-adsk authored Apr 29, 2024
2 parents 8624d4a + 98b477c commit cfcbd08
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 13 deletions.
38 changes: 31 additions & 7 deletions lib/mayaUsd/ufe/UsdUndoDeleteCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@

#include "private/UfeNotifGuard.h"

#include <usdUfe/base/tokens.h>
#include <usdUfe/ufe/Utils.h>
#include <usdUfe/undo/UsdUndoBlock.h>
#include <usdUfe/utils/editRouter.h>
#include <usdUfe/utils/layers.h>
#include <usdUfe/utils/usdUtils.h>

Expand Down Expand Up @@ -60,20 +62,42 @@ void UsdUndoDeleteCommand::execute()
const auto& stage = _prim.GetStage();
auto targetPrimSpec = stage->GetEditTarget().GetPrimSpecForScenePath(_prim.GetPath());

if (UsdUfe::applyCommandRestrictionNoThrow(_prim, "delete")) {
PXR_NS::UsdEditTarget routingEditTarget
= getEditRouterEditTarget(UsdUfe::EditRoutingTokens->RouteDelete, _prim);

#ifdef UFE_V4_FEATURES_AVAILABLE
UsdAttributes::removeAttributesConnections(_prim);
UsdAttributes::removeAttributesConnections(_prim);
#endif
// Let removeAttributesConnections be run first as it will also cleanup
// attributes that were authored only to be the destination of a connection.
if (!UsdUfe::cleanReferencedPath(_prim)) {
const std::string error = TfStringPrintf(
"Failed to cleanup references to prim \"%s\".", _prim.GetPath().GetText());
// Let removeAttributesConnections be run first as it will also cleanup
// attributes that were authored only to be the destination of a connection.
if (!UsdUfe::cleanReferencedPath(_prim)) {
const std::string error = TfStringPrintf(
"Failed to cleanup references to prim \"%s\".", _prim.GetPath().GetText());
TF_WARN("%s", error.c_str());
throw std::runtime_error(error);
}

if (!routingEditTarget.IsNull()) {
PXR_NS::UsdEditContext ctx(stage, routingEditTarget);

if (!UsdUfe::applyCommandRestrictionNoThrow(_prim, "delete", true))
return;

if (!stage->RemovePrim(_prim.GetPath())) {
const std::string error
= TfStringPrintf("Failed to delete prim \"%s\".", _prim.GetPath().GetText());
TF_WARN("%s", error.c_str());
throw std::runtime_error(error);
}
} else {
if (!UsdUfe::applyCommandRestrictionNoThrow(_prim, "delete"))
return;

PrimSpecFunc deleteFunc
= [stage](const UsdPrim& prim, const SdfPrimSpecHandle& primSpec) -> void {
if (!primSpec)
return;

PXR_NS::UsdEditContext ctx(stage, primSpec->GetLayer());
if (!stage->RemovePrim(prim.GetPath())) {
const std::string error
Expand Down
2 changes: 2 additions & 0 deletions lib/usdUfe/base/tokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ namespace USDUFE_NS_DEF {
((Operation, "operation")) \
/* Stage received in the context of some router */ \
((Stage, "stage")) \
((EditTarget, "editTarget")) \
\
/* Routing operations */ \
\
((RouteParent, "parent")) \
((RouteDuplicate, "duplicate")) \
((RouteVisibility, "visibility")) \
((RouteAttribute, "attribute")) \
((RouteDelete, "delete")) \
\
// clang-format on

Expand Down
35 changes: 31 additions & 4 deletions lib/usdUfe/python/wrapEditRouter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class PyEditRouter : public UsdUfe::EditRouter
if (!PyCallable_Check(_pyCb)) {
return;
}
boost::python::object dictObject(routingData);
boost::python::dict dictObject(routingData);
try {
call<void>(_pyCb, context, dictObject);
} catch (const boost::python::error_already_set&) {
Expand All @@ -77,9 +77,36 @@ class PyEditRouter : public UsdUfe::EditRouter
TF_WARN("%s", ex.what());
throw;
}
boost::python::extract<PXR_NS::VtDictionary> extractedDict(dictObject);
if (extractedDict.check()) {
routingData = extractedDict;

// Extract keys and values individually so that we can extract
// PXR_NS::UsdEditTarget correctly from PXR_NS::TfPyObjWrapper.
const boost::python::object items = dictObject.items();
for (boost::python::ssize_t i = 0; i < len(items); ++i) {

boost::python::extract<std::string> keyExtractor(items[i][0]);
if (!keyExtractor.check()) {
continue;
}

boost::python::extract<PXR_NS::VtValue> valueExtractor(items[i][1]);
if (!valueExtractor.check()) {
continue;
}

auto vtvalue = valueExtractor();

if (vtvalue.IsHolding<PXR_NS::TfPyObjWrapper>()) {
const auto wrapper = vtvalue.Get<PXR_NS::TfPyObjWrapper>();

PXR_NS::TfPyLock lock;
boost::python::extract<PXR_NS::UsdEditTarget> editTargetExtractor(wrapper.Get());
if (editTargetExtractor.check()) {
auto editTarget = editTargetExtractor();
routingData[keyExtractor()] = PXR_NS::VtValue(editTarget);
}
} else {
routingData[keyExtractor()] = vtvalue;
}
}
}

Expand Down
10 changes: 9 additions & 1 deletion lib/usdUfe/ufe/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,15 @@ bool allowedInStrongerLayer(
? stage->GetSessionLayer()
: stage->GetRootLayer();

return getStrongerLayer(searchRoot, targetLayer, topLayer) == targetLayer;
auto strongerLayer = getStrongerLayer(searchRoot, targetLayer, topLayer);

// This happens when the edit target layer is within the reference.
// In this cae, we return true to allow it to be edited.
if (!strongerLayer) {
return true;
}

return strongerLayer == targetLayer;
}

} // namespace
Expand Down
26 changes: 26 additions & 0 deletions lib/usdUfe/utils/editRouter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,30 @@ getAttrEditRouterLayer(const PXR_NS::UsdPrim& prim, const PXR_NS::TfToken& attrN
}
}

PXR_NS::UsdEditTarget
getEditRouterEditTarget(const PXR_NS::TfToken& operation, const PXR_NS::UsdPrim& prim)
{
const auto dstEditRouter = getEditRouter(operation);
if (!dstEditRouter) {
return PXR_NS::UsdEditTarget();
}

PXR_NS::VtDictionary context;
PXR_NS::VtDictionary routingData;
context[EditRoutingTokens->Prim] = PXR_NS::VtValue(prim);
context[EditRoutingTokens->Operation] = operation;
(*dstEditRouter)(context, routingData);

const auto found = routingData.find(EditRoutingTokens->EditTarget);

if (found != routingData.end()) {
const auto& value = found->second;
if (value.IsHolding<PXR_NS::UsdEditTarget>()) {
const auto editTarget = value.Get<PXR_NS::UsdEditTarget>();
return editTarget;
}
}
return PXR_NS::UsdEditTarget();
}

} // namespace USDUFE_NS_DEF
8 changes: 8 additions & 0 deletions lib/usdUfe/utils/editRouter.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ USDUFE_PUBLIC
PXR_NS::SdfLayerHandle
getAttrEditRouterLayer(const PXR_NS::UsdPrim& prim, const PXR_NS::TfToken& attrName);

// Utility function that returns a UsdEditTarget for the argument operation.
// If no edit router exists for that operation, a null UsdEditTarget is returned.
// The edit router is given the prim in the context with key "prim", and is
// expected to return the UsdEditTarget which can be used to set edit target.
USDUFE_PUBLIC
PXR_NS::UsdEditTarget
getEditRouterEditTarget(const PXR_NS::TfToken& operation, const PXR_NS::UsdPrim& prim);

// Register an edit router for the argument operation.
USDUFE_PUBLIC
void registerEditRouter(const PXR_NS::TfToken& operation, const EditRouter::Ptr& editRouter);
Expand Down
66 changes: 65 additions & 1 deletion test/lib/ufe/testDeleteCmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@
import testUtils

import mayaUsd.ufe
from mayaUsd import lib as mayaUsdLib

from maya import cmds
from maya import standalone

import ufe

from pxr import Sdf, UsdShade
from pxr import Sdf, UsdShade, Usd

import os
import unittest
Expand Down Expand Up @@ -647,6 +648,69 @@ def testDeleteRestrictionMutedLayer(self):
with self.assertRaises(RuntimeError):
cmds.delete('%s,/A/ball' % proxyShapePathStr)
self.assertTrue(stage.GetPrimAtPath('/A/ball'))

def testDeletePrimUsingEditRouter(self):
'''Test deleting prims using edit target layer from an EditRouter.'''

def editRouterCallback(context, routingData):
prim = context.get('prim')
if prim is None:
return

stage = prim.GetStage()
targetLayer = None
editTarget = None

# Get non-local referenced layer for edit target layer.
for layer in stage.GetUsedLayers():
if 'subCRef' in layer.identifier:
targetLayer = layer
break

query = Usd.PrimCompositionQuery(prim)
for arc in query.GetCompositionArcs():
if (arc.GetTargetNode().layerStack.identifier.rootLayer == targetLayer
and not arc.GetTargetNode().path.ContainsPrimVariantSelection()):
editTarget = Usd.EditTarget(targetLayer, arc.GetTargetNode())
break

routingData['layer'] = editTarget.GetLayer().identifier
routingData['editTarget'] = editTarget

cmds.file(new=True, force=True)

# We use an example usd file from attributeBlock which has non-local referenced layers.
testFile = testUtils.getTestScene('attributeBlock', 'root.usda')
pathString, stage = mayaUtils.createProxyFromFile(testFile)

self.assertTrue(stage)
self.assertTrue(pathString)

primA = stage.GetPrimAtPath('/root/a')
self.assertTrue(primA.IsValid())

primPathString = pathString + ',/root/a'

# Delete a prim defined within an non-local layer without using EditRouter.
cmds.delete(primPathString)

# The deletion should fail so the prim should still be valid
primA = stage.GetPrimAtPath('/root/a')
self.assertTrue(primA.IsValid())

# Register an EditRouter callback for prim deletion.
mayaUsdLib.registerEditRouter('delete', editRouterCallback)

cmds.delete(primPathString)

# The prim should be invalid becaue we deleted it from the layer sent by the EditRouter.
primA = stage.GetPrimAtPath('/root/a')
self.assertFalse(primA.IsValid())

# Restore EditRouter callbacks to default.
mayaUsdLib.restoreAllDefaultEditRouters()



if __name__ == '__main__':
unittest.main(verbosity=2)

0 comments on commit cfcbd08

Please sign in to comment.