From b8672f58ae174b39d3be9be78c6590f55af926dd Mon Sep 17 00:00:00 2001 From: Pierre Baillargeon Date: Wed, 13 Dec 2023 16:29:35 -0500 Subject: [PATCH] EMSUSD-671 fix reloading scene with layers saved in the Maya scene When reloading a scene that contained layers serialized directly in the scene, sub-layers would be lost because they were anonymous layers but on reload that status was lost and the sub-layer would be lost whenever the stage got recomputed. Fix this by remembering that the sub-layer are anonymous on reload. Add unit test to verify lost layer fix - Add comments to the layer manager serialization test and fix the layer being targeted. - Refactor common code in proxy shape saving tests. - Add more checks in the proxy shape saving test to verify that the contents are correct afetr reload and after recompute. --- lib/mayaUsd/nodes/proxyShapeBase.cpp | 12 +- .../nodes/ProxyShapeBaseTest/CubeModel.usda | 1 + .../nodes/testLayerManagerSerialization.py | 17 ++- test/lib/mayaUsd/nodes/testProxyShapeBase.py | 125 ++++++++++-------- 4 files changed, 100 insertions(+), 55 deletions(-) diff --git a/lib/mayaUsd/nodes/proxyShapeBase.cpp b/lib/mayaUsd/nodes/proxyShapeBase.cpp index 4d8ab81663..31941fcc93 100644 --- a/lib/mayaUsd/nodes/proxyShapeBase.cpp +++ b/lib/mayaUsd/nodes/proxyShapeBase.cpp @@ -940,8 +940,18 @@ MStatus MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock) SdfLayerRefPtr rootLayer = sharableStage ? computeRootLayer(dataBlock, fileString) : nullptr; - if (nullptr == rootLayer) + if (nullptr == rootLayer) { rootLayer = SdfLayer::FindOrOpen(fileString); + } else { + // When reloading a Maya scene in which the root layer was saved in + // the scene, the root layer will be anonymous. In order for the next + // compute to find the root layer again, we need to set it as the + // _anonymousRootLayer, as done below when creating a new proxy shape + // and the root layer is initially created. + if (rootLayer->IsAnonymous()) { + _anonymousRootLayer = rootLayer; + } + } if (nullptr == rootLayer) { // Create an empty in-memory root layer so that a new stage in memory diff --git a/test/lib/mayaUsd/nodes/ProxyShapeBaseTest/CubeModel.usda b/test/lib/mayaUsd/nodes/ProxyShapeBaseTest/CubeModel.usda index 8ec93e1d68..143964d425 100644 --- a/test/lib/mayaUsd/nodes/ProxyShapeBaseTest/CubeModel.usda +++ b/test/lib/mayaUsd/nodes/ProxyShapeBaseTest/CubeModel.usda @@ -24,3 +24,4 @@ def Xform "CubeModel" ( } } } + diff --git a/test/lib/mayaUsd/nodes/testLayerManagerSerialization.py b/test/lib/mayaUsd/nodes/testLayerManagerSerialization.py index 3694f703c3..4e4bfc85d4 100644 --- a/test/lib/mayaUsd/nodes/testLayerManagerSerialization.py +++ b/test/lib/mayaUsd/nodes/testLayerManagerSerialization.py @@ -56,6 +56,10 @@ def setupEmptyScene(self): cmds.file(rename=self._tempMayaFile) def copyTestFilesAndMakeEdits(self): + ''' + Copy an existing Maya scene that contains a stage and a few layer, + creates a few USD prim in the root, session and 1_1 layers. + ''' self._currentTestDir = tempfile.mkdtemp(prefix='LayerManagerTest') fromDirectory = os.path.join( self._inputPath, 'LayerManagerSerializationTest') @@ -79,13 +83,20 @@ def copyTestFilesAndMakeEdits(self): stage = mayaUsd.ufe.getStage( "|SerializationTest|SerializationTestShape") stack = stage.GetLayerStack() + # Note: layers are: + # 0: session + # 1: root + # 2: 1 + # 3: 1_1 + # 4: 2 + # 5: 2_1 self.assertEqual(6, len(stack)) stage.SetEditTarget(stage.GetRootLayer()) newPrimPath = "/ChangeInRoot" stage.DefinePrim(newPrimPath, "xform") - stage.SetEditTarget(stack[2]) + stage.SetEditTarget(stack[3]) newPrimPath = "/ChangeInLayer_1_1" stage.DefinePrim(newPrimPath, "xform") @@ -111,6 +122,10 @@ def copyTestFilesAndMakeEdits(self): return stage def confirmEditsSavedStatus(self, fileBackedSavedStatus, sessionSavedStatus): + ''' + Clears the Maya scene, creates a new USD stage with the root layer + and verify various prim existence based on given flags. + ''' cmds.file(new=True, force=True) proxyNode, stage = createProxyFromFile(self._rootUsdFile) diff --git a/test/lib/mayaUsd/nodes/testProxyShapeBase.py b/test/lib/mayaUsd/nodes/testProxyShapeBase.py index 93e017e353..f938940bb0 100644 --- a/test/lib/mayaUsd/nodes/testProxyShapeBase.py +++ b/test/lib/mayaUsd/nodes/testProxyShapeBase.py @@ -396,6 +396,46 @@ def testShareStagePreserveTCPS(self): self.assertEqual(stage.GetTimeCodesPerSecond(), tcps) self.assertEqual(stage.GetRootLayer().timeCodesPerSecond, tcps) + def _getStage(self): + ''' + Helper to get the stage, Needed since the stage instance will change + after saving. + ''' + proxyShapes = cmds.ls(type="mayaUsdProxyShapeBase", long=True) + self.assertGreater(len(proxyShapes), 0) + proxyShapePath = proxyShapes[0] + return mayaUsd.lib.GetPrim(proxyShapePath).GetStage(), proxyShapePath + + def _defineDummyPrim(self, stage = None, target = None): + ''' + Define a prim named "dummy". Can be create in an edit target if given one. + ''' + if stage is None: + stage, _ = self._getStage() + if target is not None: + stage.SetEditTarget(target) + return stage.DefinePrim("/dummy", "xform") + + def _verifyPrim(self, isActive = True): + ''' + Verify that the prim named "dummy" exists and its active state. + ''' + stage, _ = self._getStage() + prim = stage.GetPrimAtPath("/dummy") + self.assertTrue(prim) + self.assertEqual(prim.IsActive(), isActive) + + def _verifySubLayer(self, expectedCount = 3): + ''' + Verify that the stage still contains a sub-layer under the root. + Can pass the expected count if the setup is modified, for example + when the stage is not shared, an extra layer added. + ''' + stage, _ = self._getStage() + stack = stage.GetLayerStack() + # Layer stack: session, root, sub-layer + self.assertEqual(expectedCount, len(stack)) + def testShareStagePreserveSession(self): ''' Verify share/unshare stage preserves the data in the session layer @@ -406,37 +446,23 @@ def testShareStagePreserveSession(self): # Open usdCylinder.ma scene in testSamples mayaUtils.openCylinderScene() - # get the stage - def getStage(): - proxyShapes = cmds.ls(type="mayaUsdProxyShapeBase", long=True) - self.assertGreater(len(proxyShapes), 0) - proxyShapePath = proxyShapes[0] - return mayaUsd.lib.GetPrim(proxyShapePath).GetStage(), proxyShapePath - # check that the stage is shared and the root is the right one - stage, proxyShapePath = getStage() + stage, proxyShapePath = self._getStage() self.assertTrue(cmds.getAttr('{}.{}'.format(proxyShapePath,"shareStage"))) # create a prim in the session layer. - stage.SetEditTarget(stage.GetSessionLayer()) - stage.DefinePrim("/dummy", "xform") - - # verify that the prim exists. - def verifyPrim(): - stage, _ = getStage() - self.assertTrue(stage.GetPrimAtPath("/dummy")) - - verifyPrim() + self._defineDummyPrim(stage, stage.GetSessionLayer()) + self._verifyPrim() # unshare the stage and verify the prim in the session layer still exists. cmds.setAttr('{}.{}'.format(proxyShapePath,"shareStage"), False) - verifyPrim() + self._verifyPrim() # re-share the stage and verify the prim in the session layer still exists. cmds.setAttr('{}.{}'.format(proxyShapePath,"shareStage"), True) - verifyPrim() + self._verifyPrim() def _saveStagePreserveLayerHelper(self, targetRoot, saveInMaya): ''' @@ -451,15 +477,11 @@ def _saveStagePreserveLayerHelper(self, targetRoot, saveInMaya): cmds.file(new=True, force=True) mayaUtils.createProxyAndStage() - # Helper to get the stage, Needed since the stage instance will change - # after saving. - def getStage(): - proxyShapes = cmds.ls(type="mayaUsdProxyShapeBase", long=True) - self.assertGreater(len(proxyShapes), 0) - proxyShapePath = proxyShapes[0] - return mayaUsd.lib.GetPrim(proxyShapePath).GetStage(), proxyShapePath + stage, proxyShapePath = self._getStage() - stage, proxyShapePath = getStage() + # Create an anonymous sub-layer. + subLayer = Sdf.Layer.CreateAnonymous("middleLayer") + stage.GetRootLayer().subLayerPaths = [subLayer.identifier] # Create a prim in the root layer. stage.SetEditTarget(stage.GetRootLayer()) @@ -467,18 +489,11 @@ def getStage(): # Make the prim inactive in the desired target layer. target = stage.GetRootLayer() if targetRoot else stage.GetSessionLayer() - stage.SetEditTarget(target) - prim = stage.GetPrimAtPath("/dummy") + prim = self._defineDummyPrim(stage, target) prim.SetActive(False) # verify that the prim exists but is inactive. - def verifyPrim(): - stage, _ = getStage() - prim = stage.GetPrimAtPath("/dummy") - self.assertIsNotNone(prim) - self.assertFalse(prim.IsActive()) - - verifyPrim() + self._verifyPrim(False) # Temp file names for Maya scene and USD file. with testUtils.TemporaryDirectory(prefix='ProxyShapeBase', ignore_errors=True) as testDir: @@ -496,8 +511,24 @@ def verifyPrim(): cmds.file(save=True, force=True, type='mayaAscii') # Verify that the prim is still inactive in the target layer. + self._verifyPrim(False) + self._verifySubLayer() + + # Reload the file and verify again. + cmds.file(new=True, force=True) + cmds.file(tempMayaFile, force=True, open=True) - verifyPrim() + self._verifyPrim(False) + self._verifySubLayer() + + # Change shared status and verify again. Changing the shared flag recomputes + # the stage and its layer, which is what we really want to test here. So we + # change an input attribute that we know will recompute the layers. + cmds.setAttr('{}.{}'.format(proxyShapePath,"shareStage"), False) + + # Verify that the prim is still inactive in the target layer. + self._verifyPrim(False) + self._verifySubLayer(4) cmds.file(new=True, force=True) @@ -537,31 +568,19 @@ def testUnsavedStagePreserveRootLayerWhenUpdated(self): cmds.file(new=True, force=True) mayaUtils.createProxyAndStage() - # Helper to get the stage, - def getStage(): - proxyShapes = cmds.ls(type="mayaUsdProxyShapeBase", long=True) - self.assertGreater(len(proxyShapes), 0) - proxyShapePath = proxyShapes[0] - return mayaUsd.lib.GetPrim(proxyShapePath).GetStage(), proxyShapePath - # create a prim in the root layer. - stage, proxyShapePath = getStage() - stage.SetEditTarget(stage.GetRootLayer()) - stage.DefinePrim("/dummy", "xform") + self._defineDummyPrim() # verify that the prim exists. - def verifyPrim(): - stage, _ = getStage() - self.assertTrue(stage.GetPrimAtPath("/dummy")) - - verifyPrim() + self._verifyPrim() # Set an attribute on the proxy shape. Here we set the loadPayloads. # It was already set, this only triggers a Maya node recompute. + _, proxyShapePath = self._getStage() cmds.setAttr('{}.{}'.format(proxyShapePath,"loadPayloads"), True) # Verify that we did not lose the data on the root layer. - verifyPrim() + self._verifyPrim() def testSettingStageViaIdPreservedWhenSaved(self): '''