diff --git a/lib/mayaUsd/listeners/proxyShapeNotice.cpp b/lib/mayaUsd/listeners/proxyShapeNotice.cpp index c7c6194089..35be33f3d7 100644 --- a/lib/mayaUsd/listeners/proxyShapeNotice.cpp +++ b/lib/mayaUsd/listeners/proxyShapeNotice.cpp @@ -19,17 +19,19 @@ PXR_NAMESPACE_OPEN_SCOPE -TF_INSTANTIATE_TYPE(UsdMayaProxyStageSetNotice, +TF_INSTANTIATE_TYPE(MayaUsdProxyStageSetNotice, + TfType::CONCRETE, TF_1_PARENT(TfNotice)); +TF_INSTANTIATE_TYPE(MayaUsdProxyStageInvalidateNotice, TfType::CONCRETE, TF_1_PARENT(TfNotice)); -UsdMayaProxyStageSetNotice::UsdMayaProxyStageSetNotice( +MayaUsdProxyStageBaseNotice::MayaUsdProxyStageBaseNotice( const MayaUsdProxyShapeBase& proxy) : _proxy(proxy) { } const MayaUsdProxyShapeBase& -UsdMayaProxyStageSetNotice::GetProxyShape() const +MayaUsdProxyStageBaseNotice::GetProxyShape() const { return _proxy; } diff --git a/lib/mayaUsd/listeners/proxyShapeNotice.h b/lib/mayaUsd/listeners/proxyShapeNotice.h index 6e0e78b972..dd44c59b59 100644 --- a/lib/mayaUsd/listeners/proxyShapeNotice.h +++ b/lib/mayaUsd/listeners/proxyShapeNotice.h @@ -13,8 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. // -#ifndef USDMAYA_PROXYSTAGE_NOTICE_H -#define USDMAYA_PROXYSTAGE_NOTICE_H +#ifndef MAYAUSD_PROXYSTAGE_NOTICE_H +#define MAYAUSD_PROXYSTAGE_NOTICE_H #include @@ -27,11 +27,11 @@ PXR_NAMESPACE_OPEN_SCOPE class MayaUsdProxyShapeBase; /// Notice sent when the ProxyShape loads a new stage -class UsdMayaProxyStageSetNotice : public TfNotice +class MayaUsdProxyStageBaseNotice : public TfNotice { public: MAYAUSD_CORE_PUBLIC - UsdMayaProxyStageSetNotice(const MayaUsdProxyShapeBase& proxy); + MayaUsdProxyStageBaseNotice(const MayaUsdProxyShapeBase& proxy); /// Get proxy shape which had stage set MAYAUSD_CORE_PUBLIC @@ -41,6 +41,18 @@ class UsdMayaProxyStageSetNotice : public TfNotice const MayaUsdProxyShapeBase& _proxy; }; +class MayaUsdProxyStageSetNotice : public MayaUsdProxyStageBaseNotice +{ +public: + using MayaUsdProxyStageBaseNotice::MayaUsdProxyStageBaseNotice; +}; + +class MayaUsdProxyStageInvalidateNotice : public MayaUsdProxyStageBaseNotice +{ +public: + using MayaUsdProxyStageBaseNotice::MayaUsdProxyStageBaseNotice; +}; + PXR_NAMESPACE_CLOSE_SCOPE #endif diff --git a/lib/mayaUsd/nodes/proxyShapeBase.cpp b/lib/mayaUsd/nodes/proxyShapeBase.cpp index 11525da048..768a783bff 100644 --- a/lib/mayaUsd/nodes/proxyShapeBase.cpp +++ b/lib/mayaUsd/nodes/proxyShapeBase.cpp @@ -496,6 +496,11 @@ MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock) usdStage->SetEditTarget(usdStage->GetSessionLayer()); } + else if (!usdStage) { + // Create a new stage in memory with an anonymous root layer. + UsdStageCacheContext ctx(UsdMayaStageCache::Get()); + usdStage = UsdStage::CreateInMemory("", loadSet); + } if (usdStage) { primPath = usdStage->GetPseudoRoot().GetPath(); @@ -621,7 +626,7 @@ MayaUsdProxyShapeBase::computeOutStageData(MDataBlock& dataBlock) this, std::placeholders::_1)); - UsdMayaProxyStageSetNotice(*this).Send(); + MayaUsdProxyStageSetNotice(*this).Send(); return MS::kSuccess; } @@ -797,6 +802,7 @@ MayaUsdProxyShapeBase::setDependentsDirty(const MPlug& plug, MPlugArray& plugArr plug == loadPayloadsAttr || plug == inStageDataAttr) { _IncreaseUsdStageVersion(); + MayaUsdProxyStageInvalidateNotice(*this).Send(); } return MPxSurfaceShape::setDependentsDirty(plug, plugArray); diff --git a/lib/mayaUsd/ufe/StagesSubject.cpp b/lib/mayaUsd/ufe/StagesSubject.cpp index 358dc03d67..78c8b73bb4 100644 --- a/lib/mayaUsd/ufe/StagesSubject.cpp +++ b/lib/mayaUsd/ufe/StagesSubject.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "private/InPathChange.h" @@ -100,6 +101,7 @@ StagesSubject::StagesSubject() TfWeakPtr me(this); TfNotice::Register(me, &StagesSubject::onStageSet); + TfNotice::Register(me, &StagesSubject::onStageInvalidate); } StagesSubject::~StagesSubject() @@ -170,13 +172,7 @@ void StagesSubject::afterOpen() // frequent, we won't implement this for now. PPT, 22-Dec-2017. std::for_each(std::begin(fStageListeners), std::end(fStageListeners), [](StageListenerMap::value_type element) { TfNotice::Revoke(element.second); } ); - - StagesSubject::Ptr me(this); - for (auto stage : ProxyShapeHandler::getAllStages()) - { - fStageListeners[stage] = TfNotice::Register( - me, &StagesSubject::stageChanged, stage); - } + fStageListeners.clear(); // Set up our stage to proxy shape UFE path (and reverse) // mapping. We do this with the following steps: @@ -184,15 +180,7 @@ void StagesSubject::afterOpen() // - get their Dag paths. // - convert the Dag paths to UFE paths. // - get their stage. - g_StageMap.clear(); - auto proxyShapeNames = ProxyShapeHandler::getAllNames(); - for (const auto& psn : proxyShapeNames) - { - MDagPath dag = nameToDagPath(psn); - Ufe::Path ufePath = dagPathToUfe(dag); - auto stage = ProxyShapeHandler::dagPathToStage(psn); - g_StageMap.addItem(ufePath, stage); - } + g_StageMap.setDirty(); } void StagesSubject::stageChanged(UsdNotice::ObjectsChanged const& notice, UsdStageWeakPtr const& sender) @@ -277,9 +265,29 @@ void StagesSubject::stageChanged(UsdNotice::ObjectsChanged const& notice, UsdSta } } -void StagesSubject::onStageSet(const UsdMayaProxyStageSetNotice& notice) +void StagesSubject::onStageSet(const MayaUsdProxyStageSetNotice& notice) +{ + // We should have no listerners and stage map is dirty. + TF_VERIFY(g_StageMap.isDirty()); + TF_VERIFY(fStageListeners.empty()); + + StagesSubject::Ptr me(this); + for (auto stage : ProxyShapeHandler::getAllStages()) + { + fStageListeners[stage] = TfNotice::Register( + me, &StagesSubject::stageChanged, stage); + } +} + +void StagesSubject::onStageInvalidate(const MayaUsdProxyStageInvalidateNotice& notice) { afterOpen(); + +#if UFE_PREVIEW_VERSION_NUM >= 2014 + Ufe::SceneItem::Ptr sceneItem = Ufe::Hierarchy::createItem(notice.GetProxyShape().ufePath()); + auto notification = Ufe::SubtreeInvalidate(sceneItem); + Ufe::Scene::notifySubtreeInvalidate(notification); +#endif } #ifdef UFE_V2_FEATURES_AVAILABLE diff --git a/lib/mayaUsd/ufe/StagesSubject.h b/lib/mayaUsd/ufe/StagesSubject.h index d8d6917d8f..369f223149 100644 --- a/lib/mayaUsd/ufe/StagesSubject.h +++ b/lib/mayaUsd/ufe/StagesSubject.h @@ -76,7 +76,10 @@ class MAYAUSD_CORE_PUBLIC StagesSubject : public TfWeakBase private: // Notice listener method for proxy stage set - void onStageSet(const UsdMayaProxyStageSetNotice& notice); + void onStageSet(const MayaUsdProxyStageSetNotice& notice); + + // Notice listener method for proxy stage invalidate. + void onStageInvalidate(const MayaUsdProxyStageInvalidateNotice& notice); // Map of per-stage listeners, indexed by stage. typedef TfHashMap StageListenerMap; diff --git a/lib/mayaUsd/ufe/UsdStageMap.cpp b/lib/mayaUsd/ufe/UsdStageMap.cpp index 0b9c1af926..6cd290cf39 100644 --- a/lib/mayaUsd/ufe/UsdStageMap.cpp +++ b/lib/mayaUsd/ufe/UsdStageMap.cpp @@ -19,6 +19,7 @@ #include +#include #include namespace { @@ -92,8 +93,10 @@ void UsdStageMap::addItem(const Ufe::Path& path, UsdStageWeakPtr stage) fStageToObject[stage] = proxyShape; } -UsdStageWeakPtr UsdStageMap::stage(const Ufe::Path& path) const +UsdStageWeakPtr UsdStageMap::stage(const Ufe::Path& path) { + rebuildIfDirty(); + auto proxyShape = proxyShapeHandle(path); if (!proxyShape.isValid()) { return nullptr; @@ -106,8 +109,10 @@ UsdStageWeakPtr UsdStageMap::stage(const Ufe::Path& path) const return nullptr; } -Ufe::Path UsdStageMap::path(UsdStageWeakPtr stage) const +Ufe::Path UsdStageMap::path(UsdStageWeakPtr stage) { + rebuildIfDirty(); + // A stage is bound to a single Dag proxy shape. auto iter = fStageToObject.find(stage); if (iter != std::end(fStageToObject)) @@ -115,10 +120,26 @@ Ufe::Path UsdStageMap::path(UsdStageWeakPtr stage) const return Ufe::Path(); } -void UsdStageMap::clear() +void UsdStageMap::setDirty() { fObjectToStage.clear(); fStageToObject.clear(); + fDirty = true; +} + +void UsdStageMap::rebuildIfDirty() +{ + if (!fDirty) return; + + auto proxyShapeNames = ProxyShapeHandler::getAllNames(); + for (const auto& psn : proxyShapeNames) + { + MDagPath dag = nameToDagPath(psn); + Ufe::Path ufePath = dagPathToUfe(dag); + auto stage = ProxyShapeHandler::dagPathToStage(psn); + addItem(ufePath, stage); + } + fDirty = false; } } // namespace ufe diff --git a/lib/mayaUsd/ufe/UsdStageMap.h b/lib/mayaUsd/ufe/UsdStageMap.h index 2abecd3573..fac4894e33 100644 --- a/lib/mayaUsd/ufe/UsdStageMap.h +++ b/lib/mayaUsd/ufe/UsdStageMap.h @@ -66,16 +66,22 @@ class MAYAUSD_CORE_PUBLIC UsdStageMap UsdStageMap(UsdStageMap&&) = delete; UsdStageMap& operator=(UsdStageMap&&) = delete; - //!Add the input Ufe path and USD Stage to the map. - void addItem(const Ufe::Path& path, UsdStageWeakPtr stage); - //! Get USD stage corresponding to argument Maya Dag path. - UsdStageWeakPtr stage(const Ufe::Path& path) const; + UsdStageWeakPtr stage(const Ufe::Path& path); //! Return the ProxyShape node UFE path for the argument stage. - Ufe::Path path(UsdStageWeakPtr stage) const; + Ufe::Path path(UsdStageWeakPtr stage); + + //! Set the stage map as dirty. It will be cleared immediately, but + //! only repopulated when stage info is requested. + void setDirty(); - void clear(); + //! Returns true if the stage map is dirty (meaning it needs to be filled in). + bool isDirty() const { return fDirty; } + +private: + void addItem(const Ufe::Path& path, UsdStageWeakPtr stage); + void rebuildIfDirty(); private: // We keep two maps for fast lookup when there are many proxy shapes. @@ -83,6 +89,7 @@ class MAYAUSD_CORE_PUBLIC UsdStageMap using StageToObject = TfHashMap; ObjectToStage fObjectToStage; StageToObject fStageToObject; + bool fDirty{true}; }; // UsdStageMap diff --git a/lib/usd/hdMaya/adapters/proxyAdapter.cpp b/lib/usd/hdMaya/adapters/proxyAdapter.cpp index 6796c615eb..d90d81b5d2 100644 --- a/lib/usd/hdMaya/adapters/proxyAdapter.cpp +++ b/lib/usd/hdMaya/adapters/proxyAdapter.cpp @@ -165,7 +165,7 @@ void HdMayaProxyAdapter::PreFrame() { _usdDelegate->PostSyncCleanup(); } -void HdMayaProxyAdapter::_OnStageSet(const UsdMayaProxyStageSetNotice& notice) +void HdMayaProxyAdapter::_OnStageSet(const MayaUsdProxyStageSetNotice& notice) { if(¬ice.GetProxyShape() == _proxy) { diff --git a/lib/usd/hdMaya/adapters/proxyAdapter.h b/lib/usd/hdMaya/adapters/proxyAdapter.h index 6863ba04dc..1d842a2d7c 100644 --- a/lib/usd/hdMaya/adapters/proxyAdapter.h +++ b/lib/usd/hdMaya/adapters/proxyAdapter.h @@ -68,7 +68,7 @@ class HdMayaProxyAdapter : public HdMayaShapeAdapter, public TfWeakBase { private: /// Notice listener method for proxy stage set - void _OnStageSet(const UsdMayaProxyStageSetNotice& notice); + void _OnStageSet(const MayaUsdProxyStageSetNotice& notice); MayaUsdProxyShapeBase* _proxy{ nullptr }; std::unique_ptr _usdDelegate; diff --git a/plugin/adsk/scripts/CMakeLists.txt b/plugin/adsk/scripts/CMakeLists.txt index e0f9ef1008..6b7aa9e3e0 100644 --- a/plugin/adsk/scripts/CMakeLists.txt +++ b/plugin/adsk/scripts/CMakeLists.txt @@ -4,6 +4,7 @@ list(APPEND scripts_src AEmayaUsdProxyShapeTemplate.mel mayaUsdMenu.mel mayaUsd_createStageFromFile.mel + mayaUsd_createStageWithNewLayer.py mayaUsd_createStageFromAsset.mel ) diff --git a/plugin/adsk/scripts/mayaUsdMenu.mel b/plugin/adsk/scripts/mayaUsdMenu.mel index 2bf2d7e7ca..6f218329b6 100644 --- a/plugin/adsk/scripts/mayaUsdMenu.mel +++ b/plugin/adsk/scripts/mayaUsdMenu.mel @@ -58,6 +58,15 @@ proc removeMenuCallback(string $menuName, string $cmd) { // initRuntimeCommands // create all the runtime commands we'll use and the user can map to hotkeys proc initRuntimeCommands() { + if (!`runTimeCommand -exists mayaUsdCreateStageWithNewLayer`) { + runTimeCommand -default true + -label "Stage with New Layer" + -annotation "Create a new, empty USD Stage" + -category "Menu items.Maya USD" + -command "python(\"import mayaUsd_createStageWithNewLayer; mayaUsd_createStageWithNewLayer.createStageWithNewLayer()\")" + mayaUsdCreateStageWithNewLayer; + } + if (!`runTimeCommand -exists mayaUsdCreateStageFromFile`) { runTimeCommand -default true -label "Stage From File..." @@ -103,6 +112,7 @@ global proc mayaUsdMenu_createMenuCallback() { -label "Universal Scene Description (USD)" -annotation "Create a USD stage" -version $mayaVersion`; + menuItem -runTimeCommand mayaUsdCreateStageWithNewLayer; menuItem -runTimeCommand mayaUsdCreateStageFromFile; menuItem -runTimeCommand mayaUsdCreateStageFromFileOptions -optionBox true; } else { diff --git a/plugin/adsk/scripts/mayaUsd_createStageWithNewLayer.py b/plugin/adsk/scripts/mayaUsd_createStageWithNewLayer.py new file mode 100644 index 0000000000..fe0d92634e --- /dev/null +++ b/plugin/adsk/scripts/mayaUsd_createStageWithNewLayer.py @@ -0,0 +1,32 @@ +# Copyright 2020 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 maya.cmds as cmds + +def createStageWithNewLayer(): + """For use in aggregating USD (Assembly) and creating layer structures (Layout) + users need to be able to create a new empty stage. + + Executing this command should produce the following: + - Proxyshape + - Stage + - Session Layer + - Anonymous Root Layer (this is set as the target layer) + """ + + # Simply create a proxy shape. Since it does not have a USD file associated + # (in the .filePath attribute), the proxy shape base will create an empty + # stage in memory. This will create the session and root layer as well. + shapeNode = cmds.createNode('mayaUsdProxyShape', name='stageShape') diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.cpp b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.cpp index 62aba1c34e..7c4836021a 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.cpp +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.cpp @@ -398,6 +398,18 @@ MStatus ProxyShape::setDependentsDirty(const MPlug& plugBeingDirtied, MPlugArray { MHWRender::MRenderer::setGeometryDrawDirty(thisMObject(), true); } + + if (plugBeingDirtied == outStageData() || + // All the plugs that affect outStageDataAttr + plugBeingDirtied == filePath() || + plugBeingDirtied == primPath() || + plugBeingDirtied == m_populationMaskIncludePaths || + plugBeingDirtied == m_stageDataDirty || + plugBeingDirtied == m_assetResolverConfig) + { + MayaUsdProxyStageInvalidateNotice(*this).Send(); + } + return MPxSurfaceShape::setDependentsDirty(plugBeingDirtied, plugs); } @@ -1432,7 +1444,7 @@ MStatus ProxyShape::computeOutStageData(const MPlug& plug, MDataBlock& dataBlock return MS::kFailure; } - UsdMayaProxyStageSetNotice(*this).Send(); + MayaUsdProxyStageSetNotice(*this).Send(); return status; } diff --git a/test/lib/ufe/testObject3d.py b/test/lib/ufe/testObject3d.py index 1a1c3ab3d8..fdef73688f 100644 --- a/test/lib/ufe/testObject3d.py +++ b/test/lib/ufe/testObject3d.py @@ -220,19 +220,14 @@ def testVisibility(self): self.assertFalse(object3d.visibility()) # We should have got 'one' notification. - # USD Attribute Notification doubling problem: - # Note: because we are using set on the usd attribute (just above) - # directly we we receive TWO notifs in our transform3d observer. - # See UsdAttribute.cpp function setUsdAttr() for details. - self.assertEqual(visObs.notifications(), 2) + self.assertEqual(visObs.notifications(), 1) # Make it visible. object3d.setVisibility(True) self.assertTrue(object3d.visibility()) # We should have got one more notification. - # Note: same double notif as above. - self.assertEqual(visObs.notifications(), 4) + self.assertEqual(visObs.notifications(), 2) # Remove the observer. ufe.Object3d.removeObserver(visObs) diff --git a/test/lib/ufe/testTransform3dTranslate.py b/test/lib/ufe/testTransform3dTranslate.py index eb322462e1..08c92dad34 100644 --- a/test/lib/ufe/testTransform3dTranslate.py +++ b/test/lib/ufe/testTransform3dTranslate.py @@ -234,8 +234,4 @@ def testObservation(self): Gf.Vec3d(10, 20, 30)) # Notified. - # USD Attribute Notification doubling problem: - # Note: because we are using set on the usd attribute (just above) - # directly we we receive TWO notifs in our transform3d observer. - # See UsdAttribute.cpp function setUsdAttr() for details. - self.assertEqual(t3dObs.notifications(), 2) + self.assertEqual(t3dObs.notifications(), 1)