diff --git a/lib/mayaUsd/fileio/orphanedNodesManager.cpp b/lib/mayaUsd/fileio/orphanedNodesManager.cpp index 40525079a3..36c39aa03c 100644 --- a/lib/mayaUsd/fileio/orphanedNodesManager.cpp +++ b/lib/mayaUsd/fileio/orphanedNodesManager.cpp @@ -62,7 +62,7 @@ OrphanedNodesManager::Memento& OrphanedNodesManager::Memento::operator=(Memento& return *this; } -Ufe::Trie OrphanedNodesManager::Memento::release() +OrphanedNodesManager::PulledPrims OrphanedNodesManager::Memento::release() { return std::move(_pulledPrims); } @@ -74,10 +74,11 @@ Ufe::Trie OrphanedNodesManager::Memento:: namespace { using PullVariantInfo = OrphanedNodesManager::PullVariantInfo; +using PullVariantInfos = OrphanedNodesManager::PullVariantInfos; using VariantSetDescriptor = OrphanedNodesManager::VariantSetDescriptor; using VariantSelection = OrphanedNodesManager::VariantSelection; using PulledPrims = OrphanedNodesManager::PulledPrims; -using PulledPrimNode = Ufe::TrieNode; +using PulledPrimNode = OrphanedNodesManager::PulledPrimNode; Ufe::PathSegment::Components trieNodeToPathComponents(PulledPrimNode::Ptr trieNode); Ufe::Path trieNodeToPulledPrimUfePath(PulledPrimNode::Ptr trieNode); @@ -102,11 +103,13 @@ void renameVariantInfo( { // Note: TrieNode has no non-const data() function, so to modify the // data we must make a copy, modify the copy and call setData(). - PullVariantInfo newVariantInfo = trieNode->data(); + PullVariantInfos newVariantInfos = trieNode->data(); - renameVariantDescriptors(newVariantInfo.variantSetDescriptors, oldPath, newPath); + for (auto& info : newVariantInfos) { + renameVariantDescriptors(info.variantSetDescriptors, oldPath, newPath); + } - trieNode->setData(newVariantInfo); + trieNode->setData(newVariantInfos); } void renamePullInformation( @@ -141,8 +144,10 @@ void renamePullInformation( } } - const MDagPath& mayaPath = trieNode->data().editedAsMayaRoot; - TF_VERIFY(writePullInformation(pulledPath, mayaPath)); + for (const PullVariantInfo& info : trieNode->data()) { + const MDagPath& mayaPath = info.editedAsMayaRoot; + TF_VERIFY(writePullInformation(pulledPath, mayaPath)); + } } void recursiveRename( @@ -206,21 +211,49 @@ OrphanedNodesManager::OrphanedNodesManager() { } +bool OrphanedNodesManager::has(const Ufe::Path& pulledPath, const MDagPath& editedAsMayaRoot) const +{ + PulledPrimNode::Ptr node = _pulledPrims.find(pulledPath); + if (!node) + return false; + + const PullVariantInfos& infos = node->data(); + for (const PullVariantInfo& info : infos) + if (info.editedAsMayaRoot == editedAsMayaRoot) + return true; + + return false; +} + +bool OrphanedNodesManager::has(const Ufe::Path& pulledPath) const +{ + PulledPrimNode::Ptr node = _pulledPrims.find(pulledPath); + if (!node) + return false; + + // We store a list of (path, list of (variant set, variant set selection)), + // for all ancestors, starting at closest ancestor. + auto ancestorPath = pulledPath.pop(); + auto vsd = variantSetDescriptors(ancestorPath); + + const PullVariantInfos& infos = node->data(); + for (const PullVariantInfo& info : infos) + if (info.variantSetDescriptors == vsd) + return true; + + return false; +} + void OrphanedNodesManager::add(const Ufe::Path& pulledPath, const MDagPath& editedAsMayaRoot) { // Adding a node twice to the orphan manager is idem-potent. The manager was already - // racking that node. - if (_pulledPrims.containsDescendantInclusive(pulledPath)) + // tracking that node. + if (_pulledPrims.containsDescendant(pulledPath)) return; - // Add the edited-as-Maya root to our pulled prims prefix tree. Also add the full - // configuration of variant set selections for each ancestor, up to the USD - // pseudo-root. Variants on the pulled path itself are ignored, as once - // pulled into Maya they cannot be changed. - if (_pulledPrims.containsDescendantInclusive(pulledPath)) { - TF_WARN("Trying to edit-as-Maya a descendant of an already edited prim."); + if (has(pulledPath, editedAsMayaRoot)) return; - } + if (pulledPath.runTimeId() != MayaUsd::ufe::getUsdRunTimeId()) { TF_WARN("Trying to monitor a non-USD node for edit-as-Maya orphaning."); return; @@ -231,25 +264,36 @@ void OrphanedNodesManager::add(const Ufe::Path& pulledPath, const MDagPath& edit auto ancestorPath = pulledPath.pop(); auto vsd = variantSetDescriptors(ancestorPath); - _pulledPrims.add(pulledPath, PullVariantInfo(editedAsMayaRoot, vsd)); + PulledPrimNode::Ptr node = _pulledPrims.find(pulledPath); + if (node) { + PullVariantInfos infos = node->data(); + infos.emplace_back(PullVariantInfo(editedAsMayaRoot, vsd)); + node->setData(infos); + } else { + _pulledPrims.add(pulledPath, { PullVariantInfo(editedAsMayaRoot, vsd) }); + } } -OrphanedNodesManager::Memento OrphanedNodesManager::remove(const Ufe::Path& pulledPath) +OrphanedNodesManager::Memento +OrphanedNodesManager::remove(const Ufe::Path& pulledPath, const MDagPath& editedAsMayaRoot) { - Memento oldPulledPrims(preserve()); - TF_VERIFY(_pulledPrims.remove(pulledPath) != nullptr); - return oldPulledPrims; -} + Memento oldPulledPrims(preserve()); + PulledPrimNode::Ptr node = _pulledPrims.find(pulledPath); + if (node) { + PullVariantInfos infos = node->data(); + for (size_t i = infos.size() - 1; i != size_t(0) - size_t(1); --i) { + if (infos[i].editedAsMayaRoot == editedAsMayaRoot) { + infos.erase(infos.begin() + i); + } + } -const PullVariantInfo& OrphanedNodesManager::get(const Ufe::Path& pulledPath) const -{ - const auto infoNode = _pulledPrims.find(pulledPath); - if (!infoNode || !infoNode->hasData()) { - static const PullVariantInfo empty; - return empty; + if (infos.size() > 0) { + node->setData(infos); + } else { + _pulledPrims.remove(pulledPath); + } } - - return infoNode->data(); + return oldPulledPrims; } void OrphanedNodesManager::operator()(const Ufe::Notification& n) @@ -310,7 +354,8 @@ void OrphanedNodesManager::handleOp(const Ufe::SceneCompositeNotification::Op& o // point. It may be an internal node, without data. auto ancestorNode = _pulledPrims.node(op.path); TF_VERIFY(ancestorNode); - recursiveSwitch(ancestorNode, op.path); + recursiveSwitch(ancestorNode, op.path, true); + recursiveSwitch(ancestorNode, op.path, false); } break; case Ufe::SceneCompositeNotification::OpType::ObjectDelete: { // The following cases will generate object delete: @@ -345,63 +390,54 @@ void OrphanedNodesManager::handleOp(const Ufe::SceneCompositeNotification::Op& o return; } - auto parentHier = Ufe::Hierarchy::hierarchy(parentItem); - if (!parentHier->hasChildren()) { + // USD sends resync changes (UFE subtree invalidate) on the + // pseudo-root itself. Since the pseudo-root has no payload or + // variant, ignore these. + auto parentUsdItem = std::dynamic_pointer_cast(parentItem); + if (!parentUsdItem) { + return; + } + + // On variant switch, given a pulled prim, the session layer will + // have path-based USD overs for pull information and active + // (false) for that prim in the session layer. If a prim child + // brought in by variant switching has the same name as that of the + // pulled prim in a previous variant, the overs will apply to to + // the new prim, which would then get a path mapping, which is + // inappropriate. Read children using the USD API, including + // inactive children (since pulled prims are inactivated), to + // support a variant switch to variant child with the same name. + auto parentPrim = parentUsdItem->prim(); + bool foundChild { false }; + for (const auto& child : + parentPrim.GetFilteredChildren(UsdPrimIsDefined && !UsdPrimIsAbstract)) { + auto childPath = parentItem->path().popSegment(); + childPath = childPath + + Ufe::PathSegment( + child.GetPath().GetAsString(), MayaUsd::ufe::getUsdRunTimeId(), '/'); + + auto ancestorNode = _pulledPrims.node(childPath); + // If there is no ancestor node in the trie, this means that + // the new hierarchy is completely different from the one when + // the pull occurred, which means that the pulled object must + // stay hidden. + if (!ancestorNode) + continue; + + foundChild = true; + recursiveSwitch(ancestorNode, childPath, true); + recursiveSwitch(ancestorNode, childPath, false); + } + + // Following a subtree invalidate, if none of the now-valid + // children appear in the trie, means that we've switched to a + // different variant or it was a payload that got unloaded, + // so everything below that path should be hidden. + if (!foundChild) { auto ancestorNode = _pulledPrims.node(op.path); if (ancestorNode) { recursiveSetOrphaned(ancestorNode, true); } - return; - } else { - // On variant switch, given a pulled prim, the session layer will - // have path-based USD overs for pull information and active - // (false) for that prim in the session layer. If a prim child - // brought in by variant switching has the same name as that of the - // pulled prim in a previous variant, the overs will apply to to - // the new prim, which would then get a path mapping, which is - // inappropriate. Read children using the USD API, including - // inactive children (since pulled prims are inactivated), to - // support a variant switch to variant child with the same name. - - auto parentUsdItem = std::dynamic_pointer_cast(parentItem); - if (!parentUsdItem) { - // USD sends resync changes (UFE subtree invalidate) on the - // pseudo-root itself. Since the pseudo-root has no payload or - // variant, ignore these. - TF_VERIFY(parentItem->path().nbSegments() == 1); - return; - } - auto parentPrim = parentUsdItem->prim(); - bool foundChild { false }; - for (const auto& child : - parentPrim.GetFilteredChildren(UsdPrimIsDefined && !UsdPrimIsAbstract)) { - auto childPath = parentItem->path().popSegment(); - childPath - = childPath - + Ufe::PathSegment( - child.GetPath().GetAsString(), MayaUsd::ufe::getUsdRunTimeId(), '/'); - - auto ancestorNode = _pulledPrims.node(childPath); - // If there is no ancestor node in the trie, this means that - // the new hierarchy is completely different from the one when - // the pull occurred, which means that the pulled object must - // stay hidden. - if (!ancestorNode) - continue; - - foundChild = true; - recursiveSwitch(ancestorNode, childPath); - } - if (!foundChild) { - // Following a subtree invalidate, if none of the now-valid - // children appear in the trie, means that we've switched to a - // different variant, and everything below that path should be - // hidden. - auto ancestorNode = _pulledPrims.node(op.path); - if (ancestorNode) { - recursiveSetOrphaned(ancestorNode, true); - } - } } } break; #ifdef UFE_V4_FEATURES_AVAILABLE @@ -429,7 +465,8 @@ OrphanedNodesManager::Memento OrphanedNodesManager::preserve() const void OrphanedNodesManager::restore(Memento&& previous) { _pulledPrims = previous.release(); } -bool OrphanedNodesManager::isOrphaned(const Ufe::Path& pulledPath) const +bool OrphanedNodesManager::isOrphaned(const Ufe::Path& pulledPath, const MDagPath& editedAsMayaRoot) + const { auto trieNode = _pulledPrims.node(pulledPath); if (!trieNode) { @@ -442,15 +479,24 @@ bool OrphanedNodesManager::isOrphaned(const Ufe::Path& pulledPath) const return false; } - const PullVariantInfo& variantInfo = trieNode->data(); + const std::vector& variantInfos = trieNode->data(); - // If the pull parent is visible, the pulled path is not orphaned. - MDagPath pullParentPath = variantInfo.editedAsMayaRoot; - pullParentPath.pop(); + for (const PullVariantInfo& variantInfo : variantInfos) { - MFnDagNode fn(pullParentPath); - auto visibilityPlug = fn.findPlug("visibility", /* tryNetworked */ true); - return !visibilityPlug.asBool(); + if (!(variantInfo.editedAsMayaRoot == editedAsMayaRoot)) { + continue; + } + + // If the pull parent is visible, the pulled path is not orphaned. + MDagPath pullParentPath = editedAsMayaRoot; + pullParentPath.pop(); + + MFnDagNode fn(pullParentPath); + auto visibilityPlug = fn.findPlug("visibility", /* tryNetworked */ true); + return !visibilityPlug.asBool(); + } + + return false; } namespace { @@ -525,10 +571,21 @@ MStatus setNodeVisibility(const MDagPath& dagPath, bool visibility) /* static */ bool OrphanedNodesManager::setOrphaned(const PulledPrimNode::Ptr& trieNode, bool orphaned) { - TF_VERIFY(trieNode->hasData()); + if (!trieNode->hasData()) + return true; - const PullVariantInfo& variantInfo = trieNode->data(); + for (const PullVariantInfo& variantInfo : trieNode->data()) + setOrphaned(trieNode, variantInfo, orphaned); + return true; +} + +/* static */ +bool OrphanedNodesManager::setOrphaned( + const PulledPrimNode::Ptr& trieNode, + const PullVariantInfo& variantInfo, + bool orphaned) +{ // Note: the change to USD data must be done *after* changes to Maya data because // the outliner reacts to UFE notifications received following the USD edits // to rebuild the node tree and the Maya node we want to hide must have been @@ -537,23 +594,34 @@ bool OrphanedNodesManager::setOrphaned(const PulledPrimNode::Ptr& trieNode, bool pullParentPath.pop(); CHECK_MSTATUS_AND_RETURN(setNodeVisibility(pullParentPath, !orphaned), false); - const Ufe::Path pulledPrimPath = trieNodeToPulledPrimUfePath(trieNode); - // Note: if we are called due to the user deleting the stage, then the pulled prim // path will be invalid and trying to add or remove information on it will // fail, and cause spurious warnings in the script editor, so avoid it. - if (!pulledPrimPath.empty()) { - if (orphaned) { - removePulledPrimMetadata(pulledPrimPath); - removeExcludeFromRendering(pulledPrimPath); - } else { - writePulledPrimMetadata(pulledPrimPath, variantInfo.editedAsMayaRoot); - addExcludeFromRendering(pulledPrimPath); - } + const Ufe::Path pulledPrimPath = trieNodeToPulledPrimUfePath(trieNode); + if (pulledPrimPath.empty()) + return true; + + // Note: if we are called due to the user deleting the stage, then the stage + // will be invalid, don't treat this as an error. + UsdStagePtr stage = getStage(pulledPrimPath); + if (!stage) + return true; + + if (orphaned) { + removePulledPrimMetadata(pulledPrimPath); + removeExcludeFromRendering(pulledPrimPath); + } else { + writePulledPrimMetadata(pulledPrimPath, variantInfo.editedAsMayaRoot); + addExcludeFromRendering(pulledPrimPath); } + TF_STATUS( + "Edited-as-Maya prim \"%s\" %s.", + pulledPrimPath.string().c_str(), + orphaned ? "was orphaned and is now hidden" : "no longer orphaned and is now shown"); + return true; -} +} // namespace MAYAUSD_NS_DEF /* static */ void OrphanedNodesManager::recursiveSetOrphaned(const PulledPrimNode::Ptr& trieNode, bool orphaned) @@ -574,7 +642,8 @@ void OrphanedNodesManager::recursiveSetOrphaned(const PulledPrimNode::Ptr& trieN /* static */ void OrphanedNodesManager::recursiveSwitch( const PulledPrimNode::Ptr& trieNode, - const Ufe::Path& ufePath) + const Ufe::Path& ufePath, + const bool processOrphans) { // We know in our case that a trie node with data can't have children, // since descendants of a pulled prim can't be pulled. A trie node with @@ -592,11 +661,15 @@ void OrphanedNodesManager::recursiveSwitch( // tree state don't match, the pulled node must be made invisible. // Inactivation must not be considered, as the USD pulled node is made // inactive on pull, to avoid rendering it. - const auto& originalDesc = trieNode->data().variantSetDescriptors; - const auto currentDesc = variantSetDescriptors(ufePath.pop()); - const bool variantSetsMatch = (originalDesc == currentDesc); - const bool orphaned = (pulledNode && !variantSetsMatch); - TF_VERIFY(setOrphaned(trieNode, orphaned)); + const auto currentDesc = variantSetDescriptors(ufePath.pop()); + const PullVariantInfos infos = trieNode->data(); + for (const PullVariantInfo& variantInfo : infos) { + const auto& originalDesc = variantInfo.variantSetDescriptors; + const bool variantSetsMatch = (originalDesc == currentDesc); + const bool orphaned = (pulledNode && !variantSetsMatch); + if (processOrphans == orphaned) + TF_VERIFY(setOrphaned(trieNode, variantInfo, orphaned)); + } } else { const bool isGatewayToUsd = Ufe::SceneSegmentHandler::isGateway(ufePath); for (const auto& c : trieNode->childrenComponents()) { @@ -606,10 +679,10 @@ void OrphanedNodesManager::recursiveSwitch( // component stored in the trie. When crossing runtimes, we // need to create a segment instead with the new runtime ID. if (!isGatewayToUsd) { - recursiveSwitch(childTrieNode, ufePath + c); + recursiveSwitch(childTrieNode, ufePath + c, processOrphans); } else { Ufe::PathSegment childSegment(c, ufe::getUsdRunTimeId(), '/'); - recursiveSwitch(childTrieNode, ufePath + childSegment); + recursiveSwitch(childTrieNode, ufePath + childSegment, processOrphans); } } } @@ -626,8 +699,10 @@ OrphanedNodesManager::variantSetDescriptors(const Ufe::Path& p) auto ancestor = Ufe::Hierarchy::createItem(path); auto usdAncestor = std::static_pointer_cast(ancestor); auto variantSets = usdAncestor->prim().GetVariantSets(); + auto setNames = variantSets.GetNames(); + std::sort(setNames.begin(), setNames.end()); std::list vs; - for (const auto& vsn : variantSets.GetNames()) { + for (const auto& vsn : setNames) { vs.emplace_back(vsn, variantSets.GetVariantSelection(vsn)); } vsd.emplace_back(path, vs); diff --git a/lib/mayaUsd/fileio/orphanedNodesManager.h b/lib/mayaUsd/fileio/orphanedNodesManager.h index 2d31d9103d..784e79c23e 100644 --- a/lib/mayaUsd/fileio/orphanedNodesManager.h +++ b/lib/mayaUsd/fileio/orphanedNodesManager.h @@ -96,6 +96,10 @@ class MAYAUSD_CORE_PUBLIC OrphanedNodesManager : public Ufe::Observer std::list variantSetDescriptors; }; + using PullVariantInfos = std::vector; + using PulledPrims = Ufe::Trie; + using PulledPrimNode = Ufe::TrieNode; + /// \brief Entire state of the OrphanedNodesManager at a point in time, used for undo/redo. class MAYAUSD_CORE_PUBLIC Memento { @@ -117,11 +121,11 @@ class MAYAUSD_CORE_PUBLIC OrphanedNodesManager : public Ufe::Observer // Private, for opacity. friend class OrphanedNodesManager; - Memento(Ufe::Trie&& pulledPrims); + Memento(PulledPrims&& pulledPrims); - Ufe::Trie release(); + PulledPrims release(); - Ufe::Trie _pulledPrims; + PulledPrims _pulledPrims; }; // Construct an empty orphan manager. @@ -135,14 +139,17 @@ class MAYAUSD_CORE_PUBLIC OrphanedNodesManager : public Ufe::Observer // Asserts that the pulled path is not in the trie. void add(const Ufe::Path& pulledPath, const MDagPath& editedAsMayaRoot); + // Verify if the pulled path and the root of the generated + // Maya nodes to the trie of pulled prims are alredy tracked. + bool has(const Ufe::Path& pulledPath, const MDagPath& editedAsMayaRoot) const; + + // Verify if the pulled path with its current variant set is alredy tracked. + bool has(const Ufe::Path& pulledPath) const; + // Remove the pulled path from the trie of pulled prims. Asserts that the // path is in the trie. Returns a memento (see Memento Pattern) for undo // purposes, to be used as argument to restore(). - Memento remove(const Ufe::Path& pulledPath); - - // Retrieve the variant information of a pulled prim. - // Returns an empty info if the prim was not tracked by the orphan manager. - const PullVariantInfo& get(const Ufe::Path& pulledPath) const; + Memento remove(const Ufe::Path& pulledPath, const MDagPath& editedAsMayaRoot); // Preserve the trie of pulled prims into a memento. Memento preserve() const; @@ -158,19 +165,24 @@ class MAYAUSD_CORE_PUBLIC OrphanedNodesManager : public Ufe::Observer // Return whether the Dag hierarchy corresponding to the pulled path is // orphaned. - bool isOrphaned(const Ufe::Path& pulledPath) const; + bool isOrphaned(const Ufe::Path& pulledPath, const MDagPath& editedAsMayaRoot) const; - using PulledPrims = Ufe::Trie; - using PulledPrimNode = Ufe::TrieNode; const PulledPrims& getPulledPrims() const { return _pulledPrims; } private: void handleOp(const Ufe::SceneCompositeNotification::Op& op); static void recursiveSetOrphaned(const PulledPrimNode::Ptr& trieNode, bool orphaned); - static void recursiveSwitch(const PulledPrimNode::Ptr& trieNode, const Ufe::Path& ufePath); + static void recursiveSwitch( + const PulledPrimNode::Ptr& trieNode, + const Ufe::Path& ufePath, + const bool processOrphans); static bool setOrphaned(const PulledPrimNode::Ptr& trieNode, bool orphaned); + static bool setOrphaned( + const PulledPrimNode::Ptr& trieNode, + const PullVariantInfo& variantInfo, + bool orphaned); // Member function to access private nested classes. static std::list variantSetDescriptors(const Ufe::Path& path); diff --git a/lib/mayaUsd/fileio/orphanedNodesManagerIO.cpp b/lib/mayaUsd/fileio/orphanedNodesManagerIO.cpp index eae899ee18..7b9c13a4ad 100644 --- a/lib/mayaUsd/fileio/orphanedNodesManagerIO.cpp +++ b/lib/mayaUsd/fileio/orphanedNodesManagerIO.cpp @@ -49,6 +49,20 @@ namespace { // ], // }, // ], +// "more pull info": [ +// { +// "editedAsMayaRoot": "DAG-path-of-root-of-generated-Maya-data" +// "variantSetDescriptors": [ +// { +// "path": "UFE-path-of-one-ancestor", +// "variantSelections": [ +// [ "variant-set-1-name", "variant-set-1-selection" ], +// [ "variant-set-2-name", "variant-set-2-selection" ], +// ], +// }, +// ], +// }, +// ], // }, // }, // }, @@ -59,6 +73,7 @@ namespace { static const std::string ufeComponentPrefix = "/"; static const std::string pullInfoJsonKey = "pull info"; +static const std::string morePullInfoJsonKey = "more pull info"; static const std::string editedAsMayaRootJsonKey = "editedAsMayaRoot"; static const std::string variantSetDescriptorsJsonKey = "variantSetDescriptors"; static const std::string pathJsonKey = "path"; @@ -74,8 +89,9 @@ using VariantSelection = OrphanedNodesManager::VariantSelection; using VariantSetDesc = OrphanedNodesManager::VariantSetDescriptor; using VariantSetDescList = std::list; using PullVariantInfo = OrphanedNodesManager::PullVariantInfo; -using PullInfoTrie = Ufe::Trie; -using PullInfoTrieNode = Ufe::TrieNode; +using PullVariantInfos = std::vector; +using PullInfoTrie = OrphanedNodesManager::PulledPrims; +using PullInfoTrieNode = OrphanedNodesManager::PulledPrimNode; using Memento = OrphanedNodesManager::Memento; //////////////////////////////////////////////////////////////////////////// @@ -90,6 +106,7 @@ PXR_NS::JsArray convertToArray(const VariantSelection& variantSel); PXR_NS::JsObject convertToObject(const VariantSetDesc& variantDesc); PXR_NS::JsArray convertToArray(const std::list& allVariantDesc); PXR_NS::JsObject convertToObject(const PullVariantInfo& pullInfo); +PXR_NS::JsObject convertToObject(const PullVariantInfos& pullInfos); PXR_NS::JsObject convertToObject(const PullInfoTrieNode::Ptr& pullInfoNode); PXR_NS::JsObject convertToObject(const PullInfoTrie& allPulledInfo); @@ -97,6 +114,7 @@ VariantSelection convertToVariantSelection(const PXR_NS::JsArray& variantSelJs VariantSetDesc convertToVariantSetDescriptor(const PXR_NS::JsObject& variantDescJson); VariantSetDescList convertToVariantSetDescList(const PXR_NS::JsArray& allVariantDescJson); PullVariantInfo convertToPullVariantInfo(const PXR_NS::JsObject& pullInfoJson); +PullVariantInfos convertToPullVariantInfos(const PXR_NS::JsObject& pullInfoJson); void convertToPullInfoTrieNodePtr(const PXR_NS::JsObject&, PullInfoTrieNode::Ptr intoRoot); PullInfoTrie convertToPullInfoTrie(const PXR_NS::JsObject& allPulledInfoJson); @@ -199,6 +217,44 @@ PullVariantInfo convertToPullVariantInfo(const PXR_NS::JsObject& pullInfoJson) return pullInfo; } +PXR_NS::JsObject convertToObject(const PullVariantInfos& pullInfos) +{ + PXR_NS::JsObject pullInfoJson; + + if (pullInfos.size() > 0) { + pullInfoJson = convertToObject(pullInfos[0]); + } + + if (pullInfos.size() > 1) { + PXR_NS::JsArray morePullInfoJson; + for (size_t i = 1; i < pullInfos.size(); ++i) { + PXR_NS::JsObject moreInfoJson = convertToObject(pullInfos[i]); + morePullInfoJson.emplace_back(moreInfoJson); + } + pullInfoJson[morePullInfoJsonKey] = morePullInfoJson; + } + + return pullInfoJson; +} + +PullVariantInfos convertToPullVariantInfos(const PXR_NS::JsObject& pullInfoJson) +{ + PullVariantInfos pullInfos; + + if (pullInfoJson.count(editedAsMayaRootJsonKey)) { + pullInfos.emplace_back(convertToPullVariantInfo(pullInfoJson)); + } + + if (pullInfoJson.count(morePullInfoJsonKey)) { + PXR_NS::JsArray morePullInfoJson + = convertToArray(convertJsonKeyToValue(pullInfoJson, morePullInfoJsonKey)); + for (const PXR_NS::JsValue& value : morePullInfoJson) { + pullInfos.emplace_back(convertToPullVariantInfo(convertToObject(value))); + } + } + return pullInfos; +} + PXR_NS::JsObject convertToObject(const PullInfoTrieNode::Ptr& pullInfoNodePtr) { if (!pullInfoNodePtr) @@ -208,7 +264,7 @@ PXR_NS::JsObject convertToObject(const PullInfoTrieNode::Ptr& pullInfoNodePtr) PXR_NS::JsObject pullInfoNodeJson; - if (pullInfoNode.hasData()) { + if (pullInfoNode.hasData() && pullInfoNode.data().size() > 0) { pullInfoNodeJson[pullInfoJsonKey] = convertToObject(pullInfoNode.data()); } @@ -232,7 +288,7 @@ void convertToPullInfoTrieNodePtr( if (key.size() <= 0) { continue; } else if (key == pullInfoJsonKey) { - intoRoot->setData(convertToPullVariantInfo(convertToObject(value))); + intoRoot->setData(convertToPullVariantInfos(convertToObject(value))); } else if (key[0] == '/') { PullInfoTrieNode::Ptr child = std::make_shared(key.substr(1)); diff --git a/lib/mayaUsd/fileio/primUpdaterManager.cpp b/lib/mayaUsd/fileio/primUpdaterManager.cpp index 10228e4d0b..c7c4594514 100644 --- a/lib/mayaUsd/fileio/primUpdaterManager.cpp +++ b/lib/mayaUsd/fileio/primUpdaterManager.cpp @@ -119,12 +119,18 @@ SdfPath makeDstPath(const SdfPath& dstRootParentPath, const SdfPath& srcPath) auto relativeSrcPath = srcPath.MakeRelativePath(SdfPath::AbsoluteRootPath()); return dstRootParentPath.AppendPath(relativeSrcPath); } +} // namespace //------------------------------------------------------------------------------ // // Verify if the given prim under the given UFE path is an ancestor of an already edited prim. -bool hasEditedDescendant(const Ufe::Path& ufeQueryPath) +bool PrimUpdaterManager::hasEditedDescendant(const Ufe::Path& ufeQueryPath) const { +#ifdef HAS_ORPHANED_NODES_MANAGER + if (_orphanedNodesManager->has(ufeQueryPath)) + return true; +#endif + MObject pullSetObj; auto status = UsdMayaUtil::GetMObjectByName(kPullSetName, pullSetObj); if (status != MStatus::kSuccess) @@ -142,6 +148,15 @@ bool hasEditedDescendant(const Ufe::Path& ufeQueryPath) if (!readPullInformation(pulledDagPath, pulledUfePath)) continue; +#ifdef HAS_ORPHANED_NODES_MANAGER + // If the alread-edited node is orphaned, don't take it into consideration. + if (_orphanedNodesManager) { + if (_orphanedNodesManager->isOrphaned(pulledUfePath, pulledDagPath)) { + continue; + } + } +#endif + if (pulledUfePath.startsWith(ufeQueryPath)) return true; } @@ -149,6 +164,7 @@ bool hasEditedDescendant(const Ufe::Path& ufeQueryPath) return false; } +namespace { //------------------------------------------------------------------------------ // // The UFE path is to the pulled prim, and the Dag path is the corresponding @@ -198,22 +214,9 @@ bool writeAllPullInformation(const Ufe::Path& ufePulledPath, const MDagPath& edi // void removeAllPullInformation(const Ufe::Path& ufePulledPath) { - UsdPrim pulledPrim = MayaUsd::ufe::ufePathToPrim(ufePulledPath); - UsdStagePtr stage = pulledPrim.GetStage(); - if (!stage) - return; - MayaUsd::ProgressBarScope progressBar(1); - removePulledPrimMetadata(stage, pulledPrim); + removePulledPrimMetadata(ufePulledPath); progressBar.advance(); - - // Session layer cleanup - auto rootPrims = stage->GetSessionLayer()->GetRootPrims(); - MayaUsd::ProgressBarLoopScope rootPrimsLoop(rootPrims.size()); - for (const SdfPrimSpecHandle& rootPrimSpec : rootPrims) { - stage->GetSessionLayer()->RemovePrimIfInert(rootPrimSpec); - rootPrimsLoop.loopAdvance(); - } } //------------------------------------------------------------------------------ @@ -865,7 +868,7 @@ class RecordPullVariantInfoUndoItem : public MayaUsd::OpUndoItem bool undo() override { - _orphanedNodesManager->remove(_pulledPath); + _orphanedNodesManager->remove(_pulledPath, _editedAsMayaRoot); return true; } @@ -888,13 +891,14 @@ class RemovePullVariantInfoUndoItem : public MayaUsd::OpUndoItem // the global undo list. static bool execute( const std::shared_ptr& orphanedNodesManager, - const Ufe::Path& pulledPath) + const Ufe::Path& pulledPath, + const MDagPath& editedAsMayaRoot) { // Get the global undo list. auto& undoInfo = OpUndoItemList::instance(); - auto item - = std::make_unique(orphanedNodesManager, pulledPath); + auto item = std::make_unique( + orphanedNodesManager, pulledPath, editedAsMayaRoot); if (!item->redo()) { return false; } @@ -906,10 +910,12 @@ class RemovePullVariantInfoUndoItem : public MayaUsd::OpUndoItem RemovePullVariantInfoUndoItem( const std::shared_ptr& orphanedNodesManager, - const Ufe::Path& pulledPath) + const Ufe::Path& pulledPath, + const MDagPath& editedAsMayaRoot) : OpUndoItem(std::string("Remove pull path ") + Ufe::PathString::string(pulledPath)) , _orphanedNodesManager(orphanedNodesManager) , _pulledPath(pulledPath) + , _editedAsMayaRoot(editedAsMayaRoot) { } @@ -921,13 +927,14 @@ class RemovePullVariantInfoUndoItem : public MayaUsd::OpUndoItem bool redo() override { - _memento = _orphanedNodesManager->remove(_pulledPath); + _memento = _orphanedNodesManager->remove(_pulledPath, _editedAsMayaRoot); return true; } private: const std::shared_ptr _orphanedNodesManager; const Ufe::Path _pulledPath; + const MDagPath _editedAsMayaRoot; // Created by redo(). OrphanedNodesManager::Memento _memento; @@ -1056,7 +1063,8 @@ bool PrimUpdaterManager::mergeToUsd( // thinking the Maya data shoudl be shown again... #ifdef HAS_ORPHANED_NODES_MANAGER if (_orphanedNodesManager) { - if (!TF_VERIFY(RemovePullVariantInfoUndoItem::execute(_orphanedNodesManager, pulledPath))) { + if (!TF_VERIFY(RemovePullVariantInfoUndoItem::execute( + _orphanedNodesManager, pulledPath, mayaDagPath))) { return false; } } @@ -1161,7 +1169,7 @@ bool PrimUpdaterManager::mergeToUsd( bool PrimUpdaterManager::editAsMaya(const Ufe::Path& path, const VtDictionary& userArgs) { if (hasEditedDescendant(path)) { - TF_WARN("Cannot edit an ancestor of an already edited node."); + TF_WARN("Cannot edit an ancestor (%s) of an already edited node.", path.string().c_str()); return false; } @@ -1294,7 +1302,7 @@ bool PrimUpdaterManager::discardEdits(const MDagPath& dagPath) auto usdPrim = MayaUsd::ufe::ufePathToPrim(pulledPath); #ifdef HAS_ORPHANED_NODES_MANAGER - auto ret = _orphanedNodesManager->isOrphaned(pulledPath) + auto ret = _orphanedNodesManager->isOrphaned(pulledPath, dagPath) ? discardOrphanedEdits(dagPath, pulledPath) : discardPrimEdits(pulledPath); #else @@ -1373,7 +1381,8 @@ bool PrimUpdaterManager::discardPrimEdits(const Ufe::Path& pulledPath) #ifdef HAS_ORPHANED_NODES_MANAGER if (_orphanedNodesManager) { - if (!TF_VERIFY(RemovePullVariantInfoUndoItem::execute(_orphanedNodesManager, pulledPath))) { + if (!TF_VERIFY(RemovePullVariantInfoUndoItem::execute( + _orphanedNodesManager, pulledPath, mayaDagPath))) { return false; } } @@ -1464,7 +1473,8 @@ bool PrimUpdaterManager::discardOrphanedEdits(const MDagPath& dagPath, const Ufe #ifdef HAS_ORPHANED_NODES_MANAGER if (_orphanedNodesManager) { - if (!TF_VERIFY(RemovePullVariantInfoUndoItem::execute(_orphanedNodesManager, pulledPath))) { + if (!TF_VERIFY(RemovePullVariantInfoUndoItem::execute( + _orphanedNodesManager, pulledPath, dagPath))) { return false; } } @@ -1883,12 +1893,12 @@ PrimUpdaterManager::PulledPrimPaths PrimUpdaterManager::getPulledPrimPaths() con return pulledPaths; const OrphanedNodesManager::PulledPrims& pulledPrims = _orphanedNodesManager->getPulledPrims(); - MayaUsd::TrieVisitor::visit( + MayaUsd::TrieVisitor::visit( pulledPrims, - [&pulledPaths]( - const Ufe::Path& path, - const Ufe::TrieNode& node) { - pulledPaths.emplace_back(path, node.data().editedAsMayaRoot); + [&pulledPaths](const Ufe::Path& path, const OrphanedNodesManager::PulledPrimNode& node) { + for (const OrphanedNodesManager::PullVariantInfo& info : node.data()) { + pulledPaths.emplace_back(path, info.editedAsMayaRoot); + } }); #endif diff --git a/lib/mayaUsd/fileio/primUpdaterManager.h b/lib/mayaUsd/fileio/primUpdaterManager.h index 2f455ff53d..f13d707cd9 100644 --- a/lib/mayaUsd/fileio/primUpdaterManager.h +++ b/lib/mayaUsd/fileio/primUpdaterManager.h @@ -114,8 +114,11 @@ class PrimUpdaterManager : public PXR_NS::TfWeakBase //! Create the pull parent and set it into the prim updater context. MDagPath setupPullParent(const Ufe::Path& pulledPath, VtDictionary& args); - //! Record pull information for the pulled path, for inspection on - //! scene changes. + //! Verify if the given prim at the given UFE path is an ancestor of an already edited prim. + bool hasEditedDescendant(const Ufe::Path& ufeQueryPath) const; + +//! Record pull information for the pulled path, for inspection on +//! scene changes. #ifdef HAS_ORPHANED_NODES_MANAGER // Maya file new or open callback. Member function to access other private // member functions. diff --git a/lib/mayaUsd/fileio/pullInformation.cpp b/lib/mayaUsd/fileio/pullInformation.cpp index 4751496734..4863e6a86f 100644 --- a/lib/mayaUsd/fileio/pullInformation.cpp +++ b/lib/mayaUsd/fileio/pullInformation.cpp @@ -53,7 +53,7 @@ const MString kPullDGMetadataKey("Pull_UfePath"); bool readPullInformation(const PXR_NS::UsdPrim& prim, std::string& dagPathStr) { - auto value = prim.GetCustomDataByKey(kPullPrimMetadataKey); + VtValue value = prim.GetCustomDataByKey(kPullPrimMetadataKey); if (!value.IsEmpty() && value.CanCast()) { dagPathStr = value.Get(); return !dagPathStr.empty(); @@ -151,8 +151,9 @@ bool writePulledPrimMetadata(PXR_NS::UsdPrim& pulledPrim, const MDagPath& edited if (!stage) return false; - PXR_NS::UsdEditContext editContext(stage, stage->GetSessionLayer()); - PXR_NS::VtValue value(editedAsMayaRoot.fullPathName().asChar()); + const PXR_NS::UsdEditContext editContext(stage, stage->GetSessionLayer()); + const PXR_NS::VtValue value(editedAsMayaRoot.fullPathName().asChar()); + return pulledPrim.SetMetadataByDictKey(SdfFieldKeys->CustomData, kPullPrimMetadataKey, value); } @@ -164,8 +165,12 @@ bool writePulledPrimMetadata(PXR_NS::UsdPrim& pulledPrim, const MDagPath& edited void removePulledPrimMetadata(const Ufe::Path& ufePulledPath) { PXR_NS::UsdPrim prim = MayaUsd::ufe::ufePathToPrim(ufePulledPath); - if (!prim.IsValid()) + if (!prim.IsValid()) { + TF_WARN( + "Could not find prim to remove pulled prim metadata on %s.", + ufePulledPath.string().c_str()); return; + } PXR_NS::UsdStagePtr stage = prim.GetStage(); if (!stage) @@ -173,10 +178,16 @@ void removePulledPrimMetadata(const Ufe::Path& ufePulledPath) removePulledPrimMetadata(stage, prim); } -void removePulledPrimMetadata(const PXR_NS::UsdStagePtr& stage, PXR_NS::UsdPrim& prim) +void removePulledPrimMetadata(const PXR_NS::UsdStagePtr& stage, PXR_NS::UsdPrim& pulledPrim) { - PXR_NS::UsdEditContext editContext(stage, stage->GetSessionLayer()); - prim.ClearCustomDataByKey(kPullPrimMetadataKey); + const PXR_NS::UsdEditContext editContext(stage, stage->GetSessionLayer()); + pulledPrim.ClearCustomDataByKey(kPullPrimMetadataKey); + + // Session layer cleanup. + auto rootPrims = stage->GetSessionLayer()->GetRootPrims(); + for (const SdfPrimSpecHandle& rootPrimSpec : rootPrims) { + stage->GetSessionLayer()->RemovePrimIfInert(rootPrimSpec); + } } //------------------------------------------------------------------------------ diff --git a/lib/mayaUsd/fileio/pullInformation.h b/lib/mayaUsd/fileio/pullInformation.h index e4f6c0dbdf..b5afdf4c4f 100644 --- a/lib/mayaUsd/fileio/pullInformation.h +++ b/lib/mayaUsd/fileio/pullInformation.h @@ -28,6 +28,11 @@ UFE_NS_DEF { class Path; } namespace MAYAUSD_NS_DEF { +//////////////////////////////////////////////////////////////////////////// +// +// Helper functions to write, read and remove edited-as-Maya (pull) +// information set on the Maya DAG node. + /// @brief Write on the Maya node the information necessary later-on to merge /// the USD prim that is edited as Maya. MAYAUSD_CORE_PUBLIC @@ -37,17 +42,28 @@ bool writePullInformation(const Ufe::Path& ufePulledPath, const MDagPath& edited /// that is edited as Maya. MAYAUSD_CORE_PUBLIC bool readPullInformation(const PXR_NS::UsdPrim& prim, std::string& dagPathStr); + MAYAUSD_CORE_PUBLIC bool readPullInformation(const PXR_NS::UsdPrim& prim, Ufe::SceneItem::Ptr& dagPathItem); + MAYAUSD_CORE_PUBLIC bool readPullInformation(const Ufe::Path& ufePath, MDagPath& dagPath); + MAYAUSD_CORE_PUBLIC bool readPullInformation(const MDagPath& dagpath, Ufe::Path& ufePath); +//////////////////////////////////////////////////////////////////////////// +// +// Helper functions to write, read and remove edited-as-Maya (pull) +// information set on the edited USD prim. + /// @brief Write on the USD prim the information necessary later-on to merge /// the USD prim that is edited as Maya. MAYAUSD_CORE_PUBLIC bool writePulledPrimMetadata(const Ufe::Path& ufePulledPath, const MDagPath& editedRoot); + +/// @brief Write on the USD prim the information necessary later-on to merge +/// the USD prim that is edited as Maya. MAYAUSD_CORE_PUBLIC bool writePulledPrimMetadata(PXR_NS::UsdPrim& pulledPrim, const MDagPath& editedRoot); @@ -55,9 +71,16 @@ bool writePulledPrimMetadata(PXR_NS::UsdPrim& pulledPrim, const MDagPath& edited /// that was edited as Maya. MAYAUSD_CORE_PUBLIC void removePulledPrimMetadata(const Ufe::Path& ufePulledPath); + +/// @brief Remove from the USD prim the information necessary to merge the USD prim +/// that was edited as Maya. MAYAUSD_CORE_PUBLIC void removePulledPrimMetadata(const PXR_NS::UsdStagePtr& stage, PXR_NS::UsdPrim& prim); +//////////////////////////////////////////////////////////////////////////// +// +// Helper functions to hide and show the edited prim. + /// @brief Hide the USD prim that is edited as Maya. /// This is done so that the USD prim and edited Maya data are not superposed /// in the viewport. @@ -69,6 +92,10 @@ bool addExcludeFromRendering(const Ufe::Path& ufePulledPath); MAYAUSD_CORE_PUBLIC bool removeExcludeFromRendering(const Ufe::Path& ufePulledPath); +//////////////////////////////////////////////////////////////////////////// +// +// Helper functions to check if a prim is already edited-as-Maya. + /// @brief Verify if the edited as Maya nodes corresponding to the given prim is orphaned. MAYAUSD_CORE_PUBLIC bool isEditedAsMayaOrphaned(const PXR_NS::UsdPrim& prim); diff --git a/lib/mayaUsd/fileio/translators/translatorMayaReference.cpp b/lib/mayaUsd/fileio/translators/translatorMayaReference.cpp index 446877ec2b..62c39a3a58 100644 --- a/lib/mayaUsd/fileio/translators/translatorMayaReference.cpp +++ b/lib/mayaUsd/fileio/translators/translatorMayaReference.cpp @@ -36,6 +36,8 @@ #include #include +#include +#include #include #include @@ -157,6 +159,12 @@ const TfToken MayaReferenceNodeName("MayaReferenceNodeName"); MStatus setMayaRefCustomAttribute(const UsdPrim& prim, const MFnReference& refDependNode) { + PXR_NS::UsdStagePtr stage = prim.GetStage(); + if (!stage) + return MS::kFailure; + + const PXR_NS::UsdEditContext editContext(stage, stage->GetSessionLayer()); + // Always have to try to create the attribute to make sure it is in the prim scope and not // inherited. If it was already created, it will be used. UsdAttribute attr = prim.CreateAttribute(MayaReferenceNodeName, SdfValueTypeNames->String); @@ -182,10 +190,13 @@ MString GetMayaRefCustomAttribute(const UsdPrim& prim) return MString(); // Check if the attribute type is correct. - if (value.GetType() != SdfValueTypeNames->String.GetType()) + if (value.IsEmpty()) return MString(); - return MString(value.Get().c_str()); + if (!value.CanCast()) + return MString(); + + return value.Get().c_str(); } MStatus LoadOrUnloadMayaReferenceWithUndo(const MObject& referenceObject, bool load) @@ -216,18 +227,15 @@ MStatus UnloadMayaReferenceWithUndo(const MObject& referenceObject) return LoadOrUnloadMayaReferenceWithUndo(referenceObject, false); } -} // namespace - -const TfToken UsdMayaTranslatorMayaReference::m_namespaceName = TfToken("mayaNamespace"); -const TfToken UsdMayaTranslatorMayaReference::m_referenceName = TfToken("mayaReference"); -const TfToken UsdMayaTranslatorMayaReference::m_mergeNamespacesOnClash - = TfToken("mergeNamespacesOnClash"); +const TfToken namespaceNamePrimAttrName = TfToken("mayaNamespace"); +const TfToken referenceNameAttrName = TfToken("mayaReference"); +const TfToken mergeNamespacesOnClashAttrName = TfToken("mergeNamespacesOnClash"); // Get the namespace attribute from prim -MString UsdMayaTranslatorMayaReference::namespaceFromPrim(const UsdPrim& prim) +MString namespaceFromPrim(const UsdPrim& prim) { std::string ns; - if (UsdAttribute namespaceAttribute = prim.GetAttribute(m_namespaceName)) { + if (UsdAttribute namespaceAttribute = prim.GetAttribute(namespaceNamePrimAttrName)) { TF_DEBUG(PXRUSDMAYA_TRANSLATORS) .Msg( "MayaReferenceLogic::update Checking namespace on prim \"%s\".\n", @@ -247,7 +255,7 @@ MString UsdMayaTranslatorMayaReference::namespaceFromPrim(const UsdPrim& prim) return MString(ns.c_str(), ns.size()); } -MString UsdMayaTranslatorMayaReference::getUniqueRefNodeName( +MString getUniqueRefNodeName( const UsdPrim& prim, const MFnDagNode& parentDag, const MFnReference& refDependNode) @@ -279,7 +287,11 @@ MString UsdMayaTranslatorMayaReference::getUniqueRefNodeName( return uniqueRefNodeName; } -MStatus UsdMayaTranslatorMayaReference::LoadMayaReference( +} // namespace + +static MStatus connectReferenceAssociatedNode(MFnDagNode& dagNode, MFnReference& refNode); + +MStatus UsdMayaTranslatorMayaReference::CreateMayaReference( const UsdPrim& prim, MObject& parent, MString& mayaReferencePath, @@ -287,7 +299,7 @@ MStatus UsdMayaTranslatorMayaReference::LoadMayaReference( bool mergeNamespacesOnClash) { TF_DEBUG(PXRUSDMAYA_TRANSLATORS) - .Msg("MayaReferenceLogic::LoadMayaReference prim=%s\n", prim.GetPath().GetText()); + .Msg("MayaReferenceLogic::CreateMayaReference prim=%s\n", prim.GetPath().GetText()); MStatus status; MFnDagNode parentDag(parent, &status); @@ -334,7 +346,7 @@ MStatus UsdMayaTranslatorMayaReference::LoadMayaReference( TF_DEBUG(PXRUSDMAYA_TRANSLATORS) .Msg( - "MayaReferenceLogic::LoadMayaReference prim=%s execute \"%s\"\n", + "MayaReferenceLogic::CreateMayaReference prim=%s execute \"%s\"\n", prim.GetPath().GetText(), referenceCommand.asChar()); status = MGlobal::executeCommand(referenceCommand, createdNodes); @@ -398,9 +410,7 @@ MStatus UsdMayaTranslatorMayaReference::UnloadMayaReference(const MObject& paren return status; } -MStatus UsdMayaTranslatorMayaReference::connectReferenceAssociatedNode( - MFnDagNode& dagNode, - MFnReference& refNode) +static MStatus connectReferenceAssociatedNode(MFnDagNode& dagNode, MFnReference& refNode) { MPlug srcPlug(dagNode.object(), getMessageAttr()); /* @@ -433,60 +443,58 @@ MStatus UsdMayaTranslatorMayaReference::connectReferenceAssociatedNode( return result; } -MStatus UsdMayaTranslatorMayaReference::update(const UsdPrim& prim, MObject parent) +static MObject findConnectedMayaReference(MObject& parent) { - MStatus status; - SdfAssetPath mayaReferenceAssetPath; - // Check to see if we have a valid Maya reference node name - UsdAttribute mayaReferenceNodeName = prim.GetAttribute(m_referenceName); - mayaReferenceNodeName.Get(&mayaReferenceAssetPath); - MString mayaReferencePath(mayaReferenceAssetPath.GetResolvedPath().c_str()); + MStatus status; + MFnDependencyNode fnParent(parent, &status); + if (!status) + return {}; - // The resolved path is empty if the maya reference is a full path. - if (!mayaReferencePath.length()) { - mayaReferencePath = mayaReferenceAssetPath.GetAssetPath().c_str(); + MPlugArray referencePlugs; + { + MPlug messagePlug(fnParent.object(), getMessageAttr()); + messagePlug.connectedTo(referencePlugs, false, true); } - // If the path is still empty return, there is no reference to import - if (!mayaReferencePath.length()) { - return MS::kFailure; + for (uint32_t i = 0, n = referencePlugs.length(); i < n; ++i) { + MObject temp = referencePlugs[i].node(); + if (!temp.hasFn(MFn::kReference)) + continue; + return temp; } - MFileObject fileObj; - fileObj.setRawFullName(mayaReferencePath); - mayaReferencePath = fileObj.resolvedFullName(); - - TF_DEBUG(PXRUSDMAYA_TRANSLATORS) - .Msg( - "MayaReferenceLogic::update Looking for attribute on \"%s\".\"%s\"\n", - prim.GetTypeName().GetText(), - m_namespaceName.GetText()); - MFnDagNode parentDag(parent, &status); - CHECK_MSTATUS_AND_RETURN_IT(status); - - // Get required namespace attribute from prim - MString rigNamespaceM = namespaceFromPrim(prim); - std::string rigNamespace = rigNamespaceM.asChar(); + return {}; +} - MObject refNode; +static bool isSameFileName(const MString& found, const MString& expected) +{ + const auto expectedPath = ghc::filesystem::path(expected.asChar()); + auto foundPath = ghc::filesystem::path(found.asChar()); + if (foundPath == expectedPath) + return true; + + // If the expected file is not absolute, we might not get an exact match + // when comparing the filesystem paths. Since we're only interested in + // figuring if two references might point to the same file once resolved, + // chop off the existing resolved reference to the length of the relative + // file. Don't forget to remove the initial relative-path since the absolute + // path won't contain them. + const std::string expectedStr = TfStringTrimLeft(expectedPath.string(), "./\\"); + std::string foundStr = foundPath.string(); + if (foundStr.size() < expectedStr.size()) + return false; - // First, see if a reference is already attached - MFnDependencyNode fnParent(parent, &status); - if (status) { - MPlug messagePlug(fnParent.object(), getMessageAttr()); - MPlugArray referencePlugs; - messagePlug.connectedTo(referencePlugs, false, true); - for (uint32_t i = 0, n = referencePlugs.length(); i < n; ++i) { - MObject temp = referencePlugs[i].node(); - if (temp.hasFn(MFn::kReference)) { - refNode = temp; - } - } - } + const size_t offsetToChop = foundStr.size() - expectedStr.size(); + foundStr = foundPath.string().substr(offsetToChop); + return ghc::filesystem::path(foundStr) == ghc::filesystem::path(expectedStr); +} - // Check to see whether we have previously created a reference node for this - // prim. If so, we can just reuse it. - // +static MObject findExistingMayaReference( + const UsdPrim& prim, + MFnDagNode& parentDag, + const MString& expectedRefFilePath, + const MString& expectedUnresolvedFilePath) +{ // Notes for the legacy ref-naming scheme: // // The check is based on comparing the prim's full path and the name of the @@ -512,33 +520,131 @@ MStatus UsdMayaTranslatorMayaReference::update(const UsdPrim& prim, MObject pare const bool useLegacyScheme = useLegacyMayaRefNaming(prim); const MString expectedRefName = useLegacyScheme ? refNameFromPath(parentDag) : GetMayaRefCustomAttribute(prim); - if (refNode.isNull() && expectedRefName.length() > 0) { - for (MItDependencyNodes refIter(MFn::kReference); !refIter.isDone(); refIter.next()) { - MObject tempRefNode = refIter.item(); - MFnReference tempRefFn(tempRefNode); - if (!tempRefFn.isFromReferencedFile()) { - if (expectedRefName == tempRefFn.name()) { - // Reconnect the reference node's `associatedNode` attr before - // loading it, since the previous connection may be gone. - connectReferenceAssociatedNode(parentDag, tempRefFn); - refNode = tempRefNode; - - if (!useLegacyScheme) { - // On reconnect, the Maya reference node is renamed to match - // the prim. - MString uniqueRefNodeName - = getUniqueRefNodeName(prim, parentDag, tempRefFn); - tempRefFn.setName(uniqueRefNodeName); - - status = setMayaRefCustomAttribute(prim, tempRefFn); - CHECK_MSTATUS_AND_RETURN_IT(status); - } + const bool hasExpectedRefName = (expectedRefName.length() > 0); +#ifdef MAYAUSD_DEBUG_MAYA_REFERENCE_LOCATION + TF_WARN( + "%s Maya custom ref attribute for %s", + hasExpectedRefName ? "Has" : "No", + prim.GetPath().GetText()); +#endif + if (!hasExpectedRefName) { + return {}; + } - // Found a matching Maya reference node, stop searching. - break; - } + for (MItDependencyNodes refIter(MFn::kReference); !refIter.isDone(); refIter.next()) { + MObject tempObj = refIter.item(); + MFnReference tempRefFn(tempObj); + + // Only take into consideration reference nodes that are directly in + // the Maya scene file, not nodes that may be inside other referenced + // files. + if (tempRefFn.isFromReferencedFile()) + continue; + + // If the reference is not named as we expected, then even if it would + // refer to the same file we don't use it: it may belong to another stage + // or to the user. + const bool hasMatchingName = (expectedRefName == tempRefFn.name()); +#ifdef MAYAUSD_DEBUG_MAYA_REFERENCE_LOCATION + TF_WARN( + "Found Maya reference with %s name (%s != %s)", + hasMatchingName ? "matching" : "non-matching", + expectedRefName.asChar(), + tempRefFn.name().asChar()); +#endif + if (!hasMatchingName) + continue; + + // If the reference is not to the expected referenced file, don't use it. + // It might be because two prims with the same name, which can happen when + // they are under different USD variants, result in the same reference node + // name but are referencing two different files. For example, it could be + // that the user wants to switch between two referenced rigs by switching + // between two variants. + const bool resolvedName = true; + const bool includePath = false; // Weirdly, false means we get full path! + const bool includeCopy = false; + const MString refFilePath = tempRefFn.fileName(resolvedName, includePath, includeCopy); + if (!isSameFileName(refFilePath, expectedUnresolvedFilePath)) { +#ifdef MAYAUSD_DEBUG_MAYA_REFERENCE_LOCATION + TF_WARN( + "Maya reference node [%s] does not refer to the expected file: " + "expected [%s], found [%s].", + expectedRefName.asChar(), + expectedUnresolvedFilePath.asChar(), + refFilePath.asChar()); +#endif + continue; + } + + // If we get here, we have found the desired reference node. + // Reconnect the reference node's `associatedNode` attr before + // loading it, since the previous connection may be gone. + connectReferenceAssociatedNode(parentDag, tempRefFn); + + if (!useLegacyScheme) { + // On reconnect, rename the Maya reference node to match the prim. + MString uniqueRefNodeName = getUniqueRefNodeName(prim, parentDag, tempRefFn); + tempRefFn.setName(uniqueRefNodeName); + + if (!setMayaRefCustomAttribute(prim, tempRefFn)) { + TF_WARN( + "The custom prim attribute used to track the Maya reference %s could not be " + "updated.", + prim.GetPath().GetText()); } } + + // Found a matching Maya reference node, stop searching. + return tempObj; + } + + return {}; +} + +MStatus UsdMayaTranslatorMayaReference::update(const UsdPrim& prim, MObject parent) +{ + MStatus status; + SdfAssetPath mayaReferenceAssetPath; + // Check to see if we have a valid Maya reference node name + UsdAttribute mayaReferenceNodeName = prim.GetAttribute(referenceNameAttrName); + mayaReferenceNodeName.Get(&mayaReferenceAssetPath); + MString unresolvedPath(mayaReferenceAssetPath.GetAssetPath().c_str()); + MString mayaReferencePath(mayaReferenceAssetPath.GetResolvedPath().c_str()); + + // The resolved path is empty if the maya reference is a full path. + if (!mayaReferencePath.length()) { + mayaReferencePath = mayaReferenceAssetPath.GetAssetPath().c_str(); + } + + // If the path is still empty return, there is no reference to import + if (!mayaReferencePath.length()) { + return MS::kFailure; + } + MFileObject fileObj; + fileObj.setRawFullName(mayaReferencePath); + mayaReferencePath = fileObj.resolvedFullName(); + + TF_DEBUG(PXRUSDMAYA_TRANSLATORS) + .Msg( + "MayaReferenceLogic::update Looking for attribute on \"%s\".\"%s\"\n", + prim.GetTypeName().GetText(), + namespaceNamePrimAttrName.GetText()); + + MFnDagNode parentDag(parent, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + // Get required namespace attribute from prim + MString rigNamespaceM = namespaceFromPrim(prim); + std::string rigNamespace = rigNamespaceM.asChar(); + + // First, see if a reference is already attached + MObject refNode = findConnectedMayaReference(parent); + + // Check to see whether we have previously created a reference node for this + // prim. If so, we can just reuse it. + if (refNode.isNull()) { + refNode = findExistingMayaReference(prim, parentDag, mayaReferencePath, unresolvedPath); } // If no reference found, we'll need to create it. This may be the first time we are @@ -546,11 +652,11 @@ MStatus UsdMayaTranslatorMayaReference::update(const UsdPrim& prim, MObject pare if (refNode.isNull()) { bool mergeNamespacesOnClash = false; if (UsdAttribute mergeNamespacesOnClashAttribute - = prim.GetAttribute(m_mergeNamespacesOnClash)) { + = prim.GetAttribute(mergeNamespacesOnClashAttrName)) { mergeNamespacesOnClashAttribute.Get(&mergeNamespacesOnClash); } - return LoadMayaReference( + return CreateMayaReference( prim, parent, mayaReferencePath, rigNamespaceM, mergeNamespacesOnClash); } diff --git a/lib/mayaUsd/fileio/translators/translatorMayaReference.h b/lib/mayaUsd/fileio/translators/translatorMayaReference.h index 835dbe79ba..f91e6d388b 100644 --- a/lib/mayaUsd/fileio/translators/translatorMayaReference.h +++ b/lib/mayaUsd/fileio/translators/translatorMayaReference.h @@ -48,7 +48,7 @@ PXR_NAMESPACE_OPEN_SCOPE struct UsdMayaTranslatorMayaReference { MAYAUSD_CORE_PUBLIC - static MStatus LoadMayaReference( + static MStatus CreateMayaReference( const UsdPrim& prim, MObject& parent, MString& mayaReferencePath, @@ -60,18 +60,6 @@ struct UsdMayaTranslatorMayaReference MAYAUSD_CORE_PUBLIC static MStatus update(const UsdPrim& prim, MObject parent); - -private: - static MString namespaceFromPrim(const UsdPrim& prim); - static MString getUniqueRefNodeName( - const UsdPrim& prim, - const MFnDagNode& parentDag, - const MFnReference& refDependNode); - static MStatus connectReferenceAssociatedNode(MFnDagNode& dagNode, MFnReference& refNode); - - static const TfToken m_namespaceName; - static const TfToken m_referenceName; - static const TfToken m_mergeNamespacesOnClash; }; PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaUsd/utils/variants.cpp b/lib/mayaUsd/utils/variants.cpp index 7b78fe2d2f..50d7bb6fc2 100644 --- a/lib/mayaUsd/utils/variants.cpp +++ b/lib/mayaUsd/utils/variants.cpp @@ -15,6 +15,12 @@ // #include "variants.h" +#include + +#include +#include +#include +#include #include /// General utility functions for variants @@ -59,4 +65,51 @@ void applyToAllVariants( func(); } +PXR_NS::UsdEditTarget +getEditTargetForVariants(const PXR_NS::UsdPrim& prim, const PXR_NS::SdfLayerHandle& layer) +{ + PXR_NS::UsdEditTarget editTarget(layer); + +#ifdef MAYAUSD_DEBUG_EDIT_TARGET_FOR_VARIANTS + std::vector variantPaths; +#endif + + for (const PXR_NS::SdfPath& p : prim.GetPath().GetAncestorsRange()) { + PXR_NS::UsdPrim ancestor = prim.GetStage()->GetPrimAtPath(p); + PXR_NS::UsdVariantSets variantSets = ancestor.GetVariantSets(); + std::vector setNames = variantSets.GetNames(); + for (const std::string& setName : setNames) { + PXR_NS::UsdVariantSet variant = variantSets.GetVariantSet(setName); + const std::string selection = variant.GetVariantSelection(); +#ifdef MAYAUSD_DEBUG_EDIT_TARGET_FOR_VARIANTS + variantPaths.emplace_back(setName + std::string("=") + selection); +#endif + editTarget = editTarget.ComposeOver(variant.GetVariantEditTarget(layer)); + } + } + +#ifdef MAYAUSD_DEBUG_EDIT_TARGET_FOR_VARIANTS + using namespace PXR_NS; + TF_STATUS( + "edit target for variants for %s: %s", + prim.GetPath().GetText(), + PXR_NS::TfStringJoin(variantPaths).c_str()); +#endif + + return editTarget; +} + +PXR_NS::SdfPath getVariantPath( + const Ufe::Path& path, + const std::string& variantSetName, + const std::string& variantSelection) +{ + if (path.runTimeId() != MayaUsd::ufe::getUsdRunTimeId()) + return {}; + + const Ufe::Path::Segments& segments = path.getSegments(); + PXR_NS::SdfPath primPath(segments.back().string()); + return primPath.AppendVariantSelection(variantSetName, variantSelection); +} + } // namespace MAYAUSD_NS_DEF diff --git a/lib/mayaUsd/utils/variants.h b/lib/mayaUsd/utils/variants.h index c3b9ff1c9d..6ba0b57e4c 100644 --- a/lib/mayaUsd/utils/variants.h +++ b/lib/mayaUsd/utils/variants.h @@ -18,10 +18,13 @@ #include +#include #include #include #include +#include + #include #include @@ -72,6 +75,24 @@ class AutoVariantRestore std::string _variant; }; +/*! \brief Creates an edit target for all variants that might affect the given prim. + + Note that this includes variants on ancestors that affect this prim. To find + those ancestor variant is the main prupose of this function. For prim-specific + variant selections, OpenUSD already provides a built-in function on UsdPrim. + */ +MAYAUSD_CORE_PUBLIC +PXR_NS::UsdEditTarget +getEditTargetForVariants(const PXR_NS::UsdPrim& prim, const PXR_NS::SdfLayerHandle& layer); + +/*! \brief Creates an USD variant path for the given UFE path and variant selection. + */ +MAYAUSD_CORE_PUBLIC +PXR_NS::SdfPath getVariantPath( + const Ufe::Path& path, + const std::string& variantSetName, + const std::string& variantSelection); + } // namespace MAYAUSD_NS_DEF #endif // MAYAUSD_UTILS_VARIANTS_H diff --git a/test/lib/mayaUsd/fileio/CMakeLists.txt b/test/lib/mayaUsd/fileio/CMakeLists.txt index 87abdfdef5..a86d06935e 100644 --- a/test/lib/mayaUsd/fileio/CMakeLists.txt +++ b/test/lib/mayaUsd/fileio/CMakeLists.txt @@ -23,6 +23,7 @@ if(CMAKE_UFE_V3_FEATURES_AVAILABLE) testPrimUpdater.py testCacheToUsd.py testMayaUsdOptions.py + testSwitchMayaReference.py ) endif() diff --git a/test/lib/mayaUsd/fileio/testAddMayaReference.py b/test/lib/mayaUsd/fileio/testAddMayaReference.py index eeb50c26b4..14a9e12de5 100644 --- a/test/lib/mayaUsd/fileio/testAddMayaReference.py +++ b/test/lib/mayaUsd/fileio/testAddMayaReference.py @@ -67,7 +67,7 @@ def removeCacheFile(self): ''' try: os.remove(self.getCacheFileName()) - except: + except Exception: pass def setUp(self): diff --git a/test/lib/mayaUsd/fileio/testCacheToUsd.py b/test/lib/mayaUsd/fileio/testCacheToUsd.py index db16729fd8..53144cf030 100644 --- a/test/lib/mayaUsd/fileio/testCacheToUsd.py +++ b/test/lib/mayaUsd/fileio/testCacheToUsd.py @@ -125,7 +125,7 @@ def removeCacheFile(self): ''' try: os.remove(self.getCacheFileName()) - except: + except Exception: pass def getRootLayerFileName(self): @@ -137,7 +137,7 @@ def removeRootLayerFile(self): ''' try: os.remove(self.getRootLayerFileName()) - except: + except Exception: pass def setUp(self): @@ -564,10 +564,8 @@ def runTestMayaRefPrimTransform(self, createMayaRefPrimFn, checkCacheParentFn): checkCacheParentFn(self, cacheParentChildren, variantSet, cacheVariantName) # Maya reference prim should now have the updated transformation. - editTarget = self.stage.GetEditTarget() - if variantSet: - variantSet.SetVariantSelection('Rig') - editTarget = variantSet.GetVariantEditTarget(editTarget.GetLayer()) + if variantSetName: + cacheParent.GetVariantSet(variantSetName).SetVariantSelection('Rig') with Usd.EditContext(self.stage, editTarget): xformable = UsdGeom.Xformable(mayaRefPrim) diff --git a/test/lib/mayaUsd/fileio/testCustomRig.py b/test/lib/mayaUsd/fileio/testCustomRig.py index 12b3267a00..b47978cbb6 100644 --- a/test/lib/mayaUsd/fileio/testCustomRig.py +++ b/test/lib/mayaUsd/fileio/testCustomRig.py @@ -104,7 +104,7 @@ def _GetMFnDagNode(self, objectName): selectionList = OpenMaya.MSelectionList() try: selectionList.add(objectName) - except: + except Exception: return None mObj = selectionList.getDependNode(0) diff --git a/test/lib/mayaUsd/fileio/testDiscardEdits.py b/test/lib/mayaUsd/fileio/testDiscardEdits.py index 39355a37ff..d05da01372 100644 --- a/test/lib/mayaUsd/fileio/testDiscardEdits.py +++ b/test/lib/mayaUsd/fileio/testDiscardEdits.py @@ -67,7 +67,7 @@ def _GetMayaDependencyNode(self, objectName): selectionList = om.MSelectionList() try: selectionList.add(objectName) - except: + except Exception: return None mObj = selectionList.getDependNode(0) diff --git a/test/lib/mayaUsd/fileio/testMergeToUsd.py b/test/lib/mayaUsd/fileio/testMergeToUsd.py index 49b741a9cc..7e165a229e 100644 --- a/test/lib/mayaUsd/fileio/testMergeToUsd.py +++ b/test/lib/mayaUsd/fileio/testMergeToUsd.py @@ -213,7 +213,7 @@ def verifyMergeIsUndone(): for mayaPathStr in [aMayaPathStr, bMayaPathStr]: try: om.MSelectionList().add(mayaPathStr) - except: + except Exception: self.assertTrue(False, "Selecting node should not have raise an exception") # Selection is restored. self.assertEqual(cmds.ls(sl=True, ufe=True, long=True), previousSn) diff --git a/test/lib/mayaUsd/fileio/testSwitchMayaReference.py b/test/lib/mayaUsd/fileio/testSwitchMayaReference.py new file mode 100644 index 0000000000..af26694fca --- /dev/null +++ b/test/lib/mayaUsd/fileio/testSwitchMayaReference.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python + +# +# Copyright 2023 Autodesk +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import fixturesUtils + +import mayaUsd +import mayaUtils +import ufeUtils +import usdUtils +import testUtils + +from pxr import Tf, Usd, Kind, Sdf + +from maya import cmds +import maya.mel as mel +from maya import standalone +from maya.api import OpenMaya as om + +import mayaUsdAddMayaReference +import mayaUsdMayaReferenceUtils as mayaRefUtils +from mayaUsd.lib import cacheToUsd + +import ufe + +import os, unittest + +class SwitchMayaReferenceTestCase(unittest.TestCase): + '''Test Switching Maya Reference when switching variants. + ''' + pluginsLoaded = False + mayaSceneStr = None + stage = None + kDefaultNamespace = 'simpleSphere' + + @classmethod + def setUpClass(cls): + fixturesUtils.setUpClass(__file__) + + @classmethod + def tearDownClass(cls): + standalone.uninitialize() + + def getCacheFileName(self): + return 'testSwitchMayaRefCache.usda' + + def removeCacheFile(self): + ''' + Remove the cache file if it exists. Ignore error if it does not exists. + ''' + try: + os.remove(self.getCacheFileName()) + except Exception: + pass + + def setUp(self): + # Start each test with a new scene with empty stage. + cmds.file(new=True, force=True) + self.removeCacheFile() + + def tearDown(self): + self.removeCacheFile() + + def testMultiRefInMultiVariants(self): + ''' + Test that switching variant in a stage that contains multiple prims + of the Maya Reference type with the same name but under different + variant works: + - The Maya references are loaded and unload as the variant are switched, + - The Maya reference can be cached. + ''' + test_file = testUtils.getTestScene("switchVariants", "asset.usda") + ps, stage = mayaUtils.createProxyFromFile(test_file) + + prim_with_variant = stage.GetPrimAtPath('/cube') + self.assertTrue(prim_with_variant) + + variant_set = 'rig' + no_rig_variant = 'none' + anim_rig_variant = 'anim' + layout_rig_variant = 'layout' + + anim_rig_path = '|__mayaUsd__|rigParent' + layout_rig_path = '|__mayaUsd__|rigParent1' + + # Verify none of the rigs are loaded. + def verify_rig_loaded(name, loaded): + item = ufeUtils.createItem(name) + if item: + self.assertEqual(cmds.getAttr(name + '.visibility'), loaded) + else: + self.assertFalse(loaded) + + verify_rig_loaded(anim_rig_path, False) + verify_rig_loaded(layout_rig_path, False) + + # Switch to anim variant and verify the presence of the edited + # Maya reference. + prim_with_variant.GetVariantSet(variant_set).SetVariantSelection(anim_rig_variant) + verify_rig_loaded(anim_rig_path, True) + verify_rig_loaded(layout_rig_path, False) + + # Switch to layout variant and verify the presence of the edited + # Maya reference. + prim_with_variant.GetVariantSet(variant_set).SetVariantSelection(layout_rig_variant) + verify_rig_loaded(anim_rig_path, False) + verify_rig_loaded(layout_rig_path, True) + + # Switch to none variant and verify the absence of all references. + prim_with_variant.GetVariantSet(variant_set).SetVariantSelection(no_rig_variant) + verify_rig_loaded(anim_rig_path, False) + verify_rig_loaded(layout_rig_path, False) + + # Switch to anim variant and verify the presence of the edited + # Maya reference. + prim_with_variant.GetVariantSet(variant_set).SetVariantSelection(anim_rig_variant) + verify_rig_loaded(anim_rig_path, True) + verify_rig_loaded(layout_rig_path, False) + + # Cache to USD anim rig and verify the Maya rig is gone. + defaultExportOptions = cacheToUsd.getDefaultExportOptions() + cacheFile = self.getCacheFileName() + cachePrimName = 'cachePrimName' + payloadOrReference = 'Payload' + listEditType = 'Prepend' + cacheVariantName = 'cache' + relativePath = True + # In the sibling cache case variantSetName and cacheVariantName will be + # None. + cacheOptions = cacheToUsd.createCacheCreationOptions( + defaultExportOptions, cacheFile, cachePrimName, + payloadOrReference, listEditType, variant_set, cacheVariantName, relativePath) + with mayaUsd.lib.OpUndoItemList(): + self.assertTrue(mayaUsd.lib.PrimUpdaterManager.mergeToUsd(anim_rig_path + '|rig', cacheOptions)) + + verify_rig_loaded(anim_rig_path, False) + verify_rig_loaded(layout_rig_path, False) + + # Switch to layout variant and verify the presence of the edited + # Maya reference. + prim_with_variant.GetVariantSet(variant_set).SetVariantSelection(layout_rig_variant) + verify_rig_loaded(anim_rig_path, False) + verify_rig_loaded(layout_rig_path, True) + + # Discard edits on layout rig and verify the Maya rig is gone. + with mayaUsd.lib.OpUndoItemList(): + self.assertTrue(mayaUsd.lib.PrimUpdaterManager.discardEdits(layout_rig_path + '|rig')) + verify_rig_loaded(anim_rig_path, False) + verify_rig_loaded(layout_rig_path, False) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/test/lib/mayaUsd/render/pxrUsdMayaGL/testProxyShapeLiveSurface.py b/test/lib/mayaUsd/render/pxrUsdMayaGL/testProxyShapeLiveSurface.py index 263298c11e..b67a94fd20 100644 --- a/test/lib/mayaUsd/render/pxrUsdMayaGL/testProxyShapeLiveSurface.py +++ b/test/lib/mayaUsd/render/pxrUsdMayaGL/testProxyShapeLiveSurface.py @@ -25,7 +25,7 @@ from PySide2.QtTest import QTest from PySide2.QtWidgets import QWidget from shiboken2 import wrapInstance -except: +except Exception: from PySide6 import QtCore from PySide6.QtTest import QTest from PySide6.QtWidgets import QWidget diff --git a/test/lib/mayaUsd/render/pxrUsdMayaGL/testProxyShapeSelectionPerformance.py b/test/lib/mayaUsd/render/pxrUsdMayaGL/testProxyShapeSelectionPerformance.py index 5484d9c4bb..f5d9f58ade 100644 --- a/test/lib/mayaUsd/render/pxrUsdMayaGL/testProxyShapeSelectionPerformance.py +++ b/test/lib/mayaUsd/render/pxrUsdMayaGL/testProxyShapeSelectionPerformance.py @@ -32,7 +32,7 @@ from PySide2.QtWidgets import QApplication from PySide2.QtWidgets import QWidget from shiboken2 import wrapInstance -except: +except Exception: from PySide6 import QtCore from PySide6.QtTest import QTest from PySide6.QtWidgets import QApplication diff --git a/test/lib/mayaUsd/render/vp2RenderDelegate/testVP2RenderDelegatePointInstancesPickMode.py b/test/lib/mayaUsd/render/vp2RenderDelegate/testVP2RenderDelegatePointInstancesPickMode.py index 9bae5afb07..a2ec51b746 100644 --- a/test/lib/mayaUsd/render/vp2RenderDelegate/testVP2RenderDelegatePointInstancesPickMode.py +++ b/test/lib/mayaUsd/render/vp2RenderDelegate/testVP2RenderDelegatePointInstancesPickMode.py @@ -33,7 +33,7 @@ from PySide2.QtTest import QTest from PySide2.QtWidgets import QWidget from shiboken2 import wrapInstance -except: +except Exception: from PySide6 import QtCore from PySide6.QtTest import QTest from PySide6.QtWidgets import QWidget diff --git a/test/lib/mayaUsd/utils/testDiagnosticDelegate.py b/test/lib/mayaUsd/utils/testDiagnosticDelegate.py index e29accbb74..f93854ca27 100644 --- a/test/lib/mayaUsd/utils/testDiagnosticDelegate.py +++ b/test/lib/mayaUsd/utils/testDiagnosticDelegate.py @@ -152,7 +152,7 @@ def testBatching(self): try: Tf.RaiseCodingError("coding error!") - except: + except Exception: pass log = self._StopRecording() diff --git a/test/lib/mayaUsd/utils/testUtilsSelectability.py b/test/lib/mayaUsd/utils/testUtilsSelectability.py index aa966547be..90518964e0 100644 --- a/test/lib/mayaUsd/utils/testUtilsSelectability.py +++ b/test/lib/mayaUsd/utils/testUtilsSelectability.py @@ -36,7 +36,7 @@ from PySide2.QtTest import QTest from PySide2.QtWidgets import QWidget from shiboken2 import wrapInstance -except: +except Exception: from PySide6 import QtCore from PySide6.QtTest import QTest from PySide6.QtWidgets import QWidget diff --git a/test/lib/mayaUsd/utils/testUtilsSelectabilityPointInstanceSelection.py b/test/lib/mayaUsd/utils/testUtilsSelectabilityPointInstanceSelection.py index aba1d537fa..286996462d 100644 --- a/test/lib/mayaUsd/utils/testUtilsSelectabilityPointInstanceSelection.py +++ b/test/lib/mayaUsd/utils/testUtilsSelectabilityPointInstanceSelection.py @@ -37,7 +37,7 @@ from PySide2.QtTest import QTest from PySide2.QtWidgets import QWidget from shiboken2 import wrapInstance -except: +except Exception: from PySide6 import QtCore from PySide6.QtTest import QTest from PySide6.QtWidgets import QWidget diff --git a/test/lib/testMayaUsdPlugVersionCheck.py b/test/lib/testMayaUsdPlugVersionCheck.py index 605f7de348..70b7cebac0 100644 --- a/test/lib/testMayaUsdPlugVersionCheck.py +++ b/test/lib/testMayaUsdPlugVersionCheck.py @@ -37,7 +37,7 @@ def testVersionCheck(self): try: if not cmds.pluginInfo( "mayaUsdPlugin", loaded=True, query=True): cmds.loadPlugin( "mayaUsdPlugin", quiet = True ) - except: + except Exception: print(sys.exc_info()[1]) print("Unable to load mayaUsdPlugin") diff --git a/test/lib/ufe/testAttribute.py b/test/lib/ufe/testAttribute.py index 66c3778266..5e0da4d4fd 100644 --- a/test/lib/ufe/testAttribute.py +++ b/test/lib/ufe/testAttribute.py @@ -129,7 +129,7 @@ def setUpClass(cls): try: cmds.getAttr('|stage1|stageShape1,/A.visibility') cls._getAttrSupportsUfe = True - except: + except Exception: _getAttrSupportsUfe = False # Maya's setAttr command was only made Ufe aware in Maya PR129 and Maya 2022.3 @@ -137,7 +137,7 @@ def setUpClass(cls): try: cmds.setAttr('|stage1|stageShape1,/A.visibility', UsdGeom.Tokens.invisible) cls._setAttrSupportsUfe = True - except: + except Exception: _setAttrSupportsUfe = False # Cleanup for all tests. diff --git a/test/lib/ufe/testUIIcons.py b/test/lib/ufe/testUIIcons.py index 29266fa744..b4eaaf2954 100644 --- a/test/lib/ufe/testUIIcons.py +++ b/test/lib/ufe/testUIIcons.py @@ -29,7 +29,7 @@ try: from shiboken2 import wrapInstance from PySide2.QtGui import QIcon -except: +except Exception: from shiboken6 import wrapInstance from PySide6.QtGui import QIcon diff --git a/test/testSamples/switchVariants/asset.usda b/test/testSamples/switchVariants/asset.usda new file mode 100644 index 0000000000..bd98e11f17 --- /dev/null +++ b/test/testSamples/switchVariants/asset.usda @@ -0,0 +1,57 @@ +#usda 1.0 +( + defaultPrim = "cube" + metersPerUnit = 0.01 + upAxis = "Y" +) + +def Xform "cube" ( + kind = "component" + variants = { + string rig = "none" + } + prepend variantSets = "rig" +) +{ + def Xform "geo" ( + prepend references = @model.usda@ + ) + { + } + variantSet "rig" = { + "anim" { + def MayaReference "rig" + { + bool mayaAutoEdit = 1 + string mayaNamespace = "rig" + asset mayaReference = @rig.anim.mb@ + } + + over "geo" ( + active = false + ) + { + } + + } + "layout" { + def MayaReference "rig" + { + bool mayaAutoEdit = 1 + string mayaNamespace = "rig" + asset mayaReference = @rig.layout.mb@ + } + + over "geo" ( + active = false + ) + { + } + + } + "none" { + + } + } +} + diff --git a/test/testSamples/switchVariants/model.usda b/test/testSamples/switchVariants/model.usda new file mode 100644 index 0000000000..c20114e826 --- /dev/null +++ b/test/testSamples/switchVariants/model.usda @@ -0,0 +1,30 @@ +#usda 1.0 +( + defaultPrim = "cube" + metersPerUnit = 0.01 + upAxis = "Y" +) + +def Xform "cube" ( + kind = "component" +) +{ + def Xform "geo" + { + def Mesh "mesh" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4] + normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5)] + uniform token subdivisionScheme = "none" + } + } +} + diff --git a/test/testSamples/switchVariants/rig.anim.mb b/test/testSamples/switchVariants/rig.anim.mb new file mode 100644 index 0000000000..dec95c690d Binary files /dev/null and b/test/testSamples/switchVariants/rig.anim.mb differ diff --git a/test/testSamples/switchVariants/rig.layout.mb b/test/testSamples/switchVariants/rig.layout.mb new file mode 100644 index 0000000000..83692fcb04 Binary files /dev/null and b/test/testSamples/switchVariants/rig.layout.mb differ diff --git a/test/testUtils/testUtils.py b/test/testUtils/testUtils.py index 2769320aef..27b4e85382 100644 --- a/test/testUtils/testUtils.py +++ b/test/testUtils/testUtils.py @@ -75,7 +75,7 @@ def __exit__(self, exc_type, exc_value, traceback): return try: shutil.rmtree(self.name) - except: + except Exception: if not self.ignore_errors: raise