diff --git a/lib/mayaUsd/commands/layerEditorCommand.cpp b/lib/mayaUsd/commands/layerEditorCommand.cpp index 8e93246dee..bcb21a182b 100644 --- a/lib/mayaUsd/commands/layerEditorCommand.cpp +++ b/lib/mayaUsd/commands/layerEditorCommand.cpp @@ -146,15 +146,64 @@ class InsertRemoveSubPathBase : public BaseCmd _subPath = layer->GetSubLayerPaths()[_index]; holdOnPathIfDirty(layer, _subPath); - // if the layer to remove is the current edit target, + // if the current edit target is the layer to remove or + // a sublayer of the layer to remove, // set the root layer as the current edit target - auto subLayerHandle = SdfLayer::FindRelativeToLayer(layer, _subPath); + auto layerToRemove = SdfLayer::FindRelativeToLayer(layer, _subPath); auto stage = getStage(); auto currentTarget = stage->GetEditTarget().GetLayer(); - if (currentTarget && subLayerHandle - && currentTarget->GetIdentifier() == subLayerHandle->GetIdentifier()) { - _isEditTarget = true; - stage->SetEditTarget(stage->GetRootLayer()); + + // Helper function to find if a layer is in the + // hierarchy of another layer + // + // rootLayer: The root layer of the hierarchy + // layer: The layer to find + // ignore : Optional layer used has the root of a hierarchy that + // we don't want to check in. + // ignoreSubPath : Optional subpath used whith ignore layer. + auto isInHierarchy = [](const SdfLayerHandle& rootLayer, + const SdfLayerHandle& layer, + const SdfLayerHandle* ignore = nullptr, + const std::string* ignoreSubPath = nullptr) { + // Impl used for recursive call + auto isInHierarchyImpl = [](const SdfLayerHandle& rootLayer, + const SdfLayerHandle& layer, + const SdfLayerHandle* ignore, + const std::string* ignoreSubPath, + auto& implRef) { + if (!rootLayer || !layer) + return false; + + if (rootLayer->GetIdentifier() == layer->GetIdentifier()) + return true; + + const auto subLayerPaths = rootLayer->GetSubLayerPaths(); + for (const auto& subLayerPath : subLayerPaths) { + + if (ignore && ignoreSubPath + && (*ignore)->GetIdentifier() == rootLayer->GetIdentifier() + && *ignoreSubPath == subLayerPath) + continue; + + const auto subLayer + = SdfLayer::FindRelativeToLayer(rootLayer, subLayerPath); + if (implRef(subLayer, layer, ignore, ignoreSubPath, implRef)) + return true; + } + return false; + }; + return isInHierarchyImpl( + rootLayer, layer, ignore, ignoreSubPath, isInHierarchyImpl); + }; + + if (isInHierarchy(layerToRemove, currentTarget)) { + // The current edit layer is in the hierarchy of the layer to remove, + // now we need to be sure the edit target layer is not also a sublayer + // of another layer in the stage. + if (!isInHierarchy(stage->GetRootLayer(), currentTarget, &layer, &_subPath)) { + _editTargetPath = currentTarget->GetIdentifier(); + stage->SetEditTarget(stage->GetRootLayer()); + } } layer->RemoveSubLayerPath(_index); @@ -181,9 +230,9 @@ class InsertRemoveSubPathBase : public BaseCmd // if the removed layer was the edit target, // set it back to the current edit target - if (_isEditTarget) { + if (!_editTargetPath.empty()) { auto stage = getStage(); - auto subLayerHandle = SdfLayer::FindRelativeToLayer(layer, _subPath); + auto subLayerHandle = SdfLayer::FindRelativeToLayer(layer, _editTargetPath); stage->SetEditTarget(subLayerHandle); } } else { @@ -210,7 +259,7 @@ class InsertRemoveSubPathBase : public BaseCmd } protected: - bool _isEditTarget = false; + std::string _editTargetPath; UsdStageWeakPtr getStage() { diff --git a/test/lib/testMayaUsdLayerEditorCommands.py b/test/lib/testMayaUsdLayerEditorCommands.py index 7f640bd49e..ce5d14a7e7 100644 --- a/test/lib/testMayaUsdLayerEditorCommands.py +++ b/test/lib/testMayaUsdLayerEditorCommands.py @@ -18,6 +18,7 @@ import unittest import tempfile +from os import path from maya import cmds import mayaUsd_createStageWithNewLayer import mayaUsd @@ -262,3 +263,105 @@ def testSubLayerEditing(self): self.assertEqual(rootLayer.subLayerPaths[0], childLayerId) childLayer = Sdf.Layer.Find(childLayerId) self.assertEqual(childLayer.subLayerPaths[0], grandChildLayerId) + + def testRemoveEditTarget(self): + + shapePath, stage = getCleanMayaStage() + rootLayer = stage.GetRootLayer() + rootLayerID = rootLayer.identifier + + def setAndRemoveEditTargetRecursive(layer, index, editLayer): + cmds.mayaUsdEditTarget(shapePath, edit=True, editTarget=editLayer) + cmds.mayaUsdLayerEditor(layer.identifier, edit=True, removeSubPath=[index,shapePath]) + self.assertEqual(stage.GetEditTarget().GetLayer().identifier, rootLayerID) + cmds.undo() + self.assertEqual(stage.GetEditTarget().GetLayer().identifier, editLayer) + + subLayer = Sdf.Layer.FindRelativeToLayer(layer, editLayer) + for subLayerOffset, subLayerId in enumerate(subLayer.subLayerPaths): + setAndRemoveEditTargetRecursive(subLayer, subLayerOffset, subLayerId) + + # build a layer hierarchy + layerColorId = cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, addAnonymous="Color")[0] + myLayerId = cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, addAnonymous="MyLayer")[0] + + layerRedId = cmds.mayaUsdLayerEditor(layerColorId, edit=True, addAnonymous="Red")[0] + layerGreen = cmds.mayaUsdLayerEditor(layerColorId, edit=True, addAnonymous="Green")[0] + layerBlue = cmds.mayaUsdLayerEditor(layerColorId, edit=True, addAnonymous="Blue")[0] + + layerDarkRed = cmds.mayaUsdLayerEditor(layerRedId, edit=True, addAnonymous="DarkRed")[0] + layerLightRed = cmds.mayaUsdLayerEditor(layerRedId, edit=True, addAnonymous="LightRed")[0] + + mySubLayerId = cmds.mayaUsdLayerEditor(myLayerId, edit=True, addAnonymous="MySubLayer")[0] + + # traverse the layer tree + # for each layer, set it as the edit target and remove it. + for subLayerOffset, subLayerPath in enumerate(rootLayer.subLayerPaths): + setAndRemoveEditTargetRecursive(rootLayer, subLayerOffset, subLayerPath) + + # + # Test when the editTarget's parent (direct/ indirect) layer is removed + # + cmds.mayaUsdEditTarget(shapePath, edit=True, editTarget=layerDarkRed) + + # remove the Red layer (direct parent of DarkRed layer) + cmds.mayaUsdLayerEditor(layerColorId, edit=True, removeSubPath=[2,shapePath]) + self.assertEqual(stage.GetEditTarget().GetLayer().identifier, rootLayerID) + cmds.undo() + self.assertEqual(stage.GetEditTarget().GetLayer().identifier, layerDarkRed) + + # remove the Color layer (indirect parent of DarkRed layer) + cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, removeSubPath=[1,shapePath]) + self.assertEqual(stage.GetEditTarget().GetLayer().identifier, rootLayerID) + cmds.undo() + self.assertEqual(stage.GetEditTarget().GetLayer().identifier, layerDarkRed) + + # + # Test with a layer that is a sublayer multiple times. + # + sharedLayerFile = tempfile.NamedTemporaryFile(suffix=".usda", prefix="sharedLayer", delete=False, mode="w") + sharedLayerFile.write("#usda 1.0") + sharedLayerFile.close() + + sharedLayer = path.normcase(sharedLayerFile.name) + + cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, insertSubPath=[0, sharedLayer]) + cmds.mayaUsdLayerEditor(mySubLayerId, edit=True, insertSubPath=[0, sharedLayer]) + + cmds.mayaUsdEditTarget(shapePath, edit=True, editTarget=sharedLayer) + + # remove the sharedLayer under the root layer. + # the edit target should still be the shared layer + cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, removeSubPath=[0,shapePath]) + self.assertEqual(path.normcase(stage.GetEditTarget().GetLayer().identifier), sharedLayer) + cmds.undo() + self.assertEqual(path.normcase(stage.GetEditTarget().GetLayer().identifier), sharedLayer) + + # remove the sharedLayer under the MySubLayer. + # the edit target should still be the shared layer + cmds.mayaUsdLayerEditor(mySubLayerId, edit=True, removeSubPath=[0,shapePath]) + self.assertEqual(path.normcase(stage.GetEditTarget().GetLayer().identifier), sharedLayer) + cmds.undo() + self.assertEqual(path.normcase(stage.GetEditTarget().GetLayer().identifier), sharedLayer) + + # remove MySubLayer (Direct parent of SharedLayer). + # the edit target should still be the shared layer + cmds.mayaUsdLayerEditor(myLayerId, edit=True, removeSubPath=[0,shapePath]) + self.assertEqual(path.normcase(stage.GetEditTarget().GetLayer().identifier), sharedLayer) + cmds.undo() + self.assertEqual(path.normcase(stage.GetEditTarget().GetLayer().identifier), sharedLayer) + + # remove MyLayer (Indirect parent of SharedLayer). + # the edit target should still be the shared layer + cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, removeSubPath=[0,shapePath]) + self.assertEqual(path.normcase(stage.GetEditTarget().GetLayer().identifier), sharedLayer) + cmds.undo() + self.assertEqual(path.normcase(stage.GetEditTarget().GetLayer().identifier), sharedLayer) + + # remove SharedLayer everywhere. + # the edit target should become the root layer + cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, removeSubPath=[0,shapePath]) + cmds.mayaUsdLayerEditor(mySubLayerId, edit=True, removeSubPath=[0,shapePath]) + self.assertEqual(stage.GetEditTarget().GetLayer().identifier, rootLayerID) + cmds.undo() + self.assertEqual(path.normcase(stage.GetEditTarget().GetLayer().identifier), sharedLayer)