diff --git a/lib/mayaUsd/resources/scripts/CMakeLists.txt b/lib/mayaUsd/resources/scripts/CMakeLists.txt index fcc0beb654..89d0905156 100644 --- a/lib/mayaUsd/resources/scripts/CMakeLists.txt +++ b/lib/mayaUsd/resources/scripts/CMakeLists.txt @@ -14,6 +14,7 @@ list(APPEND scripts_src mayaUsdMergeToUSDOptions.py mayaUsdMergeToUsd.py mayaUsdOptions.py + mayaUsdStageConversion.py mayaUsdUtils.py mayaUsdMayaReferenceUtils.py ) diff --git a/lib/mayaUsd/resources/scripts/mayaUsdLibRegisterStrings.py b/lib/mayaUsd/resources/scripts/mayaUsdLibRegisterStrings.py index 3e26e55ebb..bcc6f71a8b 100644 --- a/lib/mayaUsd/resources/scripts/mayaUsdLibRegisterStrings.py +++ b/lib/mayaUsd/resources/scripts/mayaUsdLibRegisterStrings.py @@ -129,6 +129,11 @@ def mayaUsdLibRegisterStrings(): register('kMenuCacheToUsd', 'Cache to USD...') register('kMenuMergeMayaEdits', 'Merge Maya Edits to USD'); + # mayaUsdStageConversion.py + register("kStageConversionUnknownMethod", "Unknown stage conversion method: %s") + register("kStageConversionSuccessful", "Mismatching axis/unit have been converted for accurate scale.") + + def registerPluginResource(pluginId, stringId, resourceStr): '''See registerPluginResource.mel in Maya. diff --git a/lib/mayaUsd/resources/scripts/mayaUsdStageConversion.py b/lib/mayaUsd/resources/scripts/mayaUsdStageConversion.py new file mode 100644 index 0000000000..35e41baa94 --- /dev/null +++ b/lib/mayaUsd/resources/scripts/mayaUsdStageConversion.py @@ -0,0 +1,135 @@ +import maya.cmds as _cmds +import maya.api.OpenMaya as _om +import mayaUsd.ufe as _ufe + +from mayaUsdLibRegisterStrings import getMayaUsdLibString as _getMayaUsdLibString + +from pxr import UsdGeom as _UsdGeom + + +def convertUpAxisAndUnit(shapeNode, convertUpAxis, convertUnit, conversionMethod): + ''' + Edit the USD stage associated with the given Maya stage proxy node to + convert the up-axis or the units used to match what is in the USD file + with what Maya is using. + ''' + # If neither up-axis nor unit are requested to be modified, return immediately. + conversionInfo = StageConversionInfo(shapeNode, convertUpAxis, convertUnit) + if not conversionInfo.needUnitsConversion and not conversionInfo.needUpAxisConversion: + return + + resultMsg = _getMayaUsdLibString("kStageConversionSuccessful") + + if conversionMethod.lower() == 'rotatescale': + convertUpAxisAndUnitByModifyingStage(conversionInfo) + elif conversionMethod.lower() == 'overwriteprefs': + convertUpAxisAndUnitByModifyingPrefs(conversionInfo) + else: + resultMsg = _getMayaUsdLibString("kStageConversionUnknownMethod") % conversionMethod + + print(resultMsg) + + +class StageConversionInfo: + ''' + Analyze the contents of the USD file and compare it to the Maya settings + to determine what actions need to be done to match them. + ''' + + @staticmethod + def _isMayaUpAxisZ(): + return _cmds.upAxis(query=True, axis=True).lower() == 'z' + + @staticmethod + def _isUsdUpAxisZ(stage): + return _UsdGeom.GetStageUpAxis(stage).lower() == 'z' + + _mayaToMetersPerUnit = { + _om.MDistance.kInches : _UsdGeom.LinearUnits.inches, + _om.MDistance.kFeet : _UsdGeom.LinearUnits.feet, + _om.MDistance.kYards : _UsdGeom.LinearUnits.yards, + _om.MDistance.kMiles : _UsdGeom.LinearUnits.miles, + _om.MDistance.kMillimeters : _UsdGeom.LinearUnits.millimeters, + _om.MDistance.kCentimeters : _UsdGeom.LinearUnits.centimeters, + _om.MDistance.kKilometers : _UsdGeom.LinearUnits.kilometers, + _om.MDistance.kMeters : _UsdGeom.LinearUnits.meters, + } + + @staticmethod + def _convertMayaUnitToMetersPerUnit(mayaUnits): + if mayaUnits not in StageConversionInfo._mayaToMetersPerUnit: + return _UsdGeom.LinearUnits.centimeters + return StageConversionInfo._mayaToMetersPerUnit[mayaUnits] + + _metersPerUnitToMayaUnitName = { + _UsdGeom.LinearUnits.inches : "inch", + _UsdGeom.LinearUnits.feet : "foot", + _UsdGeom.LinearUnits.yards : "yard", + _UsdGeom.LinearUnits.miles : "mile", + _UsdGeom.LinearUnits.millimeters : "mm", + _UsdGeom.LinearUnits.centimeters : "cn", + _UsdGeom.LinearUnits.kilometers : "km", + _UsdGeom.LinearUnits.meters : "m", + } + + @staticmethod + def _convertMetersPerUnitToMayaUnitName(metersPerUnit): + if metersPerUnit not in StageConversionInfo._metersPerUnitToMayaUnitName: + return "cm" + return StageConversionInfo._metersPerUnitToMayaUnitName[metersPerUnit] + + @staticmethod + def _getMayaMetersPerUnit(): + mayaUnits = _om.MDistance.internalUnit() + return StageConversionInfo._convertMayaUnitToMetersPerUnit(mayaUnits) + + @staticmethod + def _getUsdMetersPerUnit(stage): + return _UsdGeom.GetStageMetersPerUnit(stage) + + @staticmethod + def _getStageFromShapeNode(shapeNode): + res = _cmds.ls(shapeNode, l=True) + fullStageName = res[0] + return _ufe.getStage(fullStageName) + + def __init__(self, shapeNode, convertUpAxis, convertUnit): + self.shapeNode = shapeNode + self.stage = self._getStageFromShapeNode(shapeNode) + + self.isMayaUpAxisZ = self._isMayaUpAxisZ() + self.isUsdUpAxisZ = self._isUsdUpAxisZ(self.stage) + self.needUpAxisConversion = convertUpAxis and (self.isMayaUpAxisZ != self.isUsdUpAxisZ) + + self.mayaMetersPerUnit = self._getMayaMetersPerUnit() + self.usdMetersPerUnit = self._getUsdMetersPerUnit(self.stage) + self.needUnitsConversion = convertUnit and (self.mayaMetersPerUnit != self.usdMetersPerUnit) + + +def convertUpAxisAndUnitByModifyingStage(conversionInfo): + ''' + Handle the differences of up-axis and units from the USD file by modifying + the Maya proxy shape node transform to compensate for the differences. + ''' + if conversionInfo.needUpAxisConversion: + angle = 90 if conversionInfo.isMayaUpAxisZ else -90 + _cmds.rotate(angle, 0, 0, conversionInfo.shapeNode, relative=True, euler=True, pivot=(0, 0, 0), forceOrderXYZ=True) + + if conversionInfo.needUnitsConversion: + factor = conversionInfo.usdMetersPerUnit / conversionInfo.mayaMetersPerUnit + _cmds.scale(factor, factor, factor, conversionInfo.shapeNode, relative=True, pivot=(0, 0, 0), scaleXYZ=True) + + +def convertUpAxisAndUnitByModifyingPrefs(conversionInfo): + ''' + Handle the differences of up-axis and units from the USD file by modifying + the Maya preferences to match the USD file. + ''' + if conversionInfo.needUpAxisConversion: + newAxis = 'z' if conversionInfo.isUsdUpAxisZ else 'y' + _cmds.upAxis(axis=newAxis) + + if conversionInfo.needUnitsConversion: + newUnit = conversionInfo._convertMetersPerUnitToMayaUnitName(conversionInfo.usdMetersPerUnit) + _cmds.currentUnit(linear=newUnit) + diff --git a/lib/mayaUsd/utils/util.h b/lib/mayaUsd/utils/util.h index 5e44d439eb..9a01e45eb6 100644 --- a/lib/mayaUsd/utils/util.h +++ b/lib/mayaUsd/utils/util.h @@ -152,7 +152,7 @@ double ConvertMDistanceUnitToUsdGeomLinearUnit(const MDistance::Unit mdistanceUn MAYAUSD_CORE_PUBLIC MDistance::Unit ConvertUsdGeomLinearUnitToMDistanceUnit(const double linearUnit); -/// Convert the given \p mdistanceYnit into its text representation suitable +/// Convert the given \p mdistanceUnit into its text representation suitable /// to be used with the currentUnit MEL command. Invalid units return "cm". MAYAUSD_CORE_PUBLIC MString ConvertMDistanceUnitToText(const MDistance::Unit mdistanceUnit); diff --git a/plugin/adsk/scripts/mayaUSDRegisterStrings.mel b/plugin/adsk/scripts/mayaUSDRegisterStrings.mel index 142cfc92ba..e67f86073a 100644 --- a/plugin/adsk/scripts/mayaUSDRegisterStrings.mel +++ b/plugin/adsk/scripts/mayaUSDRegisterStrings.mel @@ -46,6 +46,24 @@ global proc mayaUSDRegisterStrings() register("kLabelStage", "Stage"); register("kLabelStageSource", "Stage Source"); register("kLabelStageDisplay", "Stage Display"); + register("kStageAdvancedLabel", "Advanced"); + register("kStageAxisUnitConversionLabel", "Axis & Unit Conversion"); + register("kStageUpAxisLabel", "Up Axis"); + register("kStageUpAxisAnn", "If selected, when an up axis mismatch is detected\n" + + "between the imported data and your scene preferences,\n" + + "an automatic correction will be performed."); + register("kStageUnitLabel", "Unit"); + register("kStageUnitAnn", "If selected, when a unit mismatch is detected\n" + + "between the imported data and your scene preferences,\n" + + "an automatic correction will be performed."); + register("kStageAxisAndUnitMethod", "Method"); + // Note: initial is used to force Qt to render the text as HTML. + register("kStageAxisAndUnitMethodAnn", "Select the method for axis/unit conversions.
" + + "
" + + "Rotate/Scale: Rotate/Scale the stage.
" + + "Overwrite Maya Preferences: Update Maya's axis/unit preferences based on the imported data."); + register("kStageAxisAndUnitRotateScale", "Rotate/Scale"); + register("kStageAxisAndUnitOverwritePrefs", "Overwrite Maya Preferences"); register("kLoad", "Load"); register("kLoadPayloads", "Load Payloads:"); register("kLoadPayloadsAnn", "When on, loads all prims marked as payloads. When off, all prims marked as payloads and their children are not loaded."); diff --git a/plugin/adsk/scripts/mayaUsdTranslatorExport.mel b/plugin/adsk/scripts/mayaUsdTranslatorExport.mel index 103d548692..fe84766a5e 100644 --- a/plugin/adsk/scripts/mayaUsdTranslatorExport.mel +++ b/plugin/adsk/scripts/mayaUsdTranslatorExport.mel @@ -1333,8 +1333,7 @@ global proc int mayaUsdTranslatorExport (string $parent, } if ($canControlUpAxisAndUnit) { - int $collapse = stringArrayContains("axisAndUnit", $expandedSections) ? false : true; - frameLayout -label `getMayaUsdString("kExportAxisAndUnitLbl")` -collapsable true -collapse $collapse axisAndUnitFrameLayout; + frameLayout -label `getMayaUsdString("kExportAxisAndUnitLbl")` -collapsable true -collapse false axisAndUnitFrameLayout; separator -style "none"; optionMenuGrp -l `getMayaUsdString("kExportUpAxisLbl")` -annotation `getMayaUsdString("kExportUpAxisAnn")` upAxisPopup; diff --git a/plugin/adsk/scripts/mayaUsdTranslatorImport.mel b/plugin/adsk/scripts/mayaUsdTranslatorImport.mel index f5b4d8b23f..dd7ae2aee9 100644 --- a/plugin/adsk/scripts/mayaUsdTranslatorImport.mel +++ b/plugin/adsk/scripts/mayaUsdTranslatorImport.mel @@ -410,8 +410,10 @@ global proc mayaUsdTranslatorImport_SetFromOptions(string $currentOptions, int $ intFieldGrp -e -value2 $endTime -en2 $enable mayaUsdTranslator_CustomFrameRange; } else if ($optionBreakDown[0] == "upAxis") { mayaUsdTranslatorImport_SetCheckBoxGrp($optionBreakDown[1], $enable, "mayaUsdTranslator_ImportUpAxisCheckBox"); + mayaUsdTranslatorImport_upAxisUnitCB(); } else if ($optionBreakDown[0] == "unit") { mayaUsdTranslatorImport_SetCheckBoxGrp($optionBreakDown[1], $enable, "mayaUsdTranslator_ImportUnitCheckBox"); + mayaUsdTranslatorImport_upAxisUnitCB(); } else if ($optionBreakDown[0] == "axisAndUnitMethod") { mayaUsdTranslatorImport_SetOptionMenuByAnnotation($optionBreakDown[1], $enable, "mayaUsdTranslator_ImportkImportAxisAndUnitMethodMenu"); } else if ($optionBreakDown[0] == "useCustomFrameRange") { @@ -519,7 +521,7 @@ global proc int mayaUsdTranslatorImport (string $parent, // menuItem -label "Object space"; // menuItem -label "World space"; // optionMenuGrp -e -sl 1 mayaUsdTranslator_CoordSystemOptionMenu; - frameLayout -label `getMayaUsdString("kImportMaterialsLbl")` materialsFrameLayout; + frameLayout -label `getMayaUsdString("kImportMaterialsLbl")` -collapsable true -collapse false materialsFrameLayout; separator -style "none"; checkBoxGrp -label "" -label1 `getMayaUsdString("kImportMaterialsLbl")` -cw 1 $cw1 -value1 1 -ann `getMayaUsdString("kImportMaterialsAnn")` -cc ("mayaUsdTranslatorImport_MaterialsCB") mayaUsdTranslator_MaterialsCheckBox; @@ -565,7 +567,7 @@ global proc int mayaUsdTranslatorImport (string $parent, // menuItem "Custom"; // optionMenuGrp -e -sl 2 mayaUsdTranslator_IncludeCustomAttribOptionMenu; - frameLayout -label "Animation" animationFrameLayout; + frameLayout -label "Animation" -collapsable true -collapse false animationFrameLayout; separator -style "none"; checkBoxGrp -label "" -label1 `getMayaUsdString("kImportAnimationDataLbl")` -cw 1 $cw1 -cc ("mayaUsdTranslatorImport_AnimationCB") mayaUsdTranslator_AnimDataCheckBox; @@ -580,18 +582,26 @@ global proc int mayaUsdTranslatorImport (string $parent, separator -style "none"; setParent ..; - frameLayout -label "Advanced" advancedFrameLayout; + frameLayout -label "Advanced" -collapsable true -collapse true advancedFrameLayout; separator -style "none"; checkBoxGrp -label "" -label1 `getMayaUsdString("kImportToInstanceOpt")` -cw 1 $cw1 -value1 1 -ann `getMayaUsdString("kImportToInstanceAnn")` mayaUsdTranslator_ImportInstancesCheckBox; separator -style "none"; - frameLayout -label `getMayaUsdString("kImportAxisAndUnit")` axisAndUnitFrameLayout; - checkBoxGrp -label "" -label1 `getMayaUsdString("kImportUpAxis")` -cw 1 $cw1 -value1 1 -ann `getMayaUsdString("kImportUpAxisAnn")` mayaUsdTranslator_ImportUpAxisCheckBox; - checkBoxGrp -label "" -label1 `getMayaUsdString("kImportUnit")` -cw 1 $cw1 -value1 1 -ann `getMayaUsdString("kImportUnitAnn")` mayaUsdTranslator_ImportUnitCheckBox; + frameLayout -label `getMayaUsdString("kImportAxisAndUnit")` -collapsable true -collapse false axisAndUnitFrameLayout; + separator -style "none"; + checkBoxGrp -label "" -label1 `getMayaUsdString("kImportUpAxis")` -ann `getMayaUsdString("kImportUpAxisAnn")` + -cw 1 $cw1 -value1 1 + -cc ("mayaUsdTranslatorImport_upAxisUnitCB") + mayaUsdTranslator_ImportUpAxisCheckBox; + checkBoxGrp -label "" -label1 `getMayaUsdString("kImportUnit")` -ann `getMayaUsdString("kImportUnitAnn")` + -cw 1 $cw1 -value1 1 + -cc ("mayaUsdTranslatorImport_upAxisUnitCB") + mayaUsdTranslator_ImportUnitCheckBox; optionMenuGrp -l `getMayaUsdString("kImportAxisAndUnitMethod")` -cw 1 $cw1 -ann `getMayaUsdString("kImportAxisAndUnitMethodAnn")` mayaUsdTranslator_ImportkImportAxisAndUnitMethodMenu; menuItem -l `getMayaUsdString("kImportAxisAndUnitRotateScale")` -ann "rotateScale"; menuItem -l `getMayaUsdString("kImportAxisAndUnitAddTransform")` -ann "addTransform"; menuItem -l `getMayaUsdString("kImportAxisAndUnitOverwritePrefs")` -ann "overwritePrefs"; + separator -style "none"; setParent ..; setParent ..; @@ -677,6 +687,7 @@ global proc int mayaUsdTranslatorImport (string $parent, } mayaUsdTranslatorImport_AnimationCB(); + mayaUsdTranslatorImport_upAxisUnitCB(); return $bResult; } @@ -717,6 +728,14 @@ global proc mayaUsdTranslatorImport_AnimationCB() } } +// Call when the up-axis or unit checkbox are modified bythe user. +global proc mayaUsdTranslatorImport_upAxisUnitCB() +{ + int $upAxisEnabled = `checkBoxGrp -q -value1 mayaUsdTranslator_ImportUpAxisCheckBox`; + int $unitEnabled = `checkBoxGrp -q -value1 mayaUsdTranslator_ImportUnitCheckBox`; + optionMenuGrp -e -en ($unitEnabled + $upAxisEnabled) mayaUsdTranslator_ImportkImportAxisAndUnitMethodMenu; +} + global proc mayaUsdTranslatorImport_MaterialsCB() { if (`checkBoxGrp -q -value1 mayaUsdTranslator_MaterialsCheckBox` == 1) { diff --git a/plugin/adsk/scripts/mayaUsd_createStageFromFile.mel b/plugin/adsk/scripts/mayaUsd_createStageFromFile.mel index 23be813bc3..9d320c2fc1 100644 --- a/plugin/adsk/scripts/mayaUsd_createStageFromFile.mel +++ b/plugin/adsk/scripts/mayaUsd_createStageFromFile.mel @@ -26,6 +26,18 @@ proc setOptionVars(int $forceFactorySettings) if ($forceFactorySettings || !`optionVar -exists stageFromFile_loadAllPayloads`) { optionVar -intValue stageFromFile_loadAllPayloads 1; } + + if ($forceFactorySettings || !`optionVar -exists stageFromFile_convertUpAxis`) { + optionVar -intValue stageFromFile_convertUpAxis 1; + } + + if ($forceFactorySettings || !`optionVar -exists stageFromFile_convertUnit`) { + optionVar -intValue stageFromFile_convertUnit 1; + } + + if ($forceFactorySettings || !`optionVar -exists stageFromFile_conversionMethod`) { + optionVar -intValue stageFromFile_conversionMethod 1; + } } global proc setLatestLoadStageFolder( string $sceneFolder ) @@ -62,6 +74,14 @@ global proc string getLatestLoadStageFolder() return $sceneFolder; } +// Call when the up-axis or unit checkbox are modified bythe user. +global proc stageFromFile_upAxisUnitCB() +{ + int $upAxisEnabled = `checkBoxGrp -q -value1 upAxisCheckBox`; + int $unitEnabled = `checkBoxGrp -q -value1 unitCheckBox`; + optionMenuGrp -e -en ($unitEnabled + $upAxisEnabled) axisAndUnitMethodMenu; +} + // stageFromFile_UISetup // creates the options of the stageFromFile dialog global proc string stageFromFile_UISetup(string $parent) @@ -83,7 +103,7 @@ global proc string stageFromFile_UISetup(string $parent) -sbm `getMayaUsdString("kLoadPayloadsSbm")` loadAllPayloadsCheckBox; - frameLayout -label `getMayaUsdString("kLabelStageDisplay")` -parent $frame -collapsable false; + frameLayout -label `getMayaUsdString("kLabelStageDisplay")` -parent $frame -collapsable true -collapse false; textFieldGrp -l `getMayaUsdString("kPrimPath")` -ann `getMayaUsdString("kPrimPathAnn")` -sbm `getMayaUsdString("kPrimPathSbm")` @@ -93,6 +113,24 @@ global proc string stageFromFile_UISetup(string $parent) -sbm `getMayaUsdString("kExcludePrimPathsSbm")` excludePrimPathField; + frameLayout -label `getMayaUsdString("kStageAdvancedLabel")` -parent $frame -collapsable true -collapse true advancedSection; + frameLayout -label `getMayaUsdString("kStageAxisUnitConversionLabel")` -parent advancedSection -collapsable true -collapse false; + checkBoxGrp -l `getMayaUsdString("kStageUpAxisLabel")` + -ann `getMayaUsdString("kStageUpAxisAnn")` + -value1 1 + -cc ("stageFromFile_upAxisUnitCB") + upAxisCheckBox; + checkBoxGrp -l `getMayaUsdString("kStageUnitLabel")` + -ann `getMayaUsdString("kStageUnitAnn")` + -value1 1 + -cc ("stageFromFile_upAxisUnitCB") + unitCheckBox; + optionMenuGrp -l `getMayaUsdString("kStageAxisAndUnitMethod")` -ann `getMayaUsdString("kStageAxisAndUnitMethodAnn")` axisAndUnitMethodMenu; + menuItem -l `getMayaUsdString("kStageAxisAndUnitRotateScale")` -ann "rotateScale"; + menuItem -l `getMayaUsdString("kStageAxisAndUnitOverwritePrefs")` -ann "overwritePrefs"; + // This default-select Rotate/Scale item + optionMenuGrp -edit -select 1 axisAndUnitMethodMenu; + return $layout; } @@ -113,6 +151,23 @@ global proc stageFromFile_UIInit(string $parent, string $filterType) textFieldGrp -e -text $ppath primPathField; textFieldGrp -e -text $exppath excludePrimPathField; checkBoxGrp -e -value1 $loadp loadAllPayloadsCheckBox; + + if (`optionVar -exists stageFromFile_convertUpAxis`) { + int $convertUpAxis = `optionVar -q stageFromFile_convertUpAxis`; + checkBoxGrp -e -value1 $convertUpAxis upAxisCheckBox; + } + + if (`optionVar -exists stageFromFile_convertUnit`) { + int $convertUnit = `optionVar -q stageFromFile_convertUnit`; + checkBoxGrp -e -value1 $convertUnit unitCheckBox; + } + + if (`optionVar -exists stageFromFile_conversionMethod`) { + int $conversionMethod = `optionVar -q stageFromFile_conversionMethod`; + optionMenuGrp -e -select $conversionMethod axisAndUnitMethodMenu; + } + + stageFromFile_upAxisUnitCB(); } global proc stageFromFile_UICommit(string $parent) @@ -129,6 +184,30 @@ global proc stageFromFile_UICommit(string $parent) (`textFieldGrp -q -text excludePrimPathField`); optionVar -intValue stageFromFile_loadAllPayloads (`checkBoxGrp -q -value1 loadAllPayloadsCheckBox`); + + optionVar -intValue stageFromFile_convertUpAxis + (`checkBoxGrp -q -value1 upAxisCheckBox`); + optionVar -intValue stageFromFile_convertUnit + (`checkBoxGrp -q -value1 unitCheckBox`); + optionVar -intValue stageFromFile_conversionMethod + (`optionMenuGrp -q -select axisAndUnitMethodMenu`); +} + +proc convertUpAxisAndUnit(string $shapeNode) +{ + int $convertUpAxis = `optionVar -q stageFromFile_convertUpAxis`; + int $convertUnit = `optionVar -q stageFromFile_convertUnit`; + int $conversionMethod = `optionVar -q stageFromFile_conversionMethod`; + string $conversionMethodStr = ($conversionMethod == 1) ? "rotateScale" : "overwritePrefs"; + + python( + "import mayaUsdStageConversion; " + + "mayaUsdStageConversion.convertUpAxisAndUnit(" + + "'''" + $shapeNode + "'''" + ", " + + ($convertUpAxis ? "True" : "False") + ", " + + ($convertUnit ? "True" : "False") + ", " + + "'''" + $conversionMethodStr + "'''" + + ")"); } proc string doCreateStage(string $fileName) @@ -153,13 +232,14 @@ proc string doCreateStage(string $fileName) } string $shapeNode = `createNode "mayaUsdProxyShape" -skipSelect -name ($baseName+"Shape")`; - // Note: load rules must be the first thing set so the stage gets loaded in teh correct state right away. + // Note: load rules must be the first thing set so the stage gets loaded in the correct state right away. python("import mayaUsd.lib as mayaUsdLib; mayaUsdLib.setLoadRulesAttribute('" + $shapeNode + "', " + $loadpStr + ")"); setAttr -type "string" ($shapeNode+".filePath") $fileNameToSave; setAttr ($shapeNode+".filePathRelative") $requireRelative; setAttr -type "string" ($shapeNode+".primPath") $ppath; setAttr -type "string" ($shapeNode+".excludePrimPaths") $exppath; connectAttr time1.outTime ($shapeNode+".time"); + convertUpAxisAndUnit($shapeNode); select -r $shapeNode; string $fullPath[] = `ls -l $shapeNode`; return $fullPath[0]; diff --git a/test/lib/mayaUsd/nodes/CMakeLists.txt b/test/lib/mayaUsd/nodes/CMakeLists.txt index a19f6f41b2..13ff3cc206 100644 --- a/test/lib/mayaUsd/nodes/CMakeLists.txt +++ b/test/lib/mayaUsd/nodes/CMakeLists.txt @@ -3,6 +3,7 @@ set(TEST_SCRIPT_FILES testLayerManagerSerialization.py testPointBasedDeformerNode.py testProxyShapeBase.py + testProxyShapeConversion.py ) foreach(script ${TEST_SCRIPT_FILES}) diff --git a/test/lib/mayaUsd/nodes/ProxyShapeConversionTest/UnitsCentimeters.usda b/test/lib/mayaUsd/nodes/ProxyShapeConversionTest/UnitsCentimeters.usda new file mode 100644 index 0000000000..bedcdf76c0 --- /dev/null +++ b/test/lib/mayaUsd/nodes/ProxyShapeConversionTest/UnitsCentimeters.usda @@ -0,0 +1,20 @@ +#usda 1.0 +( + defaultPrim = "RootPrim" + metersPerUnit = 0.01 + upAxis = "Y" +) + +def Xform "RootPrim" +{ + def Mesh "SimpleMesh" + { + uniform bool doubleSided = 1 + float3[] extent = [(-1, -1, 0), (1, 1, 0)] + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(-1, -1, 0), (1, -1, 0), (0, 1, 0)] + color3f[] primvars:displayColor = [(0.2, 0, 0)] + } +} + diff --git a/test/lib/mayaUsd/nodes/ProxyShapeConversionTest/UnitsMillimeters.usda b/test/lib/mayaUsd/nodes/ProxyShapeConversionTest/UnitsMillimeters.usda new file mode 100644 index 0000000000..438c5fce34 --- /dev/null +++ b/test/lib/mayaUsd/nodes/ProxyShapeConversionTest/UnitsMillimeters.usda @@ -0,0 +1,20 @@ +#usda 1.0 +( + defaultPrim = "RootPrim" + metersPerUnit = 0.001 + upAxis = "Y" +) + +def Xform "RootPrim" +{ + def Mesh "SimpleMesh" + { + uniform bool doubleSided = 1 + float3[] extent = [(-1, -1, 0), (1, 1, 0)] + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(-1, -1, 0), (1, -1, 0), (0, 1, 0)] + color3f[] primvars:displayColor = [(0.2, 0, 0)] + } +} + diff --git a/test/lib/mayaUsd/nodes/ProxyShapeConversionTest/UpAxisY.usda b/test/lib/mayaUsd/nodes/ProxyShapeConversionTest/UpAxisY.usda new file mode 100644 index 0000000000..bedcdf76c0 --- /dev/null +++ b/test/lib/mayaUsd/nodes/ProxyShapeConversionTest/UpAxisY.usda @@ -0,0 +1,20 @@ +#usda 1.0 +( + defaultPrim = "RootPrim" + metersPerUnit = 0.01 + upAxis = "Y" +) + +def Xform "RootPrim" +{ + def Mesh "SimpleMesh" + { + uniform bool doubleSided = 1 + float3[] extent = [(-1, -1, 0), (1, 1, 0)] + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(-1, -1, 0), (1, -1, 0), (0, 1, 0)] + color3f[] primvars:displayColor = [(0.2, 0, 0)] + } +} + diff --git a/test/lib/mayaUsd/nodes/testProxyShapeConversion.py b/test/lib/mayaUsd/nodes/testProxyShapeConversion.py new file mode 100644 index 0000000000..53a1f2fca9 --- /dev/null +++ b/test/lib/mayaUsd/nodes/testProxyShapeConversion.py @@ -0,0 +1,251 @@ +#!/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 mayaUsdStageConversion + +import maya.api.OpenMaya as om +from maya import cmds +from maya import standalone + +from pxr import Gf + +import os +import unittest +from math import radians + +import fixturesUtils +import transformUtils +from mayaUtils import createProxyFromFile + + +class testProxyShapeConversion(unittest.TestCase): + """ + Test for modifying the stage or Maya prefs to match + the up-axis and units when opening a USD stage. + """ + + @classmethod + def setUpClass(cls): + cls._path = fixturesUtils.setUpClass(__file__) + + @classmethod + def tearDownClass(cls): + standalone.uninitialize() + + def setUp(self): + """Clear the scene and setup the Maya preferences.""" + cmds.file(f=True, new=True) + # Make sure up-axis is Z. + cmds.upAxis(axis='z') + # Make sure the units are centimeters. + cmds.currentUnit(linear='cm') + + def testCreateStageChangeUpAxisMayaPrefs(self): + """Test creating a stage and changing the Maya up-axis preference.""" + usd_file = os.path.join(self._path, "ProxyShapeConversionTest", "UpAxisY.usda") + proxyShapeNode, stage = createProxyFromFile(usd_file) + + convertUpAxis = True + convertUnit = False + conversionMethod = 'overwritePrefs' + mayaUsdStageConversion.convertUpAxisAndUnit(proxyShapeNode, convertUpAxis, convertUnit, conversionMethod) + + expectedAxis = 'y' + actualAxis = cmds.upAxis(query=True, axis=True) + self.assertEqual(actualAxis, expectedAxis) + + def testCreateStageRotateUpAxis(self): + """Test creating a stage and rotate the proxy shape.""" + usd_file = os.path.join(self._path, "ProxyShapeConversionTest", "UpAxisY.usda") + proxyShapeNode, stage = createProxyFromFile(usd_file) + + convertUpAxis = True + convertUnit = False + conversionMethod = 'rotateScale' + mayaUsdStageConversion.convertUpAxisAndUnit(proxyShapeNode, convertUpAxis, convertUnit, conversionMethod) + + expectedAxis = 'z' + actualAxis = cmds.upAxis(query=True, axis=True) + self.assertEqual(actualAxis, expectedAxis) + + rootNodes = cmds.ls('stage') + self.assertEqual(len(rootNodes), 1) + + expectedRotation = transformUtils.getMayaMatrixRotation( + om.MEulerRotation(radians(90.), radians(0.), radians(0.)).asMatrix()) + actualRotation = transformUtils.getMayaNodeRotation(rootNodes[0]) + self.assertTrue(actualRotation.isEquivalent(expectedRotation)) + + def testCreateStageUpAxisNothingToDo(self): + """Test creating a stage and having the up-axis and units already match.""" + # Make sure all preferences will match. + cmds.upAxis(axis='y') + cmds.currentUnit(linear='mm') + + usd_file = os.path.join(self._path, "ProxyShapeConversionTest", "UpAxisY.usda") + proxyShapeNode, stage = createProxyFromFile(usd_file) + + convertUpAxis = True + convertUnit = True + conversionMethod = 'rotateScale' + mayaUsdStageConversion.convertUpAxisAndUnit(proxyShapeNode, convertUpAxis, convertUnit, conversionMethod) + + # Verify Maya prefs were not changed. + expectedAxis = 'y' + actualAxis = cmds.upAxis(query=True, axis=True) + self.assertEqual(actualAxis, expectedAxis) + + rootNodes = cmds.ls('stage') + self.assertEqual(len(rootNodes), 1) + + # Verify no rotation was applied. + expectedRotation = transformUtils.getMayaMatrixRotation( + om.MEulerRotation(radians(0.), radians(0.), radians(0.)).asMatrix()) + actualRotation = transformUtils.getMayaNodeRotation(rootNodes[0]) + self.assertTrue(actualRotation.isEquivalent(expectedRotation)) + + def testCreateStageUpAxisDoNotModify(self): + """Test creating a stage and asking to leave everything alone.""" + usd_file = os.path.join(self._path, "ProxyShapeConversionTest", "UpAxisY.usda") + proxyShapeNode, stage = createProxyFromFile(usd_file) + + # Make sure we ask not to convert up-axis and units + convertUpAxis = False + convertUnit = False + conversionMethod = 'rotateScale' + mayaUsdStageConversion.convertUpAxisAndUnit(proxyShapeNode, convertUpAxis, convertUnit, conversionMethod) + + # Verify Maya prefs were not changed. + expectedAxis = 'z' + actualAxis = cmds.upAxis(query=True, axis=True) + self.assertEqual(actualAxis, expectedAxis) + + expectedUnits = 'cm' + actualUnits = cmds.currentUnit(query=True, linear=True) + self.assertEqual(actualUnits, expectedUnits) + + rootNodes = cmds.ls('stage') + self.assertEqual(len(rootNodes), 1) + + # Verify no rotation was applied. + expectedRotation = transformUtils.getMayaMatrixRotation( + om.MEulerRotation(radians(0.), radians(0.), radians(0.)).asMatrix()) + actualRotation = transformUtils.getMayaNodeRotation(rootNodes[0]) + self.assertTrue(actualRotation.isEquivalent(expectedRotation)) + + def testCreateStageChangeUnitsMayaPrefs(self): + """Test creating a stage and changing the Maya unit preference.""" + usd_file = os.path.join(self._path, "ProxyShapeConversionTest", "UnitsMillimeters.usda") + proxyShapeNode, stage = createProxyFromFile(usd_file) + + convertUpAxis = False + convertUnit = True + conversionMethod = 'overwritePrefs' + mayaUsdStageConversion.convertUpAxisAndUnit(proxyShapeNode, convertUpAxis, convertUnit, conversionMethod) + + # Preference should have been changed. + expectedUnits = 'mm' + actualUnits = cmds.currentUnit(query=True, linear=True) + self.assertEqual(actualUnits, expectedUnits) + + def testCreateStageScale(self): + """Test creating a stage and scaling the proxy shape node.""" + usd_file = os.path.join(self._path, "ProxyShapeConversionTest", "UnitsMillimeters.usda") + proxyShapeNode, stage = createProxyFromFile(usd_file) + + convertUpAxis = False + convertUnit = True + conversionMethod = 'rotateScale' + mayaUsdStageConversion.convertUpAxisAndUnit(proxyShapeNode, convertUpAxis, convertUnit, conversionMethod) + + # Preference should have been left alone. + expectedUnits = 'cm' + actualUnits = cmds.currentUnit(query=True, linear=True) + self.assertEqual(actualUnits, expectedUnits) + + rootNodes = cmds.ls('stage') + self.assertEqual(len(rootNodes), 1) + + EPSILON = 1e-6 + expectedScaling = [0.1, 0.1, 0.1] + actualScaling = transformUtils.getMayaNodeScaling(rootNodes[0]) + self.assertTrue(Gf.IsClose(actualScaling, expectedScaling, EPSILON)) + + def testCreateStageUnitsNothingToDo(self): + """Test creating a stage and having the up-axis and units already match.""" + # Make sure all preferences will match. + cmds.upAxis(axis='y') + cmds.currentUnit(linear='cm') + + usd_file = os.path.join(self._path, "ProxyShapeConversionTest", "UnitsCentimeters.usda") + proxyShapeNode, stage = createProxyFromFile(usd_file) + + convertUpAxis = True + convertUnit = True + conversionMethod = 'rotateScale' + mayaUsdStageConversion.convertUpAxisAndUnit(proxyShapeNode, convertUpAxis, convertUnit, conversionMethod) + + # Verify Maya prefs were not changed. + expectedAxis = 'y' + actualAxis = cmds.upAxis(query=True, axis=True) + self.assertEqual(actualAxis, expectedAxis) + + expectedUnits = 'cm' + actualUnits = cmds.currentUnit(query=True, linear=True) + self.assertEqual(actualUnits, expectedUnits) + + rootNodes = cmds.ls('stage') + self.assertEqual(len(rootNodes), 1) + + # Verify no scaling was applied. + EPSILON = 1e-6 + expectedScaling = [1.0, 1.0, 1.0] + actualScaling = transformUtils.getMayaNodeScaling(rootNodes[0]) + self.assertTrue(Gf.IsClose(actualScaling, expectedScaling, EPSILON)) + + def testCreateStageUnitsDoNotModify(self): + """Test creating a stage and asking to leave everything alone.""" + usd_file = os.path.join(self._path, "ProxyShapeConversionTest", "UnitsMillimeters.usda") + proxyShapeNode, stage = createProxyFromFile(usd_file) + + # Make sure we ask not to convert up-axis and units + convertUpAxis = False + convertUnit = False + conversionMethod = 'rotateScale' + mayaUsdStageConversion.convertUpAxisAndUnit(proxyShapeNode, convertUpAxis, convertUnit, conversionMethod) + + # Verify Maya prefs were not changed. + expectedAxis = 'z' + actualAxis = cmds.upAxis(query=True, axis=True) + self.assertEqual(actualAxis, expectedAxis) + + expectedUnits = 'cm' + actualUnits = cmds.currentUnit(query=True, linear=True) + self.assertEqual(actualUnits, expectedUnits) + + rootNodes = cmds.ls('stage') + self.assertEqual(len(rootNodes), 1) + + # Verify no scaling was applied. + EPSILON = 1e-6 + expectedScaling = [1.0, 1.0, 1.0] + actualScaling = transformUtils.getMayaNodeScaling(rootNodes[0]) + self.assertTrue(Gf.IsClose(actualScaling, expectedScaling, EPSILON)) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/test/lib/usd/translators/testUsdExportRoots.py b/test/lib/usd/translators/testUsdExportRoots.py index 6208357ccd..5e42a5f45b 100644 --- a/test/lib/usd/translators/testUsdExportRoots.py +++ b/test/lib/usd/translators/testUsdExportRoots.py @@ -22,11 +22,9 @@ from maya import standalone from pxr import Usd -from pxr import UsdGeom -from pxr import Vt -from pxr import Gf import fixturesUtils +import transformUtils class testUsdExportRoot(unittest.TestCase): @@ -113,22 +111,6 @@ def assertPrim(self, stage, path, type): self.assertTrue(prim.IsValid(), "Expected to find %s" % path) self.assertEqual(prim.GetTypeName(), type, "Expected prim %s to have type %s" % (path, type)) - def assertPrimXform(self, stage, path, 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. - ''' - prim = stage.GetPrimAtPath(path) - 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.assertEqual(attr.Get(), value) - # Chop off the first xofrm op for the next loop. - xformOpOrder = xformOpOrder[1:] - def assertNotPrim(self, stage, path): self.assertFalse(stage.GetPrimAtPath(path).IsValid(), "Did not expect to find %s" % path) @@ -146,13 +128,13 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertPrim(stage, '/Top/OtherMid', 'Xform') self.assertPrim(stage, '/Top/Mid/OtherLowest', 'Xform') - self.assertPrimXform(stage, '/Top', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top', [ ('xformOp:translate', (2., 0., 0.)), ('xformOp:rotateXYZ', (45., 0., 0.))]) - self.assertPrimXform(stage, '/Top/Mid', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top/Mid', [ ('xformOp:translate', (0., 2., 0.)), ('xformOp:rotateXYZ', (0., 0., 45.))]) - self.assertPrimXform(stage, '/Top/Mid/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top/Mid/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Top') @@ -164,13 +146,13 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertPrim(stage, '/Top/OtherMid', 'Xform') self.assertPrim(stage, '/Top/Mid/OtherLowest', 'Xform') - self.assertPrimXform(stage, '/Top', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top', [ ('xformOp:translate', (2., 0., 0.)), ('xformOp:rotateXYZ', (45., 0., 0.))]) - self.assertPrimXform(stage, '/Top/Mid', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top/Mid', [ ('xformOp:translate', (0., 2., 0.)), ('xformOp:rotateXYZ', (0., 0., 45.))]) - self.assertPrimXform(stage, '/Top/Mid/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top/Mid/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Top', worldspace=True) @@ -182,13 +164,13 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertPrim(stage, '/Top/OtherMid', 'Xform') self.assertPrim(stage, '/Top/Mid/OtherLowest', 'Xform') - self.assertPrimXform(stage, '/Top', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top', [ ('xformOp:translate', (2., 0., 0.)), ('xformOp:rotateXYZ', (45., 0., 0.))]) - self.assertPrimXform(stage, '/Top/Mid', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top/Mid', [ ('xformOp:translate', (0., 2., 0.)), ('xformOp:rotateXYZ', (0., 0., 45.))]) - self.assertPrimXform(stage, '/Top/Mid/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top/Mid/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Top', selection='Top') @@ -200,13 +182,13 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/Top/OtherMid') self.assertPrim(stage, '/Top/Mid/OtherLowest', 'Xform') - self.assertPrimXform(stage, '/Top', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top', [ ('xformOp:translate', (2., 0., 0.)), ('xformOp:rotateXYZ', (45., 0., 0.))]) - self.assertPrimXform(stage, '/Top/Mid', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top/Mid', [ ('xformOp:translate', (0., 2., 0.)), ('xformOp:rotateXYZ', (0., 0., 45.))]) - self.assertPrimXform(stage, '/Top/Mid/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top/Mid/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Top', selection='Mid') @@ -218,13 +200,13 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/Top/OtherMid') self.assertNotPrim(stage, '/Top/Mid/OtherLowest') - self.assertPrimXform(stage, '/Top', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top', [ ('xformOp:translate', (2., 0., 0.)), ('xformOp:rotateXYZ', (45., 0., 0.))]) - self.assertPrimXform(stage, '/Top/Mid', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top/Mid', [ ('xformOp:translate', (0., 2., 0.)), ('xformOp:rotateXYZ', (0., 0., 45.))]) - self.assertPrimXform(stage, '/Top/Mid/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Top/Mid/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Top', selection='Cube') @@ -237,10 +219,10 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertPrim(stage, '/Mid/OtherLowest', 'Xform') - self.assertPrimXform(stage, '/Mid', [ + transformUtils.assertStagePrimXforms(self, stage, '/Mid', [ ('xformOp:translate', (0., 2., 0.)), ('xformOp:rotateXYZ', (0., 0., 45.))]) - self.assertPrimXform(stage, '/Mid/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Mid/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Mid') @@ -253,12 +235,12 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertPrim(stage, '/Mid/OtherLowest', 'Xform') - self.assertPrimXform(stage, '/Mid', [ + transformUtils.assertStagePrimXforms(self, stage, '/Mid', [ ('xformOp:translate', (2., 0., 0.)), ('xformOp:rotateXYZ', (45., 0., 0.)), ('xformOp:translate:channel1', (0., 2., 0.)), ('xformOp:rotateXYZ:channel1', (0., 0., 45.))]) - self.assertPrimXform(stage, '/Mid/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Mid/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Mid', worldspace=True) @@ -271,10 +253,10 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertPrim(stage, '/Mid/OtherLowest', 'Xform') - self.assertPrimXform(stage, '/Mid', [ + transformUtils.assertStagePrimXforms(self, stage, '/Mid', [ ('xformOp:translate', (0., 2., 0.)), ('xformOp:rotateXYZ', (0., 0., 45.))]) - self.assertPrimXform(stage, '/Mid/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Mid/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Mid', selection='Top') @@ -287,10 +269,10 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertPrim(stage, '/Mid/OtherLowest', 'Xform') - self.assertPrimXform(stage, '/Mid', [ + transformUtils.assertStagePrimXforms(self, stage, '/Mid', [ ('xformOp:translate', (0., 2., 0.)), ('xformOp:rotateXYZ', (0., 0., 45.))]) - self.assertPrimXform(stage, '/Mid/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Mid/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Mid', selection='Mid') @@ -303,10 +285,10 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertNotPrim(stage, '/Mid/OtherLowest') - self.assertPrimXform(stage, '/Mid', [ + transformUtils.assertStagePrimXforms(self, stage, '/Mid', [ ('xformOp:translate', (0., 2., 0.)), ('xformOp:rotateXYZ', (0., 0., 45.))]) - self.assertPrimXform(stage, '/Mid/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Mid/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Mid', selection='Cube') @@ -320,7 +302,7 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertNotPrim(stage, '/OtherLowest') - self.assertPrimXform(stage, '/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Cube') @@ -334,7 +316,7 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertNotPrim(stage, '/OtherLowest') - self.assertPrimXform(stage, '/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Cube', [ ('xformOp:translate', (2., 0., 0.)), ('xformOp:rotateXYZ', (45., 0., 0.)), ('xformOp:translate:channel1', (0., 2., 0.)), @@ -352,7 +334,7 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertNotPrim(stage, '/OtherLowest') - self.assertPrimXform(stage, '/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Cube', selection='Top') @@ -366,7 +348,7 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertNotPrim(stage, '/OtherLowest') - self.assertPrimXform(stage, '/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Cube', selection='Mid') @@ -380,7 +362,7 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertNotPrim(stage, '/OtherLowest') - self.assertPrimXform(stage, '/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='Cube', selection='Cube') @@ -394,7 +376,7 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertNotPrim(stage, '/OtherLowest') - self.assertPrimXform(stage, '/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='', selection='Cube') @@ -408,7 +390,7 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertPrim(stage, '/OtherLowest', 'Xform') - self.assertPrimXform(stage, '/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root='', selection=['Cube','OtherLowest']) @@ -422,7 +404,7 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertPrim(stage, '/OtherLowest', 'Xform') - self.assertPrimXform(stage, '/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root=['Cube','OtherLowest']) @@ -436,7 +418,7 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertNotPrim(stage, '/OtherLowest') - self.assertPrimXform(stage, '/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root=['Cube','OtherLowest'], selection='Cube') @@ -449,10 +431,10 @@ def validator(stage): self.assertNotPrim(stage, '/OtherTop') self.assertNotPrim(stage, '/OtherMid') self.assertPrim(stage, '/OtherLowest', 'Xform') - self.assertPrimXform(stage, '/Mid', [ + transformUtils.assertStagePrimXforms(self, stage, '/Mid', [ ('xformOp:translate', (0., 2., 0.)), ('xformOp:rotateXYZ', (0., 0., 45.))]) - self.assertPrimXform(stage, '/Mid/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Mid/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root=['Mid','OtherLowest'], selection=['Cube','OtherLowest']) @@ -468,7 +450,7 @@ def validator(stage): self.assertNotPrim(stage, '/OtherLowest') self.assertPrim(stage, '/Cube1', 'Mesh') self.assertPrim(stage, '/Looks/lambert3SG/lambert3', 'Shader') - self.assertPrimXform(stage, '/Cube', [ + transformUtils.assertStagePrimXforms(self, stage, '/Cube', [ ('xformOp:translate', (0., 0., 3.)), ('xformOp:rotateXYZ', (0., 45., 0.))]) self.doExportImportTest(validator, root=['Cube','Cube1'], selection=['Cube','Cube1']) diff --git a/test/lib/usd/translators/testUsdExportUnits.py b/test/lib/usd/translators/testUsdExportUnits.py index c0d69d1880..6d8c9e99ca 100644 --- a/test/lib/usd/translators/testUsdExportUnits.py +++ b/test/lib/usd/translators/testUsdExportUnits.py @@ -15,16 +15,16 @@ # 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 +from pxr import Usd, UsdGeom import fixturesUtils +import transformUtils class testUsdExportUnits(unittest.TestCase): """Test for modifying the units when exporting.""" @@ -42,22 +42,6 @@ def setUp(self): cmds.file(f=True, new=True) cmds.currentUnit(linear='cm') - 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 testExportUnitsNone(self): """Test exporting without any units.""" cmds.polySphere() @@ -74,7 +58,7 @@ def testExportUnitsNone(self): spherePrim = stage.GetPrimAtPath('/pSphere1') self.assertTrue(spherePrim) - self.assertPrimXform(spherePrim, [ + transformUtils.assertPrimXforms(self, spherePrim, [ ('xformOp:translate', (3., 0., 0.))]) def testExportUnitsFollowMayaPrefs(self): @@ -97,7 +81,7 @@ def testExportUnitsFollowMayaPrefs(self): spherePrim = stage.GetPrimAtPath('/pSphere1') self.assertTrue(spherePrim) - self.assertPrimXform(spherePrim, [ + transformUtils.assertPrimXforms(self, spherePrim, [ ('xformOp:translate', (0., 0., 3.))]) def testExportUnitsFollowDifferentMayaPrefs(self): @@ -122,7 +106,7 @@ def testExportUnitsFollowDifferentMayaPrefs(self): spherePrim = stage.GetPrimAtPath('/pSphere1') self.assertTrue(spherePrim) - self.assertPrimXform(spherePrim, [ + transformUtils.assertPrimXforms(self, spherePrim, [ ('xformOp:translate', (0., 0., 30.)), ('xformOp:scale', (10., 10., 10.))]) @@ -146,7 +130,7 @@ def testExportUnitsDifferentUnits(self): spherePrim = stage.GetPrimAtPath('/pSphere1') self.assertTrue(spherePrim) - self.assertPrimXform(spherePrim, [ + transformUtils.assertPrimXforms(self, spherePrim, [ ('xformOp:translate', (0., 0., 0.00003)), ('xformOp:scale', (0.00001, 0.00001, 0.00001))]) diff --git a/test/lib/usd/translators/testUsdExportUpAxis.py b/test/lib/usd/translators/testUsdExportUpAxis.py index a36f42d5b8..56d7517e9f 100644 --- a/test/lib/usd/translators/testUsdExportUpAxis.py +++ b/test/lib/usd/translators/testUsdExportUpAxis.py @@ -15,16 +15,16 @@ # 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 +from pxr import Usd, UsdGeom import fixturesUtils +import transformUtils class testUsdExportUpAxis(unittest.TestCase): """Test for modifying the up-axis when exporting.""" @@ -42,22 +42,6 @@ def setUp(self): 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() @@ -93,7 +77,7 @@ def testExportUpAxisFollowMayaPrefs(self): spherePrim = stage.GetPrimAtPath('/pSphere1') self.assertTrue(spherePrim) - self.assertPrimXform(spherePrim, [ + transformUtils.assertPrimXforms(self, spherePrim, [ ('xformOp:translate', (0., 0., 3.))]) def testExportUpAxisDifferentY(self): @@ -115,7 +99,7 @@ def testExportUpAxisDifferentY(self): spherePrim = stage.GetPrimAtPath('/pSphere1') self.assertTrue(spherePrim) - self.assertPrimXform(spherePrim, [ + transformUtils.assertPrimXforms(self, spherePrim, [ ('xformOp:translate', (0., 3., 0.)), ('xformOp:rotateXYZ', (-90., 0., 0.))]) diff --git a/test/lib/usd/translators/testUsdImportUnits.py b/test/lib/usd/translators/testUsdImportUnits.py index ee8ac19457..687666bb68 100644 --- a/test/lib/usd/translators/testUsdImportUnits.py +++ b/test/lib/usd/translators/testUsdImportUnits.py @@ -15,7 +15,6 @@ # limitations under the License. # -import maya.api.OpenMaya as om import os import unittest @@ -26,27 +25,7 @@ import fixturesUtils - -def _GetMayaTransform(transformName): - '''Retrieve the Maya SDK transformation API (MFnTransform) of a Maya node.''' - selectionList = om.MSelectionList() - selectionList.add(transformName) - node = selectionList.getDependNode(0) - return om.MFnTransform(node) - -def _GetMayaMatrix(transformName): - '''Retrieve the transformation matrix (MMatrix) of a Maya node.''' - mayaTransform = _GetMayaTransform(transformName) - transformation = mayaTransform.transformation() - return transformation.asMatrix() - -def _GetScalingFromMatrix(matrix): - '''Extract the scaling from a Maya matrix.''' - return om.MTransformationMatrix(matrix).scale(om.MSpace.kObject) - -def _GetMayaScaling(transformName): - '''Extract the scaling from a Maya node.''' - return _GetScalingFromMatrix(_GetMayaMatrix(transformName)) +import transformUtils class testUsdImportUpAxis(unittest.TestCase): @@ -105,7 +84,7 @@ def testImportScaleGroup(self): EPSILON = 1e-6 expectedScaling = [0.1, 0.1, 0.1] - actualScaling = _GetMayaScaling(rootNodes[0]) + actualScaling = transformUtils.getMayaNodeScaling(rootNodes[0]) self.assertTrue(Gf.IsClose(actualScaling, expectedScaling, EPSILON)) self.assertEqual(cmds.getAttr('%s.OriginalUSDMetersPerUnit' % rootNodes[0]), '0.001') @@ -130,7 +109,7 @@ def testImportScaleRootNodes(self): EPSILON = 1e-6 expectedScaling = [0.1, 0.1, 0.1] - actualScaling = _GetMayaScaling(rootNodes[0]) + actualScaling = transformUtils.getMayaNodeScaling(rootNodes[0]) self.assertTrue(Gf.IsClose(actualScaling, expectedScaling, EPSILON)) self.assertEqual(cmds.getAttr('%s.OriginalUSDMetersPerUnit' % rootNodes[0]), '0.001') diff --git a/test/lib/usd/translators/testUsdImportUpAxis.py b/test/lib/usd/translators/testUsdImportUpAxis.py index 548fd8c4ab..886e2b7860 100644 --- a/test/lib/usd/translators/testUsdImportUpAxis.py +++ b/test/lib/usd/translators/testUsdImportUpAxis.py @@ -26,25 +26,8 @@ from pxr import Gf import fixturesUtils +import transformUtils -def _GetMayaTransform(transformName): - '''Retrieve the transformation matrix of a Maya node.''' - selectionList = om.MSelectionList() - selectionList.add(transformName) - node = selectionList.getDependNode(0) - return om.MFnTransform(node) - -def _GetMayaMatrix(transformName): - '''Retrieve the three XYZ local rotation angles of a Maya node.''' - mayaTransform = _GetMayaTransform(transformName) - transformation = mayaTransform.transformation() - return transformation.asMatrix() - -def _GetRotationFromMatrix(matrix): - return om.MTransformationMatrix(matrix).rotation() - -def _GetMayaRotation(transformName): - return _GetRotationFromMatrix(_GetMayaMatrix(transformName)) class testUsdImportUpAxis(unittest.TestCase): """Test for modifying the up-axis when importing.""" @@ -93,9 +76,9 @@ def testImportRotateGroup(self): EPSILON = 1e-3 - expectedRotation = _GetRotationFromMatrix( + expectedRotation = transformUtils.getMayaMatrixRotation( om.MEulerRotation(radians(90.), radians(0.), radians(0.)).asMatrix()) - actualRotation = _GetMayaRotation(rootNodes[0]) + actualRotation = transformUtils.getMayaNodeRotation(rootNodes[0]) self.assertTrue(actualRotation.isEquivalent(expectedRotation)) self.assertEqual(cmds.getAttr('%s.OriginalUSDUpAxis' % rootNodes[0]), 'Y') @@ -118,9 +101,9 @@ def testImportRotateRootNodes(self): EPSILON = 1e-3 - expectedRotation = _GetRotationFromMatrix( + expectedRotation = transformUtils.getMayaMatrixRotation( om.MEulerRotation(radians(90.), radians(0.), radians(0.)).asMatrix()) - actualRotation = _GetMayaRotation(rootNodes[0]) + actualRotation = transformUtils.getMayaNodeRotation(rootNodes[0]) self.assertTrue(actualRotation.isEquivalent(expectedRotation)) self.assertEqual(cmds.getAttr('%s.OriginalUSDUpAxis' % rootNodes[0]), 'Y') diff --git a/test/testUtils/transformUtils.py b/test/testUtils/transformUtils.py new file mode 100644 index 0000000000..8ece38cc53 --- /dev/null +++ b/test/testUtils/transformUtils.py @@ -0,0 +1,83 @@ +import maya.api.OpenMaya as om + +from pxr import Gf + +def getMayaNodeTransform(transformName): + '''Retrieve the transformation object (MFnTransform) of a Maya node.''' + selectionList = om.MSelectionList() + selectionList.add(transformName) + node = selectionList.getDependNode(0) + return om.MFnTransform(node) + + +def getMayaNodeMatrix(transformName): + '''Retrieve the matrix (MMatrix) of a Maya node.''' + mayaTransform = getMayaNodeTransform(transformName) + transformation = mayaTransform.transformation() + return transformation.asMatrix() + + +def getMayaMatrixRotation(matrix): + '''Extract the three local rotation angles (XYZ) from a Maya matrix.''' + return om.MTransformationMatrix(matrix).rotation() + + +def getMayaNodeRotation(transformName): + '''Extract the three local rotation angles (XYZ) from a Maya node.''' + return getMayaMatrixRotation(getMayaNodeMatrix(transformName)) + + +def getMayaMatrixScaling(matrix): + '''Extract the scaling from a Maya matrix.''' + return om.MTransformationMatrix(matrix).scale(om.MSpace.kObject) + + +def getMayaNodeScaling(transformName): + '''Extract the scaling from a Maya node.''' + return getMayaMatrixScaling(getMayaNodeMatrix(transformName)) + + +def assertPrimXforms(test, prim, xforms): + ''' + Verify that the prim has the given xform in the roder given. + The verifications are done by assertions on the given test. + + The xforms should be a list of pairs, each containing the + xform op name and its value. + + For example: + [ + ('xformOp:translate', (0., 0., 30.)), + ('xformOp:scale', (10., 10., 10.)) + ] + ''' + EPSILON = 1e-6 + xformOpOrder = prim.GetAttribute('xformOpOrder').Get() + test.assertEqual(len(xformOpOrder), len(xforms)) + for name, value in xforms: + test.assertEqual(xformOpOrder[0], name) + attr = prim.GetAttribute(name) + test.assertIsNotNone(attr) + test.assertTrue(Gf.IsClose(attr.Get(), value, EPSILON)) + # Chop off the first xofrm op for the next loop. + xformOpOrder = xformOpOrder[1:] + + +def assertStagePrimXforms(test, stage, path, xforms): + ''' + Verify that the prim at the given path in the given stage + has the given xform in the roder given. The verifications + are done by assertions on the given test. + + The xforms should be a list of pairs, each containing the + xform op name and its value. + + For example: + [ + ('xformOp:translate', (0., 0., 30.)), + ('xformOp:scale', (10., 10., 10.)) + ] + ''' + prim = stage.GetPrimAtPath(path) + return assertPrimXforms(test, prim, xforms) +