From 0d9de70e37eb7f5c7bd2f5081f091c76b25dcf1e Mon Sep 17 00:00:00 2001 From: Pierre Baillargeon Date: Wed, 18 Oct 2023 15:09:24 -0400 Subject: [PATCH 1/3] EMSUSD-632 prevent root metadata changes when not targeting root - Add a applyRootLayerMetadataRestriction helper function to enforce not modifying the root layer metadata when not targeting the root layer. - The only restriction applied is about the default prim. - Call it from within the applyCommandRestriction helper function. - Call it from within the default prim commands. - Add unit tests for the rename, parent and group default prim restrictions. - Add tests for the default prim commands. --- .../ufe/UsdUndoClearDefaultPrimCommand.cpp | 16 +- .../ufe/UsdUndoSetDefaultPrimCommand.cpp | 17 +- lib/usdUfe/ufe/Utils.cpp | 75 +++++++-- lib/usdUfe/ufe/Utils.h | 13 +- test/lib/ufe/CMakeLists.txt | 1 + test/lib/ufe/testDefaultPrimCmds.py | 150 ++++++++++++++++++ test/lib/ufe/testGroupCmd.py | 24 +++ test/lib/ufe/testParentCmd.py | 23 +++ test/lib/ufe/testRename.py | 22 +++ test/testSamples/defaultPrimInSub/root.usda | 8 + test/testSamples/defaultPrimInSub/sub.usda | 6 + 11 files changed, 321 insertions(+), 34 deletions(-) create mode 100644 test/lib/ufe/testDefaultPrimCmds.py create mode 100644 test/testSamples/defaultPrimInSub/root.usda create mode 100644 test/testSamples/defaultPrimInSub/sub.usda diff --git a/lib/usdUfe/ufe/UsdUndoClearDefaultPrimCommand.cpp b/lib/usdUfe/ufe/UsdUndoClearDefaultPrimCommand.cpp index 0e1d52054e..5d0287f50d 100644 --- a/lib/usdUfe/ufe/UsdUndoClearDefaultPrimCommand.cpp +++ b/lib/usdUfe/ufe/UsdUndoClearDefaultPrimCommand.cpp @@ -35,19 +35,15 @@ UsdUndoClearDefaultPrimCommand::~UsdUndoClearDefaultPrimCommand() { } void UsdUndoClearDefaultPrimCommand::execute() { - UsdUndoBlock undoBlock(&_undoableItem); + const PXR_NS::UsdStageRefPtr stage = _prim.GetStage(); + if (!stage) + return; - PXR_NS::UsdStageWeakPtr stage = _prim.GetStage(); + // Check if the default prim can be cleared. + applyRootLayerMetadataRestriction(stage, "clear default prim"); - // Check if the layer selected is the root layer. - if (!UsdUfe::isRootLayer(stage)) { - TF_WARN( - "Stage metadata [defaultPrim] can only be modified when the root layer is targeted " - "[%s]", - stage->GetRootLayer()->GetDisplayName().c_str()); - return; - } // Clear the stage's default prim. + UsdUndoBlock undoBlock(&_undoableItem); stage->ClearDefaultPrim(); } diff --git a/lib/usdUfe/ufe/UsdUndoSetDefaultPrimCommand.cpp b/lib/usdUfe/ufe/UsdUndoSetDefaultPrimCommand.cpp index 4c524d290a..764ee2cb87 100644 --- a/lib/usdUfe/ufe/UsdUndoSetDefaultPrimCommand.cpp +++ b/lib/usdUfe/ufe/UsdUndoSetDefaultPrimCommand.cpp @@ -35,18 +35,15 @@ UsdUndoSetDefaultPrimCommand::~UsdUndoSetDefaultPrimCommand() { } void UsdUndoSetDefaultPrimCommand::execute() { - UsdUndoBlock undoBlock(&_undoableItem); - PXR_NS::UsdStageWeakPtr stage = _prim.GetStage(); - - // Check if the layer selected is the root layer. - if (!UsdUfe::isRootLayer(stage)) { - TF_WARN( - "Stage metadata [defaultPrim] can only be modified when the root layer is targeted " - "[%s]", - stage->GetRootLayer()->GetDisplayName().c_str()); + const PXR_NS::UsdStageRefPtr stage = _prim.GetStage(); + if (!stage) return; - } + + // Check if the default prim can be set. + applyRootLayerMetadataRestriction(stage, "set default prim"); + // Set the stage's default prim to the given prim. + UsdUndoBlock undoBlock(&_undoableItem); stage->SetDefaultPrim(_prim); } diff --git a/lib/usdUfe/ufe/Utils.cpp b/lib/usdUfe/ufe/Utils.cpp index 4c19fe9d57..2553d1e555 100644 --- a/lib/usdUfe/ufe/Utils.cpp +++ b/lib/usdUfe/ufe/Utils.cpp @@ -410,6 +410,66 @@ Ufe::BBox3d combineUfeBBox(const Ufe::BBox3d& ufeBBox1, const Ufe::BBox3d& ufeBB return combinedBBox; } +void applyRootLayerMetadataRestriction(const UsdPrim& prim, const std::string& commandName) +{ + // return early if prim is the pseudo-root. + // this is a special case and could happen when one tries to drag a prim under the + // proxy shape in outliner. Also note if prim is the pseudo-root, no def primSpec will be found. + if (prim.IsPseudoRoot()) { + return; + } + + const auto stage = prim.GetStage(); + if (!stage) + return; + + // If the target layer is the root layer, then the restrictions + // do not apply since the edit target is on the layer that contains + // the metadata. + const SdfLayerHandle targetLayer = stage->GetEditTarget().GetLayer(); + const SdfLayerHandle rootLayer = stage->GetRootLayer(); + if (targetLayer == rootLayer) + return; + + // Enforce the restriction that we cannot change the default prim + // from a layer other than the root layer. + if (prim == stage->GetDefaultPrim()) { + const std::string layerName = rootLayer->GetDisplayName(); + const std::string err = TfStringPrintf( + "Cannot %s [%s]. This prim is defined as the default prim on [%s]", + commandName.c_str(), + prim.GetName().GetString().c_str(), + layerName.c_str()); + throw std::runtime_error(err.c_str()); + } +} + +void applyRootLayerMetadataRestriction( + const PXR_NS::UsdStageRefPtr& stage, + const std::string& commandName) +{ + if (!stage) + return; + + // If the target layer is the root layer, then the restrictions + // do not apply since the edit target is on the layer that contains + // the metadata. + const SdfLayerHandle targetLayer = stage->GetEditTarget().GetLayer(); + const SdfLayerHandle rootLayer = stage->GetRootLayer(); + if (targetLayer == rootLayer) + return; + + // Enforce the restriction that we cannot change the default prim + // from a layer other than the root layer. + const std::string layerName = rootLayer->GetDisplayName(); + const std::string err = TfStringPrintf( + "Cannot %s. The stage default prim metadata can only be modified when the root layer [%s] " + "is targeted.", + commandName.c_str(), + layerName.c_str()); + throw std::runtime_error(err.c_str()); +} + void applyCommandRestriction( const UsdPrim& prim, const std::string& commandName, @@ -439,7 +499,7 @@ void applyCommandRestriction( // the target. std::string message = allowStronger ? "It is defined on another layer. " : ""; std::string instructions = allowStronger ? "Please set %s as the target layer to proceed." - : "It would orphan opinions on the layer %s."; + : "It would orphan opinions on the layer %s"; // iterate over the prim stack, starting at the highest-priority layer. for (const auto& spec : primStack) { @@ -497,7 +557,7 @@ void applyCommandRestriction( if (allowedInStrongerLayer(prim, primStack, sessionLayers, allowStronger)) return; std::string err = TfStringPrintf( - "Cannot %s [%s] because it is defined inside the variant composition arc %s.", + "Cannot %s [%s] because it is defined inside the variant composition arc %s", commandName.c_str(), prim.GetName().GetString().c_str(), layerDisplayName.c_str()); @@ -518,6 +578,8 @@ void applyCommandRestriction( formattedInstructions.c_str()); throw std::runtime_error(err.c_str()); } + + applyRootLayerMetadataRestriction(prim, commandName); } bool applyCommandRestrictionNoThrow( @@ -800,13 +862,4 @@ Ufe::Selection recreateDescendants(const Ufe::Selection& src, const Ufe::Path& f return dst; } -bool isRootLayer(const PXR_NS::UsdStageWeakPtr stage) -{ - // Check if the layer selected is the root layer. - if (stage->GetRootLayer() != stage->GetEditTarget().GetLayer()) { - return false; - } - return true; -} - } // namespace USDUFE_NS_DEF diff --git a/lib/usdUfe/ufe/Utils.h b/lib/usdUfe/ufe/Utils.h index cfa9ab7244..7b0c8af21f 100644 --- a/lib/usdUfe/ufe/Utils.h +++ b/lib/usdUfe/ufe/Utils.h @@ -264,6 +264,16 @@ bool applyCommandRestrictionNoThrow( const std::string& commandName, bool allowStronger = false); +//! Apply restriction rules for root layer metadata on the given prim +USDUFE_PUBLIC +void applyRootLayerMetadataRestriction(const PXR_NS::UsdPrim& prim, const std::string& commandName); + +//! Apply restriction rules for root layer metadata on the given stage +USDUFE_PUBLIC +void applyRootLayerMetadataRestriction( + const PXR_NS::UsdStageRefPtr& stage, + const std::string& commandName); + //! Check if the edit target in the stage is allowed to be changed. //! \return True, if the edit target layer in the stage is allowed to be changed USDUFE_PUBLIC @@ -275,7 +285,4 @@ bool isEditTargetLayerModifiable( USDUFE_PUBLIC Ufe::BBox3d combineUfeBBox(const Ufe::BBox3d& ufeBBox1, const Ufe::BBox3d& ufeBBox2); -USDUFE_PUBLIC -bool isRootLayer(const PXR_NS::UsdStageWeakPtr prim); - } // namespace USDUFE_NS_DEF diff --git a/test/lib/ufe/CMakeLists.txt b/test/lib/ufe/CMakeLists.txt index 3152bf0242..9b9f5dd941 100644 --- a/test/lib/ufe/CMakeLists.txt +++ b/test/lib/ufe/CMakeLists.txt @@ -8,6 +8,7 @@ set(TEST_SCRIPT_FILES testChildFilter.py testComboCmd.py testContextOps.py + testDefaultPrimCmds.py testDuplicateCmd.py testEditRouting.py testGroupCmd.py diff --git a/test/lib/ufe/testDefaultPrimCmds.py b/test/lib/ufe/testDefaultPrimCmds.py new file mode 100644 index 0000000000..d9988b431a --- /dev/null +++ b/test/lib/ufe/testDefaultPrimCmds.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python + +# +# 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 fixturesUtils +import mayaUtils +import testUtils +from testUtils import assertVectorAlmostEqual, assertVectorNotAlmostEqual +import usdUtils +from usdUtils import filterUsdStr + +import mayaUsd.ufe + +from pxr import UsdGeom, Vt, Gf, Sdf, Usd + +from maya import cmds +from maya import standalone + +import ufe + +import os +import unittest + +class DefaultPrimCmdsTestCase(unittest.TestCase): + + pluginsLoaded = False + + @classmethod + def setUpClass(cls): + fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False) + + if not cls.pluginsLoaded: + cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() + + @classmethod + def tearDownClass(cls): + cmds.file(new=True, force=True) + standalone.uninitialize() + + def setUp(self): + # Load plugins + self.assertTrue(self.pluginsLoaded) + + def testClearDefaultPrim(self): + ''' + Verify that we can clear the default prim. + ''' + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('defaultPrimInSub', 'root.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + + capsulePathStr = '|stage|stageShape,/Capsule1' + capsulePath = ufe.PathString.path(capsulePathStr) + capsuleItem = ufe.Hierarchy.createItem(capsulePath) + self.assertIsNotNone(capsuleItem) + + contextOps = ufe.ContextOps.contextOps(capsuleItem) + contextOps.doOp(['Clear Default Prim']) + + self.assertFalse(stage.GetDefaultPrim()) + + def testSetDefaultPrimRestriction(self): + ''' + Verify that we can set the default prim. + ''' + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('defaultPrimInSub', 'root.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + + x1 = stage.DefinePrim('/Xform1', 'Xform') + self.assertIsNotNone(x1) + x1PathStr = '|stage|stageShape,/Xform1' + x1Path = ufe.PathString.path(x1PathStr) + x1Item = ufe.Hierarchy.createItem(x1Path) + self.assertIsNotNone(x1Item) + + x1Prim = stage.GetPrimAtPath('/Xform1') + self.assertIsNotNone(x1Prim) + self.assertIsTrue(x1Prim) + + contextOps = ufe.ContextOps.contextOps(x1Item) + contextOps.doOp(['Set as Default Prim']) + + self.assertEqual(stage.GetDefaultPrim(), x1Prim) + + def testClearDefaultPrimRestriction(self): + ''' + Verify that we cannot clear the default prim + when not targeting the root layer. + ''' + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('defaultPrimInSub', 'root.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + + layer = Sdf.Layer.FindRelativeToLayer(stage.GetRootLayer(), stage.GetRootLayer().subLayerPaths[0]) + self.assertIsNotNone(layer) + stage.SetEditTarget(layer) + self.assertEqual(stage.GetEditTarget().GetLayer(), layer) + + capsulePathStr = '|stage|stageShape,/Capsule1' + capsulePath = ufe.PathString.path(capsulePathStr) + capsuleItem = ufe.Hierarchy.createItem(capsulePath) + self.assertIsNotNone(capsuleItem) + + contextOps = ufe.ContextOps.contextOps(capsuleItem) + with self.assertRaises(RuntimeError): + contextOps.doOp(['Clear Default Prim']) + + def testSetDefaultPrimRestriction(self): + ''' + Verify that we cannot set the default prim + when not targeting the root layer. + ''' + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('defaultPrimInSub', 'root.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + + layer = Sdf.Layer.FindRelativeToLayer(stage.GetRootLayer(), stage.GetRootLayer().subLayerPaths[0]) + self.assertIsNotNone(layer) + stage.SetEditTarget(layer) + self.assertEqual(stage.GetEditTarget().GetLayer(), layer) + + x1 = stage.DefinePrim('/Xform1', 'Xform') + self.assertIsNotNone(x1) + x1PathStr = '|stage|stageShape,/Xform1' + x1Path = ufe.PathString.path(x1PathStr) + x1Item = ufe.Hierarchy.createItem(x1Path) + self.assertIsNotNone(x1Item) + + contextOps = ufe.ContextOps.contextOps(x1Item) + with self.assertRaises(RuntimeError): + contextOps.doOp(['Set as Default Prim']) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/test/lib/ufe/testGroupCmd.py b/test/lib/ufe/testGroupCmd.py index a8b2665587..64c0d1da86 100644 --- a/test/lib/ufe/testGroupCmd.py +++ b/test/lib/ufe/testGroupCmd.py @@ -373,6 +373,30 @@ def testGroupRestriction(self): stage.GetPrimAtPath("/Ball_set/Props/Ball_6"), stage.GetPrimAtPath("/Sphere1")]) + def testGroupRestrictionDefaultPrim(self): + ''' + Verify that a prim that is the default prim prevent + grouping that prim when not targeting the root layer. + ''' + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('defaultPrimInSub', 'root.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + + capsulePathStr = '|stage|stageShape,/Capsule1' + + layer = Sdf.Layer.FindRelativeToLayer(stage.GetRootLayer(), stage.GetRootLayer().subLayerPaths[0]) + self.assertIsNotNone(layer) + stage.SetEditTarget(layer) + self.assertEqual(stage.GetEditTarget().GetLayer(), layer) + + capsuleItem = ufe.Hierarchy.createItem(ufe.PathString.path(capsulePathStr)) + ufeSelection = ufe.GlobalSelection.get() + ufeSelection.clear() + ufeSelection.append(capsuleItem) + + with self.assertRaises(RuntimeError): + cmds.group(name="newGroup") + @unittest.skipUnless(ufeUtils.ufeFeatureSetVersion() >= 3, 'testGroupAbsolute is only available in UFE v3 or greater.') def testGroupAbsolute(self): '''Verify -absolute flag.''' diff --git a/test/lib/ufe/testParentCmd.py b/test/lib/ufe/testParentCmd.py index 35344a39b1..4ddecbb9c9 100644 --- a/test/lib/ufe/testParentCmd.py +++ b/test/lib/ufe/testParentCmd.py @@ -1057,6 +1057,29 @@ def testParentingToGPrim(self): cmds.parent("|Tree_usd|Tree_usdShape,/TreeBase/trunk", "|Tree_usd|Tree_usdShape,/TreeBase/leavesXform/leaves") + def testParentRestrictionDefaultPrim(self): + ''' + Verify that a prim that is the default prim prevent + parenting that prim when not targeting the root layer. + ''' + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('defaultPrimInSub', 'root.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + + capsulePathStr = '|stage|stageShape,/Capsule1' + + layer = Sdf.Layer.FindRelativeToLayer(stage.GetRootLayer(), stage.GetRootLayer().subLayerPaths[0]) + self.assertIsNotNone(layer) + stage.SetEditTarget(layer) + self.assertEqual(stage.GetEditTarget().GetLayer(), layer) + + x1 = stage.DefinePrim('/Xform1', 'Xform') + self.assertIsNotNone(x1) + x1PathStr = '|stage|stageShape,/Xform1' + + with self.assertRaises(RuntimeError): + cmds.parent(capsulePathStr, x1PathStr) + @unittest.skipUnless(mayaUtils.mayaMajorVersion() >= 2023, 'Requires Maya fixes only available in Maya 2023 or greater.') def testParentShader(self): '''Shaders can only have NodeGraphs and Materials as parent.''' diff --git a/test/lib/ufe/testRename.py b/test/lib/ufe/testRename.py index eec6d3e67f..b21e5249c4 100644 --- a/test/lib/ufe/testRename.py +++ b/test/lib/ufe/testRename.py @@ -537,6 +537,28 @@ def testRenameRestrictionHasSpecs(self): with self.assertRaises(RuntimeError): cmds.rename("geo_renamed") + def testRenameRestrictionDefaultPrim(self): + ''' + Verify that a prim that is the default prim prevent + renaming that prim when not targeting the root layer. + ''' + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('defaultPrimInSub', 'root.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + + capsulePath = ufe.PathString.path('|stage|stageShape,/Capsule1') + capsuleItem = ufe.Hierarchy.createItem(capsulePath) + self.assertIsNotNone(capsuleItem) + ufe.GlobalSelection.get().append(capsuleItem) + + layer = Sdf.Layer.FindRelativeToLayer(stage.GetRootLayer(), stage.GetRootLayer().subLayerPaths[0]) + self.assertIsNotNone(layer) + stage.SetEditTarget(layer) + self.assertEqual(stage.GetEditTarget().GetLayer(), layer) + + with self.assertRaises(RuntimeError): + cmds.rename("banana") + def testRenameUniqueName(self): # open tree.ma scene in testSamples mayaUtils.openTreeScene() diff --git a/test/testSamples/defaultPrimInSub/root.usda b/test/testSamples/defaultPrimInSub/root.usda new file mode 100644 index 0000000000..93b5d1e269 --- /dev/null +++ b/test/testSamples/defaultPrimInSub/root.usda @@ -0,0 +1,8 @@ +#usda 1.0 +( + defaultPrim = "Capsule1" + subLayers = [ + @sub.usda@ + ] +) + diff --git a/test/testSamples/defaultPrimInSub/sub.usda b/test/testSamples/defaultPrimInSub/sub.usda new file mode 100644 index 0000000000..9aa9a34d32 --- /dev/null +++ b/test/testSamples/defaultPrimInSub/sub.usda @@ -0,0 +1,6 @@ +#usda 1.0 + +def Capsule "Capsule1" +{ +} + From e0dd84264794bb84d1d210d7e4c396029734045b Mon Sep 17 00:00:00 2001 From: Pierre Baillargeon Date: Thu, 19 Oct 2023 14:38:22 -0400 Subject: [PATCH 2/3] EMSUSD-632 use commands in the AE - Wrap commands for Python. - Use commands instead of USD calls in AE. - Allow clearing the default prim with only a stage in addition to with a prim. --- lib/usdUfe/python/wrapCommands.cpp | 29 ++++++++++++++ .../ufe/UsdUndoClearDefaultPrimCommand.cpp | 14 ++++--- .../ufe/UsdUndoClearDefaultPrimCommand.h | 5 ++- lib/usdUfe/ufe/UsdUndoSetDefaultPrimCommand.h | 2 +- plugin/adsk/scripts/AETemplateHelpers.py | 40 ++++++++++++------- .../AEmayaUsdProxyShapeBaseTemplate.mel | 20 +++++++--- 6 files changed, 82 insertions(+), 28 deletions(-) diff --git a/lib/usdUfe/python/wrapCommands.cpp b/lib/usdUfe/python/wrapCommands.cpp index bd45c94003..e8014a6d49 100644 --- a/lib/usdUfe/python/wrapCommands.cpp +++ b/lib/usdUfe/python/wrapCommands.cpp @@ -15,9 +15,11 @@ // #include #include +#include #include #include #include +#include #include #include @@ -71,10 +73,37 @@ UsdUfe::UsdUndoUnloadPayloadCommand* UnloadPayloadCommandInit(const PXR_NS::UsdP return new UsdUfe::UsdUndoUnloadPayloadCommand(prim); } +UsdUfe::UsdUndoClearDefaultPrimCommand* +ClearDefaultPrimCommandInit(const PXR_NS::UsdStageRefPtr& stage) +{ + return new UsdUfe::UsdUndoClearDefaultPrimCommand(stage); +} + +UsdUfe::UsdUndoSetDefaultPrimCommand* SetDefaultPrimCommandInit(const PXR_NS::UsdPrim& prim) +{ + return new UsdUfe::UsdUndoSetDefaultPrimCommand(prim); +} + } // namespace void wrapCommands() { + { + using This = UsdUfe::UsdUndoClearDefaultPrimCommand; + class_("ClearDefaultPrimCommand", no_init) + .def("__init__", make_constructor(ClearDefaultPrimCommandInit)) + .def("execute", &UsdUfe::UsdUndoClearDefaultPrimCommand::execute) + .def("undo", &UsdUfe::UsdUndoClearDefaultPrimCommand::undo) + .def("redo", &UsdUfe::UsdUndoClearDefaultPrimCommand::redo); + } + { + using This = UsdUfe::UsdUndoSetDefaultPrimCommand; + class_("SetDefaultPrimCommand", no_init) + .def("__init__", make_constructor(SetDefaultPrimCommandInit)) + .def("execute", &UsdUfe::UsdUndoSetDefaultPrimCommand::execute) + .def("undo", &UsdUfe::UsdUndoSetDefaultPrimCommand::undo) + .def("redo", &UsdUfe::UsdUndoSetDefaultPrimCommand::redo); + } { using This = UsdUfe::UsdUndoAddPayloadCommand; class_("AddPayloadCommand", no_init) diff --git a/lib/usdUfe/ufe/UsdUndoClearDefaultPrimCommand.cpp b/lib/usdUfe/ufe/UsdUndoClearDefaultPrimCommand.cpp index 5d0287f50d..39216aeaa9 100644 --- a/lib/usdUfe/ufe/UsdUndoClearDefaultPrimCommand.cpp +++ b/lib/usdUfe/ufe/UsdUndoClearDefaultPrimCommand.cpp @@ -27,7 +27,12 @@ namespace USDUFE_NS_DEF { UsdUndoClearDefaultPrimCommand::UsdUndoClearDefaultPrimCommand(const UsdPrim& prim) : Ufe::UndoableCommand() - , _prim(prim) + , _stage(prim.GetStage()) +{ +} + +UsdUndoClearDefaultPrimCommand::UsdUndoClearDefaultPrimCommand(const PXR_NS::UsdStageRefPtr& stage) + : _stage(stage) { } @@ -35,16 +40,15 @@ UsdUndoClearDefaultPrimCommand::~UsdUndoClearDefaultPrimCommand() { } void UsdUndoClearDefaultPrimCommand::execute() { - const PXR_NS::UsdStageRefPtr stage = _prim.GetStage(); - if (!stage) + if (!_stage) return; // Check if the default prim can be cleared. - applyRootLayerMetadataRestriction(stage, "clear default prim"); + applyRootLayerMetadataRestriction(_stage, "clear default prim"); // Clear the stage's default prim. UsdUndoBlock undoBlock(&_undoableItem); - stage->ClearDefaultPrim(); + _stage->ClearDefaultPrim(); } void UsdUndoClearDefaultPrimCommand::redo() { _undoableItem.redo(); } diff --git a/lib/usdUfe/ufe/UsdUndoClearDefaultPrimCommand.h b/lib/usdUfe/ufe/UsdUndoClearDefaultPrimCommand.h index f747b6795c..ff60d2a296 100644 --- a/lib/usdUfe/ufe/UsdUndoClearDefaultPrimCommand.h +++ b/lib/usdUfe/ufe/UsdUndoClearDefaultPrimCommand.h @@ -30,6 +30,7 @@ class USDUFE_PUBLIC UsdUndoClearDefaultPrimCommand : public Ufe::UndoableCommand public: // Public for std::make_shared() access, use create() instead. UsdUndoClearDefaultPrimCommand(const PXR_NS::UsdPrim& prim); + UsdUndoClearDefaultPrimCommand(const PXR_NS::UsdStageRefPtr& stage); ~UsdUndoClearDefaultPrimCommand() override; // Delete the copy/move constructors assignment operators. @@ -38,12 +39,12 @@ class USDUFE_PUBLIC UsdUndoClearDefaultPrimCommand : public Ufe::UndoableCommand UsdUndoClearDefaultPrimCommand(UsdUndoClearDefaultPrimCommand&&) = delete; UsdUndoClearDefaultPrimCommand& operator=(UsdUndoClearDefaultPrimCommand&&) = delete; -private: void execute() override; void undo() override; void redo() override; - PXR_NS::UsdPrim _prim; +private: + PXR_NS::UsdStageRefPtr _stage; UsdUndoableItem _undoableItem; diff --git a/lib/usdUfe/ufe/UsdUndoSetDefaultPrimCommand.h b/lib/usdUfe/ufe/UsdUndoSetDefaultPrimCommand.h index e13c6ef10b..2b9415f10d 100644 --- a/lib/usdUfe/ufe/UsdUndoSetDefaultPrimCommand.h +++ b/lib/usdUfe/ufe/UsdUndoSetDefaultPrimCommand.h @@ -38,11 +38,11 @@ class USDUFE_PUBLIC UsdUndoSetDefaultPrimCommand : public Ufe::UndoableCommand UsdUndoSetDefaultPrimCommand(UsdUndoSetDefaultPrimCommand&&) = delete; UsdUndoSetDefaultPrimCommand& operator=(UsdUndoSetDefaultPrimCommand&&) = delete; -private: void execute() override; void undo() override; void redo() override; +private: PXR_NS::UsdPrim _prim; UsdUndoableItem _undoableItem; diff --git a/plugin/adsk/scripts/AETemplateHelpers.py b/plugin/adsk/scripts/AETemplateHelpers.py index 5a0e2516b0..13c65fb66b 100644 --- a/plugin/adsk/scripts/AETemplateHelpers.py +++ b/plugin/adsk/scripts/AETemplateHelpers.py @@ -1,7 +1,8 @@ -import functools import os.path import maya.cmds as cmds -import ufe +import maya.api.OpenMaya as OpenMaya +import maya.internal.ufeSupport.ufeCmdWrapper as ufeCmd +import usdUfe import mayaUsd.ufe import mayaUsd.lib as mayaUsdLib import re @@ -13,10 +14,12 @@ def debugMessage(msg): if DEBUG: print(msg) +__naturalOrderRE = re.compile(r'([0-9]+)') + def GetAllRootPrimNamesNaturalOrder(proxyShape): # Custom comparator key def natural_key(item): - return [int(s) if s.isdigit() else s.lower() for s in re.split(r'([0-9]+)', item)] + return [int(s) if s.isdigit() else s.lower() for s in __naturalOrderRE.split(item)] try: proxyStage = mayaUsd.ufe.getStage(proxyShape) primNames = [] @@ -47,19 +50,26 @@ def GetDefaultPrimName(proxyShape): def SetDefaultPrim(proxyShape, primName): try: proxyStage = mayaUsd.ufe.getStage(proxyShape) - if(not primName): - proxyStage.ClearDefaultPrim() - defautlPrim = None - if proxyStage: - for prim in proxyStage.TraverseAll(): - if(primName == prim.GetName()): - defautlPrim = prim - if defautlPrim: - proxyStage.SetDefaultPrim(defautlPrim) + if not proxyStage: + return False + + cmd = None + if not primName: + cmd = usdUfe.ClearDefaultPrimCommand(proxyStage) + else: + defaultPrim = proxyStage.GetPrimAtPath('/' + primName) + if defaultPrim: + cmd = usdUfe.SetDefaultPrimCommand(defaultPrim) + + if cmd is None: + return False + + ufeCmd.execute(cmd) + return True except Exception as e: - debugMessage('SetDefaultPrim() - Error: %s' % str(e)) - pass - return '' + # Note: we do want to tell the user why the set or clear failed. + OpenMaya.MGlobal.displayError((str(e))) + return False def GetRootLayerName(proxyShape): try: diff --git a/plugin/adsk/scripts/AEmayaUsdProxyShapeBaseTemplate.mel b/plugin/adsk/scripts/AEmayaUsdProxyShapeBaseTemplate.mel index f5b6394cc0..7f29b91f99 100644 --- a/plugin/adsk/scripts/AEmayaUsdProxyShapeBaseTemplate.mel +++ b/plugin/adsk/scripts/AEmayaUsdProxyShapeBaseTemplate.mel @@ -20,10 +20,17 @@ global proc string AEmayaUsdProxyShapeInfoChanged(string $input) // Call python helpers to get the info we want to display. string $rootLayer = `python("import AETemplateHelpers; AETemplateHelpers.GetRootLayerName('" + $fullNodeName + "')")`; - string $defaultPrim = `python("import AETemplateHelpers; AETemplateHelpers.GetDefaultPrimName('" + $fullNodeName + "')")`; - string $allRootPrims[] = `python("import AETemplateHelpers; AETemplateHelpers.GetAllRootPrimNamesNaturalOrder('" + $fullNodeName + "')")`; textFieldGrp -edit -text $rootLayer mayaUsdProxyShapeRootLayer; + mayaUsd_DefaultPrimInit($fullNodeName); + + return $fullNodeName; +} + +global proc mayaUsd_DefaultPrimInit(string $fullNodeName) +{ + string $defaultPrim = `python("import AETemplateHelpers; AETemplateHelpers.GetDefaultPrimName('" + $fullNodeName + "')")`; + string $allRootPrims[] = `python("import AETemplateHelpers; AETemplateHelpers.GetAllRootPrimNamesNaturalOrder('" + $fullNodeName + "')")`; optionMenuGrp -edit -deleteAllItems -changeCommand ("mayaUsd_DefaultPrimChanged(\"" + $fullNodeName + "\"\)") mayaUsdProxyShapeDefaultPrim; string $menuName = `optionMenuGrp -q -fullPathName mayaUsdProxyShapeDefaultPrim`; @@ -43,14 +50,17 @@ global proc string AEmayaUsdProxyShapeInfoChanged(string $input) $index = 1; } optionMenuGrp -e -select $index mayaUsdProxyShapeDefaultPrim; - - return $fullNodeName; } global proc mayaUsd_DefaultPrimChanged(string $fullNodeName) { string $primName = `optionMenuGrp -q -value mayaUsdProxyShapeDefaultPrim`; - python("import AETemplateHelpers; AETemplateHelpers.SetDefaultPrim('" + $fullNodeName + "', primName ='" + $primName + "')"); + int $result = `python("import AETemplateHelpers; AETemplateHelpers.SetDefaultPrim('" + $fullNodeName + "', primName ='" + $primName + "')")`; + // On failure, we re-initialize the UI since the command failed + // and thus we need to restore the previous UI state. + if ($result == 0) { + mayaUsd_DefaultPrimInit($fullNodeName); + } } global proc AEmayaUsdProxyShapeInfoReplace(string $input) From 106b4eb421e4a15de5cc1898656d41a0b1a6188a Mon Sep 17 00:00:00 2001 From: Pierre Baillargeon Date: Fri, 20 Oct 2023 16:18:57 -0400 Subject: [PATCH 3/3] EMSUSD-632 fix copyright and other things --- plugin/adsk/scripts/AETemplateHelpers.py | 2 +- test/lib/ufe/testDefaultPrimCmds.py | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/plugin/adsk/scripts/AETemplateHelpers.py b/plugin/adsk/scripts/AETemplateHelpers.py index 13c65fb66b..3ada2045aa 100644 --- a/plugin/adsk/scripts/AETemplateHelpers.py +++ b/plugin/adsk/scripts/AETemplateHelpers.py @@ -68,7 +68,7 @@ def SetDefaultPrim(proxyShape, primName): return True except Exception as e: # Note: we do want to tell the user why the set or clear failed. - OpenMaya.MGlobal.displayError((str(e))) + OpenMaya.MGlobal.displayError(str(e)) return False def GetRootLayerName(proxyShape): diff --git a/test/lib/ufe/testDefaultPrimCmds.py b/test/lib/ufe/testDefaultPrimCmds.py index d9988b431a..ccc1ac5abb 100644 --- a/test/lib/ufe/testDefaultPrimCmds.py +++ b/test/lib/ufe/testDefaultPrimCmds.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -# Copyright 2020 Autodesk +# 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. @@ -19,20 +19,14 @@ import fixturesUtils import mayaUtils import testUtils -from testUtils import assertVectorAlmostEqual, assertVectorNotAlmostEqual -import usdUtils -from usdUtils import filterUsdStr -import mayaUsd.ufe - -from pxr import UsdGeom, Vt, Gf, Sdf, Usd +from pxr import Sdf from maya import cmds from maya import standalone import ufe -import os import unittest class DefaultPrimCmdsTestCase(unittest.TestCase):