diff --git a/lib/mayaUsd/commands/layerEditorCommand.cpp b/lib/mayaUsd/commands/layerEditorCommand.cpp index d456376c10..f225a59e47 100644 --- a/lib/mayaUsd/commands/layerEditorCommand.cpp +++ b/lib/mayaUsd/commands/layerEditorCommand.cpp @@ -45,6 +45,8 @@ const char kRemoveSubPathFlag[] = "rs"; const char kRemoveSubPathFlagL[] = "removeSubPath"; const char kReplaceSubPathFlag[] = "rp"; const char kReplaceSubPathFlagL[] = "replaceSubPath"; +const char kMoveSubPathFlag[] = "mv"; +const char kMoveSubPathFlagL[] = "moveSubPath"; const char kDiscardEditsFlag[] = "de"; const char kDiscardEditsFlagL[] = "discardEdits"; const char kClearLayerFlag[] = "cl"; @@ -64,6 +66,7 @@ enum class CmdId { kInsert, kRemove, + kMove, kReplace, kDiscardEdit, kClearLayer, @@ -301,6 +304,109 @@ class RemoveSubPath : public InsertRemoveSubPathBase } }; +// Move a sublayer into another layer. +// @param path The layer's path to move. +// @param newParentLayer The new parent layer's path +// @param newIndex The index where the moved layer will be in the new parent. +class MoveSubPath : public BaseCmd +{ +public: + MoveSubPath(const std::string& path, const std::string& newParentLayer, unsigned newIndex) + : BaseCmd(CmdId::kMove) + , _path(path) + , _newParentLayer(newParentLayer) + , _newIndex(newIndex) + { + } + + bool doIt(SdfLayerHandle layer) override + { + auto proxy = layer->GetSubLayerPaths(); + auto subPathIndex = proxy.Find(_path); + if (subPathIndex == size_t(-1)) { + std::string message = std::string("path ") + _path + std::string(" not found on layer ") + + layer->GetIdentifier(); + MPxCommand::displayError(message.c_str()); + return false; + } + + _oldIndex = subPathIndex; // save for undo + + SdfLayerHandle newParentLayer; + + if (layer->GetIdentifier() == _newParentLayer) { + + if (_newIndex > layer->GetNumSubLayerPaths() - 1) { + std::string message = std::string("Index ") + std::to_string(_newIndex) + + std::string(" out-of-bound for ") + layer->GetIdentifier(); + MPxCommand::displayError(message.c_str()); + return false; + } + + newParentLayer = layer; + } else { + newParentLayer = SdfLayer::Find(_newParentLayer); + if (!newParentLayer) { + std::string message + = std::string("Layer ") + _newParentLayer + std::string(" not found!"); + return false; + } + + if (_newIndex > newParentLayer->GetNumSubLayerPaths()) { + std::string message = std::string("Index ") + std::to_string(_newIndex) + + std::string(" out-of-bound for ") + newParentLayer->GetIdentifier(); + MPxCommand::displayError(message.c_str()); + return false; + } + + // make sure the subpath is not already in the new parent layer. + // Otherwise, the SdfLayer::InsertSubLayerPath() below will do nothing + // and the subpath will be removed from it's current parent. + if (newParentLayer->GetSubLayerPaths().Find(_path) != size_t(-1)) { + std::string message = std::string("SubPath ") + _path + + std::string(" already exist in layer ") + newParentLayer->GetIdentifier(); + MPxCommand::displayError(message.c_str()); + return false; + } + } + + // When the subLayer is moved inside the current parent, + // Remove it from it's current location and insert it into it's + // new location. The order of remove / insert is important + // oterwise InsertSubLayerPath() will fail because the subLayer + // already exists. + layer->RemoveSubLayerPath(subPathIndex); + newParentLayer->InsertSubLayerPath(_path, _newIndex); + + return true; + } + + bool undoIt(SdfLayerHandle layer) override + { + if (layer->GetIdentifier() == _newParentLayer) { + // When the subLayer is moved inside the current parent, + // Remove it from it's current location and insert it into it's + // new location. The order of remove / insert is important + // oterwise InsertSubLayerPath() will fail because the subLayer + // already exists. + layer->RemoveSubLayerPath(_newIndex); + layer->InsertSubLayerPath(_path, _oldIndex); + } else { + auto newParentLayer = SdfLayer::Find(_newParentLayer); + newParentLayer->RemoveSubLayerPath(_newIndex); + layer->InsertSubLayerPath(_path, _oldIndex); + } + + return true; + } + +private: + std::string _path; + std::string _newParentLayer; + unsigned int _newIndex; + unsigned int _oldIndex { 0 }; +}; + class ReplaceSubPath : public BaseCmd { public: @@ -548,6 +654,12 @@ MSyntax LayerEditorCommand::createSyntax() syntax.makeFlagMultiUse(kRemoveSubPathFlag); syntax.addFlag(kReplaceSubPathFlag, kReplaceSubPathFlagL, MSyntax::kString, MSyntax::kString); syntax.makeFlagMultiUse(kReplaceSubPathFlag); + syntax.addFlag( + kMoveSubPathFlag, + kMoveSubPathFlagL, + MSyntax::kString, // path to move + MSyntax::kString, // new parent layer + MSyntax::kUnsigned); // layer index inside the new parent syntax.addFlag(kDiscardEditsFlag, kDiscardEditsFlagL); syntax.addFlag(kClearLayerFlag, kClearLayerFlagL); // parameter: new layer name @@ -630,6 +742,21 @@ MStatus LayerEditorCommand::parseArgs(const MArgList& argList) } } + if (argParser.isFlagSet(kMoveSubPathFlag)) { + MString subPath; + argParser.getFlagArgument(kMoveSubPathFlag, 0, subPath); + + MString newParentLayer; + argParser.getFlagArgument(kMoveSubPathFlag, 1, newParentLayer); + + unsigned int index { 0 }; + argParser.getFlagArgument(kMoveSubPathFlag, 2, index); + + auto cmd = std::make_shared( + subPath.asUTF8(), newParentLayer.asUTF8(), index); + _subCommands.push_back(std::move(cmd)); + } + if (argParser.isFlagSet(kDiscardEditsFlag)) { auto cmd = std::make_shared(); _subCommands.push_back(std::move(cmd)); diff --git a/lib/usd/ui/layerEditor/abstractCommandHook.h b/lib/usd/ui/layerEditor/abstractCommandHook.h index f4a9020bf3..45b3cc3317 100644 --- a/lib/usd/ui/layerEditor/abstractCommandHook.h +++ b/lib/usd/ui/layerEditor/abstractCommandHook.h @@ -62,6 +62,11 @@ class AbstractCommandHook // replaces a path in the layer stack virtual void replaceSubLayerPath(UsdLayer usdLayer, Path oldPath, Path newPath) = 0; + // move a path at a given index inside the same layer or another layer. + virtual void + moveSubLayerPath(Path path, UsdLayer oldParentUsdLayer, UsdLayer newParentUsdLayer, int index) + = 0; + // discard edit on a layer virtual void discardEdits(UsdLayer usdLayer) = 0; diff --git a/lib/usd/ui/layerEditor/layerTreeModel.cpp b/lib/usd/ui/layerEditor/layerTreeModel.cpp index ffaae07220..6639404d94 100644 --- a/lib/usd/ui/layerEditor/layerTreeModel.cpp +++ b/lib/usd/ui/layerEditor/layerTreeModel.cpp @@ -177,14 +177,15 @@ bool LayerTreeModel::dropMimeData( auto oldParent = layerItem->parentLayerItem()->layer(); int index = (int)oldParent->GetSubLayerPaths().Find(layerItem->subLayerPath()); auto itemSubLayerPath = layerItem->subLayerPath(); - context.hook()->removeSubLayerPath(oldParent, itemSubLayerPath); + // When we are moving an item (underneath the same parent) // to a new location higher up we have to adjust the row // (new location) to account for the remove we just did. if (oldParent == parentItem->layer() && (index < row)) { row -= 1; } - context.hook()->insertSubLayerPath(parentItem->layer(), itemSubLayerPath, row); + context.hook()->moveSubLayerPath( + itemSubLayerPath, oldParent, parentItem->layer(), row); } } } diff --git a/lib/usd/ui/layerEditor/mayaCommandHook.cpp b/lib/usd/ui/layerEditor/mayaCommandHook.cpp index 09b80a5b21..6f7ec4e103 100644 --- a/lib/usd/ui/layerEditor/mayaCommandHook.cpp +++ b/lib/usd/ui/layerEditor/mayaCommandHook.cpp @@ -108,6 +108,21 @@ void MayaCommandHook::removeSubLayerPath(UsdLayer usdLayer, Path path) executeMel(cmd); } +void MayaCommandHook::moveSubLayerPath( + Path path, + UsdLayer oldParentUsdLayer, + UsdLayer newParentUsdLayer, + int index) +{ + std::string cmd; + cmd = "mayaUsdLayerEditor -edit -moveSubPath "; + cmd += quote(path); + cmd += quote(newParentUsdLayer->GetIdentifier()); + cmd += std::to_string(index); + cmd += quote(oldParentUsdLayer->GetIdentifier()); + executeMel(cmd); +} + // replaces a path in the layer stack void MayaCommandHook::replaceSubLayerPath(UsdLayer usdLayer, Path oldPath, Path newPath) { diff --git a/lib/usd/ui/layerEditor/mayaCommandHook.h b/lib/usd/ui/layerEditor/mayaCommandHook.h index 0e97a2d066..a989f6d466 100644 --- a/lib/usd/ui/layerEditor/mayaCommandHook.h +++ b/lib/usd/ui/layerEditor/mayaCommandHook.h @@ -44,6 +44,11 @@ class MayaCommandHook : public AbstractCommandHook // replaces a path in the layer stack void replaceSubLayerPath(UsdLayer usdLayer, Path oldPath, Path newPath) override; + // move a path at a given index inside the same layer or another layer. + void + moveSubLayerPath(Path path, UsdLayer oldParentUsdLayer, UsdLayer newParentUsdLayer, int index) + override; + // discard edit on a layer void discardEdits(UsdLayer usdLayer) override; diff --git a/test/lib/testMayaUsdLayerEditorCommands.py b/test/lib/testMayaUsdLayerEditorCommands.py index f42fd35583..593ce443b7 100644 --- a/test/lib/testMayaUsdLayerEditorCommands.py +++ b/test/lib/testMayaUsdLayerEditorCommands.py @@ -362,6 +362,100 @@ def setAndRemoveEditTargetRecursive(layer, index, editLayer): cmds.undo() self.assertEqual(path.normcase(stage.GetEditTarget().GetLayer().identifier), sharedLayer) + def testMoveSubPath(self): + """ test 'mayaUsdLayerEditor' command 'moveSubPath' paramater """ + + def moveSubPath(parentLayer, subPath, newParentLayer, index, originalSubLayerPaths, newSubLayerPaths): + cmds.mayaUsdLayerEditor(parentLayer.identifier, edit=True, moveSubPath=[subPath, newParentLayer.identifier, index]) + self.assertEqual(newParentLayer.subLayerPaths, newSubLayerPaths) + + cmds.undo() + self.assertEqual(newParentLayer.subLayerPaths, originalSubLayerPaths) + + cmds.redo() + self.assertEqual(newParentLayer.subLayerPaths, newSubLayerPaths) + + cmds.undo() + self.assertEqual(newParentLayer.subLayerPaths, originalSubLayerPaths) + + def moveElement(list, item, index): + l = list[:] #copy the list + l.remove(item) + l.insert(index, item) + return l + + shapePath, stage = getCleanMayaStage() + rootLayer = stage.GetRootLayer() + + layerId1 = cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, addAnonymous="MyLayer1")[0] + layerId2 = cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, addAnonymous="MyLayer2")[0] + layerId3 = cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, addAnonymous="MyLayer3")[0] + + originalSubLayerPaths = [layerId3, layerId2, layerId1] + self.assertEqual(rootLayer.subLayerPaths, originalSubLayerPaths) + + # Test moving each layers under the root layer to any valid index in the root layer + # and test out-of-bound index + for layer in originalSubLayerPaths: + for i in range(len(originalSubLayerPaths)): + expectedSubPath = moveElement(originalSubLayerPaths, layer, i) + moveSubPath(rootLayer, layer, rootLayer, i, originalSubLayerPaths, expectedSubPath) + + with self.assertRaises(RuntimeError): + cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, moveSubPath=[layer, rootLayer.identifier, len(originalSubLayerPaths)]) + self.assertEqual(rootLayer.subLayerPaths, originalSubLayerPaths) + + # + # Test moving sublayer (layer2 and layer3) inside a new parent layer (layer1) + # + + layer1 = Sdf.Layer.Find(layerId1) + self.assertTrue(len(layer1.subLayerPaths) == 0) + + # layer1 has no subLayer so index 1 is invalide + with self.assertRaises(RuntimeError): + cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, moveSubPath=[layerId3, layerId1, 1]) + self.assertTrue(len(layer1.subLayerPaths) == 0) + self.assertEqual(rootLayer.subLayerPaths, originalSubLayerPaths) + + # Move layer3 from the root layer inside layer1 at index 0 + cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, moveSubPath=[layerId3, layerId1, 0]) + self.assertEqual(rootLayer.subLayerPaths, [layerId2, layerId1]) + self.assertEqual(layer1.subLayerPaths, [layerId3]) + + # Move layer2 from the root layer inside layer1 at index 0 + cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, moveSubPath=[layerId2, layerId1, 0]) + self.assertEqual(rootLayer.subLayerPaths, [layerId1]) + self.assertEqual(layer1.subLayerPaths, [layerId2, layerId3]) + + cmds.undo() + self.assertEqual(rootLayer.subLayerPaths, [layerId2, layerId1]) + self.assertEqual(layer1.subLayerPaths, [layerId3]) + + # Move layer2 from the root layer inside layer1 at index 1 + cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, moveSubPath=[layerId2, layerId1, 1]) + self.assertEqual(rootLayer.subLayerPaths, [layerId1]) + self.assertEqual(layer1.subLayerPaths, [layerId3, layerId2]) + + cmds.undo() + self.assertEqual(rootLayer.subLayerPaths, [layerId2, layerId1]) + self.assertEqual(layer1.subLayerPaths, [layerId3]) + + # Undo moving layer3 inside layer1 + cmds.undo() + self.assertEqual(rootLayer.subLayerPaths, [layerId3, layerId2, layerId1]) + self.assertEqual(layer1.subLayerPaths, []) + + # Insert layer3 inside layer1 and try to move the layer3 from the root layer + # inside layer1. This should fail. + cmds.mayaUsdLayerEditor(layerId1, edit=True, insertSubPath=[0, layerId3]) + self.assertEqual(layer1.subLayerPaths, [layerId3]) + + with self.assertRaises(RuntimeError): + cmds.mayaUsdLayerEditor(rootLayer.identifier, edit=True, moveSubPath=[layerId3, layerId1, 0]) + self.assertEqual(rootLayer.subLayerPaths, originalSubLayerPaths) + self.assertEqual(layer1.subLayerPaths, [layerId3]) + def testMuteLayer(self): """ test 'mayaUsdLayerEditor' command 'muteLayer' paramater """