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.
- Add AutoUndoCommands class to execute and automatically undo a set of
  MEL commands.
- Use AutoUndoCommand to automatically change and restore the up-axis.
- Implement temporarily changing Maya up-axis. We rotate all root nodes
  90 degrees before and after the export.
- We use undo so that the values are perfectly restored.
- Add unit test.
  • Loading branch information
pierrebai-adsk committed Sep 26, 2024
1 parent 6ef3a6b commit 57b23cc
Show file tree
Hide file tree
Showing 16 changed files with 449 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
1 change: 1 addition & 0 deletions lib/mayaUsd/commands/baseExportCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ MSyntax MayaUSDExportCommand::createSyntax()
syntax.addFlag(kRootPrimFlag, UsdMayaJobExportArgsTokens->rootPrim.GetText(), MSyntax::kString);
syntax.addFlag(
kRootPrimTypeFlag, UsdMayaJobExportArgsTokens->rootPrimType.GetText(), MSyntax::kString);
syntax.addFlag(kUpAxisFlag, UsdMayaJobExportArgsTokens->upAxis.GetText(), MSyntax::kString);
syntax.addFlag(
kRenderableOnlyFlag, UsdMayaJobExportArgsTokens->renderableOnly.GetText(), MSyntax::kNoArg);
syntax.addFlag(
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
81 changes: 71 additions & 10 deletions lib/mayaUsd/fileio/jobs/writeJob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
#include <mayaUsd/fileio/shading/shadingModeExporterContext.h>
#include <mayaUsd/fileio/transformWriter.h>
#include <mayaUsd/fileio/translators/translatorMaterial.h>
#include <mayaUsd/utils/autoUndoCommands.h>
#include <mayaUsd/utils/progressBarScope.h>
#include <mayaUsd/utils/util.h>

Expand Down Expand Up @@ -112,6 +113,68 @@ 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
{
public:
AutoUpAxisChanger(const PXR_NS::UsdStageRefPtr& stage, const PXR_NS::TfToken& upAxisOption)
: AutoUndoCommands("change up-axis", _prepareCommands(stage, upAxisOption))
{
}

private:
static std::string
_prepareCommands(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)
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();
const bool 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 {};

static const char fullScriptFormat[] =
// 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 %d 0 0 $groupName;\n"
// 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);
}
};

bool UsdMaya_WriteJob::Write(const std::string& fileName, bool append)
{
const std::vector<double>& timeSamples = mJobCtx.mArgs.timeSamples;
Expand Down Expand Up @@ -159,7 +222,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 +393,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 +661,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 +717,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
4 changes: 4 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,9 @@ 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
2 changes: 2 additions & 0 deletions lib/mayaUsd/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# -----------------------------------------------------------------------------
target_sources(${PROJECT_NAME}
PRIVATE
autoUndoCommands.cpp
blockSceneModificationContext.cpp
colorSpace.cpp
converter.cpp
Expand Down Expand Up @@ -36,6 +37,7 @@ target_sources(${PROJECT_NAME}
)

set(HEADERS
autoUndoCommands.h
blockSceneModificationContext.h
colorSpace.h
customLayerData.h
Expand Down
119 changes: 119 additions & 0 deletions lib/mayaUsd/utils/autoUndoCommands.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//
// 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.
//
// Modifications copyright (C) 2020 Autodesk
//

#include "autoUndoCommands.h"

#include <maya/MGlobal.h>
#include <maya/MString.h>

namespace MAYAUSD_NS_DEF {

AutoUndoCommands::AutoUndoCommands(const char* scriptName, const std::string& commands)
: _scriptName(scriptName)
{
_executeCommands(commands);
}

AutoUndoCommands::~AutoUndoCommands()
{
try {
_undoCommands();
} catch (const std::exception&) {
}
}

void AutoUndoCommands::undo() { _undoCommands(); }

void AutoUndoCommands::disableUndo() { _needUndo = false; }

void AutoUndoCommands::_executeCommands(const std::string& commands)
{
// If no commands were provided, we do nothing.
// This allow sub-classes to cancel the execution if needed by providing
// no commands.
if (commands.empty())
return;

// Put all the requested commands inside a function to isolate any
// variable they migh declare.
static const char scriptPrefix[] = "proc _executeCommandsToBeUndone() {\n";

// Wrap all commands in a undo block (undo chunk in Maya parlance).
static const char scriptSuffix[]
= "}\n"
"proc _executeCommandsWithUndo() {\n"
// We need to re-enable undo for this because we may be executed
// in a context that disbaled undo.
" int $undoWereActive = `undoInfo -query -state`;\n"
" undoInfo -stateWithoutFlush 1;\n"
// Open the undo block. to make all commands undoable as a unit.
" undoInfo -openChunk;\n"
// Execute the commands.
" _executeCommandsToBeUndone();\n"
// Close the undo bloack to make the whole process undoable as a unit.
" undoInfo -closeChunk;\n"
// Restore the undo active flag.
" undoInfo -stateWithoutFlush $undoWereActive;\n"
"}\n"
"_executeCommandsWithUndo();\n";

std::string fullScript = std::string(scriptPrefix) + commands + std::string(scriptSuffix);

const bool displayEnabled = false;
const bool undoEnabled = true;

if (!MGlobal::executeCommand(fullScript.c_str(), displayEnabled, undoEnabled)) {
MString errMsg;
errMsg.format("Failed to ^1s.", _scriptName.c_str());
MGlobal::displayWarning(errMsg);
return;
}

_needUndo = true;
}

void AutoUndoCommands::_undoCommands()
{
if (!_needUndo)
return;

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

static const char fullScript[] = "proc _undoCommands() {\n"
// We need to re-enable undo for this because we may be
// executed in a context that disbaled undo.
" int $undoWereActive = `undoInfo -query -state`;\n"
" undoInfo -stateWithoutFlush 1;\n"
// Undo the commands.
" undo;\n"
// Restore the undo active flag.
" undoInfo -stateWithoutFlush $undoWereActive;\n"
"}\n"
"_undoCommands();\n";

const bool displayEnabled = false;
const bool undoEnabled = true;

if (!MGlobal::executeCommand(fullScript, displayEnabled, undoEnabled)) {
MString errMsg;
errMsg.format("Failed to undo ^1s.", _scriptName.c_str());
MGlobal::displayWarning(errMsg);
}
}
} // namespace MAYAUSD_NS_DEF
Loading

0 comments on commit 57b23cc

Please sign in to comment.