diff --git a/third_party/maya/lib/usdMaya/CMakeLists.txt b/third_party/maya/lib/usdMaya/CMakeLists.txt index 5568665c33..c4763a0b86 100644 --- a/third_party/maya/lib/usdMaya/CMakeLists.txt +++ b/third_party/maya/lib/usdMaya/CMakeLists.txt @@ -171,6 +171,7 @@ pxr_test_scripts( testenv/testUsdExportCamera.py testenv/testUsdExportColorSets.py testenv/testUsdExportDisplayColor.py + testenv/testUsdExportEulerFilter.py testenv/testUsdExportFilterTypes.py testenv/testUsdExportFrameOffset.py testenv/testUsdExportInstances.py @@ -306,6 +307,21 @@ pxr_register_test(testUsdExportDisplayColor MAYA_APP_DIR=/maya_profile ) +pxr_install_test_dir( + SRC testenv/UsdExportEulerFilterTest + DEST testUsdExportEulerFilter +) +pxr_register_test(testUsdExportEulerFilter + CUSTOM_PYTHON "${MAYA_BASE_DIR}/bin/mayapy" + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportEulerFilter" + TESTENV testUsdExportEulerFilter + ENV + MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/third_party/maya/plugin + MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/third_party/maya/share/usd/plugins/usdMaya/resources + MAYA_DISABLE_CIP=1 + MAYA_APP_DIR=/maya_profile +) + pxr_install_test_dir( SRC testenv/UsdExportFilterTypesTest DEST testUsdExportFilterTypes diff --git a/third_party/maya/lib/usdMaya/MayaTransformWriter.cpp b/third_party/maya/lib/usdMaya/MayaTransformWriter.cpp index 13cb801ae5..ec5399f3ed 100644 --- a/third_party/maya/lib/usdMaya/MayaTransformWriter.cpp +++ b/third_party/maya/lib/usdMaya/MayaTransformWriter.cpp @@ -28,6 +28,7 @@ #include "usdMaya/primWriterRegistry.h" #include "usdMaya/util.h" #include "usdMaya/usdWriteJob.h" +#include "usdMaya/xformStack.h" #include "pxr/usd/usdGeom/xform.h" #include "pxr/usd/usdGeom/xformCommonAPI.h" @@ -98,6 +99,8 @@ computeXFormOps( const UsdGeomXformable& usdXformable, const std::vector& animChanList, const UsdTimeCode &usdTime, + bool eulerFilter, + MayaTransformWriter::TokenRotationMap& previousRotates, UsdUtilsSparseValueWriter *valueWriter) { // Iterate over each AnimChannel, retrieve the default value and pull the @@ -113,12 +116,7 @@ computeXFormOps( bool hasAnimated = false, hasStatic = false; for (unsigned int i = 0; i<3; i++) { if (animChannel.sampleType[i] == ANIMATED) { - // NOTE the default value has already been converted to - // radians. - double chanVal = animChannel.plug[i].asDouble(); - value[i] = animChannel.opType == ROTATE ? - GfRadiansToDegrees(chanVal) : - chanVal; + value[i] = animChannel.plug[i].asDouble(); hasAnimated = true; } else if (animChannel.sampleType[i] == STATIC) { @@ -136,6 +134,40 @@ computeXFormOps( // animating ones are actually animating if ((usdTime == UsdTimeCode::Default() && hasStatic && !hasAnimated) || (usdTime != UsdTimeCode::Default() && hasAnimated)) { + + if (animChannel.opType == ROTATE) { + if (hasAnimated && eulerFilter) { + const TfToken& lookupName = animChannel.opName.IsEmpty() ? + UsdGeomXformOp::GetOpTypeToken(animChannel.usdOpType) : + animChannel.opName; + auto findResult = previousRotates.find(lookupName); + if (findResult == previousRotates.end()) { + MEulerRotation::RotationOrder rotOrder = + PxrUsdMayaXformStack::RotateOrderFromOpType( + animChannel.usdOpType, + MEulerRotation::kXYZ); + MEulerRotation currentRotate(value[0], value[1], value[2], rotOrder); + previousRotates[lookupName] = currentRotate; + } + else { + MEulerRotation& previousRotate = findResult->second; + MEulerRotation::RotationOrder rotOrder = + PxrUsdMayaXformStack::RotateOrderFromOpType( + animChannel.usdOpType, + previousRotate.order); + MEulerRotation currentRotate(value[0], value[1], value[2], rotOrder); + currentRotate.setToClosestSolution(previousRotate); + for (unsigned int i = 0; i<3; i++) { + value[i] = currentRotate[i]; + } + previousRotates[lookupName] = currentRotate; + } + } + for (unsigned int i = 0; i<3; i++) { + value[i] = GfRadiansToDegrees(value[i]); + } + } + setXformOp(animChannel.op, value, usdTime, valueWriter); } } @@ -150,7 +182,7 @@ static bool _GatherAnimChannel( XFormOpType opType, const MFnTransform& iTrans, - MString parentName, + const TfToken& parentName, MString xName, MString yName, MString zName, std::vector* oAnimChanList, bool isWritingAnimation, @@ -160,8 +192,9 @@ _GatherAnimChannel( chan.opType = opType; chan.isInverse = false; if (setOpName) { - chan.opName = parentName.asChar(); + chan.opName = parentName; } + MString parentNameMStr = parentName.GetText(); // We default to single precision (later we set the main translate op and // shear to double) @@ -172,14 +205,14 @@ _GatherAnimChannel( // this is to handle the case where there is a connection to the parent // plug but not to the child plugs, if the connection is there and you are // not forcing static, then all of the children are considered animated - int parentSample = PxrUsdMayaUtil::getSampledType(iTrans.findPlug(parentName),false); + int parentSample = PxrUsdMayaUtil::getSampledType(iTrans.findPlug(parentNameMStr),false); // Determine what plug are needed based on default value & being // connected/animated MStringArray channels; - channels.append(parentName+xName); - channels.append(parentName+yName); - channels.append(parentName+zName); + channels.append(parentNameMStr+xName); + channels.append(parentNameMStr+yName); + channels.append(parentNameMStr+zName); GfVec3d nullValue(opType == SCALE ? 1.0 : 0.0); for (unsigned int i = 0; i<3; i++) { @@ -187,7 +220,7 @@ _GatherAnimChannel( // won't be updated if the channel is NOT ANIMATED chan.plug[i] = iTrans.findPlug(channels[i]); double plugValue = chan.plug[i].asDouble(); - chan.defValue[i] = opType == ROTATE ? GfRadiansToDegrees(plugValue) : plugValue; + chan.defValue[i] = plugValue; chan.sampleType[i] = NO_XFORM; // If we allow animation and either the parentsample or local sample is // not 0 then we havea ANIMATED sample else we have a scale and the @@ -211,7 +244,7 @@ _GatherAnimChannel( } else if (opType == TRANSLATE) { chan.usdOpType = UsdGeomXformOp::TypeTranslate; // The main translate is set to double precision - if (parentName == "translate") { + if (parentName == PxrUsdMayaXformStackTokens->translate) { chan.precision = UsdGeomXformOp::PrecisionDouble; } } else if (opType == ROTATE) { @@ -223,7 +256,7 @@ _GatherAnimChannel( } else { // Rotation Order ONLY applies to the "rotate" attribute - if (parentName == "rotate") { + if (parentName == PxrUsdMayaXformStackTokens->rotate) { switch (iTrans.rotationOrder()) { case MTransformationMatrix::kYZX: chan.usdOpType = UsdGeomXformOp::TypeRotateYZX; @@ -285,24 +318,24 @@ void MayaTransformWriter::_PushTransformStack( } // inspect the translate, no suffix to be closer compatibility with common API - _GatherAnimChannel(TRANSLATE, iTrans, "translate", "X", "Y", "Z", &_animChannels, writeAnim, false); + _GatherAnimChannel(TRANSLATE, iTrans, PxrUsdMayaXformStackTokens->translate, "X", "Y", "Z", &_animChannels, writeAnim, false); // inspect the rotate pivot translate - if (_GatherAnimChannel(TRANSLATE, iTrans, "rotatePivotTranslate", "X", "Y", "Z", &_animChannels, writeAnim, true)) { + if (_GatherAnimChannel(TRANSLATE, iTrans, PxrUsdMayaXformStackTokens->rotatePivotTranslate, "X", "Y", "Z", &_animChannels, writeAnim, true)) { conformsToCommonAPI = false; } // inspect the rotate pivot - bool hasRotatePivot = _GatherAnimChannel(TRANSLATE, iTrans, "rotatePivot", "X", "Y", "Z", &_animChannels, writeAnim, true); + bool hasRotatePivot = _GatherAnimChannel(TRANSLATE, iTrans, PxrUsdMayaXformStackTokens->rotatePivot, "X", "Y", "Z", &_animChannels, writeAnim, true); if (hasRotatePivot) { rotPivotIdx = _animChannels.size()-1; } // inspect the rotate, no suffix to be closer compatibility with common API - _GatherAnimChannel(ROTATE, iTrans, "rotate", "X", "Y", "Z", &_animChannels, writeAnim, false); + _GatherAnimChannel(ROTATE, iTrans, PxrUsdMayaXformStackTokens->rotate, "X", "Y", "Z", &_animChannels, writeAnim, false); // inspect the rotateAxis/orientation - if (_GatherAnimChannel(ROTATE, iTrans, "rotateAxis", "X", "Y", "Z", &_animChannels, writeAnim, true)) { + if (_GatherAnimChannel(ROTATE, iTrans, PxrUsdMayaXformStackTokens->rotateAxis, "X", "Y", "Z", &_animChannels, writeAnim, true)) { conformsToCommonAPI = false; } @@ -311,37 +344,37 @@ void MayaTransformWriter::_PushTransformStack( AnimChannel chan; chan.usdOpType = UsdGeomXformOp::TypeTranslate; chan.precision = UsdGeomXformOp::PrecisionFloat; - chan.opName = "rotatePivot"; + chan.opName = PxrUsdMayaXformStackTokens->rotatePivot; chan.isInverse = true; _animChannels.push_back(chan); rotPivotINVIdx = _animChannels.size()-1; } // inspect the scale pivot translation - if (_GatherAnimChannel(TRANSLATE, iTrans, "scalePivotTranslate", "X", "Y", "Z", &_animChannels, writeAnim, true)) { + if (_GatherAnimChannel(TRANSLATE, iTrans, PxrUsdMayaXformStackTokens->scalePivotTranslate, "X", "Y", "Z", &_animChannels, writeAnim, true)) { conformsToCommonAPI = false; } // inspect the scale pivot point - bool hasScalePivot = _GatherAnimChannel(TRANSLATE, iTrans, "scalePivot", "X", "Y", "Z", &_animChannels, writeAnim, true); + bool hasScalePivot = _GatherAnimChannel(TRANSLATE, iTrans, PxrUsdMayaXformStackTokens->scalePivot, "X", "Y", "Z", &_animChannels, writeAnim, true); if (hasScalePivot) { scalePivotIdx = _animChannels.size()-1; } // inspect the shear. Even if we have one xform on the xform list, it represents a share so we should name it - if (_GatherAnimChannel(SHEAR, iTrans, "shear", "XY", "XZ", "YZ", &_animChannels, writeAnim, true)) { + if (_GatherAnimChannel(SHEAR, iTrans, PxrUsdMayaXformStackTokens->shear, "XY", "XZ", "YZ", &_animChannels, writeAnim, true)) { conformsToCommonAPI = false; } // add the scale. no suffix to be closer compatibility with common API - _GatherAnimChannel(SCALE, iTrans, "scale", "X", "Y", "Z", &_animChannels, writeAnim, false); + _GatherAnimChannel(SCALE, iTrans, PxrUsdMayaXformStackTokens->scale, "X", "Y", "Z", &_animChannels, writeAnim, false); // inverse the scale pivot point if (hasScalePivot) { AnimChannel chan; chan.usdOpType = UsdGeomXformOp::TypeTranslate; chan.precision = UsdGeomXformOp::PrecisionFloat; - chan.opName = "scalePivot"; + chan.opName = PxrUsdMayaXformStackTokens->scalePivot; chan.isInverse = true; _animChannels.push_back(chan); scalePivotINVIdx = _animChannels.size()-1; @@ -385,8 +418,8 @@ void MayaTransformWriter::_PushTransformStack( // since no other ops have been found // // NOTE: scalePivotIdx > rotPivotINVIdx - _animChannels[rotPivotIdx].opName = "pivot"; - _animChannels[scalePivotINVIdx].opName = "pivot"; + _animChannels[rotPivotIdx].opName = PxrUsdMayaXformStackTokens->pivot; + _animChannels[scalePivotINVIdx].opName = PxrUsdMayaXformStackTokens->pivot; _animChannels.erase(_animChannels.begin()+scalePivotIdx); _animChannels.erase(_animChannels.begin()+rotPivotINVIdx); } @@ -398,7 +431,7 @@ void MayaTransformWriter::_PushTransformStack( AnimChannel& animChan = *iter; animChan.op = usdXformable.AddXformOp( animChan.usdOpType, animChan.precision, - TfToken(animChan.opName), + animChan.opName, animChan.isInverse); } } @@ -562,9 +595,8 @@ bool MayaTransformWriter::_WriteXformableAttrs( // Write parent class attrs _WriteImageableAttrs(_xformDagPath, usdTime, xformSchema); // for the shape - // can this use xformSchema instead? do we even need _usdXform? - computeXFormOps(xformSchema, _animChannels, usdTime, - _GetSparseValueWriter()); + computeXFormOps(xformSchema, _animChannels, usdTime, _GetExportArgs().eulerFilter, + _previousRotates, _GetSparseValueWriter()); return true; } diff --git a/third_party/maya/lib/usdMaya/MayaTransformWriter.h b/third_party/maya/lib/usdMaya/MayaTransformWriter.h index 67422a7833..a706ad15ca 100644 --- a/third_party/maya/lib/usdMaya/MayaTransformWriter.h +++ b/third_party/maya/lib/usdMaya/MayaTransformWriter.h @@ -30,9 +30,12 @@ #include "pxr/usd/usdGeom/xform.h" #include "pxr/usd/usdGeom/xformOp.h" +#include #include #include +#include + PXR_NAMESPACE_OPEN_SCOPE class UsdGeomXformable; @@ -46,13 +49,15 @@ struct AnimChannel { MPlug plug[3]; AnimChannelSampleType sampleType[3]; - // defValue should always be in "usd" space. that is, if it's a rotation - // it should be a degree not radians. + // defValue should always be in "maya" space. that is, if it's a rotation + // it should be radians, not degrees. (This is done so we only need to do + // conversion in one place, and so that, if we need to do euler filtering, + // we don't to do conversions, and then undo them to use MEulerRotation). GfVec3d defValue; XFormOpType opType; UsdGeomXformOp::Type usdOpType; UsdGeomXformOp::Precision precision; - std::string opName; + TfToken opName; bool isInverse; UsdGeomXformOp op; }; @@ -62,6 +67,8 @@ struct AnimChannel class MayaTransformWriter : public MayaPrimWriter { public: + typedef std::unordered_map TokenRotationMap; + PXRUSDMAYA_API MayaTransformWriter( const MDagPath& iDag, @@ -125,6 +132,7 @@ class MayaTransformWriter : public MayaPrimWriter std::vector _animChannels; bool _isShapeAnimated; bool _isInstanceSource; + TokenRotationMap _previousRotates; }; typedef std::shared_ptr MayaTransformWriterPtr; diff --git a/third_party/maya/lib/usdMaya/jobArgs.cpp b/third_party/maya/lib/usdMaya/jobArgs.cpp index c6a954804b..0a7530f08e 100644 --- a/third_party/maya/lib/usdMaya/jobArgs.cpp +++ b/third_party/maya/lib/usdMaya/jobArgs.cpp @@ -222,6 +222,8 @@ PxrUsdMayaJobExportArgs::PxrUsdMayaJobExportArgs( UsdGeomTokens->bilinear, UsdGeomTokens->none })), + eulerFilter( + _Boolean(userArgs, PxrUsdExportJobArgsTokens->eulerFilter)), excludeInvisible( _Boolean(userArgs, PxrUsdExportJobArgsTokens->renderableOnly)), exportCollectionBasedBindings( @@ -324,6 +326,7 @@ operator <<(std::ostream& out, const PxrUsdMayaJobExportArgs& exportArgs) << "mergeTransformAndShape: " << TfStringify(exportArgs.mergeTransformAndShape) << std::endl << "exportInstances: " << TfStringify(exportArgs.exportInstances) << std::endl << "timeInterval: " << exportArgs.timeInterval << std::endl + << "eulerFilter: " << TfStringify(exportArgs.eulerFilter) << std::endl << "excludeInvisible: " << TfStringify(exportArgs.excludeInvisible) << std::endl << "exportDefaultCameras: " << TfStringify(exportArgs.exportDefaultCameras) << std::endl << "exportSkels: " << TfStringify(exportArgs.exportSkels) << std::endl @@ -430,6 +433,7 @@ const VtDictionary& PxrUsdMayaJobExportArgs::GetDefaultDictionary() d[PxrUsdExportJobArgsTokens->shadingMode] = PxrUsdMayaShadingModeTokens->displayColor.GetString(); d[PxrUsdExportJobArgsTokens->stripNamespaces] = false; + d[PxrUsdExportJobArgsTokens->eulerFilter] = false; // plugInfo.json site defaults. // The defaults dict should be correctly-typed, so enable diff --git a/third_party/maya/lib/usdMaya/jobArgs.h b/third_party/maya/lib/usdMaya/jobArgs.h index eec9ede220..85b6684fdd 100644 --- a/third_party/maya/lib/usdMaya/jobArgs.h +++ b/third_party/maya/lib/usdMaya/jobArgs.h @@ -61,6 +61,7 @@ TF_DECLARE_PUBLIC_TOKENS(PxrUsdMayaTranslatorTokens, (chaserArgs) \ (defaultCameras) \ (defaultMeshScheme) \ + (eulerFilter) \ (exportCollectionBasedBindings) \ (exportColorSets) \ (exportDisplayColor) \ @@ -116,6 +117,7 @@ TF_DECLARE_PUBLIC_TOKENS(PxrUsdImportJobArgsTokens, struct PxrUsdMayaJobExportArgs { const TfToken defaultMeshScheme; + const bool eulerFilter; const bool excludeInvisible; const bool exportCollectionBasedBindings; const bool exportColorSets; diff --git a/third_party/maya/lib/usdMaya/testenv/UsdExportEulerFilterTest/UsdExportEulerFilterTest.ma b/third_party/maya/lib/usdMaya/testenv/UsdExportEulerFilterTest/UsdExportEulerFilterTest.ma new file mode 100644 index 0000000000..d65c95282e --- /dev/null +++ b/third_party/maya/lib/usdMaya/testenv/UsdExportEulerFilterTest/UsdExportEulerFilterTest.ma @@ -0,0 +1,240 @@ +//Maya ASCII 2018 scene +//Name: UsdExportEulerFilterTest.ma +//Last modified: Mon, Jul 02, 2018 03:17:06 PM +//Codeset: UTF-8 +requires maya "2018"; +currentUnit -l centimeter -a degree -t film; +fileInfo "application" "maya"; +fileInfo "product" "Maya 2018"; +fileInfo "version" "2018"; +fileInfo "cutIdentifier" "201711281015-8e846c9074"; +fileInfo "osv" "Linux 3.10.0-514.6.1.el7.x86_64 #1 SMP Wed Jan 18 13:06:36 UTC 2017 x86_64"; +createNode transform -s -n "persp"; + rename -uid "F51F1940-0000-1C24-5B3A-A08B0000022E"; + setAttr ".v" no; + setAttr ".t" -type "double3" -5.571768050735697 3.543982201538451 9.5468928082395568 ; + setAttr ".r" -type "double3" -19.538352729603709 -19.799999999999518 0 ; +createNode camera -s -n "perspShape" -p "persp"; + rename -uid "F51F1940-0000-1C24-5B3A-A08B0000022F"; + setAttr -k off ".v" no; + setAttr ".fl" 34.999999999999993; + setAttr ".coi" 11.619633173820645; + setAttr ".imn" -type "string" "persp"; + setAttr ".den" -type "string" "persp_depth"; + setAttr ".man" -type "string" "persp_mask"; + setAttr ".hc" -type "string" "viewSet -p %camera"; +createNode transform -s -n "top"; + rename -uid "F51F1940-0000-1C24-5B3A-A08B00000230"; + setAttr ".v" no; + setAttr ".t" -type "double3" 0 1000.1 0 ; + setAttr ".r" -type "double3" -89.999999999999986 0 0 ; +createNode camera -s -n "topShape" -p "top"; + rename -uid "F51F1940-0000-1C24-5B3A-A08B00000231"; + setAttr -k off ".v" no; + setAttr ".rnd" no; + setAttr ".coi" 1000.1; + setAttr ".ow" 30; + setAttr ".imn" -type "string" "top"; + setAttr ".den" -type "string" "top_depth"; + setAttr ".man" -type "string" "top_mask"; + setAttr ".hc" -type "string" "viewSet -t %camera"; + setAttr ".o" yes; +createNode transform -s -n "front"; + rename -uid "F51F1940-0000-1C24-5B3A-A08B00000232"; + setAttr ".v" no; + setAttr ".t" -type "double3" 0 0 1000.1 ; +createNode camera -s -n "frontShape" -p "front"; + rename -uid "F51F1940-0000-1C24-5B3A-A08B00000233"; + setAttr -k off ".v" no; + setAttr ".rnd" no; + setAttr ".coi" 1000.1; + setAttr ".ow" 30; + setAttr ".imn" -type "string" "front"; + setAttr ".den" -type "string" "front_depth"; + setAttr ".man" -type "string" "front_mask"; + setAttr ".hc" -type "string" "viewSet -f %camera"; + setAttr ".o" yes; +createNode transform -s -n "side"; + rename -uid "F51F1940-0000-1C24-5B3A-A08B00000234"; + setAttr ".v" no; + setAttr ".t" -type "double3" 1000.1 0 0 ; + setAttr ".r" -type "double3" 0 89.999999999999986 0 ; +createNode camera -s -n "sideShape" -p "side"; + rename -uid "F51F1940-0000-1C24-5B3A-A08B00000235"; + setAttr -k off ".v" no; + setAttr ".rnd" no; + setAttr ".coi" 1000.1; + setAttr ".ow" 30; + setAttr ".imn" -type "string" "side"; + setAttr ".den" -type "string" "side_depth"; + setAttr ".man" -type "string" "side_mask"; + setAttr ".hc" -type "string" "viewSet -s %camera"; + setAttr ".o" yes; +createNode joint -n "joint1"; + rename -uid "F51F1940-0000-1C24-5B3A-A0CF00000257"; + setAttr ".t" -type "double3" 0.019505787300588828 0 -0.020231899029152345 ; + setAttr ".r" -type "double3" 0.41667044254269303 -7.997163072751122 174.12845392577671 ; + setAttr ".mnrl" -type "double3" -360 -360 -360 ; + setAttr ".mxrl" -type "double3" 360 360 360 ; + setAttr ".jo" -type "double3" 0 -6.2001900056254113 0 ; + setAttr ".radi" 0.54242451325776986; +createNode joint -n "joint2" -p "joint1"; + rename -uid "F51F1940-0000-1C24-5B3A-A0D000000258"; + setAttr ".t" -type "double3" 1.8202072563168847 0 2.7755575615628914e-16 ; + setAttr ".r" -type "double3" 0 -3.6214533162687599 0 ; + setAttr ".mnrl" -type "double3" -360 -360 -360 ; + setAttr ".mxrl" -type "double3" 360 360 360 ; + setAttr ".jo" -type "double3" 0 7.5523314331760796 0 ; + setAttr ".radi" 0.55809945369367275; +createNode joint -n "joint3" -p "joint2"; + rename -uid "F51F1940-0000-1C24-5B3A-A0D000000259"; + setAttr ".t" -type "double3" 2.1232561047443403 0 -1.1171619185290638e-15 ; + setAttr ".mnrl" -type "double3" -360 -360 -360 ; + setAttr ".mxrl" -type "double3" 360 360 360 ; + setAttr ".jo" -type "double3" 0 -1.3521414275506678 0 ; + setAttr ".radi" 0.55809945369367275; +createNode ikEffector -n "effector1" -p "joint2"; + rename -uid "F51F1940-0000-1C24-5B3A-A0E10000025A"; + setAttr ".v" no; + setAttr ".hd" yes; +createNode ikHandle -n "ikHandle1"; + rename -uid "F51F1940-0000-1C24-5B3A-A0E10000025B"; + setAttr ".pv" -type "double3" -0.074452937179399353 1.7850079971217804e-09 1.9986137095860652 ; + setAttr ".roc" yes; +createNode transform -n "pCube1"; + rename -uid "81AA9940-0000-1F94-5B3A-A3F40000026B"; + setAttr ".s" -type "double3" 0.99999999999999989 0.99999999999999989 0.99999999999999978 ; +createNode parentConstraint -n "pCube1_parentConstraint1" -p "pCube1"; + rename -uid "81AA9940-0000-1F94-5B3A-A40F0000026C"; + addAttr -dcb 0 -ci true -k true -sn "w0" -ln "joint1W0" -dv 1 -min 0 -at "double"; + setAttr -k on ".nds"; + setAttr -k off ".v"; + setAttr -k off ".tx"; + setAttr -k off ".ty"; + setAttr -k off ".tz"; + setAttr -k off ".rx"; + setAttr -k off ".ry"; + setAttr -k off ".rz"; + setAttr -k off ".sx"; + setAttr -k off ".sy"; + setAttr -k off ".sz"; + setAttr ".erp" yes; + setAttr ".tg[0].tot" -type "double3" 0 -8.6736173798840355e-19 -3.4694469519536142e-18 ; + setAttr ".tg[0].tor" -type "double3" -1.4908850069360235e-16 4.9696166897867419e-17 2.9606103178100623e-15 ; + setAttr ".lr" -type "double3" -0.21669838269324138 -1.8291377972941907 174.18277840445072 ; + setAttr ".rst" -type "double3" 0.019505787300588828 8.4636621590943153e-19 -0.020231899029152348 ; + setAttr ".rsrr" -type "double3" 0.26995704769675655 0.28654200817022302 -172.73250361525626 ; + setAttr -k on ".w0"; +createNode lightLinker -s -n "lightLinker1"; + rename -uid "81AA9940-0000-1F94-5B3A-A3930000025E"; + setAttr -s 2 ".lnk"; + setAttr -s 2 ".slnk"; +createNode shapeEditorManager -n "shapeEditorManager"; + rename -uid "81AA9940-0000-1F94-5B3A-A3930000025F"; +createNode poseInterpolatorManager -n "poseInterpolatorManager"; + rename -uid "81AA9940-0000-1F94-5B3A-A39300000260"; +createNode displayLayerManager -n "layerManager"; + rename -uid "81AA9940-0000-1F94-5B3A-A39300000261"; +createNode displayLayer -n "defaultLayer"; + rename -uid "F51F1940-0000-1C24-5B3A-A08C0000024E"; +createNode renderLayerManager -n "renderLayerManager"; + rename -uid "81AA9940-0000-1F94-5B3A-A39300000263"; +createNode renderLayer -n "defaultRenderLayer"; + rename -uid "F51F1940-0000-1C24-5B3A-A08C00000250"; + setAttr ".g" yes; +createNode ikRPsolver -n "ikRPsolver"; + rename -uid "F51F1940-0000-1C24-5B3A-A0C900000254"; +createNode animCurveTL -n "ikHandle1_translateX"; + rename -uid "F51F1940-0000-1C24-5B3A-A15D00000268"; + setAttr ".tan" 18; + setAttr ".wgt" no; + setAttr -s 2 ".ktv[0:1]" 1 -3.9012512308195548 3 -3.9012512308195548; +createNode animCurveTL -n "ikHandle1_translateY"; + rename -uid "F51F1940-0000-1C24-5B3A-A15D00000269"; + setAttr ".tan" 18; + setAttr ".wgt" no; + setAttr -s 2 ".ktv[0:1]" 1 0.4 3 -0.5; +createNode animCurveTL -n "ikHandle1_translateZ"; + rename -uid "F51F1940-0000-1C24-5B3A-A15D0000026A"; + setAttr ".tan" 18; + setAttr ".wgt" no; + setAttr -s 2 ".ktv[0:1]" 1 -0.039999995549662576 3 -0.039999995549662576; +select -ne :time1; + setAttr ".o" 1; + setAttr ".unw" 1; +select -ne :hardwareRenderingGlobals; + setAttr ".otfna" -type "stringArray" 22 "NURBS Curves" "NURBS Surfaces" "Polygons" "Subdiv Surface" "Particles" "Particle Instance" "Fluids" "Strokes" "Image Planes" "UI" "Lights" "Cameras" "Locators" "Joints" "IK Handles" "Deformers" "Motion Trails" "Components" "Hair Systems" "Follicles" "Misc. UI" "Ornaments" ; + setAttr ".otfva" -type "Int32Array" 22 0 1 1 1 1 1 + 1 1 1 0 0 0 0 0 0 0 0 0 + 0 0 0 0 ; + setAttr ".fprt" yes; +select -ne :renderPartition; + setAttr -s 2 ".st"; +select -ne :renderGlobalsList1; +select -ne :defaultShaderList1; + setAttr -s 4 ".s"; +select -ne :postProcessList1; + setAttr -s 2 ".p"; +select -ne :defaultRenderingList1; +select -ne :initialShadingGroup; + setAttr ".ro" yes; +select -ne :initialParticleSE; + setAttr ".ro" yes; +select -ne :defaultResolution; + setAttr ".w" 2048; + setAttr ".h" 1556; + setAttr ".pa" 1; + setAttr ".dar" 1.3161953687667847; +select -ne :defaultColorMgtGlobals; + setAttr ".cfe" yes; + setAttr ".cfp" -type "string" "/luma/pipe/ocio/configs/luma-base/luma-base.ocio"; + setAttr ".vtn" -type "string" "gamma1.8 (default)"; + setAttr ".wsn" -type "string" "linear"; + setAttr ".pote" no; + setAttr ".otn" -type "string" "gamma1.8 (default)"; + setAttr ".potn" -type "string" "gamma1.8 (default)"; +select -ne :hardwareRenderGlobals; + setAttr ".ctrs" 256; + setAttr ".btrs" 512; +select -ne :ikSystem; +connectAttr "joint1.s" "joint2.is"; +connectAttr "joint2.s" "joint3.is"; +connectAttr "joint3.tx" "effector1.tx"; +connectAttr "joint3.ty" "effector1.ty"; +connectAttr "joint3.tz" "effector1.tz"; +connectAttr "joint1.msg" "ikHandle1.hsj"; +connectAttr "effector1.hp" "ikHandle1.hee"; +connectAttr "ikRPsolver.msg" "ikHandle1.hsv"; +connectAttr "ikHandle1_translateX.o" "ikHandle1.tx"; +connectAttr "ikHandle1_translateY.o" "ikHandle1.ty"; +connectAttr "ikHandle1_translateZ.o" "ikHandle1.tz"; +connectAttr "pCube1_parentConstraint1.ctx" "pCube1.tx"; +connectAttr "pCube1_parentConstraint1.cty" "pCube1.ty"; +connectAttr "pCube1_parentConstraint1.ctz" "pCube1.tz"; +connectAttr "pCube1_parentConstraint1.crx" "pCube1.rx"; +connectAttr "pCube1_parentConstraint1.cry" "pCube1.ry"; +connectAttr "pCube1_parentConstraint1.crz" "pCube1.rz"; +connectAttr "pCube1.ro" "pCube1_parentConstraint1.cro"; +connectAttr "pCube1.pim" "pCube1_parentConstraint1.cpim"; +connectAttr "pCube1.rp" "pCube1_parentConstraint1.crp"; +connectAttr "pCube1.rpt" "pCube1_parentConstraint1.crt"; +connectAttr "joint1.t" "pCube1_parentConstraint1.tg[0].tt"; +connectAttr "joint1.rp" "pCube1_parentConstraint1.tg[0].trp"; +connectAttr "joint1.rpt" "pCube1_parentConstraint1.tg[0].trt"; +connectAttr "joint1.r" "pCube1_parentConstraint1.tg[0].tr"; +connectAttr "joint1.ro" "pCube1_parentConstraint1.tg[0].tro"; +connectAttr "joint1.s" "pCube1_parentConstraint1.tg[0].ts"; +connectAttr "joint1.pm" "pCube1_parentConstraint1.tg[0].tpm"; +connectAttr "joint1.jo" "pCube1_parentConstraint1.tg[0].tjo"; +connectAttr "joint1.ssc" "pCube1_parentConstraint1.tg[0].tsc"; +connectAttr "joint1.is" "pCube1_parentConstraint1.tg[0].tis"; +connectAttr "pCube1_parentConstraint1.w0" "pCube1_parentConstraint1.tg[0].tw"; +relationship "link" ":lightLinker1" ":initialShadingGroup.message" ":defaultLightSet.message"; +relationship "link" ":lightLinker1" ":initialParticleSE.message" ":defaultLightSet.message"; +relationship "shadowLink" ":lightLinker1" ":initialShadingGroup.message" ":defaultLightSet.message"; +relationship "shadowLink" ":lightLinker1" ":initialParticleSE.message" ":defaultLightSet.message"; +connectAttr "layerManager.dli[0]" "defaultLayer.id"; +connectAttr "renderLayerManager.rlmi[0]" "defaultRenderLayer.rlid"; +connectAttr "defaultRenderLayer.msg" ":defaultRenderingList1.r" -na; +connectAttr "ikRPsolver.msg" ":ikSystem.sol" -na; +// End of UsdExportFilterTypesTest.ma diff --git a/third_party/maya/lib/usdMaya/testenv/testUsdExportEulerFilter.py b/third_party/maya/lib/usdMaya/testenv/testUsdExportEulerFilter.py new file mode 100644 index 0000000000..ec86b271af --- /dev/null +++ b/third_party/maya/lib/usdMaya/testenv/testUsdExportEulerFilter.py @@ -0,0 +1,131 @@ +#!/pxrpythonsubst +# +# Copyright 2018 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +import os +import unittest + +from maya import cmds +from maya import standalone + +from pxr import Usd + +class testUsdExportEulerFilter(unittest.TestCase): + longMessage = True + + expectedNoFilter = { + 1.0: (-0.21669838, -1.8291378, 174.18279), + 2.0: (0.027377421, -6.3026876, -179.2725), + 3.0: (0.26995704, 0.286542, -172.7325), + } + + expectedFilter = { + 1.0: (-0.21669838, -1.8291378, 174.18279), + 2.0: (0.027377421, -6.3026876, 180.7275), + 3.0: (0.26995704, 0.286542, 187.2675), + } + + def assertRotateSamples(self, usdFile, expected): + stage = Usd.Stage.Open(usdFile) + + xform = stage.GetPrimAtPath('/pCube1') + rot = xform.GetProperty('xformOp:rotateXYZ') + + for time, expectedVec in expected.iteritems(): + actualVec = rot.Get(time) + for i in xrange(3): + msg = "sample at time {}, index {} unequal".format(time, i) + self.assertAlmostEqual(actualVec[i], expectedVec[i], 4, msg) + + @classmethod + def setUpClass(cls): + standalone.initialize('usd') + + cmds.file(os.path.abspath('UsdExportEulerFilterTest.ma'), open=True, + force=True) + + cmds.loadPlugin('pxrUsd', quiet=True) + + @classmethod + def tearDownClass(cls): + standalone.uninitialize() + + def testExportEulerFilterCmd(self): + ''' + Export a constrained xform with and without eulerFiltering + ''' + usdFile = os.path.abspath('UsdExportEulerFilterTestCmd.usda') + + # Test with no option set (defaults to off) + cmds.select('pCube1') + cmds.usdExport(file=usdFile, shadingMode='none', + frameRange=(1.0, 3.0), selection=1) + self.assertRotateSamples(usdFile, self.expectedNoFilter) + + # Test with eulerFilter on + cmds.select('pCube1') + cmds.usdExport(file=usdFile, shadingMode='none', eulerFilter=True, + frameRange=(1.0, 3.0), selection=1) + self.assertRotateSamples(usdFile, self.expectedFilter) + + # Test with eulerFilter off + cmds.select('pCube1') + cmds.usdExport(file=usdFile, shadingMode='none', eulerFilter=False, + frameRange=(1.0, 3.0), selection=1) + self.assertRotateSamples(usdFile, self.expectedNoFilter) + + def testExportEulerFilterTranslator(self): + ''' + Export a constrained xform with and without eulerFiltering + ''' + usdFile = os.path.abspath('UsdExportEulerFilterTestTranslator.usda') + + # Test with no option set (defaults to off) + options = [ + 'shadingMode=none', + 'animation=1', + 'startTime=1', + 'endTime=3', + ] + cmds.select('pCube1') + cmds.file(usdFile, exportSelected=1, options=';'.join(options), + type='pxrUsdExport', f=1) + self.assertRotateSamples(usdFile, self.expectedNoFilter) + + # Test with eulerFilter on + options.append('eulerFilter=1') + cmds.select('pCube1') + cmds.file(usdFile, exportSelected=1, options=';'.join(options), + type='pxrUsdExport', f=1) + self.assertRotateSamples(usdFile, self.expectedFilter) + + # Test with eulerFilter off + options[-1] = 'eulerFilter=0' + cmds.select('pCube1') + cmds.file(usdFile, exportSelected=1, options=';'.join(options), + type='pxrUsdExport', f=1) + self.assertRotateSamples(usdFile, self.expectedNoFilter) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/third_party/maya/lib/usdMaya/usdExport.cpp b/third_party/maya/lib/usdMaya/usdExport.cpp index c64b0a0377..be30e715f7 100644 --- a/third_party/maya/lib/usdMaya/usdExport.cpp +++ b/third_party/maya/lib/usdMaya/usdExport.cpp @@ -98,6 +98,9 @@ MSyntax usdExport::createSyntax() syntax.addFlag("-sn", PxrUsdExportJobArgsTokens->stripNamespaces.GetText(), MSyntax::kBoolean); + syntax.addFlag("-ef" , + PxrUsdExportJobArgsTokens->eulerFilter.GetText(), + MSyntax::kBoolean); syntax.addFlag("-dms", PxrUsdExportJobArgsTokens->defaultMeshScheme.GetText(), MSyntax::kString); diff --git a/third_party/maya/lib/usdMaya/usdTranslatorExport.mel b/third_party/maya/lib/usdMaya/usdTranslatorExport.mel index 037928fde4..1ab4e364c6 100644 --- a/third_party/maya/lib/usdMaya/usdTranslatorExport.mel +++ b/third_party/maya/lib/usdMaya/usdTranslatorExport.mel @@ -1,11 +1,13 @@ global proc usdTranslatorExport_AnimationCB() { if (`checkBox -q -v animationCheckBox` == 1) { + checkBox -e -vis true eulerFilterCheckBox; intFieldGrp -e -vis true startTimeField; intFieldGrp -e -vis true endTimeField; floatFieldGrp -e -vis true frameStrideField; button -e -vis true animRangeButton; } else { + checkBox -e -vis false eulerFilterCheckBox; intFieldGrp -e -vis false startTimeField; intFieldGrp -e -vis false endTimeField; floatFieldGrp -e -vis false frameStrideField; @@ -181,6 +183,8 @@ global proc int usdTranslatorExport (string $parent, checkBox -l "Export Animation" -cc ("usdTranslatorExport_AnimationCB") animationCheckBox; + checkBox -l "Apply Euler Filter" eulerFilterCheckBox; + columnLayout -width 100 animOptsCol; intFieldGrp -l "Start Time:" -v1 1 startTimeField; intFieldGrp -l "End Time:" -v1 1 endTimeField; @@ -237,6 +241,8 @@ global proc int usdTranslatorExport (string $parent, usdTranslatorExport_SetCheckbox($optionBreakDown[1], "stripNamespacesCheckBox"); } else if ($optionBreakDown[0] == "animation") { usdTranslatorExport_SetCheckbox($optionBreakDown[1], "animationCheckBox"); + } else if ($optionBreakDown[0] == "eulerFilter") { + usdTranslatorExport_SetCheckbox($optionBreakDown[1], "eulerFilterCheckBox"); } else if ($optionBreakDown[0] == "startTime") { int $startTime=$optionBreakDown[1]; intFieldGrp -e -v1 $startTime startTimeField; @@ -279,6 +285,7 @@ global proc int usdTranslatorExport (string $parent, $currentOptions = usdTranslatorExport_AppendFromCheckbox($currentOptions, "exportVisibility", "exportVisibilityCheckBox"); $currentOptions = usdTranslatorExport_AppendFromCheckbox($currentOptions, "stripNamespaces", "stripNamespacesCheckBox"); $currentOptions = usdTranslatorExport_AppendFromCheckbox($currentOptions, "animation", "animationCheckBox"); + $currentOptions = usdTranslatorExport_AppendFromCheckbox($currentOptions, "eulerFilter", "eulerFilterCheckBox"); $currentOptions = usdTranslatorExport_AppendFromIntField($currentOptions, "startTime", "startTimeField"); $currentOptions = usdTranslatorExport_AppendFromIntField($currentOptions, "endTime", "endTimeField"); $currentOptions = usdTranslatorExport_AppendFromFloatField($currentOptions, "frameStride", "frameStrideField");