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

Fix duplication of homonyms #2746

Merged
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
57 changes: 36 additions & 21 deletions lib/mayaUsd/ufe/UsdUndoDuplicateSelectionCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,8 @@ UsdUndoDuplicateSelectionCommand::UsdUndoDuplicateSelectionCommand(
const Ufe::ValueDictionary& duplicateOptions)
: Ufe::SelectionUndoableCommand()
, _copyExternalInputs(shouldConnectExternalInputs(duplicateOptions))
, _sourceSelection(selection)
{
// TODO: MAYA-125854. If duplicating /a/b and /a/b/c, it would make sense to order the
// operations by SdfPath, and always check if the previously processed path is a prefix of the
// one currently being processed. In that case, a duplicate task is not necessary because the
// resulting SceneItem should be built by using SdfPath::ReplacePrefix on the current item to
// get its location in the previously duplicated parent item.
for (auto&& item : selection) {
if (UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast<UsdSceneItem>(item)) {
// Currently unordered_map since we need to streamline the targetItem override.
_perItemCommands[item->path()] = UsdUndoDuplicateCommand::create(usdItem);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't pre-create all the tasks because the name conflict resolution code will not incrementally see the new paths.

}
}
}

UsdUndoDuplicateSelectionCommand::~UsdUndoDuplicateSelectionCommand() { }
Expand All @@ -70,24 +60,46 @@ UsdUndoDuplicateSelectionCommand::Ptr UsdUndoDuplicateSelectionCommand::create(
const Ufe::Selection& selection,
const Ufe::ValueDictionary& duplicateOptions)
{
auto retVal = std::make_shared<UsdUndoDuplicateSelectionCommand>(selection, duplicateOptions);
if (retVal->_perItemCommands.empty()) {
// Could not find any item from this runtime in the selection.
return {};
bool canDuplicate = false;
for (auto&& item : selection) {
UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast<UsdSceneItem>(item);
if (usdItem) {
canDuplicate = true;
break;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Comment] Would it be interesting to continue the loop to remove null usdItem from the list?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.. allowing to remove lines 88 to 90 & perhaps right away create a list/set of UsdSceneItem::Ptr

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. Anyway this will be revisited at some point to fix MAYA-125854 and we will also remove all usdItems that have a parent in the list.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, that was a great idea and it also provided a very efficient way to fix MAYA-125854. Done.

}
}
return retVal;
if (canDuplicate) {
return std::make_shared<UsdUndoDuplicateSelectionCommand>(selection, duplicateOptions);
}
return {};
}

void UsdUndoDuplicateSelectionCommand::execute()
{
UsdUndoBlock undoBlock(&_undoableItem);

for (auto&& duplicateItem : _perItemCommands) {
duplicateItem.second->execute();
// TODO: MAYA-125854. If duplicating /a/b and /a/b/c, it would make sense to order the
// operations by SdfPath, and always check if the previously processed path is a prefix of the
// one currently being processed. In that case, a duplicate task is not necessary because the
// resulting SceneItem should be built by using SdfPath::ReplacePrefix on the current item to
// get its location in the previously duplicated parent item.
for (auto&& item : _sourceSelection) {
UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast<UsdSceneItem>(item);
if (!usdItem) {
continue;
}

// Need to create and execute. If we create all before executing any, then the collision
// resolution on names will merge bob1 and bob2 into a single bob3 instead of creating a
// bob3 and a bob4.
auto duplicateCmd = UsdUndoDuplicateCommand::create(usdItem);
duplicateCmd->execute();

auto dstSceneItem = duplicateItem.second->duplicatedItem();
PXR_NS::UsdPrim srcPrim = ufePathToPrim(duplicateItem.first);
PXR_NS::UsdPrim dstPrim = std::dynamic_pointer_cast<UsdSceneItem>(dstSceneItem)->prim();
// Currently unordered_map since we need to streamline the targetItem override.
_perItemCommands[item->path()] = duplicateCmd;

PXR_NS::UsdPrim srcPrim = usdItem->prim();
PXR_NS::UsdPrim dstPrim = duplicateCmd->duplicatedItem()->prim();

Ufe::Path stgPath = stagePath(dstPrim.GetStage());
auto stageIt = _duplicatesMap.find(stgPath);
Expand All @@ -100,6 +112,9 @@ void UsdUndoDuplicateSelectionCommand::execute()
stageIt->second.insert({ srcPrim.GetPath(), dstPrim.GetPath() });
}

// We no longer require the source selection:
_sourceSelection.clear();

// Fixups were grouped by stage.
for (const auto& stageData : _duplicatesMap) {
PXR_NS::UsdStageWeakPtr stage(getStage(stageData.first));
Expand Down
3 changes: 3 additions & 0 deletions lib/mayaUsd/ufe/UsdUndoDuplicateSelectionCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class MAYAUSD_CORE_PUBLIC UsdUndoDuplicateSelectionCommand : public Ufe::Selecti
UsdUndoableItem _undoableItem;
const bool _copyExternalInputs;

// Transient list of items to duplicate. Needed by execute.
Ufe::Selection _sourceSelection;

using CommandMap = std::unordered_map<Ufe::Path, UsdUndoDuplicateCommand::Ptr>;
CommandMap _perItemCommands;

Expand Down
22 changes: 22 additions & 0 deletions test/lib/ufe/testDuplicateCmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,28 @@ def testUfeDuplicateCommandAPI(self):
self.assertIsNotNone(plane7Item)
self.assertEqual(plane7Item, duplicatedItem)

@unittest.skipIf(os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '4041', 'Test only available in UFE preview version 0.4.41 and greater')
def testUfeDuplicateHomonyms(self):
'''Test that duplicating two items with similar names end up in two new duplicates.'''
testFile = testUtils.getTestScene('MaterialX', 'BatchOpsTestScene.usda')
shapeNode,shapeStage = mayaUtils.createProxyFromFile(testFile)

geomItem1 = ufeUtils.createUfeSceneItem(shapeNode, '/pPlane1')
self.assertIsNotNone(geomItem1)
geomItem2 = ufeUtils.createUfeSceneItem(shapeNode, '/pPlane2')
self.assertIsNotNone(geomItem2)

batchOpsHandler = ufe.RunTimeMgr.instance().batchOpsHandler(geomItem1.runTimeId())
self.assertIsNotNone(batchOpsHandler)

sel = ufe.Selection()
sel.append(geomItem1)
sel.append(geomItem2)
cmd = batchOpsHandler.duplicateSelectionCmd(sel, {"inputConnections": False})
cmd.execute()

self.assertNotEqual(cmd.targetItem(geomItem1.path()).path(), cmd.targetItem(geomItem2.path()).path())


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