diff --git a/lib/mayaUsd/commands/PullPushCommands.cpp b/lib/mayaUsd/commands/PullPushCommands.cpp index 841185a0c2..af576fbd78 100644 --- a/lib/mayaUsd/commands/PullPushCommands.cpp +++ b/lib/mayaUsd/commands/PullPushCommands.cpp @@ -138,14 +138,19 @@ MStatus parseUfePathArg( return parseArgAsUfePath(text, outputPath); } -MStatus parseDagPathArg(const MArgParser& argParser, int index, MDagPath& outputDagPath) +MStatus parseObjectArg(const MArgParser& argParser, int index, MObject& outputObject) { MString text; MStatus status = parseTextArg(argParser, index, text); CHECK_MSTATUS_AND_RETURN_IT(status); + return PXR_NS::UsdMayaUtil::GetMObjectByName(text, outputObject); +} + +MStatus parseDagPathArg(const MArgParser& argParser, int index, MDagPath& outputDagPath) +{ MObject obj; - status = PXR_NS::UsdMayaUtil::GetMObjectByName(text, obj); + MStatus status = parseObjectArg(argParser, index, obj); CHECK_MSTATUS_AND_RETURN_IT(status); return MDagPath::getAPathTo(obj, outputDagPath); @@ -428,11 +433,18 @@ MStatus DuplicateCommand::doIt(const MArgList& argList) MArgParser argParser(syntax(), argList, &status); CHECK_MSTATUS_AND_RETURN_IT(status); - status = parseUfePathArg(argParser, 0, _srcPath); - if (status != MS::kSuccess) - return reportError(status); + MObject srcMayaObject; + Ufe::Path srcPath; + status = parseUfePathArg(argParser, 0, srcPath); + if (status != MS::kSuccess) { + status = parseObjectArg(argParser, 0, srcMayaObject); + if (status != MS::kSuccess) { + return reportError(status); + } + } - status = parseUfePathArg(argParser, 1, _dstPath, true /*allowEmpty*/); + Ufe::Path dstPath; + status = parseUfePathArg(argParser, 1, dstPath, true /*allowEmpty*/); if (status != MS::kSuccess) return reportError(status); @@ -452,7 +464,9 @@ MStatus DuplicateCommand::doIt(const MArgList& argList) OpUndoItemRecorder undoRecorder(_undoItemList); auto& manager = PXR_NS::PrimUpdaterManager::getInstance(); - auto dstUfePaths = manager.duplicate(_srcPath, _dstPath, userArgs); + auto dstUfePaths = srcPath.empty() + ? manager.duplicateToUsd(srcMayaObject, dstPath, userArgs) + : manager.duplicate(srcPath, dstPath, userArgs); if (dstUfePaths.size() > 0) { // Select the duplicate. diff --git a/lib/mayaUsd/commands/PullPushCommands.h b/lib/mayaUsd/commands/PullPushCommands.h index 4204d1b0c4..0c9df75673 100644 --- a/lib/mayaUsd/commands/PullPushCommands.h +++ b/lib/mayaUsd/commands/PullPushCommands.h @@ -175,9 +175,6 @@ class DuplicateCommand : public PullPushBaseCommand private: // Make sure callers need to call creator(). DuplicateCommand(); - - Ufe::Path _srcPath; - Ufe::Path _dstPath; }; } // namespace ufe diff --git a/lib/mayaUsd/fileio/primUpdaterManager.cpp b/lib/mayaUsd/fileio/primUpdaterManager.cpp index 09e930bd27..5d05010c90 100644 --- a/lib/mayaUsd/fileio/primUpdaterManager.cpp +++ b/lib/mayaUsd/fileio/primUpdaterManager.cpp @@ -580,14 +580,19 @@ PushExportResult pushExport(const MObject& mayaObject, const UsdMayaPrimUpdaterC fillUserArgsFileIfEmpty(userArgs, fileName); - MFnDagNode fnDag(mayaObject); - MDagPath dagPath; - fnDag.getPath(dagPath); - UsdMayaUtil::MDagPathSet dagPaths; MSelectionList fullObjectList; - dagPaths.insert(dagPath); - fullObjectList.add(dagPath); + MDagPath dagPath; + { + MFnDagNode fnDag; + if (fnDag.setObject(mayaObject)) { + fnDag.getPath(dagPath); + dagPaths.insert(dagPath); + fullObjectList.add(dagPath); + } else { + fullObjectList.add(mayaObject); + } + } std::vector timeSamples; UsdMayaJobExportArgs::GetDictionaryTimeSamples(userArgs, timeSamples); @@ -1593,167 +1598,199 @@ std::vector PrimUpdaterManager::duplicate( MayaUsdProxyShapeBase* dstProxyShape = dstPath.empty() ? nullptr : MayaUsd::ufe::getProxyShape(dstPath); - PushPullScope scopeIt(_inPushPull); - // Copy from USD to DG if (srcProxyShape && dstProxyShape == nullptr) { - auto srcPrim = MayaUsd::ufe::ufePathToPrim(srcPath); - if (!srcPrim) { + return duplicateToMaya(srcPath, dstPath, userArgs); + } + // Copy from DG to USD + else if (srcProxyShape == nullptr && dstProxyShape) { + MDagPath dagPath = PXR_NS::UsdMayaUtil::nameToDagPath(Ufe::PathString::string(srcPath)); + if (!dagPath.isValid()) { return {}; } - MayaUsd::ProgressBarScope progressBar(3, "Duplicating to Maya Data"); + return duplicateToUsd(dagPath.node(), dstPath, userArgs); + } - auto ctxArgs = VtDictionaryOver(userArgs, UsdMayaJobImportArgs::GetDefaultDictionary()); + // Copy operations to the same data model not supported here. + return {}; +} - // We will only do copy between two data models, setting this in arguments - // to configure the updater - ctxArgs[UsdMayaPrimUpdaterArgsTokens->copyOperation] = true; +std::vector PrimUpdaterManager::duplicateToMaya( + const Ufe::Path& srcPath, + const Ufe::Path& dstPath, + const VtDictionary& userArgs) +{ + if (srcPath.empty()) + return {}; - // Note: when copying, we don't want to automatically authors a USD kind - // on the root prim. - ctxArgs[UsdMayaJobExportArgsTokens->disableModelKindProcessor] = true; + MayaUsdProxyShapeBase* srcProxyShape = MayaUsd::ufe::getProxyShape(srcPath); + if (!srcProxyShape) + return {}; - // Set destination of duplicate. The Maya world MDagPath is not valid, - // so don't try to validate the path if it is the world root. - MDagPath pullParentPath; - if (!MayaUsd::ufe::isMayaWorldPath(dstPath) && !dstPath.empty()) { - pullParentPath = MayaUsd::ufe::ufeToDagPath(dstPath); - if (!pullParentPath.isValid()) { - return {}; - } - } - ctxArgs[kPullParentPathKey] = VtValue(std::string(pullParentPath.fullPathName().asChar())); + auto srcPrim = MayaUsd::ufe::ufePathToPrim(srcPath); + if (!srcPrim) { + return {}; + } - UsdMayaPrimUpdaterContext context( - srcProxyShape->getTime(), srcProxyShape->getUsdStage(), ctxArgs); - context._pullExtras.initRecursive(Ufe::Hierarchy::createItem(srcPath)); - progressBar.advance(); + PushPullScope scopeIt(_inPushPull); - PullImportPaths importedPaths = pullImport(srcPath, srcPrim, context, true); - progressBar.advance(); + MayaUsd::ProgressBarScope progressBar(3, "Duplicating to Maya Data"); - scopeIt.end(); - executeAdditionalCommands(context); - progressBar.advance(); + auto ctxArgs = VtDictionaryOver(userArgs, UsdMayaJobImportArgs::GetDefaultDictionary()); - std::vector dstPaths; - for (const auto& dagAndUfe : importedPaths) - dstPaths.push_back(MayaUsd::ufe::dagPathToUfe(dagAndUfe.first)); + // We will only do copy between two data models, setting this in arguments + // to configure the updater + ctxArgs[UsdMayaPrimUpdaterArgsTokens->copyOperation] = true; - return dstPaths; - } - // Copy from DG to USD - else if (srcProxyShape == nullptr && dstProxyShape) { - MDagPath dagPath = PXR_NS::UsdMayaUtil::nameToDagPath(Ufe::PathString::string(srcPath)); - if (!dagPath.isValid()) { + // Note: when copying, we don't want to automatically authors a USD kind + // on the root prim. + ctxArgs[UsdMayaJobExportArgsTokens->disableModelKindProcessor] = true; + + // Set destination of duplicate. The Maya world MDagPath is not valid, + // so don't try to validate the path if it is the world root. + MDagPath pullParentPath; + if (!MayaUsd::ufe::isMayaWorldPath(dstPath) && !dstPath.empty()) { + pullParentPath = MayaUsd::ufe::ufeToDagPath(dstPath); + if (!pullParentPath.isValid()) { return {}; } + } + ctxArgs[kPullParentPathKey] = VtValue(std::string(pullParentPath.fullPathName().asChar())); - MayaUsd::ProgressBarScope progressBar(6, "Duplicating to USD"); + UsdMayaPrimUpdaterContext context( + srcProxyShape->getTime(), srcProxyShape->getUsdStage(), ctxArgs); + context._pullExtras.initRecursive(Ufe::Hierarchy::createItem(srcPath)); + progressBar.advance(); - auto ctxArgs = VtDictionaryOver(userArgs, UsdMayaJobExportArgs::GetDefaultDictionary()); + PullImportPaths importedPaths = pullImport(srcPath, srcPrim, context, true); + progressBar.advance(); - // Note: when copying, we don't want to automatically authors a USD kind - // on the root prim. - ctxArgs[UsdMayaJobExportArgsTokens->disableModelKindProcessor] = true; + scopeIt.end(); + executeAdditionalCommands(context); + progressBar.advance(); - // Setting the export-selected flag will allow filtering materials so that - // only materials in the prim selected to be copied will be included. - ctxArgs[UsdMayaJobExportArgsTokens->exportSelected] = true; + std::vector dstPaths; + for (const auto& dagAndUfe : importedPaths) + dstPaths.push_back(MayaUsd::ufe::dagPathToUfe(dagAndUfe.first)); - const UsdStageRefPtr dstStage = dstProxyShape->getUsdStage(); - const SdfLayerHandle& layer = dstStage->GetEditTarget().GetLayer(); - if (!layer->IsAnonymous()) - fillUserArgsFileIfEmpty(ctxArgs, layer->GetIdentifier()); + return dstPaths; +} - // Record all USD modifications in an undo block and item. - UsdUfe::UsdUndoBlock undoBlock( - &UsdUndoableItemUndoItem::create("Duplicate USD data modifications")); - progressBar.advance(); +std::vector PrimUpdaterManager::duplicateToUsd( + const MObject& mayaObject, + const Ufe::Path& dstPath, + const VtDictionary& userArgs) +{ + if (dstPath.empty()) + return {}; - // We will only do copy between two data models, setting this in arguments - // to configure the updater - ctxArgs[UsdMayaPrimUpdaterArgsTokens->copyOperation] = true; - UsdMayaPrimUpdaterContext context(dstProxyShape->getTime(), dstStage, ctxArgs); + MayaUsdProxyShapeBase* dstProxyShape = MayaUsd::ufe::getProxyShape(dstPath); + if (!dstProxyShape) + return {}; - // Export out to a temporary layer. - PushExportResult pushExportResult = pushExport(dagPath.node(), context); - if (pushExportResult.srcRootPath.IsEmpty()) { - return {}; - } - progressBar.advance(); + PushPullScope scopeIt(_inPushPull); - // Copy the temporary layer contents out to the proper destination. - const auto& srcStage = pushExportResult.stage; - const auto& srcLayer = pushExportResult.layer; - const auto& editTarget = dstStage->GetEditTarget(); - const auto& dstLayer = editTarget.GetLayer(); + MayaUsd::ProgressBarScope progressBar(6, "Duplicating to USD"); - // Validate that the destination parent prim is valid. - UsdPrim dstParentPrim = MayaUsd::ufe::ufePathToPrim(dstPath); - if (!dstParentPrim.IsValid()) { - return {}; - } - progressBar.advance(); + auto ctxArgs = VtDictionaryOver(userArgs, UsdMayaJobExportArgs::GetDefaultDictionary()); - // We need the parent path of the source and destination to - // fixup the paths of the source prims we copy to their - // destination paths. - const SdfPath srcParentPath = pushExportResult.srcRootPath.GetParentPath(); - const SdfPath dstParentPath = dstParentPrim.GetPath(); + // Note: when copying, we don't want to automatically authors a USD kind + // on the root prim. + ctxArgs[UsdMayaJobExportArgsTokens->disableModelKindProcessor] = true; - if (TF_VERIFY(pushExportResult.usdToDag)) { - processPushExtras( - context._pushExtras, *pushExportResult.usdToDag, srcParentPath, dstParentPath); - } + // Setting the export-selected flag will allow filtering materials so that + // only materials in the prim selected to be copied will be included. + ctxArgs[UsdMayaJobExportArgsTokens->exportSelected] = true; - CopyLayerPrimsOptions options; - options.progressBar = &progressBar; - options.mergeScopes = true; - - CopyLayerPrimsResult copyResult = copyLayerPrims( - srcStage, - srcLayer, - srcParentPath, - dstStage, - dstLayer, - dstParentPath, - { pushExportResult.srcRootPath }, - options); - - context._pushExtras.finalize(MayaUsd::ufe::stagePath(dstStage), copyResult.renamedPaths); - - auto ufeItem = Ufe::Hierarchy::createItem(dstPath); - if (TF_VERIFY(ufeItem)) { - Ufe::Scene::instance().notify(Ufe::SubtreeInvalidate(ufeItem)); - } - progressBar.advance(); + const UsdStageRefPtr dstStage = dstProxyShape->getUsdStage(); + const SdfLayerHandle& layer = dstStage->GetEditTarget().GetLayer(); + if (!layer->IsAnonymous()) + fillUserArgsFileIfEmpty(ctxArgs, layer->GetIdentifier()); - scopeIt.end(); - executeAdditionalCommands(context); - progressBar.advance(); + // Record all USD modifications in an undo block and item. + UsdUfe::UsdUndoBlock undoBlock( + &UsdUndoableItemUndoItem::create("Duplicate USD data modifications")); + progressBar.advance(); - SdfPath finalUsdPath(pushExportResult.srcRootPath); - { - auto copiedIt = copyResult.copiedPaths.find(finalUsdPath); - if (copiedIt != copyResult.copiedPaths.end()) { - finalUsdPath = copiedIt->second; - } + // We will only do copy between two data models, setting this in arguments + // to configure the updater + ctxArgs[UsdMayaPrimUpdaterArgsTokens->copyOperation] = true; + UsdMayaPrimUpdaterContext context(dstProxyShape->getTime(), dstStage, ctxArgs); + + // Export out to a temporary layer. + PushExportResult pushExportResult = pushExport(mayaObject, context); + if (pushExportResult.srcRootPath.IsEmpty()) { + return {}; + } + progressBar.advance(); + + // Copy the temporary layer contents out to the proper destination. + const auto& srcStage = pushExportResult.stage; + const auto& srcLayer = pushExportResult.layer; + const auto& editTarget = dstStage->GetEditTarget(); + const auto& dstLayer = editTarget.GetLayer(); + + // Validate that the destination parent prim is valid. + UsdPrim dstParentPrim = MayaUsd::ufe::ufePathToPrim(dstPath); + if (!dstParentPrim.IsValid()) { + return {}; + } + progressBar.advance(); + + // We need the parent path of the source and destination to + // fixup the paths of the source prims we copy to their + // destination paths. + const SdfPath srcParentPath = pushExportResult.srcRootPath.GetParentPath(); + const SdfPath dstParentPath = dstParentPrim.GetPath(); + + if (TF_VERIFY(pushExportResult.usdToDag)) { + processPushExtras( + context._pushExtras, *pushExportResult.usdToDag, srcParentPath, dstParentPath); + } + + CopyLayerPrimsOptions options; + options.progressBar = &progressBar; + options.mergeScopes = true; + + CopyLayerPrimsResult copyResult = copyLayerPrims( + srcStage, + srcLayer, + srcParentPath, + dstStage, + dstLayer, + dstParentPath, + { pushExportResult.srcRootPath }, + options); + + context._pushExtras.finalize(MayaUsd::ufe::stagePath(dstStage), copyResult.renamedPaths); + + auto ufeItem = Ufe::Hierarchy::createItem(dstPath); + if (TF_VERIFY(ufeItem)) { + Ufe::Scene::instance().notify(Ufe::SubtreeInvalidate(ufeItem)); + } + progressBar.advance(); + + scopeIt.end(); + executeAdditionalCommands(context); + progressBar.advance(); + + SdfPath finalUsdPath(pushExportResult.srcRootPath); + { + auto copiedIt = copyResult.copiedPaths.find(finalUsdPath); + if (copiedIt != copyResult.copiedPaths.end()) { + finalUsdPath = copiedIt->second; } - { - auto renamedIt = copyResult.renamedPaths.find(finalUsdPath); - if (renamedIt != copyResult.renamedPaths.end()) { - finalUsdPath = renamedIt->second; - } + } + { + auto renamedIt = copyResult.renamedPaths.find(finalUsdPath); + if (renamedIt != copyResult.renamedPaths.end()) { + finalUsdPath = renamedIt->second; } - - Ufe::PathSegment pathSegment = UsdUfe::usdPathToUfePathSegment(finalUsdPath); - return { Ufe::Path(dstPath + pathSegment) }; } - // Copy operations to the same data model not supported here. - return {}; + Ufe::PathSegment pathSegment = UsdUfe::usdPathToUfePathSegment(finalUsdPath); + return { Ufe::Path(dstPath + pathSegment) }; } void PrimUpdaterManager::onProxyContentChanged( diff --git a/lib/mayaUsd/fileio/primUpdaterManager.h b/lib/mayaUsd/fileio/primUpdaterManager.h index bfaa44ca8d..ac8f367d5b 100644 --- a/lib/mayaUsd/fileio/primUpdaterManager.h +++ b/lib/mayaUsd/fileio/primUpdaterManager.h @@ -64,7 +64,7 @@ class PrimUpdaterManager : public PXR_NS::TfWeakBase MAYAUSD_CORE_PUBLIC bool discardEdits(const MDagPath& dagPath); - /// \brief Copy USD data into USD or Maya data. + /// \brief Copy Maya nodes to USD data or USD data to Maya nodes. /// \return list of destination paths. MAYAUSD_CORE_PUBLIC std::vector duplicate( @@ -72,6 +72,22 @@ class PrimUpdaterManager : public PXR_NS::TfWeakBase const Ufe::Path& dstPath, const VtDictionary& userArgs = VtDictionary()); + /// \brief Copy USD data to Maya nodes. + /// \return list of destination paths. + MAYAUSD_CORE_PUBLIC + std::vector duplicateToMaya( + const Ufe::Path& srcPath, + const Ufe::Path& dstPath, + const VtDictionary& userArgs = VtDictionary()); + + /// \brief Copy Maya nodes to USD data. + /// \return list of destination paths. + MAYAUSD_CORE_PUBLIC + std::vector duplicateToUsd( + const MObject& mayaObject, + const Ufe::Path& dstPath, + const VtDictionary& userArgs = VtDictionary()); + /// \brief Returns the singleton prim updater manager MAYAUSD_CORE_PUBLIC static PrimUpdaterManager& getInstance(); diff --git a/test/lib/mayaUsd/fileio/testDuplicateAs.py b/test/lib/mayaUsd/fileio/testDuplicateAs.py index 531d29be5d..2aa5d6a47e 100644 --- a/test/lib/mayaUsd/fileio/testDuplicateAs.py +++ b/test/lib/mayaUsd/fileio/testDuplicateAs.py @@ -49,7 +49,7 @@ class DuplicateAsTestCase(unittest.TestCase): @classmethod def setUpClass(cls): - fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False) + cls.inputPath = fixturesUtils.setUpClass(__file__) if not cls.pluginsLoaded: cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() @@ -390,7 +390,7 @@ def testDuplicateWithoutMeshes(self): '''Duplicate a Maya sphere without the meshes, only materials.''' # Create a sphere. - sphere = cmds.polySphere(r=1) + sphere = cmds.polyCube() # Create a stage to receive the USD duplicate. psPathStr = mayaUsd_createStageWithNewLayer.createStageWithNewLayer() @@ -402,7 +402,7 @@ def testDuplicateWithoutMeshes(self): # Verify that the both the sphere and material were copied. looksPrim = stage.GetPrimAtPath("/Looks") self.assertTrue(looksPrim.IsValid()) - spherePrim = stage.GetPrimAtPath("/pSphere1") + spherePrim = stage.GetPrimAtPath("/pCube1") self.assertTrue(spherePrim.IsValid()) # Undo duplicate to USD. @@ -418,6 +418,31 @@ def testDuplicateWithoutMeshes(self): self.assertFalse(spherePrim.IsValid()) + def testDuplicateMaterial(self): + '''Duplicate a Maya material directly.''' + + # Reuse a Maya test file from the usd file IO tests. + mayaFileName = 'one-group.ma' + inputPath = self.inputPath.replace('mayaUsd', 'usd').replace('fileio', 'translators') + mayaFile = os.path.join(inputPath, 'UsdExportMaterialScopeTest', mayaFileName) + cmds.file(mayaFile, force=True, open=True) + + # Create a stage to receive the USD duplicate. + psPathStr = mayaUsd_createStageWithNewLayer.createStageWithNewLayer() + stage = mayaUsd.lib.GetPrim(psPathStr).GetStage() + + # Duplicate a Maya material to USD without meshes, with materials + cmds.mayaUsdDuplicate("standardSurface4", psPathStr, exportOptions='exportMaterials=1;excludeExportTypes=[Mesh]') + + # Verify that the material was copied. + looksPrim = stage.GetPrimAtPath("/Looks") + self.assertTrue(looksPrim.IsValid()) + materialPrim = stage.GetPrimAtPath("/Looks/standardSurface4SG") + self.assertTrue(materialPrim.IsValid()) + shaderPrim = stage.GetPrimAtPath("/Looks/standardSurface4SG/standardSurface4") + self.assertTrue(shaderPrim.IsValid()) + + def testDuplicateSelection(self): '''Duplicate a Maya sphere and cone both in selection by calling the MEL script used in the UI.'''