Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EMSUSD-277 fix lost muted anonymous layers #3320

Merged
merged 2 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions lib/mayaUsd/commands/layerEditorCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "layerEditorCommand.h"

#include <mayaUsd/ufe/Global.h>
#include <mayaUsd/utils/layerMuting.h>
#include <mayaUsd/utils/query.h>
#include <mayaUsd/utils/utilFileSystem.h>

Expand Down Expand Up @@ -620,7 +621,7 @@ class MuteLayer : public BaseCmd
// we perfer not holding to pointers needlessly, but we need to hold on to the layer if we
// mute it otherwise usd will let go of it and its modifications, and any dirty children
// will also be lost
Copy link
Collaborator

@samuelliu-adsk samuelliu-adsk Sep 12, 2023

Choose a reason for hiding this comment

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

I dont think we need this comment anymore, so does L643

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Actually, the comment says exactly why we are calling addMutedLayer, so I'll keep the comment.

_mutedLayer = layer;
addMutedLayer(layer);
return true;
}

Expand All @@ -638,8 +639,9 @@ class MuteLayer : public BaseCmd
saveSelection();
stage->MuteLayer(layer->GetIdentifier());
}

// we can release the pointer
_mutedLayer = nullptr;
removeMutedLayer(layer);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Question here: In AddMutedLayer, we recursively added all dirty sublayers to the mutedLayer list, so when remvoeMutedLayer should we do the same? For now it just removes one layer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right, I will look into it.

return true;
}

Expand Down Expand Up @@ -683,8 +685,7 @@ class MuteLayer : public BaseCmd
globalSn->replaceWith(UsdUfe::recreateDescendants(_savedSn, path));
}

Ufe::Selection _savedSn;
PXR_NS::SdfLayerRefPtr _mutedLayer;
Ufe::Selection _savedSn;
};

// We assume the indexes given to the command are the original indexes
Expand Down
73 changes: 73 additions & 0 deletions lib/mayaUsd/utils/layerMuting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

#include "layerMuting.h"

#include <mayaUsd/listeners/notice.h>

#include <pxr/base/tf/weakBase.h>

namespace MAYAUSD_NS_DEF {

MStatus copyLayerMutingToAttribute(const PXR_NS::UsdStage& stage, MayaUsdProxyShapeBase& proxyShape)
Expand All @@ -32,4 +36,73 @@ copyLayerMutingFromAttribute(const MayaUsdProxyShapeBase& proxyShape, PXR_NS::Us
return MS::kSuccess;
}

namespace {

// Automatic reset of recorded muted layers when the Maya scene is reset.
struct SceneResetListener : public PXR_NS::TfWeakBase
{
SceneResetListener()
{
PXR_NS::TfWeakPtr<SceneResetListener> me(this);
PXR_NS::TfNotice::Register(me, &SceneResetListener::OnSceneReset);
}

void OnSceneReset(const UsdMayaSceneResetNotice&)
{
// Make sure we don't hold onto muted layers now that the
// Maya scene is reset.
forgetMutedLayers();
}
};

// The set of muted layers.
//
// Kept in a function to avoid problem with the order of construction
// of global variables in C++.
using MutedLayers = std::set<PXR_NS::SdfLayerRefPtr>;
MutedLayers& getMutedLayers()
{
// Note: C++ guarantees correct multi-thread protection for static
// variables initialization in functions.
static SceneResetListener onSceneResetListener;
static MutedLayers layers;
return layers;
}
} // namespace

void addMutedLayer(const PXR_NS::SdfLayerRefPtr& layer)
{
if (!layer)
return;

// Non-dirty, non-anonymous layers can be reloaded, so we
// won't hold onto them.
const bool needHolding = (layer->IsDirty() || layer->IsAnonymous());
if (!needHolding)
return;

MutedLayers& layers = getMutedLayers();
layers.insert(layer);

// Hold onto sub-layers as well, in case they are dirty or anonymous.
// Note: the GetSubLayerPaths function returns proxies, so we have to
// hold the std::string by value, not reference.
for (const std::string subLayerPath : layer->GetSubLayerPaths()) {
auto subLayer = SdfLayer::FindRelativeToLayer(layer, subLayerPath);
addMutedLayer(subLayer);
}
}

void removeMutedLayer(const PXR_NS::SdfLayerRefPtr& layer)
{
MutedLayers& layers = getMutedLayers();
layers.erase(layer);
}

void forgetMutedLayers()
{
MutedLayers& layers = getMutedLayers();
layers.clear();
}

} // namespace MAYAUSD_NS_DEF
24 changes: 24 additions & 0 deletions lib/mayaUsd/utils/layerMuting.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <mayaUsd/base/api.h>
#include <mayaUsd/nodes/proxyShapeBase.h>

#include <pxr/usd/sdf/layer.h>
#include <pxr/usd/usd/stage.h>

namespace MAYAUSD_NS_DEF {
Expand Down Expand Up @@ -47,6 +48,29 @@ MAYAUSD_CORE_PUBLIC
MStatus
copyLayerMutingFromAttribute(const MayaUsdProxyShapeBase& proxyShape, PXR_NS::UsdStage& stage);

// OpenUSD forget everything about muted layers. The OpenUSD documentation for
// the MuteLayer function says:
//
// Note that muting a layer will cause this stage to release all references
// to that layer. If no other client is holding on to references to that
// layer, it will be unloaded.In this case, if there are unsaved edits to
// the muted layer, those edits are lost.
//
// Since anonymous layers are not serialized, muting an anonymous layer will
// cause that layer and its contents to be lost in this case.
//
// So we need to hold on to muted layers. We do this in a private global list
// of muted layers. That list gets cleared when a new Maya scene is created.

MAYAUSD_CORE_PUBLIC
void addMutedLayer(const PXR_NS::SdfLayerRefPtr& layer);

MAYAUSD_CORE_PUBLIC
void removeMutedLayer(const PXR_NS::SdfLayerRefPtr& layer);

MAYAUSD_CORE_PUBLIC
void forgetMutedLayers();

} // namespace MAYAUSD_NS_DEF

#endif
63 changes: 62 additions & 1 deletion test/lib/mayaUsd/fileio/testAddMayaReference.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
import ufeUtils
import usdUtils

from pxr import Tf, Usd, Kind
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

Expand Down Expand Up @@ -288,6 +289,66 @@ def testDiscardMayaRefUnderDeactivatedPrim(self):
self.assertTrue(mayaRefPrim.IsActive())


def testEditAndDiscardMayaRefWithMutedLayer(self):
'''
Test editing then discarding a Maya Reference when an anonymous
has been muted does not lose the muted layer.

Add a Maya Reference using auto-edit, create a new layer, mute it,
then discard the edits. Verify the muted layer still exists.
'''
kDefaultPrimName = mayaRefUtils.defaultMayaReferencePrimName()

# Create a new layer, and mute it. Make sure not to keep a reference
# to it to verify it does not get lost later. Use the layer editor
# command to mute it, as it is the one responsible to hold onto the
# anonymous layer.
anonLayer = usdUtils.addNewLayerToStage(self.stage, anonymous=True)
anonLayerId = anonLayer.identifier
mel.eval('mayaUsdLayerEditor -edit -muteLayer 1 "%s" "%s"' % (self.proxyShapePathStr, anonLayerId))
anonLayer = None

# Since this is a brand new prim, it should not have variant sets.
primTestDefault = self.stage.DefinePrim('/Test_Default', 'Xform')
primPathStr = self.proxyShapePathStr + ',/Test_Default'
self.assertFalse(primTestDefault.HasVariantSets())

mayaRefPrim = mayaUsdAddMayaReference.createMayaReferencePrim(
primPathStr,
self.mayaSceneStr,
self.kDefaultNamespace,
mayaAutoEdit=True)

# The prim should not have any variant set.
self.assertFalse(primTestDefault.HasVariantSets())

# Verify that a Maya Reference prim was created.
self.assertTrue(mayaRefPrim.IsValid())
self.assertEqual(str(mayaRefPrim.GetName()), kDefaultPrimName)
self.assertEqual(mayaRefPrim, primTestDefault.GetChild(kDefaultPrimName))
self.assertTrue(mayaRefPrim.GetPrimTypeInfo().GetTypeName(), 'MayaReference')

attr = mayaRefPrim.GetAttribute('mayaAutoEdit')
self.assertTrue(attr.IsValid())
self.assertEqual(attr.Get(), True)

# Discard Maya edits.
aMayaItem = ufe.GlobalSelection.get().front()
aMayaPath = aMayaItem.path()
aMayaPathStr = ufe.PathString.string(aMayaPath)
with mayaUsd.lib.OpUndoItemList():
self.assertTrue(mayaUsd.lib.PrimUpdaterManager.discardEdits(aMayaPathStr))

# Verify that the auto-edit has been turned off
attr = mayaRefPrim.GetAttribute('mayaAutoEdit')
self.assertTrue(attr.IsValid())
self.assertEqual(attr.Get(), False)

# Verify that the muted layer stil exists.
layerIdentifiers = [layer.identifier for layer in Sdf.Layer.GetLoadedLayers()]
self.assertIn(anonLayerId, layerIdentifiers)


def testEditAndMergeMayaRef(self):
'''Test editing then merge a Maya Reference.

Expand Down
52 changes: 42 additions & 10 deletions test/testUtils/usdUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,46 @@ def createDuoXformScene():
aXlateOp, aXlation, aUsdUfePathStr, aUsdUfePath, aUsdItem,
bXlateOp, bXlation, bUsdUfePathStr, bUsdUfePath, bUsdItem)

def createLayeredStage(layersCount = 3):
def addNewLayersToLayer(parentLayer, layersCount=1, anonymous=False):
'''
Create multiple new layers under the given layer.
Return the list of new layers.
'''
createdLayers = []
for i in range(layersCount):
if anonymous:
newLayer = Sdf.Layer.CreateAnonymous()
else:
newLayerName = 'Layer_%d' % (i+1)
usdFormat = Sdf.FileFormat.FindByExtension('usd')
newLayer = Sdf.Layer.New(usdFormat, newLayerName)
parentLayer.subLayerPaths.append(newLayer.identifier)
createdLayers.append(newLayer)
parentLayer = newLayer
return createdLayers

def addNewLayerToLayer(parentLayer, anonymous=False):
'''
Create a new layer under the given layer.
Return the new layer.
'''
return addNewLayersToLayer(parentLayer, 1, anonymous)[0]

def addNewLayersToStage(stage, layersCount=1, anonymous=False):
'''
Create multiple new layers under the root layer of a stage.
Return the list of new layers.
'''
return addNewLayersToLayer(stage.GetRootLayer(), layersCount, anonymous)

def addNewLayerToStage(stage, anonymous=False):
'''
Create a new layer under the root layer of a stage.
Return the new layer.
'''
return addNewLayersToStage(stage, 1, anonymous)[0]

def createLayeredStage(layersCount = 3, anonymous=False):
'''Create a stage with multiple layers, by default 3 extra layers:

Returns a tuple of:
Expand All @@ -222,15 +261,8 @@ def createLayeredStage(layersCount = 3):
(psPathStr, psPath, ps) = createSimpleStage()

stage = mayaUsd.lib.GetPrim(psPathStr).GetStage()
layer = stage.GetRootLayer()
layers = [layer]
for i in range(layersCount):
newLayerName = 'Layer_%d' % (i+1)
usdFormat = Sdf.FileFormat.FindByExtension('usd')
newLayer = Sdf.Layer.New(usdFormat, newLayerName)
layer.subLayerPaths.append(newLayer.identifier)
layer = newLayer
layers.append(layer)
rootLayer = stage.GetRootLayer()
layers = [rootLayer] + addNewLayersToLayer(rootLayer, layersCount, anonymous)

return (psPathStr, psPath, ps, layers)

Expand Down