Skip to content

Commit

Permalink
EMSUSD-949 up axis in USD export
Browse files Browse the repository at this point in the history
- Add "-upAxis" (-upa) option to the export command.
- Document it.
- Add upAxis to the export job arguments structure.
- Add upAxis and its possible values to the export tokens.
- Expose upAxis job args to Python.
- Add up axis UI to the export dialog.
- Implement temporarily changing Maya up-axis. We rotate all root nodes
  90 degrees before and after the export.
- Add unit test.
  • Loading branch information
pierrebai-adsk committed Sep 24, 2024
1 parent 6ef3a6b commit 1c7804f
Show file tree
Hide file tree
Showing 13 changed files with 321 additions and 12 deletions.
1 change: 1 addition & 0 deletions lib/mayaUsd/commands/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ their own purposes, similar to the Alembic export chaser example.
| `-verbose` | `-v` | noarg | false | Make the command output more verbose |
| `-customLayerData` | `-cld` | string[3](multi) | none | Set the layers customLayerData metadata. Values are a list of three strings for key, value and data type |
| `-metersPerUnit` | `-mpu` | double | 0.0 | (Evolving) Exports with the given metersPerUnit. Use with care, as only certain attributes have their dimensions converted.<br/><br/> 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.<br/><br/> Currently, the following prim types are supported: <br/><ul><li>Meshes</li><li>Transforms</li></ul> |
| `-upAxis` | `-upa` | string | mayaPrefs | How the up-axis of the exported USD is controlled. "mayaPrefs" follows the current Maya Preferences. "none" does not author up-axis. "y" or "z" author that axis and convert data if teh Maya preferences does not match. |

#### Frame Samples

Expand Down
2 changes: 2 additions & 0 deletions lib/mayaUsd/commands/baseExportCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ MSyntax MayaUSDExportCommand::createSyntax()
syntax.addFlag(kRootPrimFlag, UsdMayaJobExportArgsTokens->rootPrim.GetText(), MSyntax::kString);
syntax.addFlag(
kRootPrimTypeFlag, UsdMayaJobExportArgsTokens->rootPrimType.GetText(), MSyntax::kString);
syntax.addFlag(
kUpAxisFlag, UsdMayaJobExportArgsTokens->upAxis.GetText(), MSyntax::kString);
syntax.addFlag(
kRenderableOnlyFlag, UsdMayaJobExportArgsTokens->renderableOnly.GetText(), MSyntax::kNoArg);
syntax.addFlag(
Expand Down
1 change: 1 addition & 0 deletions lib/mayaUsd/commands/baseExportCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class MAYAUSD_CORE_PUBLIC MayaUSDExportCommand : public MPxCommand
static constexpr auto kParentScopeFlag = "psc"; // deprecated
static constexpr auto kRootPrimFlag = "rpm";
static constexpr auto kRootPrimTypeFlag = "rpt";
static constexpr auto kUpAxisFlag = "upa";
static constexpr auto kRenderableOnlyFlag = "ro";
static constexpr auto kDefaultCamerasFlag = "dc";
static constexpr auto kRenderLayerModeFlag = "rlm";
Expand Down
9 changes: 9 additions & 0 deletions lib/mayaUsd/fileio/jobs/jobArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,13 @@ UsdMayaJobExportArgs::UsdMayaJobExportArgs(
UsdMayaJobExportArgsTokens->rootPrimType,
UsdMayaJobExportArgsTokens->scope,
{ UsdMayaJobExportArgsTokens->xform }))
, upAxis(extractToken(
userArgs,
UsdMayaJobExportArgsTokens->upAxis,
UsdMayaJobExportArgsTokens->mayaPrefs,
{ UsdMayaJobExportArgsTokens->none,
UsdMayaJobExportArgsTokens->y,
UsdMayaJobExportArgsTokens->z }))
, renderLayerMode(extractToken(
userArgs,
UsdMayaJobExportArgsTokens->renderLayerMode,
Expand Down Expand Up @@ -1137,6 +1144,7 @@ const VtDictionary& UsdMayaJobExportArgs::GetDefaultDictionary()
d[UsdMayaJobExportArgsTokens->parentScope] = std::string(); // Deprecated
d[UsdMayaJobExportArgsTokens->rootPrim] = std::string();
d[UsdMayaJobExportArgsTokens->rootPrimType] = UsdMayaJobExportArgsTokens->scope.GetString();
d[UsdMayaJobExportArgsTokens->upAxis] = UsdMayaJobExportArgsTokens->mayaPrefs.GetString();
d[UsdMayaJobExportArgsTokens->pythonPerFrameCallback] = std::string();
d[UsdMayaJobExportArgsTokens->pythonPostCallback] = std::string();
d[UsdMayaJobExportArgsTokens->renderableOnly] = false;
Expand Down Expand Up @@ -1241,6 +1249,7 @@ const VtDictionary& UsdMayaJobExportArgs::GetGuideDictionary()
d[UsdMayaJobExportArgsTokens->parentScope] = _string; // Deprecated
d[UsdMayaJobExportArgsTokens->rootPrim] = _string;
d[UsdMayaJobExportArgsTokens->rootPrimType] = _string;
d[UsdMayaJobExportArgsTokens->upAxis] = _string;
d[UsdMayaJobExportArgsTokens->pythonPerFrameCallback] = _string;
d[UsdMayaJobExportArgsTokens->pythonPostCallback] = _string;
d[UsdMayaJobExportArgsTokens->renderableOnly] = _boolean;
Expand Down
6 changes: 6 additions & 0 deletions lib/mayaUsd/fileio/jobs/jobArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ TF_DECLARE_PUBLIC_TOKENS(
(parentScope) \
(rootPrim) \
(rootPrimType) \
(upAxis) \
(pythonPerFrameCallback) \
(pythonPostCallback) \
(renderableOnly) \
Expand All @@ -125,6 +126,10 @@ TF_DECLARE_PUBLIC_TOKENS(
(excludeExportTypes) \
/* Special "none" token */ \
(none) \
/* up axis values */ \
(mayaPrefs) \
(y) \
(z) \
/* relative textures values */ \
(automatic) \
(absolute) \
Expand Down Expand Up @@ -258,6 +263,7 @@ struct UsdMayaJobExportArgs
const SdfPath parentScope; // Deprecated, use rootPrim instead.
const SdfPath rootPrim;
const TfToken rootPrimType;
const TfToken upAxis;
const TfToken renderLayerMode;
const TfToken rootKind;
const bool disableModelKindProcessor;
Expand Down
124 changes: 114 additions & 10 deletions lib/mayaUsd/fileio/jobs/writeJob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,112 @@ static TfToken _GetFallbackExtension(const TfToken& compatibilityMode)
return UsdMayaTranslatorTokens->UsdFileExtensionDefault;
}

class AutoUpAxisChanger
{
public:
AutoUpAxisChanger(const UsdStageRefPtr& stage, const TfToken& upAxisOption)
: _stage(stage)
{
_PrepareUpAxis(upAxisOption);
}

~AutoUpAxisChanger()
{
try {
_CleanupUpAxis();
} catch (const std::exception&) {
}
}

void cleanup() { _CleanupUpAxis(); }

private:
void _PrepareUpAxis(const TfToken& upAxisOption)
{
// If the user don't want to author the up-axis, we won't need to change the Maya up-axis.
const bool wantAuthorUpAxis = (upAxisOption != UsdMayaJobExportArgsTokens->none);
if (!wantAuthorUpAxis)
return;

// If the user want the up-axis authored in USD, well, author it.
const bool wantMayaPrefs = (upAxisOption == UsdMayaJobExportArgsTokens->mayaPrefs);
const bool isMayaUpAxisZ = MGlobal::isZAxisUp();
_wantUpAxisZ
= (wantMayaPrefs && isMayaUpAxisZ) || (upAxisOption == UsdMayaJobExportArgsTokens->z);
UsdGeomSetStageUpAxis(_stage, _wantUpAxisZ ? UsdGeomTokens->z : UsdGeomTokens->y);

// If the Maya up-axis is already the right one, we dont have to modify the Maya scene.
if (_wantUpAxisZ == isMayaUpAxisZ)
return;

// We need to modify the up-axis. Record if it was successful or not.
_modifiedUpAxis = _RotateUpAxis(_wantUpAxisZ);
}

void _CleanupUpAxis()
{
// If we did not modify the Maya up-axis, we don't have to restore it.
if (!_modifiedUpAxis)
return;

// Make sure cleanup will not be done twice even if there are exceptions.
_modifiedUpAxis = false;

// We are restoring the Maya scene up-axis, so we pass the opposite of the up-axis flag.
_RotateUpAxis(!_wantUpAxisZ);
}

static bool _RotateUpAxis(const bool wantUpAxisZ)
{
static const char* fullScriptFormat
= "proc rotateAllNodes(int $angle) {\n"
// Preserve the selection. Grouping and ungrouping changes it.
" string $selection[] = `ls -selection`;\n"
// Find all root nodes.
" string $rootNodeNames[] = `ls -assemblies`;\n"
// Group all root node under a new group:
//
// - Use -absolute to keep the grouped node world positions
// - Use -world to create the group under the root ofthe scene
// if the import was done at the root of the scene
// - Capture the new group name in a MEL variable called $groupName
" string $groupName = `group -absolute -world $rootNodeNames`;\n"
// Rotate the group to align with the desired axis.
//
// - Use relative rotation since we want to rotate the group as it is already
// positioned
// - Use -euler to make teh angle be relative to the current angle
// - Use forceOrderXYZ to force the rotation to be relative to world
// - Use -pivot to make sure we are rotating relative to the origin
// (The group is positioned at the center of all sub-object, so we need to
// specify the pivot)
" rotate -relative -euler -pivot 0 0 0 -forceOrderXYZ $angle 0 0 $groupName;\n"
// Ungroup while preserving the rotation.
" ungroup -absolute $groupName;\n"
// Restore the selection.
" select -replace $selection;\n"
"}\n"
"rotateAllNodes(%d);\n";

const int angleYtoZ = 90;
const int angleZtoY = -90;
const std::string fullScript
= TfStringPrintf(fullScriptFormat, wantUpAxisZ ? angleYtoZ : angleZtoY);

if (!MGlobal::executeCommand(fullScript.c_str())) {
MGlobal::displayWarning(
"Failed to temporarily rotate the scene to export with modified up-axis.");
return false;
}

return true;
}

UsdStageRefPtr _stage;
bool _wantUpAxisZ { false };
bool _modifiedUpAxis { false };
};

bool UsdMaya_WriteJob::Write(const std::string& fileName, bool append)
{
const std::vector<double>& timeSamples = mJobCtx.mArgs.timeSamples;
Expand Down Expand Up @@ -159,7 +265,9 @@ bool UsdMaya_WriteJob::Write(const std::string& fileName, bool append)
if (!_FinishWriting()) {
return false;
}

progressBar.advance();

return true;
}

Expand Down Expand Up @@ -328,6 +436,9 @@ bool UsdMaya_WriteJob::_BeginWriting(const std::string& fileName, bool append)
mJobCtx.mStage->SetFramesPerSecond(UsdMayaUtil::GetSceneMTimeUnitAsDouble());
}

// Temporarily change Maya's up-axis if needed.
_autoAxisChanger = std::make_unique<AutoUpAxisChanger>(mJobCtx.mStage, mJobCtx.mArgs.upAxis);

// Set the customLayerData on the layer
if (!mJobCtx.mArgs.customLayerData.empty()) {
mJobCtx.mStage->GetRootLayer()->SetCustomLayerData(mJobCtx.mArgs.customLayerData);
Expand Down Expand Up @@ -593,16 +704,6 @@ bool UsdMaya_WriteJob::_FinishWriting()
}
progressBar.advance();

// Unfortunately, MGlobal::isZAxisUp() is merely session state that does
// not get recorded in Maya files, so we cannot rely on it being set
// properly. Since "Y" is the more common upAxis, we'll just use
// isZAxisUp as an override to whatever our pipeline is configured for.
TfToken upAxis = UsdGeomGetFallbackUpAxis();
if (MGlobal::isZAxisUp()) {
upAxis = UsdGeomTokens->z;
}
UsdGeomSetStageUpAxis(mJobCtx.mStage, upAxis);

// XXX Currently all distance values are written directly to USD, and will
// be in centimeters (Maya's internal unit) despite what the users UIUnit
// preference is.
Expand Down Expand Up @@ -659,6 +760,9 @@ bool UsdMaya_WriteJob::_FinishWriting()
_PruneEmpties();
progressBar.advance();

// Restore Maya's up-axis if needed.
_autoAxisChanger.reset();

TF_STATUS("Saving stage");
if (mJobCtx.mStage->GetRootLayer()->PermissionToSave()) {
mJobCtx.mStage->GetRootLayer()->Save();
Expand Down
2 changes: 2 additions & 0 deletions lib/mayaUsd/fileio/jobs/writeJob.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
PXR_NAMESPACE_OPEN_SCOPE

class UsdMaya_ModelKindProcessor;
class AutoUpAxisChanger;

class UsdMaya_WriteJob
{
Expand Down Expand Up @@ -110,6 +111,7 @@ class UsdMaya_WriteJob
UsdMayaWriteJobContext mJobCtx;

std::unique_ptr<UsdMaya_ModelKindProcessor> _modelKindProcessor;
std::unique_ptr<AutoUpAxisChanger> _autoAxisChanger;
};

PXR_NAMESPACE_CLOSE_SCOPE
Expand Down
5 changes: 5 additions & 0 deletions lib/mayaUsd/python/wrapPrimWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,7 @@ void wrapJobExportArgs()
.def_readonly("file", &UsdMayaJobExportArgs::file)
.def_readonly("rootPrim", &UsdMayaJobExportArgs::rootPrim)
.def_readonly("rootPrimType", &UsdMayaJobExportArgs::rootPrimType)
.def_readonly("upAxis", &UsdMayaJobExportArgs::upAxis)
.add_property(
"filteredTypeIds",
make_getter(
Expand Down Expand Up @@ -607,6 +608,10 @@ void wrapJobExportArgs()
"rootPrimType",
make_getter(
&UsdMayaJobExportArgs::rootPrimType, return_value_policy<return_by_value>()))
.add_property(
"upAxis",
make_getter(
&UsdMayaJobExportArgs::upAxis, return_value_policy<return_by_value>()))
.def_readonly("pythonPerFrameCallback", &UsdMayaJobExportArgs::pythonPerFrameCallback)
.def_readonly("pythonPostCallback", &UsdMayaJobExportArgs::pythonPostCallback)
.add_property(
Expand Down
2 changes: 1 addition & 1 deletion lib/mayaUsd/resources/scripts/mayaUsdCacheMayaReference.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def fileOptionsTabPage(tabLayout):
fileOptionsScroll = cmds.columnLayout('fileOptionsScroll')
optionsText = mayaUsdOptions.convertOptionsDictToText(cacheToUsd.loadCacheCreationOptions())
optionsText = mayaUsdOptions.setAnimateOption(_mayaRefDagPath, optionsText)
mel.eval('mayaUsdTranslatorExport("fileOptionsScroll", "post={exportOpts}", "{cacheOpts}", "")'.format(exportOpts=kTranslatorExportOptions, cacheOpts=optionsText))
mel.eval('mayaUsdTranslatorExport("fileOptionsScroll", "post={exportOpts};cacheToUSD", "{cacheOpts}", "")'.format(exportOpts=kTranslatorExportOptions, cacheOpts=optionsText))

cacheFileUsdHierarchyOptions(topForm)

Expand Down
11 changes: 11 additions & 0 deletions plugin/adsk/scripts/mayaUSDRegisterStrings.mel
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,17 @@ global proc mayaUSDRegisterStrings()
register("kExportDefaultPrimNoneLbl", "None");
register("kExportDefaultPrimAnn", "As part of its metadata, each USD stage can identify a default prim.\nThis is the primitive that is referenced in if you reference in a file.");

register("kExportAxisAndUnitLbl", "Axis & Unit Conversion");
register("kExportUpAxisLbl", "Up Axis");
register("kExportUpAxisAnn", "<b.</b>Select the up axis for the export file." +
"Rotation will be applied if converting to a different axis." +
"<b>None</b>: do not author upAxis." +
"<b>Use Maya Preferences</b>: use the axis of the current scene.");
register("kExportUpAxisNoneLbl", "None");
register("kExportUpAxisMayaPrefsLbl", "Use Maya Preferences");
register("kExportUpAxisYLbl", "Y");
register("kExportUpAxisZLbl", "Z");

// All strings for import dialog:
register("kImportAnimationDataLbl", "Animation Data");
register("kImportCustomFrameRangeLbl", "Custom Frame Range");
Expand Down
Loading

0 comments on commit 1c7804f

Please sign in to comment.