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

Support EditRouter in delete command #3695

Merged
merged 2 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
34 changes: 27 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,38 @@ 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);

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

#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);
pierrebai-adsk marked this conversation as resolved.
Show resolved Hide resolved
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 {
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
33 changes: 29 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,34 @@ class PyEditRouter : public UsdUfe::EditRouter
TF_WARN("%s", ex.what());
throw;
}
boost::python::extract<PXR_NS::VtDictionary> extractedDict(dictObject);
if (extractedDict.check()) {
routingData = extractedDict;

const boost::python::object items = dictObject.items();
pierrebai-adsk marked this conversation as resolved.
Show resolved Hide resolved
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
7 changes: 6 additions & 1 deletion lib/usdUfe/ufe/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,12 @@ bool allowedInStrongerLayer(
? stage->GetSessionLayer()
: stage->GetRootLayer();

return getStrongerLayer(searchRoot, targetLayer, topLayer) == targetLayer;
pierrebai-adsk marked this conversation as resolved.
Show resolved Hide resolved
auto strongerLayer = getStrongerLayer(searchRoot, targetLayer, topLayer);
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)