diff --git a/lib/mayaUsd/commands/Readme.md b/lib/mayaUsd/commands/Readme.md index 7b641306fb..3a76a9c609 100644 --- a/lib/mayaUsd/commands/Readme.md +++ b/lib/mayaUsd/commands/Readme.md @@ -204,6 +204,7 @@ their own purposes, similar to the Alembic export chaser example. | `-verbose` | `-v` | noarg | false | Make the command output more verbose | | `-customLayerData` | `-cld` | string[3](multi) | none | Set the layers customLayerData metadata. Values are a list of three strings for key, value and data type | | `-metersPerUnit` | `-mpu` | double | 0.0 | (Evolving) Exports with the given metersPerUnit. Use with care, as only certain attributes have their dimensions converted.

The default value of 0 will continue to use the Maya internal units (cm) and a value of -1 will use the display units. Any other positive value will be taken as an explicit metersPerUnit value to be used.

Currently, the following prim types are supported:
| +| `-upAxis` | `-upa` | string | mayaPrefs | How the up-axis of the exported USD is controlled. "mayaPrefs" follows the current Maya Preferences. "none" does not author up-axis. "y" or "z" author that axis and convert data if teh Maya preferences does not match. | #### Frame Samples diff --git a/lib/mayaUsd/commands/baseExportCommand.cpp b/lib/mayaUsd/commands/baseExportCommand.cpp index ab4b43abec..561a392518 100644 --- a/lib/mayaUsd/commands/baseExportCommand.cpp +++ b/lib/mayaUsd/commands/baseExportCommand.cpp @@ -181,6 +181,7 @@ MSyntax MayaUSDExportCommand::createSyntax() syntax.addFlag(kRootPrimFlag, UsdMayaJobExportArgsTokens->rootPrim.GetText(), MSyntax::kString); syntax.addFlag( kRootPrimTypeFlag, UsdMayaJobExportArgsTokens->rootPrimType.GetText(), MSyntax::kString); + syntax.addFlag(kUpAxisFlag, UsdMayaJobExportArgsTokens->upAxis.GetText(), MSyntax::kString); syntax.addFlag( kRenderableOnlyFlag, UsdMayaJobExportArgsTokens->renderableOnly.GetText(), MSyntax::kNoArg); syntax.addFlag( diff --git a/lib/mayaUsd/commands/baseExportCommand.h b/lib/mayaUsd/commands/baseExportCommand.h index e4596b37e0..96b4a939b7 100644 --- a/lib/mayaUsd/commands/baseExportCommand.h +++ b/lib/mayaUsd/commands/baseExportCommand.h @@ -79,6 +79,7 @@ class MAYAUSD_CORE_PUBLIC MayaUSDExportCommand : public MPxCommand static constexpr auto kParentScopeFlag = "psc"; // deprecated static constexpr auto kRootPrimFlag = "rpm"; static constexpr auto kRootPrimTypeFlag = "rpt"; + static constexpr auto kUpAxisFlag = "upa"; static constexpr auto kRenderableOnlyFlag = "ro"; static constexpr auto kDefaultCamerasFlag = "dc"; static constexpr auto kRenderLayerModeFlag = "rlm"; diff --git a/lib/mayaUsd/fileio/jobs/jobArgs.cpp b/lib/mayaUsd/fileio/jobs/jobArgs.cpp index 2c46ffd6a5..4c5e7a8258 100644 --- a/lib/mayaUsd/fileio/jobs/jobArgs.cpp +++ b/lib/mayaUsd/fileio/jobs/jobArgs.cpp @@ -747,6 +747,13 @@ UsdMayaJobExportArgs::UsdMayaJobExportArgs( UsdMayaJobExportArgsTokens->rootPrimType, UsdMayaJobExportArgsTokens->scope, { UsdMayaJobExportArgsTokens->xform })) + , upAxis(extractToken( + userArgs, + UsdMayaJobExportArgsTokens->upAxis, + UsdMayaJobExportArgsTokens->mayaPrefs, + { UsdMayaJobExportArgsTokens->none, + UsdMayaJobExportArgsTokens->y, + UsdMayaJobExportArgsTokens->z })) , renderLayerMode(extractToken( userArgs, UsdMayaJobExportArgsTokens->renderLayerMode, @@ -1137,6 +1144,7 @@ const VtDictionary& UsdMayaJobExportArgs::GetDefaultDictionary() d[UsdMayaJobExportArgsTokens->parentScope] = std::string(); // Deprecated d[UsdMayaJobExportArgsTokens->rootPrim] = std::string(); d[UsdMayaJobExportArgsTokens->rootPrimType] = UsdMayaJobExportArgsTokens->scope.GetString(); + d[UsdMayaJobExportArgsTokens->upAxis] = UsdMayaJobExportArgsTokens->mayaPrefs.GetString(); d[UsdMayaJobExportArgsTokens->pythonPerFrameCallback] = std::string(); d[UsdMayaJobExportArgsTokens->pythonPostCallback] = std::string(); d[UsdMayaJobExportArgsTokens->renderableOnly] = false; @@ -1241,6 +1249,7 @@ const VtDictionary& UsdMayaJobExportArgs::GetGuideDictionary() d[UsdMayaJobExportArgsTokens->parentScope] = _string; // Deprecated d[UsdMayaJobExportArgsTokens->rootPrim] = _string; d[UsdMayaJobExportArgsTokens->rootPrimType] = _string; + d[UsdMayaJobExportArgsTokens->upAxis] = _string; d[UsdMayaJobExportArgsTokens->pythonPerFrameCallback] = _string; d[UsdMayaJobExportArgsTokens->pythonPostCallback] = _string; d[UsdMayaJobExportArgsTokens->renderableOnly] = _boolean; diff --git a/lib/mayaUsd/fileio/jobs/jobArgs.h b/lib/mayaUsd/fileio/jobs/jobArgs.h index d7c8b24c18..49ec967e24 100644 --- a/lib/mayaUsd/fileio/jobs/jobArgs.h +++ b/lib/mayaUsd/fileio/jobs/jobArgs.h @@ -106,6 +106,7 @@ TF_DECLARE_PUBLIC_TOKENS( (parentScope) \ (rootPrim) \ (rootPrimType) \ + (upAxis) \ (pythonPerFrameCallback) \ (pythonPostCallback) \ (renderableOnly) \ @@ -125,6 +126,10 @@ TF_DECLARE_PUBLIC_TOKENS( (excludeExportTypes) \ /* Special "none" token */ \ (none) \ + /* up axis values */ \ + (mayaPrefs) \ + (y) \ + (z) \ /* relative textures values */ \ (automatic) \ (absolute) \ @@ -258,6 +263,7 @@ struct UsdMayaJobExportArgs const SdfPath parentScope; // Deprecated, use rootPrim instead. const SdfPath rootPrim; const TfToken rootPrimType; + const TfToken upAxis; const TfToken renderLayerMode; const TfToken rootKind; const bool disableModelKindProcessor; diff --git a/lib/mayaUsd/fileio/jobs/writeJob.cpp b/lib/mayaUsd/fileio/jobs/writeJob.cpp index fadd827df0..cc87210450 100644 --- a/lib/mayaUsd/fileio/jobs/writeJob.cpp +++ b/lib/mayaUsd/fileio/jobs/writeJob.cpp @@ -112,6 +112,112 @@ static TfToken _GetFallbackExtension(const TfToken& compatibilityMode) return UsdMayaTranslatorTokens->UsdFileExtensionDefault; } +class AutoUpAxisChanger +{ +public: + AutoUpAxisChanger(const UsdStageRefPtr& stage, const TfToken& upAxisOption) + : _stage(stage) + { + _PrepareUpAxis(upAxisOption); + } + + ~AutoUpAxisChanger() + { + try { + _CleanupUpAxis(); + } catch (const std::exception&) { + } + } + + void cleanup() { _CleanupUpAxis(); } + +private: + void _PrepareUpAxis(const TfToken& upAxisOption) + { + // If the user don't want to author the up-axis, we won't need to change the Maya up-axis. + const bool wantAuthorUpAxis = (upAxisOption != UsdMayaJobExportArgsTokens->none); + if (!wantAuthorUpAxis) + return; + + // If the user want the up-axis authored in USD, well, author it. + const bool wantMayaPrefs = (upAxisOption == UsdMayaJobExportArgsTokens->mayaPrefs); + const bool isMayaUpAxisZ = MGlobal::isZAxisUp(); + _wantUpAxisZ + = (wantMayaPrefs && isMayaUpAxisZ) || (upAxisOption == UsdMayaJobExportArgsTokens->z); + UsdGeomSetStageUpAxis(_stage, _wantUpAxisZ ? UsdGeomTokens->z : UsdGeomTokens->y); + + // If the Maya up-axis is already the right one, we dont have to modify the Maya scene. + if (_wantUpAxisZ == isMayaUpAxisZ) + return; + + // We need to modify the up-axis. Record if it was successful or not. + _modifiedUpAxis = _RotateUpAxis(_wantUpAxisZ); + } + + void _CleanupUpAxis() + { + // If we did not modify the Maya up-axis, we don't have to restore it. + if (!_modifiedUpAxis) + return; + + // Make sure cleanup will not be done twice even if there are exceptions. + _modifiedUpAxis = false; + + // We are restoring the Maya scene up-axis, so we pass the opposite of the up-axis flag. + _RotateUpAxis(!_wantUpAxisZ); + } + + static bool _RotateUpAxis(const bool wantUpAxisZ) + { + static const char* fullScriptFormat + = "proc rotateAllNodes(int $angle) {\n" + // Preserve the selection. Grouping and ungrouping changes it. + " string $selection[] = `ls -selection`;\n" + // Find all root nodes. + " string $rootNodeNames[] = `ls -assemblies`;\n" + // Group all root node under a new group: + // + // - Use -absolute to keep the grouped node world positions + // - Use -world to create the group under the root ofthe scene + // if the import was done at the root of the scene + // - Capture the new group name in a MEL variable called $groupName + " string $groupName = `group -absolute -world $rootNodeNames`;\n" + // Rotate the group to align with the desired axis. + // + // - Use relative rotation since we want to rotate the group as it is already + // positioned + // - Use -euler to make teh angle be relative to the current angle + // - Use forceOrderXYZ to force the rotation to be relative to world + // - Use -pivot to make sure we are rotating relative to the origin + // (The group is positioned at the center of all sub-object, so we need to + // specify the pivot) + " rotate -relative -euler -pivot 0 0 0 -forceOrderXYZ $angle 0 0 $groupName;\n" + // Ungroup while preserving the rotation. + " ungroup -absolute $groupName;\n" + // Restore the selection. + " select -replace $selection;\n" + "}\n" + "rotateAllNodes(%d);\n"; + + const int angleYtoZ = 90; + const int angleZtoY = -90; + const std::string fullScript + = TfStringPrintf(fullScriptFormat, wantUpAxisZ ? angleYtoZ : angleZtoY); + + if (!MGlobal::executeCommand(fullScript.c_str())) { + MGlobal::displayWarning( + "Failed to temporarily rotate the scene to export with modified up-axis."); + return false; + } + + return true; + } + + UsdStageRefPtr _stage; + bool _wantUpAxisZ { false }; + bool _modifiedUpAxis { false }; +}; + bool UsdMaya_WriteJob::Write(const std::string& fileName, bool append) { const std::vector& timeSamples = mJobCtx.mArgs.timeSamples; @@ -159,7 +265,9 @@ bool UsdMaya_WriteJob::Write(const std::string& fileName, bool append) if (!_FinishWriting()) { return false; } + progressBar.advance(); + return true; } @@ -328,6 +436,9 @@ bool UsdMaya_WriteJob::_BeginWriting(const std::string& fileName, bool append) mJobCtx.mStage->SetFramesPerSecond(UsdMayaUtil::GetSceneMTimeUnitAsDouble()); } + // Temporarily change Maya's up-axis if needed. + _autoAxisChanger = std::make_unique(mJobCtx.mStage, mJobCtx.mArgs.upAxis); + // Set the customLayerData on the layer if (!mJobCtx.mArgs.customLayerData.empty()) { mJobCtx.mStage->GetRootLayer()->SetCustomLayerData(mJobCtx.mArgs.customLayerData); @@ -593,16 +704,6 @@ bool UsdMaya_WriteJob::_FinishWriting() } progressBar.advance(); - // Unfortunately, MGlobal::isZAxisUp() is merely session state that does - // not get recorded in Maya files, so we cannot rely on it being set - // properly. Since "Y" is the more common upAxis, we'll just use - // isZAxisUp as an override to whatever our pipeline is configured for. - TfToken upAxis = UsdGeomGetFallbackUpAxis(); - if (MGlobal::isZAxisUp()) { - upAxis = UsdGeomTokens->z; - } - UsdGeomSetStageUpAxis(mJobCtx.mStage, upAxis); - // XXX Currently all distance values are written directly to USD, and will // be in centimeters (Maya's internal unit) despite what the users UIUnit // preference is. @@ -659,6 +760,9 @@ bool UsdMaya_WriteJob::_FinishWriting() _PruneEmpties(); progressBar.advance(); + // Restore Maya's up-axis if needed. + _autoAxisChanger.reset(); + TF_STATUS("Saving stage"); if (mJobCtx.mStage->GetRootLayer()->PermissionToSave()) { mJobCtx.mStage->GetRootLayer()->Save(); diff --git a/lib/mayaUsd/fileio/jobs/writeJob.h b/lib/mayaUsd/fileio/jobs/writeJob.h index 3449d52c84..dacdebae7a 100644 --- a/lib/mayaUsd/fileio/jobs/writeJob.h +++ b/lib/mayaUsd/fileio/jobs/writeJob.h @@ -31,6 +31,7 @@ PXR_NAMESPACE_OPEN_SCOPE class UsdMaya_ModelKindProcessor; +class AutoUpAxisChanger; class UsdMaya_WriteJob { @@ -110,6 +111,7 @@ class UsdMaya_WriteJob UsdMayaWriteJobContext mJobCtx; std::unique_ptr _modelKindProcessor; + std::unique_ptr _autoAxisChanger; }; PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaUsd/python/wrapPrimWriter.cpp b/lib/mayaUsd/python/wrapPrimWriter.cpp index 9a2e21f70e..1fb2edfc6f 100644 --- a/lib/mayaUsd/python/wrapPrimWriter.cpp +++ b/lib/mayaUsd/python/wrapPrimWriter.cpp @@ -564,6 +564,7 @@ void wrapJobExportArgs() .def_readonly("file", &UsdMayaJobExportArgs::file) .def_readonly("rootPrim", &UsdMayaJobExportArgs::rootPrim) .def_readonly("rootPrimType", &UsdMayaJobExportArgs::rootPrimType) + .def_readonly("upAxis", &UsdMayaJobExportArgs::upAxis) .add_property( "filteredTypeIds", make_getter( @@ -607,6 +608,9 @@ void wrapJobExportArgs() "rootPrimType", make_getter( &UsdMayaJobExportArgs::rootPrimType, return_value_policy())) + .add_property( + "upAxis", + make_getter(&UsdMayaJobExportArgs::upAxis, return_value_policy())) .def_readonly("pythonPerFrameCallback", &UsdMayaJobExportArgs::pythonPerFrameCallback) .def_readonly("pythonPostCallback", &UsdMayaJobExportArgs::pythonPostCallback) .add_property( diff --git a/lib/mayaUsd/resources/scripts/mayaUsdCacheMayaReference.py b/lib/mayaUsd/resources/scripts/mayaUsdCacheMayaReference.py index 36074de28d..b762662704 100644 --- a/lib/mayaUsd/resources/scripts/mayaUsdCacheMayaReference.py +++ b/lib/mayaUsd/resources/scripts/mayaUsdCacheMayaReference.py @@ -212,7 +212,7 @@ def fileOptionsTabPage(tabLayout): fileOptionsScroll = cmds.columnLayout('fileOptionsScroll') optionsText = mayaUsdOptions.convertOptionsDictToText(cacheToUsd.loadCacheCreationOptions()) optionsText = mayaUsdOptions.setAnimateOption(_mayaRefDagPath, optionsText) - mel.eval('mayaUsdTranslatorExport("fileOptionsScroll", "post={exportOpts}", "{cacheOpts}", "")'.format(exportOpts=kTranslatorExportOptions, cacheOpts=optionsText)) + mel.eval('mayaUsdTranslatorExport("fileOptionsScroll", "post={exportOpts};cacheToUSD", "{cacheOpts}", "")'.format(exportOpts=kTranslatorExportOptions, cacheOpts=optionsText)) cacheFileUsdHierarchyOptions(topForm) diff --git a/plugin/adsk/scripts/mayaUSDRegisterStrings.mel b/plugin/adsk/scripts/mayaUSDRegisterStrings.mel index ebceebd96b..791d7fdfe9 100644 --- a/plugin/adsk/scripts/mayaUSDRegisterStrings.mel +++ b/plugin/adsk/scripts/mayaUSDRegisterStrings.mel @@ -232,6 +232,17 @@ global proc mayaUSDRegisterStrings() register("kExportDefaultPrimNoneLbl", "None"); register("kExportDefaultPrimAnn", "As part of its metadata, each USD stage can identify a default prim.\nThis is the primitive that is referenced in if you reference in a file."); + register("kExportAxisAndUnitLbl", "Axis & Unit Conversion"); + register("kExportUpAxisLbl", "Up Axis"); + register("kExportUpAxisAnn", "Select the up axis for the export file." + + "Rotation will be applied if converting to a different axis." + + "None: do not author upAxis." + + "Use Maya Preferences: use the axis of the current scene."); + register("kExportUpAxisNoneLbl", "None"); + register("kExportUpAxisMayaPrefsLbl", "Use Maya Preferences"); + register("kExportUpAxisYLbl", "Y"); + register("kExportUpAxisZLbl", "Z"); + // All strings for import dialog: register("kImportAnimationDataLbl", "Animation Data"); register("kImportCustomFrameRangeLbl", "Custom Frame Range"); diff --git a/plugin/adsk/scripts/mayaUsdTranslatorExport.mel b/plugin/adsk/scripts/mayaUsdTranslatorExport.mel index bdef240e94..e5797886d4 100644 --- a/plugin/adsk/scripts/mayaUsdTranslatorExport.mel +++ b/plugin/adsk/scripts/mayaUsdTranslatorExport.mel @@ -825,6 +825,7 @@ global proc mayaUsdTranslatorExport_EnableAllControls() { checkBoxGrp -e -en 1 includeEmptyTransformsCheckBox; checkBoxGrp -e -en 1 includeNamespacesCheckBox; checkBoxGrp -e -en 1 worldspaceCheckBox; + optionMenuGrp -e -en 1 upAxisPopup; } if (stringArrayContains("context", $sectionNames)) { @@ -943,6 +944,8 @@ global proc mayaUsdTranslatorExport_SetFromOptions(string $currentOptions, int $ mayaUsdTranslatorExport_SetCheckbox($optionBreakDown[1], $enable, "exportDisplayColorCheckBox"); } else if ($optionBreakDown[0] == "exportInstances") { mayaUsdTranslatorExport_SetOptionMenuByBool($optionBreakDown[1], $enable, "exportInstancesPopup"); + } else if ($optionBreakDown[0] == "upAxis") { + mayaUsdTranslatorExport_SetOptionMenuByAnnotation($optionBreakDown[1], $enable, "upAxisPopup"); } else if ($optionBreakDown[0] == "exportVisibility") { mayaUsdTranslatorExport_SetCheckbox($optionBreakDown[1], $enable, "exportVisibilityCheckBox"); } else if ($optionBreakDown[0] == "mergeTransformAndShape") { @@ -1002,7 +1005,7 @@ proc string[] parseActionSectionNames(string $sections, string $out_expandedSect { global string $gMayaUsdTranslatorExport_SectionNames[]; - string $allSections[] = { "context", "output", "output-RootPrim", "geometry", "materials", "animation", "animation-data", "advanced" }; + string $allSections[] = { "context", "output", "output-RootPrim", "geometry", "materials", "animation", "animation-data", "advanced", "axisAndUnit" }; string $sectionList[]; tokenize($sections, ";", $sectionList); @@ -1066,6 +1069,9 @@ global proc int mayaUsdTranslatorExport (string $parent, // that section). Example: // "action=section1:expanded;section2:collapsed" // +// Special types of export can also be specified. The known special exports are: +// cacheToUSD, duplicate and mergeToUSD. +// // The section name can also be "all", "none" or prefixed with "!": // - When "all" is used, all sections are included. // - When "none" is used, all sections are removed. @@ -1126,13 +1132,20 @@ global proc int mayaUsdTranslatorExport (string $parent, // Adjust options related to which operation is being done: // export, duplicate-to-USD or merge=to-USD. int $canExportStagesAsRefs = 1; + int $canControlUpAxis = 1; if (stringArrayContains("duplicate", $sectionNames)) { $canExportStagesAsRefs = 0; + $canControlUpAxis = 0; } if (stringArrayContains("mergeToUSD", $sectionNames)) { $canExportStagesAsRefs = 0; + $canControlUpAxis = 0; + } + + if (stringArrayContains("cacheToUSD", $sectionNames)) { + $canControlUpAxis = 0; } setParent $parent; @@ -1318,6 +1331,19 @@ global proc int mayaUsdTranslatorExport (string $parent, -value1 1 exportStagesAsRefsCheckBox; } + if ($canControlUpAxis) { + int $collapse = stringArrayContains("axisAndUnit", $expandedSections) ? false : true; + frameLayout -label `getMayaUsdString("kExportAxisAndUnitLbl")` -collapsable true -collapse $collapse axisAndUnitFrameLayout; + separator -style "none"; + optionMenuGrp -l `getMayaUsdString("kExportUpAxisLbl")` -annotation `getMayaUsdString("kExportUpAxisAnn")` upAxisPopup; + menuItem -l `getMayaUsdString("kExportUpAxisNoneLbl")` -ann "none"; + menuItem -l `getMayaUsdString("kExportUpAxisMayaPrefsLbl")` -ann "mayaPrefs"; + menuItem -l `getMayaUsdString("kExportUpAxisYLbl")` -ann "y"; + menuItem -l `getMayaUsdString("kExportUpAxisZLbl")` -ann "z"; + + setParent ..; + } + separator -style "none"; setParent ..; } @@ -1382,6 +1408,7 @@ global proc int mayaUsdTranslatorExport (string $parent, $currentOptions = mayaUsdTranslatorExport_AppendOppositeFromCheckbox($currentOptions, "stripNamespaces", "includeNamespacesCheckBox"); $currentOptions = mayaUsdTranslatorExport_AppendFromCheckbox($currentOptions, "worldspace", "worldspaceCheckBox"); $currentOptions = mayaUsdTranslatorExport_AppendFromCheckbox($currentOptions, "exportStagesAsRefs", "exportStagesAsRefsCheckBox"); + $currentOptions = mayaUsdTranslatorExport_AppendFromPopup($currentOptions, "upAxis", "upAxisPopup"); } if (stringArrayContains("context", $sectionNames)) { diff --git a/test/lib/usd/translators/CMakeLists.txt b/test/lib/usd/translators/CMakeLists.txt index ed61c5556c..ab44490946 100644 --- a/test/lib/usd/translators/CMakeLists.txt +++ b/test/lib/usd/translators/CMakeLists.txt @@ -26,6 +26,7 @@ set(TEST_SCRIPT_FILES testUsdExportUsdPreviewSurface.py testUsdExportRootPrim.py testUsdExportTypes.py + testUsdExportUpAxis.py # To investigate: following test asserts in MFnParticleSystem, but passes. # PPT, 17-Jun-20. diff --git a/test/lib/usd/translators/testUsdExportUpAxis.py b/test/lib/usd/translators/testUsdExportUpAxis.py new file mode 100644 index 0000000000..a36f42d5b8 --- /dev/null +++ b/test/lib/usd/translators/testUsdExportUpAxis.py @@ -0,0 +1,124 @@ +#!/usr/bin/env mayapy +# +# 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 maya.api.OpenMaya as om +import os +import unittest + +from maya import cmds +from maya import standalone + +from pxr import Gf, Usd, UsdGeom + +import fixturesUtils + +class testUsdExportUpAxis(unittest.TestCase): + """Test for modifying the up-axis when exporting.""" + + @classmethod + def setUpClass(cls): + cls._path = fixturesUtils.setUpClass(__file__) + + @classmethod + def tearDownClass(cls): + standalone.uninitialize() + + def setUp(self): + """Clear the scene""" + cmds.file(f=True, new=True) + cmds.upAxis(axis='z') + + def assertPrimXform(self, prim, xforms): + ''' + Verify that the prim has the given xform in the roder given. + xforms should be a list of pairs, each containing the xform op name and its value. + ''' + EPSILON = 1e-3 + xformOpOrder = prim.GetAttribute('xformOpOrder').Get() + self.assertEqual(len(xformOpOrder), len(xforms)) + for name, value in xforms: + self.assertEqual(xformOpOrder[0], name) + attr = prim.GetAttribute(name) + self.assertIsNotNone(attr) + self.assertTrue(Gf.IsClose(attr.Get(), value, EPSILON)) + # Chop off the first xofrm op for the next loop. + xformOpOrder = xformOpOrder[1:] + + def testExportUpAxisNone(self): + """Test importing and adding a group to hold the rotation.""" + cmds.polySphere() + cmds.move(3, 0, 0, relative=True) + + usdFile = os.path.abspath('UsdExportUpAxis_None.usda') + cmds.mayaUSDExport(file=usdFile, + shadingMode='none', + upAxis='none') + + stage = Usd.Stage.Open(usdFile) + self.assertFalse(stage.HasAuthoredMetadata('upAxis')) + + spherePrim = UsdGeom.Mesh.Get(stage, '/pSphere1') + self.assertTrue(spherePrim) + + def testExportUpAxisFollowMayaPrefs(self): + """Test exporting and following the Maya up-axis preference.""" + cmds.polySphere() + cmds.move(0, 0, 3, relative=True) + + usdFile = os.path.abspath('UsdExportUpAxis_FollowMayaPrefs.usda') + cmds.mayaUSDExport(file=usdFile, + shadingMode='none', + upAxis='mayaPrefs') + + stage = Usd.Stage.Open(usdFile) + self.assertTrue(stage.HasAuthoredMetadata('upAxis')) + expectedAxis = 'Z' + actualAxis = UsdGeom.GetStageUpAxis(stage) + self.assertEqual(actualAxis, expectedAxis) + + spherePrim = stage.GetPrimAtPath('/pSphere1') + self.assertTrue(spherePrim) + + self.assertPrimXform(spherePrim, [ + ('xformOp:translate', (0., 0., 3.))]) + + def testExportUpAxisDifferentY(self): + """Test exporting and forcing a up-axis Y different from Maya prefs.""" + cmds.polySphere() + cmds.move(0, 0, 3, relative=True) + + usdFile = os.path.abspath('UsdExportUpAxis_DifferentY.usda') + cmds.mayaUSDExport(file=usdFile, + shadingMode='none', + upAxis='y') + + stage = Usd.Stage.Open(usdFile) + self.assertTrue(stage.HasAuthoredMetadata('upAxis')) + expectedAxis = 'Y' + actualAxis = UsdGeom.GetStageUpAxis(stage) + self.assertEqual(actualAxis, expectedAxis) + + spherePrim = stage.GetPrimAtPath('/pSphere1') + self.assertTrue(spherePrim) + + self.assertPrimXform(spherePrim, [ + ('xformOp:translate', (0., 3., 0.)), + ('xformOp:rotateXYZ', (-90., 0., 0.))]) + + +if __name__ == '__main__': + unittest.main(verbosity=2)