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

MAYA-128211 block command when layers are muted #2934

Merged
merged 3 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
33 changes: 2 additions & 31 deletions lib/mayaUsd/ufe/UsdUndoDeleteCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
#include <mayaUsd/ufe/Utils.h>
#include <mayaUsd/utils/layers.h>

#include <pxr/usd/pcp/layerStack.h>
#include <pxr/usd/sdf/layer.h>
#include <pxr/usd/usd/editContext.h>

Expand All @@ -33,29 +32,6 @@
#include <mayaUsd/ufe/UsdAttributes.h>
#endif

namespace {
#ifdef MAYA_ENABLE_NEW_PRIM_DELETE
bool hasLayersMuted(const PXR_NS::UsdPrim& prim)
{
const PXR_NS::PcpPrimIndex& primIndex = prim.GetPrimIndex();

for (const PXR_NS::PcpNodeRef node : primIndex.GetNodeRange()) {

TF_AXIOM(node);

const PXR_NS::PcpLayerStackSite& site = node.GetSite();
const PXR_NS::PcpLayerStackRefPtr& layerStack = site.layerStack;

const std::set<std::string>& mutedLayers = layerStack->GetMutedLayers();
if (mutedLayers.size() > 0) {
return true;
}
}
return false;
}
#endif
} // anonymous namespace

namespace MAYAUSD_NS_DEF {
namespace ufe {

Expand All @@ -78,6 +54,8 @@ void UsdUndoDeleteCommand::execute()
if (!_prim.IsValid())
return;

enforceMutedLayer(_prim, "remove");

MayaUsd::ufe::InAddOrDeleteOperation ad;

UsdUndoBlock undoBlock(&_undoableItem);
Expand All @@ -86,13 +64,6 @@ void UsdUndoDeleteCommand::execute()
const auto& stage = _prim.GetStage();
auto targetPrimSpec = stage->GetEditTarget().GetPrimSpecForScenePath(_prim.GetPath());

if (hasLayersMuted(_prim)) {
const std::string error = TfStringPrintf(
"Cannot remove prim \"%s\" because there are muted layers.", _prim.GetPath().GetText());
TF_WARN("%s", error.c_str());
throw std::runtime_error(error);
}

if (MayaUsd::ufe::applyCommandRestrictionNoThrow(_prim, "delete")) {
#ifdef UFE_V4_FEATURES_AVAILABLE
#if (UFE_PREVIEW_VERSION_NUM >= 4024)
Expand Down
2 changes: 2 additions & 0 deletions lib/mayaUsd/ufe/UsdUndoInsertChildCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ static UsdSceneItem::Ptr doInsertion(
const UsdPrim srcPrim = ufePathToPrim(srcUfePath);
const UsdStagePtr stage = srcPrim.GetStage();

enforceMutedLayer(srcPrim, "reparent");

// Make sure the load state of the reparented prim will be preserved.
// We copy all rules that applied to it specifically and remove the rules
// that applied to it specifically.
Expand Down
2 changes: 2 additions & 0 deletions lib/mayaUsd/ufe/UsdUndoRenameCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ static void doUsdRename(
const Ufe::Path srcPath,
const Ufe::Path dstPath)
{
enforceMutedLayer(prim, "rename");

// 1- open a changeblock to delay sending notifications.
// 2- update the Internal References paths (if any) first
// 3- set the new name
Expand Down
31 changes: 31 additions & 0 deletions lib/mayaUsd/utils/layers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,37 @@ getAllSublayers(const std::vector<std::string>& layerPaths, bool includeParents)
return layers;
}

bool hasMutedLayer(const PXR_NS::UsdPrim& prim)
{
const PXR_NS::PcpPrimIndex& primIndex = prim.GetPrimIndex();

for (const PXR_NS::PcpNodeRef node : primIndex.GetNodeRange()) {
if (!node)
continue;

const PXR_NS::PcpLayerStackRefPtr& layerStack = node.GetSite().layerStack;
if (!layerStack)
continue;

const std::set<std::string>& mutedLayers = layerStack->GetMutedLayers();
if (mutedLayers.size() > 0)
return true;
}
return false;
}

void enforceMutedLayer(const PXR_NS::UsdPrim& prim, const char* command)
{
if (hasMutedLayer(prim)) {
const std::string error = TfStringPrintf(
"Cannot %s prim \"%s\" because there is at least one muted layer.",
command && command[0] ? command : "modify",
prim.GetPath().GetText());
TF_WARN("%s", error.c_str());
throw std::runtime_error(error);
}
}

void applyToAllPrimSpecs(const UsdPrim& prim, const PrimSpecFunc& func)
{
const SdfPrimSpecHandleVector primStack = prim.GetPrimStack();
Expand Down
20 changes: 20 additions & 0 deletions lib/mayaUsd/utils/layers.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,26 @@ MAYAUSD_CORE_PUBLIC
std::set<PXR_NS::SdfLayerRefPtr>
getAllSublayerRefs(const PXR_NS::SdfLayerRefPtr& layer, bool includeTopLayer = false);

/**
* Verify if the given prim has opinions on a muted layer.
*
* @param prim The prim to be verified.
*
* @return true if there is at least one muted layer.
*/

bool hasMutedLayer(const PXR_NS::UsdPrim& prim);

/**
* Enforce that command cannot operate if the given prim has opinions on a muted layer by throwing
* an exception.
*
* @param prim The prim to be verified.
* @param command The name of the command. Will use "modify" if null or empty.
*/

void enforceMutedLayer(const PXR_NS::UsdPrim& prim, const char* command);

/**
* Apply the given function to all the opinions about the given prim.
*
Expand Down
4 changes: 2 additions & 2 deletions plugin/adsk/scripts/mayaUSDRegisterStrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ def mayaUSDRegisterStrings():
register("kMakePathRelativeToSceneFile", "Make Path Relative to Scene File")
register("kMakePathRelativeToSceneFileAnn", "If enabled, path will be relative to your Maya scene file.\nIf this option is disabled, there is no Maya scene file and the path will be absolute.\nSave your Maya scene file to disk to make this option available.")
register("kMakePathRelativeToEditTargetLayer", "Make Path Relative to Edit Target Layer Directory")
register("kMakePathRelativeToEditTargetLayerAnn", "Enable to activate relative pathing to your current edit target layers directory.\nIf this option is disabled, verify that your target layer is not anonymous and save it to disk.")
register("kMakePathRelativeToEditTargetLayerAnn", "Enable to activate relative pathing to your current edit target layer's directory.\nIf this option is disabled, verify that your target layer is not anonymous and save it to disk.")
register("kMakePathRelativeToParentLayer", "Make Path Relative to Parent Layer Directory")
register("kMakePathRelativeToParentLayerAnn", "Enable to activate relative pathing to your current parent layers directory.\nIf this option is disabled, verify that your parent layer is not anonymous and save it to disk.")
register("kMakePathRelativeToParentLayerAnn", "Enable to activate relative pathing to your current parent layer's directory.\nIf this option is disabled, verify that your parent layer is not anonymous and save it to disk.")
register("kUnresolvedPath", "Unresolved Path:")
register("kUnresolvedPathAnn", "This field indicates the path with the file name currently chosen in your text input. Note: This is the string that will be written out to the file in the chosen directory in order to enable portability.")
register("kResolvedPath", "Resolved Path:")
Expand Down
53 changes: 47 additions & 6 deletions test/lib/ufe/testDeleteCmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,7 @@ def testDeleteRestrictionDifferentLayer(self):
ufeObs = TestObserver()
ufe.Scene.addObserver(ufeObs)

# validate the default edit target to be the Rootlayer.
mayaPathSegment = mayaUtils.createUfePathSegment('|Tree_usd|Tree_usdShape')
stage = mayaUsd.ufe.getStage(str(mayaPathSegment))
self.assertTrue(stage)

# add child defined on a new layer
# retrieve the stage
mayaPathSegment = mayaUtils.createUfePathSegment('|Tree_usd|Tree_usdShape')
stage = mayaUsd.ufe.getStage(str(mayaPathSegment))
self.assertTrue(stage)
Expand Down Expand Up @@ -531,5 +526,51 @@ def testDeleteAndRemoveConnections(self):
self.assertFalse(surface2Prim.HasProperty('outputs:out'))
self.assertFalse(surface3Prim.HasProperty('inputs:bsdf'))

def testDeleteRestrictionMutedLayer(self):
'''
Test delete restriction - we don't allow removal of a prim
when there are opinions on a muted layer.
'''

# Create a stage with a xform prim named A
cmds.file(new=True, force=True)
import mayaUsd_createStageWithNewLayer

proxyShapePathStr = mayaUsd_createStageWithNewLayer.createStageWithNewLayer()
stage = mayaUsd.lib.GetPrim(proxyShapePathStr).GetStage()
self.assertTrue(stage)
stage.DefinePrim('/A', 'Xform')

# Add two new layers
usdFormat = Sdf.FileFormat.FindByExtension('usd')
topLayer = Sdf.Layer.New(usdFormat, 'Layer_1')
stage.GetRootLayer().subLayerPaths.append(topLayer.identifier)

usdFormat = Sdf.FileFormat.FindByExtension('usd')
bottomLayer = Sdf.Layer.New(usdFormat, 'Layer_2')
stage.GetRootLayer().subLayerPaths.append(bottomLayer.identifier)

# Create a sphere on the bottom layer
stage.SetEditTarget(bottomLayer)
self.assertEqual(stage.GetEditTarget().GetLayer(), bottomLayer)
spherePrim = stage.DefinePrim('/A/ball', 'Sphere')
self.assertIsNotNone(spherePrim)

# Author an opinion on the top layer
stage.SetEditTarget(topLayer)
self.assertEqual(stage.GetEditTarget().GetLayer(), topLayer)
spherePrim.GetAttribute('radius').Set(4.)

# Set target to bottom layer and mute the top layer
stage.SetEditTarget(bottomLayer)
self.assertEqual(stage.GetEditTarget().GetLayer(), bottomLayer)
# Note: mute by passing through the stage, otherwise the stage won't get recomposed
stage.MuteLayer(topLayer.identifier)

# Try to delete the prim with muted opinion: it should fail
with self.assertRaises(RuntimeError):
cmds.delete('%s,/A/ball' % proxyShapePathStr)
self.assertTrue(stage.GetPrimAtPath('/A/ball'))

if __name__ == '__main__':
unittest.main(verbosity=2)
60 changes: 60 additions & 0 deletions test/lib/ufe/testGroupCmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -1033,5 +1033,65 @@ def testGroupPivotOrigin(self):
# With group pivot origin the group pivot is at the origin.
self.runTestGroupPivotOptions("doGroup 0 1 1", [0, 0, 0])

@unittest.skipUnless(mayaUtils.mayaMajorVersion() >= 2023, 'Requires Maya fixes only available in Maya 2023 or greater.')
def testGroupRestrictionMutedLayer(self):
'''
Test group restriction - we don't allow grouping of a prim
when there are opinions on a muted layer.
'''

# Create a stage
cmds.file(new=True, force=True)
import mayaUsd_createStageWithNewLayer

proxyShapePathStr = mayaUsd_createStageWithNewLayer.createStageWithNewLayer()
stage = mayaUsd.lib.GetPrim(proxyShapePathStr).GetStage()
self.assertTrue(stage)

# Helpers
def createLayer(index):
layer = Sdf.Layer.CreateAnonymous()
stage.GetRootLayer().subLayerPaths.append(layer.identifier)
return layer

def targetSubLayer(layer):
stage.SetEditTarget(layer)
self.assertEqual(stage.GetEditTarget().GetLayer(), layer)
layer = None

def muteSubLayer(layer):
# Note: mute by passing through the stage, otherwise the stage won't get recomposed
stage.MuteLayer(layer.identifier)

def setSphereRadius(radius):
spherePrim = stage.GetPrimAtPath('/A/ball')
spherePrim.GetAttribute('radius').Set(radius)
spherePrim = None

# Add two new layers
topLayer = createLayer(0)
bottomLayer = createLayer(1)

# Create a xform prim named A and a sphere on the bottom layer
targetSubLayer(bottomLayer)
stage.DefinePrim('/A', 'Xform')
stage.DefinePrim('/A/ball', 'Sphere')
setSphereRadius(7.12)

targetSubLayer(topLayer)
setSphereRadius(4.32)

# Set target to bottom layer and mute the top layer
targetSubLayer(bottomLayer)
muteSubLayer(topLayer)

# Try to group the prim with muted opinion: it should fail
with self.assertRaises(RuntimeError):
cmds.group('%s,/A/ball' % proxyShapePathStr)

self.assertTrue(stage.GetPrimAtPath('/A/ball'))
self.assertFalse(stage.GetPrimAtPath('/A/group1/ball'))


if __name__ == '__main__':
unittest.main(verbosity=2)
58 changes: 58 additions & 0 deletions test/lib/ufe/testRename.py
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,64 @@ def testUfeRenameCommandAPI(self):
self.assertIsNotNone(carotteItem)
self.assertEqual(carotteItem, renamedItem)

def testRenameRestrictionMutedLayer(self):
'''
Test rename restriction - we don't allow renaming a prim
when there are opinions on a muted layer.
'''

# Create a stage
cmds.file(new=True, force=True)
import mayaUsd_createStageWithNewLayer

proxyShapePathStr = mayaUsd_createStageWithNewLayer.createStageWithNewLayer()
stage = mayaUsd.lib.GetPrim(proxyShapePathStr).GetStage()
self.assertTrue(stage)

# Helpers
def createLayer(index):
layer = Sdf.Layer.CreateAnonymous()
stage.GetRootLayer().subLayerPaths.append(layer.identifier)
return layer

def targetSubLayer(layer):
stage.SetEditTarget(layer)
self.assertEqual(stage.GetEditTarget().GetLayer(), layer)
layer = None

def muteSubLayer(layer):
# Note: mute by passing through the stage, otherwise the stage won't get recomposed
stage.MuteLayer(layer.identifier)

def setSphereRadius(radius):
spherePrim = stage.GetPrimAtPath('/A/ball')
spherePrim.GetAttribute('radius').Set(radius)
spherePrim = None

# Add two new layers
topLayer = createLayer(0)
bottomLayer = createLayer(1)

# Create a xform prim named A and a sphere on the bottom layer
targetSubLayer(bottomLayer)
stage.DefinePrim('/A', 'Xform')
stage.DefinePrim('/A/ball', 'Sphere')
setSphereRadius(7.12)

targetSubLayer(topLayer)
setSphereRadius(4.32)

# Set target to bottom layer and mute the top layer
targetSubLayer(bottomLayer)
muteSubLayer(topLayer)

# Try to rename the prim with muted opinion: it should fail
with self.assertRaises(RuntimeError):
cmds.rename('%s,/A/ball' % proxyShapePathStr, 'ball2')

self.assertTrue(stage.GetPrimAtPath('/A/ball'))
self.assertFalse(stage.GetPrimAtPath('/A/group1/ball'))


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