From ac3fc5669473cd4680654a87d58e5949fb29c936 Mon Sep 17 00:00:00 2001 From: Pierre Baillargeon Date: Wed, 9 Oct 2024 08:49:57 -0400 Subject: [PATCH] EMSUSD-1222 set up-axis and units on saved layers --- lib/mayaUsd/nodes/layerManager.cpp | 2 + lib/mayaUsd/utils/utilSerialization.cpp | 20 +++ lib/mayaUsd/utils/utilSerialization.h | 5 + test/lib/mayaUsd/fileio/CMakeLists.txt | 1 + .../mayaUsd/fileio/testSaveUpAxisAndUnits.py | 123 ++++++++++++++++++ 5 files changed, 151 insertions(+) create mode 100644 test/lib/mayaUsd/fileio/testSaveUpAxisAndUnits.py diff --git a/lib/mayaUsd/nodes/layerManager.cpp b/lib/mayaUsd/nodes/layerManager.cpp index a3f34b5e66..63b239a0b2 100644 --- a/lib/mayaUsd/nodes/layerManager.cpp +++ b/lib/mayaUsd/nodes/layerManager.cpp @@ -718,6 +718,8 @@ MStatus addLayerToBuilder( auto fileFormatIdToken = layer->GetFileFormat()->GetFormatId(); fileFormatIdHandle.setString(UsdMayaUtil::convert(fileFormatIdToken.GetString())); + MayaUsd::utils::setLayerUpAxisAndUnits(layer); + std::string temp; if (!stubOnly && ((exportOnlyIfDirty && layer->IsDirty()) || !exportOnlyIfDirty)) { if (!layer->ExportToString(&temp)) { diff --git a/lib/mayaUsd/utils/utilSerialization.cpp b/lib/mayaUsd/utils/utilSerialization.cpp index db03aebdcf..98f3300f6d 100644 --- a/lib/mayaUsd/utils/utilSerialization.cpp +++ b/lib/mayaUsd/utils/utilSerialization.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -359,6 +360,24 @@ static bool isCompatibleWithSave( } } +void setLayerUpAxisAndUnits(const SdfLayerRefPtr& layer) +{ + if (!layer) + return; + + const PXR_NS::TfToken upAxis + = MGlobal::isZAxisUp() ? PXR_NS::UsdGeomTokens->z : PXR_NS::UsdGeomTokens->y; + const double metersPerUnit + = UsdMayaUtil::ConvertMDistanceUnitToUsdGeomLinearUnit(MDistance::internalUnit()); + + // Note: code similar to what UsdGeomSetStageUpAxis -> UsdStage::SetMetadata end-up doing, + // but without having to have a stage. We basically set metadat on the virtual root object + // of the layer. + layer->SetField( + PXR_NS::SdfPath::AbsoluteRootPath(), PXR_NS::UsdGeomTokens->metersPerUnit, metersPerUnit); + layer->SetField(PXR_NS::SdfPath::AbsoluteRootPath(), PXR_NS::UsdGeomTokens->upAxis, upAxis); +} + bool saveLayerWithFormat( SdfLayerRefPtr layer, const std::string& requestedFilePath, @@ -371,6 +390,7 @@ bool saveLayerWithFormat( = requestedFormatArg.empty() ? usdFormatArgOption() : requestedFormatArg; UsdMayaUtilFileSystem::updatePostponedRelativePaths(layer, filePath); + setLayerUpAxisAndUnits(layer); if (isCompatibleWithSave(layer, filePath, formatArg)) { if (!layer->Save()) { diff --git a/lib/mayaUsd/utils/utilSerialization.h b/lib/mayaUsd/utils/utilSerialization.h index 3120114de6..6fbc2f0f05 100644 --- a/lib/mayaUsd/utils/utilSerialization.h +++ b/lib/mayaUsd/utils/utilSerialization.h @@ -173,6 +173,11 @@ PXR_NS::SdfLayerRefPtr saveAnonymousLayer( std::string formatArg = "", std::string* errorMsg = nullptr); +/*! \brief Set the up-axis and units metadata according to the current Maya preferences. + */ +MAYAUSD_CORE_PUBLIC +void setLayerUpAxisAndUnits(const SdfLayerRefPtr& layer); + /*! \brief Update the list of sub-layers with a new layer identity. * The new sub-layer is identified by its path explicitly, * because a given layer might get referenced through multiple diff --git a/test/lib/mayaUsd/fileio/CMakeLists.txt b/test/lib/mayaUsd/fileio/CMakeLists.txt index 713c233dd2..53a51b2cb3 100644 --- a/test/lib/mayaUsd/fileio/CMakeLists.txt +++ b/test/lib/mayaUsd/fileio/CMakeLists.txt @@ -11,6 +11,7 @@ set(TEST_SCRIPT_FILES testDisplayLayerSaveRestore.py testSaveMutedAnonLayer.py testSaveLockedAnonLayer.py + testSaveUpAxisAndUnits.py testNonLocalEditTargetLayer.py # Once of the tests in this file requires UsdMaya (from the Pixar plugin). That test diff --git a/test/lib/mayaUsd/fileio/testSaveUpAxisAndUnits.py b/test/lib/mayaUsd/fileio/testSaveUpAxisAndUnits.py new file mode 100644 index 0000000000..9d5b89b438 --- /dev/null +++ b/test/lib/mayaUsd/fileio/testSaveUpAxisAndUnits.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python + +# +# Copyright 2024 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 + +from maya import cmds +from maya import standalone + +import mayaUtils +import mayaUsd +import mayaUsd_createStageWithNewLayer + +import usdUtils + +from pxr import Usd, Sdf, UsdGeom + +import unittest + +class SaveUpAxisAndUnitsTest(unittest.TestCase): + ''' + Test saving a layer and reloading the scene to verify they have up-axis and units metadata. + ''' + + pluginsLoaded = False + + @classmethod + def setUpClass(cls): + fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False) + + if not cls.pluginsLoaded: + cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() + + @classmethod + def tearDownClass(cls): + standalone.uninitialize() + + def setUp(self): + cmds.file(new=True, force=True) + + def testSaveLayerInUSD(self): + # Save the file. Make sure the USD edits will go to a USD file. + self._runTestSaveLayer(1) + + def testSaveLayerInMaya(self): + # Save the file. Make sure the USD edits will go to the Maya file. + self._runTestSaveLayer(2) + + def _runTestSaveLayer(self, saveLocation): + ''' + The goal is to create an anonymous sub-layer, verify the up-axis and units + metadata is present when reloaded. + ''' + # Create a stage with a sub-layer. + psPathStr = mayaUsd_createStageWithNewLayer.createStageWithNewLayer() + stage = mayaUsd.lib.GetPrim(psPathStr).GetStage() + rootLayer = stage.GetRootLayer() + subLayer = usdUtils.addNewLayerToLayer(rootLayer, anonymous=True) + + # Add a prim in the root and in the sub-layer. + with Usd.EditContext(stage, Usd.EditTarget(rootLayer)): + stage.DefinePrim('/InRoot', 'Sphere') + + with Usd.EditContext(stage, Usd.EditTarget(subLayer)): + stage.DefinePrim('/InSub', 'Cube') + + def verifyPrims(stage): + inRoot = stage.GetPrimAtPath("/InRoot") + self.assertTrue(inRoot) + inRoot = None + inSub = stage.GetPrimAtPath("/InSub") + self.assertTrue(inSub) + inSub = None + + verifyPrims(stage) + + # Save the file. Make sure the edit will go where requested by saveLocation. + tempMayaFile = 'saveLockedAnonLayer.ma' + cmds.optionVar(intValue=('mayaUsd_SerializedUsdEditsLocation', saveLocation)) + cmds.file(rename=tempMayaFile) + cmds.file(save=True, force=True, type='mayaAscii') + + # Clear and reopen the file. + stage = None + rootLayer = None + subLayer = None + cmds.file(new=True, force=True) + cmds.file(tempMayaFile, open=True) + + # Retrieve the stage, root and sub-layer. + stage = mayaUsd.lib.GetPrim(psPathStr).GetStage() + rootLayer = stage.GetRootLayer() + self.assertEqual(len(rootLayer.subLayerPaths), 1) + subLayerPath = rootLayer.subLayerPaths[0] + self.assertIsNotNone(subLayerPath) + self.assertTrue(subLayerPath) + subLayer = Sdf.Layer.FindOrOpen(subLayerPath) + self.assertIsNotNone(subLayer) + + self.assertTrue(stage.HasAuthoredMetadata(UsdGeom.Tokens.metersPerUnit)) + self.assertEqual(UsdGeom.GetStageMetersPerUnit(stage), 0.01) + self.assertTrue(stage.HasAuthoredMetadata(UsdGeom.Tokens.upAxis)) + self.assertEqual(UsdGeom.GetStageUpAxis(stage), UsdGeom.Tokens.y) + + # Verify the two objects are still present. + verifyPrims(stage) + +if __name__ == '__main__': + unittest.main(verbosity=2)