diff --git a/lib/mayaUsd/commands/Readme.md b/lib/mayaUsd/commands/Readme.md
index 2ce016db62..a1a82e2c4f 100644
--- a/lib/mayaUsd/commands/Readme.md
+++ b/lib/mayaUsd/commands/Readme.md
@@ -150,7 +150,6 @@ their own purposes, similar to the Alembic export chaser example.
| `-defaultCameras` | `-dc` | noarg | false | Export the four Maya default cameras |
| `-defaultMeshScheme` | `-dms` | string | `catmullClark` | Sets the default subdivision scheme for exported Maya meshes, if the `USD_ATTR_subdivisionScheme` attribute is not present on the Mesh. Valid values are: `none`, `catmullClark`, `loop`, `bilinear` |
| `-exportDisplayColor` | `-dsp` | bool | false | Export display color |
-| `-exportDistanceUnit` | `-edu` | bool | true | Export the Maya distance unit to USD for the stage under its `metersPerUnit` attribute |
| `-jobContext` | `-jc` | string (multi) | none | Specifies an additional export context to handle. These usually contains extra schemas, primitives, and materials that are to be exported for a specific task, a target renderer for example. |
| `-defaultUSDFormat` | `-duf` | string | `usdc` | The exported USD file format, can be `usdc` for binary format or `usda` for ASCII format. |
| `-exportBlendShapes` | `-ebs` | bool | false | Enable or disable export of blend shapes |
@@ -206,7 +205,12 @@ 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:
|
+| `-exportDistanceUnit` | `-edu` | bool | false | Use the metersPerUnit option specified above for the stage under its `metersPerUnit` attribute |
| `-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 the Maya preferences does not match. |
+| `-unit` | `-unt` | string | mayaPrefs | How the measuring units of the exported USD is controlled. "mayaPrefs" follows the current Maya Preferences. "none" does not author up-axis. Explicit units (cm, inch, etc) author that and convert data if the Maya preferences does not match. |
+
+Note: the -metersPerUnit and -exportDistanceUnit are one way to change the exported units, the -unit is another.
+ We keep both to keep backward compatibility, but the -unit option is the favored way to handle the units.
#### Frame Samples
diff --git a/lib/mayaUsd/commands/baseExportCommand.cpp b/lib/mayaUsd/commands/baseExportCommand.cpp
index 561a392518..cae89d5fcf 100644
--- a/lib/mayaUsd/commands/baseExportCommand.cpp
+++ b/lib/mayaUsd/commands/baseExportCommand.cpp
@@ -182,6 +182,7 @@ MSyntax MayaUSDExportCommand::createSyntax()
syntax.addFlag(
kRootPrimTypeFlag, UsdMayaJobExportArgsTokens->rootPrimType.GetText(), MSyntax::kString);
syntax.addFlag(kUpAxisFlag, UsdMayaJobExportArgsTokens->upAxis.GetText(), MSyntax::kString);
+ syntax.addFlag(kUnitFlag, UsdMayaJobExportArgsTokens->unit.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 96b4a939b7..87e2a9ea95 100644
--- a/lib/mayaUsd/commands/baseExportCommand.h
+++ b/lib/mayaUsd/commands/baseExportCommand.h
@@ -80,6 +80,7 @@ class MAYAUSD_CORE_PUBLIC MayaUSDExportCommand : public MPxCommand
static constexpr auto kRootPrimFlag = "rpm";
static constexpr auto kRootPrimTypeFlag = "rpt";
static constexpr auto kUpAxisFlag = "upa";
+ static constexpr auto kUnitFlag = "unt";
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 04669cfb99..270cd429aa 100644
--- a/lib/mayaUsd/fileio/jobs/jobArgs.cpp
+++ b/lib/mayaUsd/fileio/jobs/jobArgs.cpp
@@ -755,6 +755,22 @@ UsdMayaJobExportArgs::UsdMayaJobExportArgs(
{ UsdMayaJobExportArgsTokens->none,
UsdMayaJobExportArgsTokens->y,
UsdMayaJobExportArgsTokens->z }))
+ , unit(extractToken(
+ userArgs,
+ UsdMayaJobExportArgsTokens->unit,
+ UsdMayaJobExportArgsTokens->mayaPrefs,
+ { UsdMayaJobExportArgsTokens->none,
+ UsdMayaJobExportArgsTokens->nm,
+ UsdMayaJobExportArgsTokens->um,
+ UsdMayaJobExportArgsTokens->mm,
+ UsdMayaJobExportArgsTokens->cm,
+ UsdMayaJobExportArgsTokens->m,
+ UsdMayaJobExportArgsTokens->km,
+ UsdMayaJobExportArgsTokens->lightyear,
+ UsdMayaJobExportArgsTokens->inch,
+ UsdMayaJobExportArgsTokens->foot,
+ UsdMayaJobExportArgsTokens->yard,
+ UsdMayaJobExportArgsTokens->mile }))
, renderLayerMode(extractToken(
userArgs,
UsdMayaJobExportArgsTokens->renderLayerMode,
@@ -1111,7 +1127,7 @@ const VtDictionary& UsdMayaJobExportArgs::GetDefaultDictionary()
d[UsdMayaJobExportArgsTokens->exportAssignedMaterials] = true;
d[UsdMayaJobExportArgsTokens->legacyMaterialScope] = false;
d[UsdMayaJobExportArgsTokens->exportDisplayColor] = false;
- d[UsdMayaJobExportArgsTokens->exportDistanceUnit] = true;
+ d[UsdMayaJobExportArgsTokens->exportDistanceUnit] = false;
d[UsdMayaJobExportArgsTokens->exportInstances] = true;
d[UsdMayaJobExportArgsTokens->exportMaterialCollections] = false;
d[UsdMayaJobExportArgsTokens->referenceObjectMode]
@@ -1147,6 +1163,7 @@ const VtDictionary& UsdMayaJobExportArgs::GetDefaultDictionary()
d[UsdMayaJobExportArgsTokens->rootPrim] = std::string();
d[UsdMayaJobExportArgsTokens->rootPrimType] = UsdMayaJobExportArgsTokens->scope.GetString();
d[UsdMayaJobExportArgsTokens->upAxis] = UsdMayaJobExportArgsTokens->mayaPrefs.GetString();
+ d[UsdMayaJobExportArgsTokens->unit] = UsdMayaJobExportArgsTokens->mayaPrefs.GetString();
d[UsdMayaJobExportArgsTokens->pythonPerFrameCallback] = std::string();
d[UsdMayaJobExportArgsTokens->pythonPostCallback] = std::string();
d[UsdMayaJobExportArgsTokens->renderableOnly] = false;
@@ -1253,6 +1270,7 @@ const VtDictionary& UsdMayaJobExportArgs::GetGuideDictionary()
d[UsdMayaJobExportArgsTokens->rootPrim] = _string;
d[UsdMayaJobExportArgsTokens->rootPrimType] = _string;
d[UsdMayaJobExportArgsTokens->upAxis] = _string;
+ d[UsdMayaJobExportArgsTokens->unit] = _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 77f74b3310..a5a3f2195e 100644
--- a/lib/mayaUsd/fileio/jobs/jobArgs.h
+++ b/lib/mayaUsd/fileio/jobs/jobArgs.h
@@ -108,6 +108,7 @@ TF_DECLARE_PUBLIC_TOKENS(
(rootPrim) \
(rootPrimType) \
(upAxis) \
+ (unit) \
(pythonPerFrameCallback) \
(pythonPostCallback) \
(renderableOnly) \
@@ -128,9 +129,24 @@ TF_DECLARE_PUBLIC_TOKENS(
/* Special "none" token */ \
(none) \
/* up axis values */ \
+ /* (none) */ \
(mayaPrefs) \
(y) \
(z) \
+ /* unit values */ \
+ /* (none) */ \
+ /* (mayaPrefs) */ \
+ (nm) \
+ (um) \
+ (mm) \
+ (cm) \
+ (m) \
+ (km) \
+ (lightyear) \
+ (inch) \
+ (foot) \
+ (yard) \
+ (mile) \
/* relative textures values */ \
(automatic) \
(absolute) \
@@ -272,6 +288,7 @@ struct UsdMayaJobExportArgs
const SdfPath rootPrim;
const TfToken rootPrimType;
const TfToken upAxis;
+ const TfToken unit;
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 ef7e887268..3c708da069 100644
--- a/lib/mayaUsd/fileio/jobs/writeJob.cpp
+++ b/lib/mayaUsd/fileio/jobs/writeJob.cpp
@@ -107,22 +107,93 @@ static TfToken _GetFallbackExtension(const TfToken& compatibilityMode)
return UsdMayaTranslatorTokens->UsdFileExtensionDefault;
}
-/// Class to automatically change and restore the up-axis of the Maya scene.
-class AutoUpAxisChanger : public MayaUsd::AutoUndoCommands
+/// Class to automatically change and restore the up-axis and units of the Maya scene.
+class AutoUpAxisAndUnitsChanger : public MayaUsd::AutoUndoCommands
{
public:
- AutoUpAxisChanger(const PXR_NS::UsdStageRefPtr& stage, const PXR_NS::TfToken& upAxisOption)
- : AutoUndoCommands("change up-axis", _prepareCommands(stage, upAxisOption))
+ AutoUpAxisAndUnitsChanger(
+ const PXR_NS::UsdStageRefPtr& stage,
+ const PXR_NS::TfToken& upAxisOption,
+ const PXR_NS::TfToken& unitsOption)
+ : AutoUndoCommands(
+ "change up-axis and units",
+ _prepareCommands(stage, upAxisOption, unitsOption))
{
}
private:
+ static double _convertOptionUnitsToUSDUnits(const TfToken& unitsOption)
+ {
+ static std::map unitsConversionMap
+ = { { UsdMayaJobExportArgsTokens->nm, UsdGeomLinearUnits::nanometers },
+ { UsdMayaJobExportArgsTokens->um, UsdGeomLinearUnits::micrometers },
+ { UsdMayaJobExportArgsTokens->mm, UsdGeomLinearUnits::millimeters },
+ { UsdMayaJobExportArgsTokens->cm, UsdGeomLinearUnits::centimeters },
+ { UsdMayaJobExportArgsTokens->m, UsdGeomLinearUnits::meters },
+ { UsdMayaJobExportArgsTokens->km, UsdGeomLinearUnits::kilometers },
+ { UsdMayaJobExportArgsTokens->lightyear, UsdGeomLinearUnits::lightYears },
+ { UsdMayaJobExportArgsTokens->inch, UsdGeomLinearUnits::inches },
+ { UsdMayaJobExportArgsTokens->foot, UsdGeomLinearUnits::feet },
+ { UsdMayaJobExportArgsTokens->yard, UsdGeomLinearUnits::yards },
+ { UsdMayaJobExportArgsTokens->mile, UsdGeomLinearUnits::miles } };
+
+ const auto iter = unitsConversionMap.find(unitsOption);
+ if (iter == unitsConversionMap.end())
+ return UsdGeomLinearUnits::centimeters;
+ return iter->second;
+ }
+
+ static TfToken _convertMayaUnitsToOptionUnits(MDistance::Unit mayaUnits)
+ {
+ static std::map unitsConversionMap
+ = { { MDistance::kMillimeters, UsdMayaJobExportArgsTokens->mm },
+ { MDistance::kCentimeters, UsdMayaJobExportArgsTokens->cm },
+ { MDistance::kMeters, UsdMayaJobExportArgsTokens->m },
+ { MDistance::kKilometers, UsdMayaJobExportArgsTokens->km },
+ { MDistance::kInches, UsdMayaJobExportArgsTokens->inch },
+ { MDistance::kFeet, UsdMayaJobExportArgsTokens->foot },
+ { MDistance::kYards, UsdMayaJobExportArgsTokens->yard },
+ { MDistance::kMiles, UsdMayaJobExportArgsTokens->mile } };
+
+ const auto iter = unitsConversionMap.find(mayaUnits);
+ if (iter == unitsConversionMap.end())
+ return UsdMayaJobExportArgsTokens->cm;
+ return iter->second;
+ }
+
+ static std::string
+ _prepareUnitsCommands(const UsdStageRefPtr& stage, const TfToken& unitsOption)
+ {
+ // If the user don't want to author the unit, we won't need to change the Maya unit.
+ if (unitsOption == UsdMayaJobExportArgsTokens->none)
+ return {};
+
+ // If the user want the unit authored in USD, well, author it.
+ const bool wantMayaPrefs = (unitsOption == UsdMayaJobExportArgsTokens->mayaPrefs);
+ const TfToken mayaUIUnits = _convertMayaUnitsToOptionUnits(MDistance::uiUnit());
+ const TfToken mayaDataUnits = _convertMayaUnitsToOptionUnits(MDistance::internalUnit());
+ const TfToken wantedUnits = wantMayaPrefs ? mayaUIUnits : unitsOption;
+ const double usdMetersPerUnit = _convertOptionUnitsToUSDUnits(wantedUnits);
+ UsdGeomSetStageMetersPerUnit(stage, usdMetersPerUnit);
+
+ // If the Maya data unit is already the right one, we dont have to modify the Maya scene.
+ if (wantedUnits == mayaDataUnits)
+ return {};
+
+ static const char scalingCommandsFormat[]
+ = "scale -relative -pivot 0 0 0 -scaleXYZ %f %f %f $groupName;\n";
+
+ const double mayaMetersPerUnit = _convertOptionUnitsToUSDUnits(mayaDataUnits);
+ const double requiredScale = mayaMetersPerUnit / usdMetersPerUnit;
+
+ return TfStringPrintf(scalingCommandsFormat, requiredScale, requiredScale, requiredScale);
+ }
+
static std::string
- _prepareCommands(const PXR_NS::UsdStageRefPtr& stage, const PXR_NS::TfToken& upAxisOption)
+ _prepareUpAxisCommands(const PXR_NS::UsdStageRefPtr& stage, const PXR_NS::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)
+ if (upAxisOption == UsdMayaJobExportArgsTokens->none)
return {};
// If the user want the up-axis authored in USD, well, author it.
@@ -136,7 +207,41 @@ class AutoUpAxisChanger : public MayaUsd::AutoUndoCommands
if (wantUpAxisZ == isMayaUpAxisZ)
return {};
- static const char fullScriptFormat[] =
+ static const char rotationCommandsFormat[] =
+ // 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 the 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 %d 0 0 $groupName;\n";
+
+ const int angleYtoZ = 90;
+ const int angleZtoY = -90;
+ const int rotationAngle = wantUpAxisZ ? angleYtoZ : angleZtoY;
+
+ return TfStringPrintf(rotationCommandsFormat, rotationAngle);
+ }
+
+ static std::string _prepareCommands(
+ const PXR_NS::UsdStageRefPtr& stage,
+ const PXR_NS::TfToken& upAxisOption,
+ const PXR_NS::TfToken& unitsOption)
+ {
+ // These commands wrap the scene-changing commands by providing:
+ //
+ // - the list of root names as the variable $rootNodeNames
+ // - a group containing all those nodes named $groupName
+ // -
+ //
+ // The scene-changing commands should mofify the group, so that ungrouping
+ // these node while preserving transform changes were done on the group will
+ // modify each root node individually.
+
+ static const char scriptPrefix[] =
// Preserve the selection. Grouping and ungrouping changes it.
"string $selection[] = `ls -selection`;\n"
// Find all root nodes.
@@ -147,25 +252,22 @@ class AutoUpAxisChanger : public MayaUsd::AutoUndoCommands
// - 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 %d 0 0 $groupName;\n"
- // Ungroup while preserving the rotation.
+ "string $groupName = `group -absolute -world $rootNodeNames`;\n";
+
+ static const char scriptSuffix[] = // Ungroup while preserving the rotation.
"ungroup -absolute $groupName;\n"
// Restore the selection.
"select -replace $selection;\n";
- const int angleYtoZ = 90;
- const int angleZtoY = -90;
- return TfStringPrintf(fullScriptFormat, wantUpAxisZ ? angleYtoZ : angleZtoY);
+ const std::string upAxisCommands = _prepareUpAxisCommands(stage, upAxisOption);
+ const std::string unitsCommands = _prepareUnitsCommands(stage, unitsOption);
+
+ // If both are empty, we don't need to do anything.
+ if (upAxisCommands.empty() && unitsCommands.empty())
+ return {};
+
+ const std::string fullScript = scriptPrefix + upAxisCommands + unitsCommands + scriptSuffix;
+ return fullScript;
}
};
@@ -388,7 +490,10 @@ bool UsdMaya_WriteJob::_BeginWriting(const std::string& fileName, bool append)
}
// Temporarily change Maya's up-axis if needed.
- _autoAxisChanger = std::make_unique(mJobCtx.mStage, mJobCtx.mArgs.upAxis);
+ _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()) {
@@ -662,8 +767,7 @@ bool UsdMaya_WriteJob::_FinishWriting()
MDistance::Unit mayaInternalUnit = MDistance::internalUnit();
auto mayaInternalUnitLinear
= UsdMayaUtil::ConvertMDistanceUnitToUsdGeomLinearUnit(mayaInternalUnit);
- if (mayaInternalUnit != MDistance::uiUnit()
- || mJobCtx.mArgs.metersPerUnit != mayaInternalUnitLinear) {
+ if (mJobCtx.mArgs.metersPerUnit != mayaInternalUnitLinear) {
TF_WARN(
"Support for Distance unit conversion is evolving. "
"All distance units will be written in %s except where conversion is supported "
@@ -721,7 +825,7 @@ bool UsdMaya_WriteJob::_FinishWriting()
progressBar.advance();
// Restore Maya's up-axis if needed.
- _autoAxisChanger.reset();
+ _autoAxisAndUnitsChanger.reset();
TF_STATUS("Saving stage");
if (mJobCtx.mStage->GetRootLayer()->PermissionToSave()) {
diff --git a/lib/mayaUsd/fileio/jobs/writeJob.h b/lib/mayaUsd/fileio/jobs/writeJob.h
index a6a5b96d22..064c18aa9e 100644
--- a/lib/mayaUsd/fileio/jobs/writeJob.h
+++ b/lib/mayaUsd/fileio/jobs/writeJob.h
@@ -31,7 +31,7 @@
PXR_NAMESPACE_OPEN_SCOPE
class UsdMaya_ModelKindProcessor;
-class AutoUpAxisChanger;
+class AutoUpAxisAndUnitsChanger;
class UsdMaya_WriteJob
{
@@ -118,7 +118,7 @@ class UsdMaya_WriteJob
UsdMayaWriteJobContext mJobCtx;
std::unique_ptr _modelKindProcessor;
- std::unique_ptr _autoAxisChanger;
+ std::unique_ptr _autoAxisAndUnitsChanger;
};
PXR_NAMESPACE_CLOSE_SCOPE
diff --git a/lib/mayaUsd/python/wrapPrimWriter.cpp b/lib/mayaUsd/python/wrapPrimWriter.cpp
index 4169cb680d..32ed0bba64 100644
--- a/lib/mayaUsd/python/wrapPrimWriter.cpp
+++ b/lib/mayaUsd/python/wrapPrimWriter.cpp
@@ -565,6 +565,7 @@ void wrapJobExportArgs()
.def_readonly("rootPrim", &UsdMayaJobExportArgs::rootPrim)
.def_readonly("rootPrimType", &UsdMayaJobExportArgs::rootPrimType)
.def_readonly("upAxis", &UsdMayaJobExportArgs::upAxis)
+ .def_readonly("unit", &UsdMayaJobExportArgs::unit)
.add_property(
"filteredTypeIds",
make_getter(
@@ -612,6 +613,9 @@ void wrapJobExportArgs()
.add_property(
"upAxis",
make_getter(&UsdMayaJobExportArgs::upAxis, return_value_policy()))
+ .add_property(
+ "unit",
+ make_getter(&UsdMayaJobExportArgs::unit, return_value_policy()))
.def_readonly("pythonPerFrameCallback", &UsdMayaJobExportArgs::pythonPerFrameCallback)
.def_readonly("pythonPostCallback", &UsdMayaJobExportArgs::pythonPostCallback)
.add_property(
diff --git a/lib/mayaUsd/utils/autoUndoCommands.cpp b/lib/mayaUsd/utils/autoUndoCommands.cpp
index c899087c12..bf3f69dd65 100644
--- a/lib/mayaUsd/utils/autoUndoCommands.cpp
+++ b/lib/mayaUsd/utils/autoUndoCommands.cpp
@@ -12,9 +12,6 @@
// 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.
-//
-// Modifications copyright (C) 2020 Autodesk
-//
#include "autoUndoCommands.h"
diff --git a/plugin/adsk/scripts/mayaUSDRegisterStrings.mel b/plugin/adsk/scripts/mayaUSDRegisterStrings.mel
index 4926fb52a0..8493519590 100644
--- a/plugin/adsk/scripts/mayaUSDRegisterStrings.mel
+++ b/plugin/adsk/scripts/mayaUSDRegisterStrings.mel
@@ -243,6 +243,22 @@ global proc mayaUSDRegisterStrings()
register("kExportUpAxisYLbl", "Y");
register("kExportUpAxisZLbl", "Z");
+ register("kExportUnitLbl", "Unit");
+ register("kExportUnitAnn", "Select the unit for the export file." +
+ " Scaling will be applied if converting to a different unit.
" +
+ "None: Do not scale or write out unit metadata.
" +
+ "Use Maya Preferences: Use the unit of the current scene.
");
+ register("kExportUnitNoneLbl", "None");
+ register("kExportUnitMayaPrefsLbl", "Use Maya Preferences");
+ register("kExportUnitMillimeterLbl", "Millimeter");
+ register("kExportUnitCentimeterLbl", "Centimeter");
+ register("kExportUnitMeterLbl", "Meter");
+ register("kExportUnitKilometerLbl", "Kilometer");
+ register("kExportUnitInchLbl", "Inch");
+ register("kExportUnitFootLbl", "Foot");
+ register("kExportUnitYardLbl", "Yard");
+ register("kExportUnitMileLbl", "Mile");
+
// 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 7967ba8224..73698b3d8e 100644
--- a/plugin/adsk/scripts/mayaUsdTranslatorExport.mel
+++ b/plugin/adsk/scripts/mayaUsdTranslatorExport.mel
@@ -826,6 +826,7 @@ global proc mayaUsdTranslatorExport_EnableAllControls() {
checkBoxGrp -e -en 1 includeNamespacesCheckBox;
checkBoxGrp -e -en 1 worldspaceCheckBox;
optionMenuGrp -e -en 1 upAxisPopup;
+ optionMenuGrp -e -en 1 unitPopup;
}
if (stringArrayContains("context", $sectionNames)) {
@@ -946,6 +947,8 @@ global proc mayaUsdTranslatorExport_SetFromOptions(string $currentOptions, int $
mayaUsdTranslatorExport_SetOptionMenuByBool($optionBreakDown[1], $enable, "exportInstancesPopup");
} else if ($optionBreakDown[0] == "upAxis") {
mayaUsdTranslatorExport_SetOptionMenuByAnnotation($optionBreakDown[1], $enable, "upAxisPopup");
+ } else if ($optionBreakDown[0] == "unit") {
+ mayaUsdTranslatorExport_SetOptionMenuByAnnotation($optionBreakDown[1], $enable, "unitPopup");
} else if ($optionBreakDown[0] == "exportVisibility") {
mayaUsdTranslatorExport_SetCheckbox($optionBreakDown[1], $enable, "exportVisibilityCheckBox");
} else if ($optionBreakDown[0] == "mergeTransformAndShape") {
@@ -1132,20 +1135,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;
+ int $canControlUpAxisAndUnit = 1;
if (stringArrayContains("duplicate", $sectionNames)) {
$canExportStagesAsRefs = 0;
- $canControlUpAxis = 0;
+ $canControlUpAxisAndUnit = 0;
}
if (stringArrayContains("mergeToUSD", $sectionNames)) {
$canExportStagesAsRefs = 0;
- $canControlUpAxis = 0;
+ $canControlUpAxisAndUnit = 0;
}
if (stringArrayContains("cacheToUSD", $sectionNames)) {
- $canControlUpAxis = 0;
+ $canControlUpAxisAndUnit = 0;
}
setParent $parent;
@@ -1331,16 +1334,35 @@ global proc int mayaUsdTranslatorExport (string $parent,
-value1 1 exportStagesAsRefsCheckBox;
}
- if ($canControlUpAxis) {
+ if ($canControlUpAxisAndUnit) {
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 -divider on;
menuItem -l `getMayaUsdString("kExportUpAxisYLbl")` -ann "y";
menuItem -l `getMayaUsdString("kExportUpAxisZLbl")` -ann "z";
+ // This default-select Maya Prefs item
+ optionMenuGrp -edit -select 2 upAxisPopup;
+
+ optionMenuGrp -l `getMayaUsdString("kExportUnitLbl")` -annotation `getMayaUsdString("kExportUnitAnn")` unitPopup;
+ menuItem -l `getMayaUsdString("kExportUnitNoneLbl")` -ann "none";
+ menuItem -l `getMayaUsdString("kExportUnitMayaPrefsLbl")` -ann "mayaPrefs";
+ menuItem -divider on;
+ menuItem -l `getMayaUsdString("kExportUnitMillimeterLbl")` -ann "mm";
+ menuItem -l `getMayaUsdString("kExportUnitCentimeterLbl")` -ann "cm";
+ menuItem -l `getMayaUsdString("kExportUnitMeterLbl")` -ann "m";
+ menuItem -l `getMayaUsdString("kExportUnitKilometerLbl")` -ann "km";
+ menuItem -divider on;
+ menuItem -l `getMayaUsdString("kExportUnitInchLbl")` -ann "inch";
+ menuItem -l `getMayaUsdString("kExportUnitFootLbl")` -ann "foot";
+ menuItem -l `getMayaUsdString("kExportUnitYardLbl")` -ann "yard";
+ menuItem -l `getMayaUsdString("kExportUnitMileLbl")` -ann "mile";
+ // This default-select Maya Prefs item
+ optionMenuGrp -edit -select 2 unitPopup;
setParent ..;
}
@@ -1410,6 +1432,7 @@ global proc int mayaUsdTranslatorExport (string $parent,
$currentOptions = mayaUsdTranslatorExport_AppendFromCheckbox($currentOptions, "worldspace", "worldspaceCheckBox");
$currentOptions = mayaUsdTranslatorExport_AppendFromCheckbox($currentOptions, "exportStagesAsRefs", "exportStagesAsRefsCheckBox");
$currentOptions = mayaUsdTranslatorExport_AppendFromPopup($currentOptions, "upAxis", "upAxisPopup");
+ $currentOptions = mayaUsdTranslatorExport_AppendFromPopup($currentOptions, "unit", "unitPopup");
}
if (stringArrayContains("context", $sectionNames)) {
diff --git a/test/lib/usd/translators/CMakeLists.txt b/test/lib/usd/translators/CMakeLists.txt
index e6ee198217..6373994287 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
+ testUsdExportUnits.py
testUsdExportUpAxis.py
# To investigate: following test asserts in MFnParticleSystem, but passes.
diff --git a/test/lib/usd/translators/testUsdExportUnits.py b/test/lib/usd/translators/testUsdExportUnits.py
new file mode 100644
index 0000000000..c0d69d1880
--- /dev/null
+++ b/test/lib/usd/translators/testUsdExportUnits.py
@@ -0,0 +1,155 @@
+#!/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 testUsdExportUnits(unittest.TestCase):
+ """Test for modifying the units 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.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()
+ cmds.move(3, 0, 0, relative=True)
+
+ usdFile = os.path.abspath('UsdExportUnits_None.usda')
+ cmds.mayaUSDExport(file=usdFile,
+ shadingMode='none',
+ unit='none')
+
+ stage = Usd.Stage.Open(usdFile)
+ self.assertFalse(stage.HasAuthoredMetadata('metersPerUnit'))
+
+ spherePrim = stage.GetPrimAtPath('/pSphere1')
+ self.assertTrue(spherePrim)
+
+ self.assertPrimXform(spherePrim, [
+ ('xformOp:translate', (3., 0., 0.))])
+
+ def testExportUnitsFollowMayaPrefs(self):
+ """Test exporting and following the Maya unit preference."""
+ cmds.polySphere()
+ cmds.move(0, 0, 3, relative=True)
+
+ usdFile = os.path.abspath('UsdExportUnits_FollowMayaPrefs.usda')
+ cmds.mayaUSDExport(file=usdFile,
+ shadingMode='none',
+ unit='mayaPrefs')
+
+ stage = Usd.Stage.Open(usdFile)
+ self.assertTrue(stage.HasAuthoredMetadata('metersPerUnit'))
+
+ expectedMetersPerUnit = 0.01
+ actualMetersPerUnit = UsdGeom.GetStageMetersPerUnit(stage)
+ self.assertEqual(actualMetersPerUnit, expectedMetersPerUnit)
+
+ spherePrim = stage.GetPrimAtPath('/pSphere1')
+ self.assertTrue(spherePrim)
+
+ self.assertPrimXform(spherePrim, [
+ ('xformOp:translate', (0., 0., 3.))])
+
+ def testExportUnitsFollowDifferentMayaPrefs(self):
+ """Test exporting and following the Maya unit preference when they differ from the internal units."""
+ cmds.polySphere()
+ cmds.move(0, 0, 3, relative=True)
+
+ cmds.currentUnit(linear='mm')
+
+ usdFile = os.path.abspath('UsdExportUnits_FollowMayaPrefs.usda')
+ cmds.mayaUSDExport(file=usdFile,
+ shadingMode='none',
+ unit='mayaPrefs')
+
+ stage = Usd.Stage.Open(usdFile)
+ self.assertTrue(stage.HasAuthoredMetadata('metersPerUnit'))
+
+ expectedMetersPerUnit = 0.001
+ actualMetersPerUnit = UsdGeom.GetStageMetersPerUnit(stage)
+ self.assertEqual(actualMetersPerUnit, expectedMetersPerUnit)
+
+ spherePrim = stage.GetPrimAtPath('/pSphere1')
+ self.assertTrue(spherePrim)
+
+ self.assertPrimXform(spherePrim, [
+ ('xformOp:translate', (0., 0., 30.)),
+ ('xformOp:scale', (10., 10., 10.))])
+
+ def testExportUnitsDifferentUnits(self):
+ """Test exporting and forcing units of kilometers, different from Maya prefs."""
+ cmds.polySphere()
+ cmds.move(0, 0, 3, relative=True)
+
+ usdFile = os.path.abspath('UsdExportUnits_DifferentY.usda')
+ cmds.mayaUSDExport(file=usdFile,
+ shadingMode='none',
+ unit='km')
+
+ stage = Usd.Stage.Open(usdFile)
+ self.assertTrue(stage.HasAuthoredMetadata('metersPerUnit'))
+
+ expectedMetersPerUnit = 1000.
+ actualMetersPerUnit = UsdGeom.GetStageMetersPerUnit(stage)
+ self.assertEqual(actualMetersPerUnit, expectedMetersPerUnit)
+
+ spherePrim = stage.GetPrimAtPath('/pSphere1')
+ self.assertTrue(spherePrim)
+
+ self.assertPrimXform(spherePrim, [
+ ('xformOp:translate', (0., 0., 0.00003)),
+ ('xformOp:scale', (0.00001, 0.00001, 0.00001))])
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)