diff --git a/lib/mayaUsd/commands/Readme.md b/lib/mayaUsd/commands/Readme.md index a1a82e2c4f..6390f1edd4 100644 --- a/lib/mayaUsd/commands/Readme.md +++ b/lib/mayaUsd/commands/Readme.md @@ -54,6 +54,7 @@ Each base command class is documented in the following sections. | `-importUSDZTexturesFilePath` | `-itf` | string | none | Specifies an explicit directory to write imported textures to from a USDZ archive. Has no effect if `-importUSDZTextures` is not specified. | | `-importRelativeTextures` | `-rtx` | string | none | Selects how textures filenames are generated: absolute, relative, automatic or none. When automatic, the filename is relative if the source filename of the texture being imported is relative. When none, the file path is left alone, for backward compatible behavior. | | `-upAxis` | `-upa` | bool | true | Enable changing axis on import. | +| `-unit` | `-unt` | bool | true | Enable changing units on import. | | `-axisAndUnitMethod` | `-aum` | string | rotateScale | Selects how the unit and axis are handled during import. | ### Return Value diff --git a/lib/mayaUsd/commands/baseImportCommand.cpp b/lib/mayaUsd/commands/baseImportCommand.cpp index 333be028f4..e191b17550 100644 --- a/lib/mayaUsd/commands/baseImportCommand.cpp +++ b/lib/mayaUsd/commands/baseImportCommand.cpp @@ -70,6 +70,7 @@ MSyntax MayaUSDImportCommand::createSyntax() MSyntax::kString); syntax.addFlag( kImportUpAxisFlag, UsdMayaJobImportArgsTokens->upAxis.GetText(), MSyntax::kBoolean); + syntax.addFlag(kImportUnitFlag, UsdMayaJobImportArgsTokens->unit.GetText(), MSyntax::kBoolean); syntax.addFlag( kImportAxisAndUnitMethodFlag, UsdMayaJobImportArgsTokens->axisAndUnitMethod.GetText(), diff --git a/lib/mayaUsd/commands/baseImportCommand.h b/lib/mayaUsd/commands/baseImportCommand.h index cfed0d7b71..516c1013d0 100644 --- a/lib/mayaUsd/commands/baseImportCommand.h +++ b/lib/mayaUsd/commands/baseImportCommand.h @@ -47,6 +47,7 @@ class MAYAUSD_CORE_PUBLIC MayaUSDImportCommand : public MPxCommand static constexpr auto kImportUSDZTexturesFilePathFlag = "itf"; static constexpr auto kImportRelativeTexturesFlag = "rtx"; static constexpr auto kImportUpAxisFlag = "upa"; + static constexpr auto kImportUnitFlag = "unt"; static constexpr auto kImportAxisAndUnitMethodFlag = "aum"; static constexpr auto kMetadataFlag = "md"; static constexpr auto kApiSchemaFlag = "api"; diff --git a/lib/mayaUsd/fileio/jobs/jobArgs.cpp b/lib/mayaUsd/fileio/jobs/jobArgs.cpp index 270cd429aa..ce6f7fa18f 100644 --- a/lib/mayaUsd/fileio/jobs/jobArgs.cpp +++ b/lib/mayaUsd/fileio/jobs/jobArgs.cpp @@ -1382,6 +1382,7 @@ UsdMayaJobImportArgs::UsdMayaJobImportArgs( UsdMayaJobImportArgsTokens->addTransform, UsdMayaJobImportArgsTokens->overwritePrefs })) , upAxis(extractBoolean(userArgs, UsdMayaJobImportArgsTokens->upAxis)) + , unit(extractBoolean(userArgs, UsdMayaJobImportArgsTokens->unit)) , importInstances(extractBoolean(userArgs, UsdMayaJobImportArgsTokens->importInstances)) , useAsAnimationCache(extractBoolean(userArgs, UsdMayaJobImportArgsTokens->useAsAnimationCache)) , importWithProxyShapes(importWithProxyShapes) @@ -1473,6 +1474,7 @@ const VtDictionary& UsdMayaJobImportArgs::GetDefaultDictionary() d[UsdMayaJobImportArgsTokens->axisAndUnitMethod] = UsdMayaJobImportArgsTokens->rotateScale.GetString(); d[UsdMayaJobImportArgsTokens->upAxis] = true; + d[UsdMayaJobImportArgsTokens->unit] = true; d[UsdMayaJobImportArgsTokens->pullImportStage] = UsdStageRefPtr(); d[UsdMayaJobImportArgsTokens->useAsAnimationCache] = false; d[UsdMayaJobImportArgsTokens->preserveTimeline] = false; @@ -1557,6 +1559,7 @@ const VtDictionary& UsdMayaJobImportArgs::GetGuideDictionary() d[UsdMayaJobImportArgsTokens->importRelativeTextures] = _string; d[UsdMayaJobImportArgsTokens->axisAndUnitMethod] = _string; d[UsdMayaJobImportArgsTokens->upAxis] = _boolean; + d[UsdMayaJobImportArgsTokens->unit] = _boolean; d[UsdMayaJobImportArgsTokens->pullImportStage] = _usdStageRefPtr; d[UsdMayaJobImportArgsTokens->useAsAnimationCache] = _boolean; d[UsdMayaJobImportArgsTokens->preserveTimeline] = _boolean; @@ -1649,6 +1652,7 @@ std::ostream& operator<<(std::ostream& out, const UsdMayaJobImportArgs& importAr << "importRelativeTextures: " << TfStringify(importArgs.importRelativeTextures) << std::endl << "axisAndUnitMethod: " << TfStringify(importArgs.axisAndUnitMethod) << std::endl << "upAxis: " << TfStringify(importArgs.upAxis) << std::endl + << "unit: " << TfStringify(importArgs.unit) << std::endl << "pullImportStage: " << TfStringify(importArgs.pullImportStage) << std::endl << std::endl << "timeInterval: " << importArgs.timeInterval << std::endl diff --git a/lib/mayaUsd/fileio/jobs/jobArgs.h b/lib/mayaUsd/fileio/jobs/jobArgs.h index a5a3f2195e..881ee10bbe 100644 --- a/lib/mayaUsd/fileio/jobs/jobArgs.h +++ b/lib/mayaUsd/fileio/jobs/jobArgs.h @@ -197,6 +197,7 @@ TF_DECLARE_PUBLIC_TOKENS( (preserveTimeline) \ (remapUVSetsTo) \ (upAxis) \ + (unit) \ (axisAndUnitMethod) \ /* values for axis and unit method */ \ (rotateScale) \ @@ -436,6 +437,7 @@ struct UsdMayaJobImportArgs const std::string importRelativeTextures; const std::string axisAndUnitMethod; const bool upAxis; + const bool unit; const bool importInstances; const bool useAsAnimationCache; const bool importWithProxyShapes; diff --git a/lib/mayaUsd/fileio/jobs/readJob.cpp b/lib/mayaUsd/fileio/jobs/readJob.cpp index f229b1a2c3..4b67fd76a2 100644 --- a/lib/mayaUsd/fileio/jobs/readJob.cpp +++ b/lib/mayaUsd/fileio/jobs/readJob.cpp @@ -199,7 +199,7 @@ bool UsdMaya_ReadJob::Read(std::vector* addedDagPaths) // When we are called from PrimUpdaterManager we should already have // a computation scope. If we are called from elsewhere don't show any // progress bar here. - MayaUsd::ProgressBarScope progressBar(17); + MayaUsd::ProgressBarScope progressBar(16); // Do not use the global undo info recording system. // The read job Undo() / redo() functions will handle all operations. @@ -259,22 +259,6 @@ bool UsdMaya_ReadJob::Read(std::vector* addedDagPaths) _setTimeSampleMultiplierFrom(stage->GetTimeCodesPerSecond()); progressBar.advance(); - // XXX Currently all distance values are set directly from USD and will be - // interpreted as centimeters (Maya's internal distance unit). Future work - // could include converting distance values based on the specified meters- - // per-unit in the USD stage metadata. For now, simply warn. - if (UsdGeomStageHasAuthoredMetersPerUnit(stage)) { - MDistance::Unit mdistanceUnit = UsdMayaUtil::ConvertUsdGeomLinearUnitToMDistanceUnit( - UsdGeomGetStageMetersPerUnit(stage)); - - if (mdistanceUnit != MDistance::internalUnit()) { - TF_WARN("Distance unit conversion is not yet supported. " - "All distance values will be imported in Maya's internal " - "distance unit."); - } - } - progressBar.advance(); - // If the import time interval isn't empty, we expand the Min/Max time // sliders to include the stage's range if necessary. AutoTimelineRestore timelineRestore(mArgs.preserveTimeline); @@ -483,7 +467,7 @@ bool UsdMaya_ReadJob::Read(std::vector* addedDagPaths) } progressBar.advance(); - _ConvertUpAxis(stage); + _ConvertUpAxisAndUnits(stage); progressBar.advance(); UsdMayaReadUtil::mapFileHashes.clear(); @@ -498,37 +482,39 @@ static bool getUSDUpAxisZ(const UsdStageRefPtr& stage) return UsdGeomGetStageUpAxis(stage) == UsdGeomTokens->z; } -void UsdMaya_ReadJob::_ConvertUpAxis(const UsdStageRefPtr& stage) +void UsdMaya_ReadJob::_ConvertUpAxisAndUnits(const UsdStageRefPtr& stage) { - // If up-axis fixing is turned off, do nothing. - if (!mArgs.upAxis) - return; - - // If up axis are the same in Maya and USD, do nothing. - const bool isMayaUpAxisZ = getMayaUpAxisZ(); - const bool isUSDUpAxisUZ = getUSDUpAxisZ(stage); - - if (isMayaUpAxisZ == isUSDUpAxisUZ) + ConversionInfo conversion; + + // Convert up-axis based if required and different between Maya and USD. + const bool convertUpAxis = mArgs.upAxis; + conversion.isMayaUpAxisZ = getMayaUpAxisZ(); + conversion.isUSDUpAxisUZ = getUSDUpAxisZ(stage); + conversion.needUpAxisConversion + = (convertUpAxis && (conversion.isMayaUpAxisZ != conversion.isUSDUpAxisUZ)); + + // Convert units if required and different between Maya and USD. + const bool convertUnits = mArgs.unit; + conversion.mayaMetersPerUnit + = UsdMayaUtil::ConvertMDistanceUnitToUsdGeomLinearUnit(MDistance::internalUnit()); + conversion.usdMetersPerUnit = UsdGeomGetStageMetersPerUnit(stage); + conversion.needUnitsConversion + = (convertUnits && (conversion.mayaMetersPerUnit != conversion.usdMetersPerUnit)); + + // If neither up-axis nor units need to change, do nothing. + if (!conversion.needUpAxisConversion && !conversion.needUnitsConversion) return; - // Convert axis based on desired method. - const bool convertYtoZ = isMayaUpAxisZ; - - bool success = false; - if (mArgs.axisAndUnitMethod == UsdMayaJobImportArgsTokens->rotateScale) - success = _ConvertUpAxisWithRotation(stage, convertYtoZ, false); + _ConvertUpAxisAndUnitsByModifyingData(stage, conversion, false); else if (mArgs.axisAndUnitMethod == UsdMayaJobImportArgsTokens->addTransform) - success = _ConvertUpAxisWithRotation(stage, convertYtoZ, true); + _ConvertUpAxisAndUnitsByModifyingData(stage, conversion, true); else if (mArgs.axisAndUnitMethod == UsdMayaJobImportArgsTokens->overwritePrefs) - success = _ConvertUpAxisByChangingMayPrefs(convertYtoZ); + _ConvertUpAxisAndUnitsByChangingMayaPrefs(stage, conversion); else TF_WARN( - "Unknown method of converting the USD up axis to Maya: %s", + "Unknown method of converting the USD up axis and units to Maya: %s", mArgs.axisAndUnitMethod.c_str()); - - if (success) - MGlobal::displayInfo("Mismatching axis have been converted for accurate orientation."); } // Construct list of top level DAG nodes. @@ -583,11 +569,9 @@ static std::string _cleanMayaNodeName(const std::string& name) return cleaned; } -static void -_addOrignalUpAxisAttribute(const std::vector& dagNodePaths, bool convertUsdYtoMayaZ) +static void _addOrignalUpAxisAttribute(const std::vector& dagNodePaths, bool isUSDUpAxisZ) { - // Note: if we're converting from Y to Z, then the original up axis was Y, otherwise Z. - const MString originalUpAxis = convertUsdYtoMayaZ ? "Y" : "Z"; + const MString originalUpAxis = isUSDUpAxisZ ? "Z" : "Y"; const MString attrName = "OriginalUSDUpAxis"; for (const MDagPath& dagPath : dagNodePaths) { MFnDependencyNode depNode(dagPath.node()); @@ -595,15 +579,27 @@ _addOrignalUpAxisAttribute(const std::vector& dagNodePaths, bool conve } } -bool UsdMaya_ReadJob::_ConvertUpAxisWithRotation( +static void +_addOrignalUnitsAttribute(const std::vector& dagNodePaths, double usdMetersPerUnit) +{ + MString originalUnits; + originalUnits.set(usdMetersPerUnit); + const MString attrName = "OriginalUSDMetersPerUnit"; + for (const MDagPath& dagPath : dagNodePaths) { + MFnDependencyNode depNode(dagPath.node()); + MayaUsd::setDynamicAttribute(depNode, attrName, originalUnits); + } +} + +void UsdMaya_ReadJob::_ConvertUpAxisAndUnitsByModifyingData( const UsdStageRefPtr& stage, - bool convertUsdYtoMayaZ, + const ConversionInfo& conversion, bool keepParentGroup) { std::vector dagNodePaths = _findAllRootDagNodePaths(mNewNodeRegistry, mMayaRootDagPath); if (dagNodePaths.size() <= 0) - return true; + return; std::vector dagNodeNames = _convertDagPathToNames(dagNodePaths); @@ -648,7 +644,7 @@ bool UsdMaya_ReadJob::_ConvertUpAxisWithRotation( if (mMayaRootDagPath.node() != MObject::kNullObj) { static const char groupUnderParentCmdFormat[] - = "string $groupName = `group -name \"%s\" -absolute -parent \"%s\" \"%s\"`;"; + = "string $groupName = `group -name \"%s\" -absolute -parent \"%s\" \"%s\"`;\n"; std::string rootName = mMayaRootDagPath.fullPathName().asChar(); groupCmd = TfStringPrintf( groupUnderParentCmdFormat, @@ -657,7 +653,7 @@ bool UsdMaya_ReadJob::_ConvertUpAxisWithRotation( rootName.c_str()); } else { static const char groupUnderWorldCmdFormat[] - = "string $groupName = `group -name \"%s\" -absolute -world \"%s\"`;"; + = "string $groupName = `group -name \"%s\" -absolute -world \"%s\"`;\n"; groupCmd = TfStringPrintf( groupUnderWorldCmdFormat, groupName.c_str(), joinedDAGNodeNames.c_str()); } @@ -673,18 +669,27 @@ bool UsdMaya_ReadJob::_ConvertUpAxisWithRotation( // - 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) - { + if (conversion.needUpAxisConversion) { static const char rotationCmdFormat[] - = "rotate -relative -euler -pivot 0 0 0 -forceOrderXYZ %d 0 0 $groupName;"; + = "rotate -relative -euler -pivot 0 0 0 -forceOrderXYZ %d 0 0 $groupName;\n"; const int angleYtoZ = 90; const int angleZtoY = -90; std::string rotationCmd - = TfStringPrintf(rotationCmdFormat, convertUsdYtoMayaZ ? angleYtoZ : angleZtoY); + = TfStringPrintf(rotationCmdFormat, conversion.isMayaUpAxisZ ? angleYtoZ : angleZtoY); fullScript += rotationCmd; } + if (conversion.needUnitsConversion) { + static const char scalingCmdFormat[] + = "scale -relative -pivot 0 0 0 -scaleXYZ %f %f %f $groupName;\n"; + const double usdToMayaScaling = conversion.usdMetersPerUnit / conversion.mayaMetersPerUnit; + std::string scalingCmd = TfStringPrintf( + scalingCmdFormat, usdToMayaScaling, usdToMayaScaling, usdToMayaScaling); + fullScript += scalingCmd; + } + if (!keepParentGroup) { - static const char ungroupCmdFormat[] = "ungroup -absolute \"%s\";"; + static const char ungroupCmdFormat[] = "ungroup -absolute \"%s\";\n"; std::string ungroupCmd = TfStringPrintf(ungroupCmdFormat, groupName.c_str()); fullScript += ungroupCmd; } @@ -692,7 +697,7 @@ bool UsdMaya_ReadJob::_ConvertUpAxisWithRotation( if (!MGlobal::executeCommand(fullScript.c_str())) { MGlobal::displayWarning("Failed to add a transform to convert the up-axis to align " "the USD data with Maya up-axis."); - return false; + return; } if (keepParentGroup) { @@ -702,26 +707,67 @@ bool UsdMaya_ReadJob::_ConvertUpAxisWithRotation( sel.add(groupName.c_str()); sel.getDagPath(0, groupDagPath); } - _addOrignalUpAxisAttribute({ groupDagPath }, convertUsdYtoMayaZ); + if (conversion.needUpAxisConversion) + _addOrignalUpAxisAttribute({ groupDagPath }, conversion.isUSDUpAxisUZ); + if (conversion.needUnitsConversion) + _addOrignalUnitsAttribute({ groupDagPath }, conversion.usdMetersPerUnit); } else { - _addOrignalUpAxisAttribute(dagNodePaths, convertUsdYtoMayaZ); + if (conversion.needUpAxisConversion) + _addOrignalUpAxisAttribute(dagNodePaths, conversion.isUSDUpAxisUZ); + if (conversion.needUnitsConversion) + _addOrignalUnitsAttribute(dagNodePaths, conversion.usdMetersPerUnit); } - return true; + MGlobal::displayInfo( + "Mismatching axis and units have been converted for accurate orientation and scale."); } -bool UsdMaya_ReadJob::_ConvertUpAxisByChangingMayPrefs(const bool convertUsdYtoMayaZ) +void UsdMaya_ReadJob::_ConvertUpAxisAndUnitsByChangingMayaPrefs( + const UsdStageRefPtr& stage, + const ConversionInfo& conversion) { - const bool rotateView = true; - const MStatus status - = convertUsdYtoMayaZ ? MGlobal::setYAxisUp(rotateView) : MGlobal::setZAxisUp(rotateView); - if (!status) { - MGlobal::displayWarning( - "Failed to change the Maya up-axis preferences to match USD data up-axis."); - return false; + bool success = true; + + // Set up-axis preferences if needed. + if (conversion.needUpAxisConversion) { + const bool rotateView = true; + const MStatus status = conversion.isUSDUpAxisUZ ? MGlobal::setZAxisUp(rotateView) + : MGlobal::setYAxisUp(rotateView); + if (!status) { + MGlobal::displayWarning( + "Failed to change the Maya up-axis preferences to match USD data up-axis."); + success = false; + } } - return true; + // Set units preferences if needed. + if (conversion.needUnitsConversion) { + const MDistance::Unit mayaUnit + = UsdMayaUtil::ConvertUsdGeomLinearUnitToMDistanceUnit(conversion.usdMetersPerUnit); + if (mayaUnit == MDistance::kInvalid) { + MGlobal::displayWarning( + "Unable to convert to a Maya unit. Supported units include millimeters, " + "centimeters, meters, kilometers, inches, feet, yards and miles."); + success = false; + } else { + const MString mayaUnitText = UsdMayaUtil::ConvertMDistanceUnitToText(mayaUnit); + MString changeUnitsCmd; + changeUnitsCmd.format("currentUnit -linear ^1s;", mayaUnitText); + + // Note: we *must* execute the units change on-idle because the import process + // saves and restores all units! If we change it now, the change would be lost. + if (!MGlobal::executeCommandOnIdle(changeUnitsCmd)) { + MGlobal::displayWarning( + "Failed to change the Maya units preferences to match USD data " + "because the units are not supported by Maya."); + success = false; + } + } + } + + if (success) + MGlobal::displayInfo( + "Changed Maya preferences to match up-axis and units from the imported USD scene."); } bool UsdMaya_ReadJob::DoImport(UsdPrimRange& rootRange, const UsdPrim& usdRootPrim) diff --git a/lib/mayaUsd/fileio/jobs/readJob.h b/lib/mayaUsd/fileio/jobs/readJob.h index a5ff7c97e3..9abe5629cc 100644 --- a/lib/mayaUsd/fileio/jobs/readJob.h +++ b/lib/mayaUsd/fileio/jobs/readJob.h @@ -141,12 +141,25 @@ class UsdMaya_ReadJob double _setTimeSampleMultiplierFrom(const double layerFPS); - void _ConvertUpAxis(const UsdStageRefPtr& stage); - bool _ConvertUpAxisWithRotation( + struct ConversionInfo + { + bool isMayaUpAxisZ { false }; + bool isUSDUpAxisUZ { false }; + bool needUpAxisConversion { false }; + + double mayaMetersPerUnit = { 0.01 }; + double usdMetersPerUnit = { 0.01 }; + bool needUnitsConversion = { false }; + }; + + void _ConvertUpAxisAndUnits(const UsdStageRefPtr& stage); + void _ConvertUpAxisAndUnitsByModifyingData( const UsdStageRefPtr& stage, - bool convertUsdYtoMayaZ, + const ConversionInfo& conversion, bool keepParentGroup); - bool _ConvertUpAxisByChangingMayPrefs(const bool convertUsdYtoMayaZ); + void _ConvertUpAxisAndUnitsByChangingMayaPrefs( + const UsdStageRefPtr& stage, + const ConversionInfo& conversion); // Data MDagModifier mDagModifierUndo; diff --git a/lib/mayaUsd/fileio/jobs/writeJob.cpp b/lib/mayaUsd/fileio/jobs/writeJob.cpp index 3c708da069..f60980a88b 100644 --- a/lib/mayaUsd/fileio/jobs/writeJob.cpp +++ b/lib/mayaUsd/fileio/jobs/writeJob.cpp @@ -493,8 +493,6 @@ bool UsdMaya_WriteJob::_BeginWriting(const std::string& fileName, bool append) _autoAxisAndUnitsChanger = std::make_unique( mJobCtx.mStage, mJobCtx.mArgs.upAxis, mJobCtx.mArgs.unit); - // TODO: handle mJobCtx.mArgs.unit - // Set the customLayerData on the layer if (!mJobCtx.mArgs.customLayerData.empty()) { mJobCtx.mStage->GetRootLayer()->SetCustomLayerData(mJobCtx.mArgs.customLayerData); diff --git a/lib/mayaUsd/python/wrapPrimReader.cpp b/lib/mayaUsd/python/wrapPrimReader.cpp index 2018ce8b6c..3282990bb7 100644 --- a/lib/mayaUsd/python/wrapPrimReader.cpp +++ b/lib/mayaUsd/python/wrapPrimReader.cpp @@ -475,6 +475,7 @@ void wrapJobImportArgs() .def_readonly("importRelativeTextures", &UsdMayaJobImportArgs::importRelativeTextures) .def_readonly("axisAndUnitMethod", &UsdMayaJobImportArgs::axisAndUnitMethod) .def_readonly("upAxis", &UsdMayaJobImportArgs::upAxis) + .def_readonly("unit", &UsdMayaJobImportArgs::unit) .def_readonly("importWithProxyShapes", &UsdMayaJobImportArgs::importWithProxyShapes) .add_property( "includeAPINames", diff --git a/lib/mayaUsd/utils/util.cpp b/lib/mayaUsd/utils/util.cpp index b1036e772f..d41052293d 100644 --- a/lib/mayaUsd/utils/util.cpp +++ b/lib/mayaUsd/utils/util.cpp @@ -148,6 +148,20 @@ double UsdMayaUtil::ConvertMDistanceUnitToUsdGeomLinearUnit(const MDistance::Uni } } +MString UsdMayaUtil::ConvertMDistanceUnitToText(const MDistance::Unit mdistanceUnit) +{ + static std::map unitsConversionMap + = { { MDistance::kMillimeters, "mm" }, { MDistance::kCentimeters, "cm" }, + { MDistance::kMeters, "m" }, { MDistance::kKilometers, "km" }, + { MDistance::kInches, "inch" }, { MDistance::kFeet, "foot" }, + { MDistance::kYards, "yard" }, { MDistance::kMiles, "mile" } }; + + const auto iter = unitsConversionMap.find(mdistanceUnit); + if (iter == unitsConversionMap.end()) + return "cm"; + return iter->second; +} + MDistance::Unit UsdMayaUtil::ConvertUsdGeomLinearUnitToMDistanceUnit(const double linearUnit) { if (UsdGeomLinearUnitsAre(linearUnit, UsdGeomLinearUnits::millimeters)) { @@ -175,8 +189,7 @@ MDistance::Unit UsdMayaUtil::ConvertUsdGeomLinearUnitToMDistanceUnit(const doubl return MDistance::kMiles; } - TF_CODING_ERROR("Invalid UsdGeomLinearUnit %f. Assuming centimeters", linearUnit); - return MDistance::kCentimeters; + return MDistance::kInvalid; } double UsdMayaUtil::GetExportDistanceConversionScalar(const double metersPerUnit) diff --git a/lib/mayaUsd/utils/util.h b/lib/mayaUsd/utils/util.h index 3a81aa8fd7..5e44d439eb 100644 --- a/lib/mayaUsd/utils/util.h +++ b/lib/mayaUsd/utils/util.h @@ -152,6 +152,11 @@ 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 +/// to be used with the currentUnit MEL command. Invalid units return "cm". +MAYAUSD_CORE_PUBLIC +MString ConvertMDistanceUnitToText(const MDistance::Unit mdistanceUnit); + /// Returns a scaling value from Maya's internal units to the specified \p metersPerUnit MAYAUSD_CORE_PUBLIC double GetExportDistanceConversionScalar(const double metersPerUnit); diff --git a/plugin/adsk/scripts/mayaUSDRegisterStrings.mel b/plugin/adsk/scripts/mayaUSDRegisterStrings.mel index 8493519590..142cfc92ba 100644 --- a/plugin/adsk/scripts/mayaUSDRegisterStrings.mel +++ b/plugin/adsk/scripts/mayaUSDRegisterStrings.mel @@ -305,6 +305,10 @@ global proc mayaUSDRegisterStrings() register("kImportUpAxisAnn", "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("kImportUnit", "Unit"); + register("kImportUnitAnn", "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("kImportAxisAndUnitMethod", "Method"); // Note: initial is used to force Qt to render the text as HTML. register("kImportAxisAndUnitMethodAnn", "Select the method for axis/unit conversions.
" + diff --git a/plugin/adsk/scripts/mayaUsdTranslatorImport.mel b/plugin/adsk/scripts/mayaUsdTranslatorImport.mel index c277c62637..f5b4d8b23f 100644 --- a/plugin/adsk/scripts/mayaUsdTranslatorImport.mel +++ b/plugin/adsk/scripts/mayaUsdTranslatorImport.mel @@ -374,6 +374,7 @@ global proc mayaUsdTranslatorImport_EnableAllControls() { intFieldGrp -e -en1 1 -en2 1 mayaUsdTranslator_CustomFrameRange; checkBoxGrp -e -en 1 mayaUsdTranslator_ImportUpAxisCheckBox; + checkBoxGrp -e -en 1 mayaUsdTranslator_ImportUnitCheckBox; optionMenuGrp -e -en 1 mayaUsdTranslator_ImportkImportAxisAndUnitMethodMenu; mayaUsdTranslatorImport_enableContextOptions(); @@ -409,6 +410,8 @@ 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"); + } else if ($optionBreakDown[0] == "unit") { + mayaUsdTranslatorImport_SetCheckBoxGrp($optionBreakDown[1], $enable, "mayaUsdTranslator_ImportUnitCheckBox"); } else if ($optionBreakDown[0] == "axisAndUnitMethod") { mayaUsdTranslatorImport_SetOptionMenuByAnnotation($optionBreakDown[1], $enable, "mayaUsdTranslator_ImportkImportAxisAndUnitMethodMenu"); } else if ($optionBreakDown[0] == "useCustomFrameRange") { @@ -584,6 +587,7 @@ global proc int mayaUsdTranslatorImport (string $parent, 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; optionMenuGrp -l `getMayaUsdString("kImportAxisAndUnitMethod")` -cw 1 $cw1 -ann `getMayaUsdString("kImportAxisAndUnitMethodAnn")` mayaUsdTranslator_ImportkImportAxisAndUnitMethodMenu; menuItem -l `getMayaUsdString("kImportAxisAndUnitRotateScale")` -ann "rotateScale"; menuItem -l `getMayaUsdString("kImportAxisAndUnitAddTransform")` -ann "addTransform"; @@ -621,6 +625,7 @@ global proc int mayaUsdTranslatorImport (string $parent, if (!$forEditAsMaya) { $currentOptions = mayaUsdTranslatorImport_AppendFromCheckBoxGrp($currentOptions, "upAxis", "mayaUsdTranslator_ImportUpAxisCheckBox"); + $currentOptions = mayaUsdTranslatorImport_AppendFromCheckBoxGrp($currentOptions, "unit", "mayaUsdTranslator_ImportUnitCheckBox"); $currentOptions = mayaUsdTranslatorImport_AppendFromPopup($currentOptions, "axisAndUnitMethod", "mayaUsdTranslator_ImportkImportAxisAndUnitMethodMenu"); } eval($resultCallback+" \""+$currentOptions+"\""); diff --git a/test/lib/usd/translators/CMakeLists.txt b/test/lib/usd/translators/CMakeLists.txt index 6373994287..62f9608a21 100644 --- a/test/lib/usd/translators/CMakeLists.txt +++ b/test/lib/usd/translators/CMakeLists.txt @@ -89,6 +89,7 @@ set(TEST_SCRIPT_FILES testUsdImportUSDZTextures.py testUsdExportImportRoundtripPreviewSurface.py testUsdImportSkeleton.py + testUsdImportUnits.py testUsdImportUpAxis.py testUsdImportXforms.py testUsdImportXformAnim.py diff --git a/test/lib/usd/translators/UsdImportUnitsTests/UnitsMillimeters.usda b/test/lib/usd/translators/UsdImportUnitsTests/UnitsMillimeters.usda new file mode 100644 index 0000000000..438c5fce34 --- /dev/null +++ b/test/lib/usd/translators/UsdImportUnitsTests/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/usd/translators/testUsdImportUnits.py b/test/lib/usd/translators/testUsdImportUnits.py new file mode 100644 index 0000000000..ee8ac19457 --- /dev/null +++ b/test/lib/usd/translators/testUsdImportUnits.py @@ -0,0 +1,140 @@ +#!/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 + +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)) + + +class testUsdImportUpAxis(unittest.TestCase): + """Test for modifying the up-axis when importing.""" + + @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) + # Make sure up-axis is Y like in teh USD file. + cmds.upAxis(axis='y') + # Make sure the units are centimeters. + cmds.currentUnit(linear='cm') + + @unittest.skip('Preference is changed on idle, so we cannot get the new settings in a test.') + def testImportChangeMayaPrefs(self): + """Test importing and changing the Maya unit preference.""" + usd_file = os.path.join(self._path, "UsdImportUnitsTests", "UnitsMillimeters.usda") + + cmds.mayaUSDImport(file=usd_file, + primPath="/", + unit=1, + axisAndUnitMethod='overwritePrefs') + + cmds.sleep + + # Preference should have been changed. + expectedUnits = 'mm' + actualUnits = cmds.currentUnit(query=True, linear=True) + self.assertEqual(actualUnits, expectedUnits) + + def testImportScaleGroup(self): + """Test importing and adding a group to hold the scaling.""" + usd_file = os.path.join(self._path, "UsdImportUnitsTests", "UnitsMillimeters.usda") + + cmds.mayaUSDImport(file=usd_file, + primPath="/", + unit=1, + axisAndUnitMethod='addTransform') + + # Preference should have been left alone. + expectedUnits = 'cm' + actualUnits = cmds.currentUnit(query=True, linear=True) + self.assertEqual(actualUnits, expectedUnits) + + rootNodes = cmds.ls('RootPrim_converted') + self.assertEqual(len(rootNodes), 1) + + EPSILON = 1e-6 + + expectedScaling = [0.1, 0.1, 0.1] + actualScaling = _GetMayaScaling(rootNodes[0]) + self.assertTrue(Gf.IsClose(actualScaling, expectedScaling, EPSILON)) + + self.assertEqual(cmds.getAttr('%s.OriginalUSDMetersPerUnit' % rootNodes[0]), '0.001') + + def testImportScaleRootNodes(self): + """Test importing and scaling the root nodes.""" + usd_file = os.path.join(self._path, "UsdImportUnitsTests", "UnitsMillimeters.usda") + + cmds.mayaUSDImport(file=usd_file, + primPath="/", + unit=1, + axisAndUnitMethod='scaleRotate') + + # Preference should have been left alone. + expectedUnits = 'cm' + actualUnits = cmds.currentUnit(query=True, linear=True) + self.assertEqual(actualUnits, expectedUnits) + + rootNodes = cmds.ls('RootPrim') + self.assertEqual(len(rootNodes), 1) + + EPSILON = 1e-6 + + expectedScaling = [0.1, 0.1, 0.1] + actualScaling = _GetMayaScaling(rootNodes[0]) + self.assertTrue(Gf.IsClose(actualScaling, expectedScaling, EPSILON)) + + self.assertEqual(cmds.getAttr('%s.OriginalUSDMetersPerUnit' % rootNodes[0]), '0.001') + + +if __name__ == '__main__': + unittest.main(verbosity=2)