From bc6f29cae6bed2cd4627fc649be25472a8e75712 Mon Sep 17 00:00:00 2001 From: Pierre Tremblay Date: Wed, 17 Feb 2021 16:09:37 -0500 Subject: [PATCH 1/6] Fallback, multi-matrix parent absolute. --- lib/mayaUsd/ufe/CMakeLists.txt | 2 + .../UsdTransform3dFallbackMayaXformStack.cpp | 54 +++- lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp | 145 +++++++--- .../ufe/UsdTransform3dMayaXformStack.cpp | 15 +- .../ufe/UsdTransform3dSetObjectMatrix.cpp | 85 ++++++ .../ufe/UsdTransform3dSetObjectMatrix.h | 95 ++++++ lib/mayaUsd/ufe/wrapUtils.cpp | 13 + test/lib/ufe/testParentCmd.py | 273 +++++++++++++++++- 8 files changed, 629 insertions(+), 53 deletions(-) create mode 100644 lib/mayaUsd/ufe/UsdTransform3dSetObjectMatrix.cpp create mode 100644 lib/mayaUsd/ufe/UsdTransform3dSetObjectMatrix.h diff --git a/lib/mayaUsd/ufe/CMakeLists.txt b/lib/mayaUsd/ufe/CMakeLists.txt index 66f5bb6487..28e4fcb1d3 100644 --- a/lib/mayaUsd/ufe/CMakeLists.txt +++ b/lib/mayaUsd/ufe/CMakeLists.txt @@ -46,6 +46,7 @@ if(CMAKE_UFE_V2_FEATURES_AVAILABLE) UsdTransform3dFallbackMayaXformStack.cpp UsdTransform3dMatrixOp.cpp UsdTransform3dMayaXformStack.cpp + UsdTransform3dSetObjectMatrix.cpp UsdUIInfoHandler.cpp UsdUndoAddNewPrimCommand.cpp UsdUndoCreateGroupCommand.cpp @@ -107,6 +108,7 @@ if(CMAKE_UFE_V2_FEATURES_AVAILABLE) UsdTransform3dFallbackMayaXformStack.h UsdTransform3dMatrixOp.h UsdTransform3dMayaXformStack.h + UsdTransform3dSetObjectMatrix.h UsdUIInfoHandler.h UsdUndoAddNewPrimCommand.h UsdUndoCreateGroupCommand.h diff --git a/lib/mayaUsd/ufe/UsdTransform3dFallbackMayaXformStack.cpp b/lib/mayaUsd/ufe/UsdTransform3dFallbackMayaXformStack.cpp index f7fdc14cbd..7bfe0093a2 100644 --- a/lib/mayaUsd/ufe/UsdTransform3dFallbackMayaXformStack.cpp +++ b/lib/mayaUsd/ufe/UsdTransform3dFallbackMayaXformStack.cpp @@ -16,6 +16,7 @@ #include "UsdTransform3dFallbackMayaXformStack.h" #include +#include #include #include @@ -143,7 +144,10 @@ void setXformOpOrder(const UsdGeomXformable& xformable) xformable.SetXformOpOrder(newOrder, resetsXformStack); } -Ufe::Transform3d::Ptr createTransform3d(const Ufe::SceneItem::Ptr& item) +Ufe::Transform3d::Ptr createEditTransform3dImp( + const Ufe::SceneItem::Ptr& item, + std::vector& xformOps, + std::vector::const_iterator& firstFallbackOp) { UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast(item); #if !defined(NDEBUG) @@ -160,7 +164,7 @@ Ufe::Transform3d::Ptr createTransform3d(const Ufe::SceneItem::Ptr& item) return nullptr; } bool resetsXformStack = false; - auto xformOps = xformSchema.GetOrderedXformOps(&resetsXformStack); + xformOps = xformSchema.GetOrderedXformOps(&resetsXformStack); // We are the fallback Transform3d handler: there must be transform ops to // match. @@ -172,10 +176,10 @@ Ufe::Transform3d::Ptr createTransform3d(const Ufe::SceneItem::Ptr& item) // fallback component token. If no transform op matches the fallback // component token, we start a new Maya transform stack at the end of the // existing stack. - auto i = findFirstFallbackOp(xformOps); + firstFallbackOp = findFirstFallbackOp(xformOps); // No transform op matched: start a new Maya transform stack at the end. - if (i == xformOps.end()) { + if (firstFallbackOp == xformOps.end()) { return UsdTransform3dFallbackMayaXformStack::create(usdItem); } @@ -183,8 +187,8 @@ Ufe::Transform3d::Ptr createTransform3d(const Ufe::SceneItem::Ptr& item) // is well, from the first fallback op onwards, we have a sub-stack that // matches the fallback Maya transform stack. std::vector candidateOps; - candidateOps.reserve(std::distance(i, xformOps.cend())); - std::copy(i, xformOps.cend(), std::back_inserter(candidateOps)); + candidateOps.reserve(std::distance(firstFallbackOp, xformOps.cend())); + std::copy(firstFallbackOp, xformOps.cend(), std::back_inserter(candidateOps)); // We're the last handler in the chain of responsibility: if the candidate // ops support the Maya transform stack, create a Maya transform stack @@ -193,6 +197,42 @@ Ufe::Transform3d::Ptr createTransform3d(const Ufe::SceneItem::Ptr& item) : nullptr; } +Ufe::Transform3d::Ptr createTransform3d(const Ufe::SceneItem::Ptr& item) +{ + // This Transform3d interface is for editing the whole object, e.g. setting + // the local transformation matrix for the complete object. We do this + // by wrapping an edit transform 3d interface into a + // UsdTransform3dSetObjectMatrix object. + std::vector xformOps; + std::vector::const_iterator firstFallbackOp; + + auto editTransform3d = createEditTransform3dImp(item, xformOps, firstFallbackOp); + if (!editTransform3d) { + return nullptr; + } + + // Ml is the transformation before the Maya fallback transform stack. + std::vector mlOps(std::distance(xformOps.cbegin(), firstFallbackOp)); + mlOps.assign(xformOps.cbegin(), firstFallbackOp); + + GfMatrix4d ml { 1 }; + if (!UsdGeomXformable::GetLocalTransformation(&ml, mlOps, getTime(item->path()))) { + TF_FATAL_ERROR( + "Local transformation computation for item %s failed.", item->path().string()); + } + + // The Maya fallback transform stack is the last group of transform ops in + // the complete transform stack, so Mr and thus inv(Mr), are the identity. + return UsdTransform3dSetObjectMatrix::create(editTransform3d, ml.GetInverse(), GfMatrix4d(1)); +} + +Ufe::Transform3d::Ptr createEditTransform3d(const Ufe::SceneItem::Ptr& item) +{ + std::vector xformOps; + std::vector::const_iterator firstFallbackOp; + return createEditTransform3dImp(item, xformOps, firstFallbackOp); +} + } // namespace UsdTransform3dFallbackMayaXformStack::UsdTransform3dFallbackMayaXformStack( @@ -325,7 +365,7 @@ UsdTransform3dFallbackMayaXformStackHandler::transform3d(const Ufe::SceneItem::P Ufe::Transform3d::Ptr UsdTransform3dFallbackMayaXformStackHandler::editTransform3d( const Ufe::SceneItem::Ptr& item UFE_V2(, const Ufe::EditTransform3dHint& hint)) const { - return createTransform3d(item); + return createEditTransform3d(item); } } // namespace ufe diff --git a/lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp b/lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp index 9e82b867a0..972390791e 100644 --- a/lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp +++ b/lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp @@ -16,6 +16,7 @@ #include "UsdTransform3dMatrixOp.h" #include +#include #include #include #include @@ -45,6 +46,52 @@ VtValue getValue(const UsdAttribute& attr, const UsdTimeCode& time) const char* getMatrixOp() { return std::getenv("MAYA_USD_MATRIX_XFORM_OP_NAME"); } +std::vector::const_iterator +findMatrixOp(const std::vector& xformOps) +{ + auto opName = getMatrixOp(); + return std::find_if(xformOps.cbegin(), xformOps.cend(), [opName](const UsdGeomXformOp& op) { + return (op.GetOpType() == UsdGeomXformOp::TypeTransform) + && (!opName || std::string(opName) == op.GetOpName()); + }); +} + +// Given a starting point i (inclusive), is there a non-matrix transform op in +// the vector? +bool findNonMatrix( + const std::vector::const_iterator& i, + const std::vector& xformOps) +{ + return std::find_if( + i, + xformOps.cend(), + [](const UsdGeomXformOp& op) { + return op.GetOpType() != UsdGeomXformOp::TypeTransform; + }) + != xformOps.cend(); +} + +// Compute the inverse of the cumulative transform for the argument xform ops. +GfMatrix4d xformInv( + const std::vector::const_iterator& begin, + const std::vector::const_iterator& end, + const Ufe::Path& path) +{ + auto nbOps = std::distance(begin, end); + if (nbOps == 0) { + return GfMatrix4d { 1 }; + } + std::vector ops(nbOps); + ops.assign(begin, end); + + GfMatrix4d m { 1 }; + if (!UsdGeomXformable::GetLocalTransformation(&m, ops, getTime(path))) { + TF_FATAL_ERROR("Local transformation computation for item %s failed.", path.string()); + } + + return m.GetInverse(); +} + // Class for setMatrixCmd() implementation. UsdUndoBlock data member and // undo() / redo() should be factored out into a future command base class. class UsdSetMatrix4dUndoableCmd : public Ufe::SetMatrix4dUndoableCommand @@ -69,7 +116,10 @@ class UsdSetMatrix4dUndoableCmd : public Ufe::SetMatrix4dUndoableCommand { UsdUndoBlock undoBlock(&_undoableItem); - auto t3d = Ufe::Transform3d::transform3d(sceneItem()); + // Use editTransform3d() to set a single matrix transform op. + // transform3d() returns a whole-object interface, which may include + // other transform ops. + auto t3d = Ufe::Transform3d::editTransform3d(sceneItem()); t3d->setMatrix(_newM); } @@ -407,32 +457,48 @@ UsdTransform3dMatrixOpHandler::create(const Ufe::Transform3dHandler::Ptr& nextHa Ufe::Transform3d::Ptr UsdTransform3dMatrixOpHandler::transform3d(const Ufe::SceneItem::Ptr& item) const { - // Remove code duplication with editTransform3d(). PPT, 21-Jan-2021. + // We must create a Transform3d interface to edit the whole object, + // e.g. setting the local transformation matrix for the complete object. UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast(item); TF_AXIOM(usdItem); - auto opName = getMatrixOp(); UsdGeomXformable xformable(usdItem->prim()); bool unused; auto xformOps = xformable.GetOrderedXformOps(&unused); - auto i = std::find_if(xformOps.begin(), xformOps.end(), [opName](const UsdGeomXformOp& op) { - return (op.GetOpType() == UsdGeomXformOp::TypeTransform) - && (!opName || std::string(opName) == op.GetOpName()); - }); - bool foundMatrix = (i != xformOps.end()); - bool moreLocalNonMatrix = foundMatrix - ? (std::find_if( - i, - xformOps.end(), - [](const UsdGeomXformOp& op) { - return op.GetOpType() != UsdGeomXformOp::TypeTransform; - }) - != xformOps.end()) - : false; + // If there is a single matrix transform op in the transform stack, then + // transform3d() and editTransform3d() are equivalent: use that matrix op. + if (xformOps.size() == 1 && xformOps.front().GetOpType() == UsdGeomXformOp::TypeTransform) { + return UsdTransform3dMatrixOp::create(usdItem, xformOps.front()); + } + + // Find the matrix op to be transformed. + auto i = findMatrixOp(xformOps); + + // If no matrix was found, pass on to the next handler. + if (i == xformOps.cend()) { + return _nextHandler->transform3d(item); + } + + // If we've found a matrix op, but there is a more local non-matrix op in + // the stack, the more local op should be used. This will happen e.g. if a + // pivot edit was done on a matrix op stack. Since matrix ops don't + // support pivot edits, a fallback Maya stack will be added, and from that + // point on the fallback Maya stack must be used. + if (findNonMatrix(i, xformOps)) { + return _nextHandler->transform3d(item); + } + + // At this point we know we have a matrix op to transform, and that it is + // not alone on the transform op stack. Wrap a matrix op Transform3d + // interface for that matrix into a UsdTransform3dSetObjectMatrix object. + // Ml is the transformation before the matrix op, Mr is the transformation + // after the matrix op. + auto mlInv = xformInv(xformOps.cbegin(), i, item->path()); + auto mrInv = xformInv(i + 1, xformOps.cend(), item->path()); - return (foundMatrix && !moreLocalNonMatrix) ? UsdTransform3dMatrixOp::create(usdItem, *i) - : _nextHandler->transform3d(item); + return UsdTransform3dSetObjectMatrix::create( + UsdTransform3dMatrixOp::create(usdItem, *i), mlInv, mrInv); } Ufe::Transform3d::Ptr UsdTransform3dMatrixOpHandler::editTransform3d( @@ -453,37 +519,32 @@ Ufe::Transform3d::Ptr UsdTransform3dMatrixOpHandler::editTransform3d( // has not been specified, we edit the first matrix op in the stack. If // the matrix op is not found, or there is no matrix op in the stack, let // the next Transform3d handler in the chain handle the request. - auto opName = getMatrixOp(); UsdGeomXformable xformable(usdItem->prim()); bool unused; auto xformOps = xformable.GetOrderedXformOps(&unused); - auto i = std::find_if(xformOps.begin(), xformOps.end(), [opName](const UsdGeomXformOp& op) { - return (op.GetOpType() == UsdGeomXformOp::TypeTransform) - && (!opName || std::string(opName) == op.GetOpName()); - }); - bool foundMatrix = (i != xformOps.end()); - // If we've found a matrix op, but there is a more local non-matrix op in - // the stack, the more local op should be used to handle the edit. - bool moreLocalNonMatrix = foundMatrix - ? (std::find_if( - i, - xformOps.end(), - [](const UsdGeomXformOp& op) { - return op.GetOpType() != UsdGeomXformOp::TypeTransform; - }) - != xformOps.end()) - : false; + // Find the matrix op to be transformed. + auto i = findMatrixOp(xformOps); - // We can't handle pivot edits, so in that case pass on to the next handler. - return (foundMatrix && !moreLocalNonMatrix + // If no matrix was found, pass on to the next handler. + if (i == xformOps.cend()) { + return _nextHandler->editTransform3d(item UFE_V2(, hint)); + } + + // If we've found a matrix op, but there is a more local non-matrix op in + // the stack, the more local op should be used. This will happen e.g. if a + // pivot edit was done on a matrix op stack. Since matrix ops don't + // support pivot edits, a fallback Maya stack will be added, and from that + // point on the fallback Maya stack must be used. Also, pass pivot edits + // on to the next handler, since we can't handle them. + return (findNonMatrix(i, xformOps) #ifdef UFE_V2_FEATURES_AVAILABLE - && (hint.type() != Ufe::EditTransform3dHint::RotatePivot) - && (hint.type() != Ufe::EditTransform3dHint::ScalePivot) + || (hint.type() == Ufe::EditTransform3dHint::RotatePivot) + || (hint.type() == Ufe::EditTransform3dHint::ScalePivot) #endif ) - ? UsdTransform3dMatrixOp::create(usdItem, *i) - : _nextHandler->editTransform3d(item UFE_V2(, hint)); + ? _nextHandler->editTransform3d(item UFE_V2(, hint)) + : UsdTransform3dMatrixOp::create(usdItem, *i); } } // namespace ufe diff --git a/lib/mayaUsd/ufe/UsdTransform3dMayaXformStack.cpp b/lib/mayaUsd/ufe/UsdTransform3dMayaXformStack.cpp index eb96bb06cd..054625e68f 100644 --- a/lib/mayaUsd/ufe/UsdTransform3dMayaXformStack.cpp +++ b/lib/mayaUsd/ufe/UsdTransform3dMayaXformStack.cpp @@ -202,7 +202,10 @@ class UsdSetMatrix4dUndoableCmd : public Ufe::SetMatrix4dUndoableCommand { UsdUndoBlock undoBlock(&_undoableItem); - auto t3d = Ufe::Transform3d::transform3d(sceneItem()); + // transform3d() and editTransform3d() are equivalent for a normal Maya + // transform stack, but not for a fallback Maya transform stack, and + // both can be edited by this command. + auto t3d = Ufe::Transform3d::editTransform3d(sceneItem()); t3d->translate(_newT.x(), _newT.y(), _newT.z()); t3d->rotate(_newR.x(), _newR.y(), _newR.z()); t3d->scale(_newS.x(), _newS.y(), _newS.z()); @@ -466,6 +469,9 @@ Ufe::Vector3d UsdTransform3dMayaXformStack::rotation() const } UsdGeomXformOp r = getOp(NdxRotate); TF_AXIOM(r); + if (!r.GetAttr().HasValue()) { + return Ufe::Vector3d(0, 0, 0); + } CvtRotXYZFromAttrFn cvt = getCvtRotXYZFromAttrFn(r.GetOpName()); return cvt(getValue(r.GetAttr(), getTime(path()))); @@ -478,6 +484,9 @@ Ufe::Vector3d UsdTransform3dMayaXformStack::scale() const } UsdGeomXformOp s = getOp(NdxScale); TF_AXIOM(s); + if (!s.GetAttr().HasValue()) { + return Ufe::Vector3d(1, 1, 1); + } GfVec3f v; s.Get(&v, getTime(path())); @@ -621,9 +630,9 @@ Ufe::Vector3d UsdTransform3dMayaXformStack::scalePivotTranslation() const template Ufe::Vector3d UsdTransform3dMayaXformStack::getVector3d(const TfToken& attrName) const { - // If the attribute doesn't exist yet, return a zero vector. + // If the attribute doesn't exist or have a value yet, return a zero vector. auto attr = prim().GetAttribute(attrName); - if (!attr) { + if (!attr || !attr.HasValue()) { return Ufe::Vector3d(0, 0, 0); } diff --git a/lib/mayaUsd/ufe/UsdTransform3dSetObjectMatrix.cpp b/lib/mayaUsd/ufe/UsdTransform3dSetObjectMatrix.cpp new file mode 100644 index 0000000000..da20fbc52a --- /dev/null +++ b/lib/mayaUsd/ufe/UsdTransform3dSetObjectMatrix.cpp @@ -0,0 +1,85 @@ +// +// Copyright 2021 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. +// + +#include "UsdTransform3dSetObjectMatrix.h" + +#include + +namespace MAYAUSD_NS_DEF { +namespace ufe { + +UsdTransform3dSetObjectMatrix::UsdTransform3dSetObjectMatrix( + const Ufe::Transform3d::Ptr& wrapped, + const GfMatrix4d& mlInv, + const GfMatrix4d& mrInv) + : UsdTransform3dBase(std::dynamic_pointer_cast(wrapped->sceneItem())) + , _wrapped(wrapped) + , _mlInv(mlInv) + , _mrInv(mrInv) +{ +} + +/* static */ +UsdTransform3dSetObjectMatrix::Ptr UsdTransform3dSetObjectMatrix::create( + const Ufe::Transform3d::Ptr& wrapped, + const GfMatrix4d& mlInv, + const GfMatrix4d& mrInv) +{ + return std::make_shared(wrapped, mlInv, mrInv); +} + +Ufe::Vector3d UsdTransform3dSetObjectMatrix::translation() const +{ + TF_CODING_ERROR("Illegal call to unimplemented UsdTransform3dSetObjectMatrix::translation()"); + return Ufe::Vector3d(0, 0, 0); +} + +Ufe::Vector3d UsdTransform3dSetObjectMatrix::rotation() const +{ + TF_CODING_ERROR("Illegal call to unimplemented UsdTransform3dSetObjectMatrix::rotation()"); + return Ufe::Vector3d(0, 0, 0); +} + +Ufe::Vector3d UsdTransform3dSetObjectMatrix::scale() const +{ + TF_CODING_ERROR("Illegal call to unimplemented UsdTransform3dSetObjectMatrix::scale()"); + return Ufe::Vector3d(1, 1, 1); +} + +Ufe::SetMatrix4dUndoableCommand::Ptr +UsdTransform3dSetObjectMatrix::setMatrixCmd(const Ufe::Matrix4d& m) +{ + return _wrapped->setMatrixCmd(mw(m)); +} + +void UsdTransform3dSetObjectMatrix::setMatrix(const Ufe::Matrix4d& m) +{ + _wrapped->setMatrix(mw(m)); +} + +Ufe::Matrix4d UsdTransform3dSetObjectMatrix::mw(const Ufe::Matrix4d& m) const +{ + // Compute the matrix required for our wrapped Transform3d. As per + // https://graphics.pixar.com/usd/docs/api/class_gf_matrix4d.html#details + // matrix multiplication order is such that the matrix to the left of the + // multiplication is more local than the one to the right. Since inv(Mr) + // is the most local and inv(Ml) the least local, we express the + // multiplication as inv(Mr) x M x inv(Ml). + return toUfe(_mrInv * toUsd(m) * _mlInv); +} + +} // namespace ufe +} // namespace MAYAUSD_NS_DEF diff --git a/lib/mayaUsd/ufe/UsdTransform3dSetObjectMatrix.h b/lib/mayaUsd/ufe/UsdTransform3dSetObjectMatrix.h new file mode 100644 index 0000000000..efa17b460f --- /dev/null +++ b/lib/mayaUsd/ufe/UsdTransform3dSetObjectMatrix.h @@ -0,0 +1,95 @@ +// +// Copyright 2021 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. +// +#pragma once + +#include +#include + +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Interface to set the matrix of objects in 3D. +// +// The UsdTransform3dMatrixOp and UsdTransform3dFallbackMayaXformStack classes +// implement the Transform3d interface for a single matrix transform op and +// for a fallback Maya transform stack, respectively. +// +// In both cases the complete transform stack will have additional transform +// ops that define the complete 3D transformation for the whole object, while +// only part of the complete transform stack (a single matrix and the fallback +// Maya transform stack) is editable. +// +// Certain Maya operations (such as parent -absolute) require the capability to +// edit the matrix transform of the whole object. This class wraps a +// UsdTransform3dMatrixOp or UsdTransform3dFallbackMayaXformStack and converts +// the whole object matrix into a matrix appropriate for UsdTransform3dMatrixOp +// or UsdTransform3dFallbackMayaXformStack. +// +// The matrix algebra is simple: given a wrapped (decorated) Transform3d Mw +// whose matrix we wish to change, we can express it as a part of the complete +// transform stack M in the following way: +// +// M = M M M +// l w r +// +// then +// -1 -1 +// M = M M M +// w l r +// +// Therefore, given M as an argument, we can compute Mw given the fixed +// matrices inv(Ml) and inv(Mr). In the case of the Maya fallback transform +// stack, Mr is the identity by definition, as the Maya fallback transform +// stack must be the last group of transform ops in the transform stack. + +class MAYAUSD_CORE_PUBLIC UsdTransform3dSetObjectMatrix : public UsdTransform3dBase +{ +public: + typedef std::shared_ptr Ptr; + + UsdTransform3dSetObjectMatrix( + const Ufe::Transform3d::Ptr& wrapped, + const GfMatrix4d& mlInv, + const GfMatrix4d& mrInv); + ~UsdTransform3dSetObjectMatrix() override = default; + + //! Create a UsdTransform3dSetObjectMatrix. + static UsdTransform3dSetObjectMatrix::Ptr + create(const Ufe::Transform3d::Ptr& wrapped, const GfMatrix4d& mlInv, const GfMatrix4d& mrInv); + + // Implementation for base class pure virtuals (illegal calls). + Ufe::Vector3d translation() const override; + Ufe::Vector3d rotation() const override; + Ufe::Vector3d scale() const override; + + Ufe::SetMatrix4dUndoableCommand::Ptr setMatrixCmd(const Ufe::Matrix4d& m) override; + void setMatrix(const Ufe::Matrix4d& m) override; + +private: + Ufe::Matrix4d mw(const Ufe::Matrix4d& m) const; + + Ufe::Transform3d::Ptr _wrapped; + const GfMatrix4d _mlInv { 1 }; + const GfMatrix4d _mrInv { 1 }; + +}; // UsdTransform3dSetObjectMatrix + +} // namespace ufe +} // namespace MAYAUSD_NS_DEF diff --git a/lib/mayaUsd/ufe/wrapUtils.cpp b/lib/mayaUsd/ufe/wrapUtils.cpp index ba28d9a42c..38c99c780d 100644 --- a/lib/mayaUsd/ufe/wrapUtils.cpp +++ b/lib/mayaUsd/ufe/wrapUtils.cpp @@ -137,6 +137,18 @@ static Ufe::Path _UfeV1StringToUsdPath(const std::string& ufePathString) } #endif +UsdTimeCode getTime(const std::string& pathStr) +{ + const Ufe::Path path = +#ifdef UFE_V2_FEATURES_AVAILABLE + Ufe::PathString::path( +#else + _UfeV1StringToUsdPath( +#endif + pathStr); + return ufe::getTime(path); +} + std::string stripInstanceIndexFromUfePath(const std::string& ufePathString) { #ifdef UFE_V2_FEATURES_AVAILABLE @@ -213,6 +225,7 @@ void wrapUtils() def("usdPathToUfePathSegment", usdPathToUfePathSegment, (arg("usdPath"), arg("instanceIndex") = UsdImagingDelegate::ALL_INSTANCES)); + def("getTime", getTime); def("stripInstanceIndexFromUfePath", stripInstanceIndexFromUfePath, (arg("ufePathString"))); def("ufePathToPrim", ufePathToPrim); def("ufePathToInstanceIndex", ufePathToInstanceIndex); diff --git a/test/lib/ufe/testParentCmd.py b/test/lib/ufe/testParentCmd.py index 881241ba2a..c91fec8762 100644 --- a/test/lib/ufe/testParentCmd.py +++ b/test/lib/ufe/testParentCmd.py @@ -24,7 +24,7 @@ import mayaUsd.ufe -from pxr import UsdGeom, Vt +from pxr import UsdGeom, Vt, Gf from maya import cmds from maya import standalone @@ -386,6 +386,277 @@ def testParentAbsoluteSingleMatrixOp(self): cylChildren = cylHier.children() self.assertEqual(len(cylChildren), 1) + @unittest.skipUnless(mayaUtils.previewReleaseVersion() >= 123, 'Requires Maya fixes only available in Maya Preview Release 123 or later.') + def testParentAbsoluteFallback(self): + """Test parent -absolute on prim with a fallback Maya transform stack.""" + # Create a scene with an xform and a capsule. + import mayaUsd_createStageWithNewLayer + + mayaUsd_createStageWithNewLayer.createStageWithNewLayer() + proxyShapePathStr = '|stage1|stageShape1' + stage = mayaUsd.lib.GetPrim(proxyShapePathStr).GetStage() + xformPrim = stage.DefinePrim('/Xform1', 'Xform') + capsulePrim = stage.DefinePrim('/Capsule1', 'Capsule') + xformXformable = UsdGeom.Xformable(xformPrim) + capsuleXformable = UsdGeom.Xformable(capsulePrim) + + proxyShapePathSegment = mayaUtils.createUfePathSegment(proxyShapePathStr) + + # Translate and rotate the xform and capsule to set up their initial + # transform ops. + xformPath = ufe.Path([proxyShapePathSegment, + usdUtils.createUfePathSegment('/Xform1')]) + xformItem = ufe.Hierarchy.createItem(xformPath) + capsulePath = ufe.Path([proxyShapePathSegment, + usdUtils.createUfePathSegment('/Capsule1')]) + capsuleItem = ufe.Hierarchy.createItem(capsulePath) + + sn = ufe.GlobalSelection.get() + sn.clear() + sn.append(xformItem) + + cmds.move(0, 5, 0, r=True, os=True, wd=True) + cmds.rotate(0, 90, 0, r=True, os=True, fo=True) + + self.assertEqual( + xformXformable.GetXformOpOrderAttr().Get(), Vt.TokenArray(( + "xformOp:translate", "xformOp:rotateXYZ"))) + + sn.clear() + sn.append(capsuleItem) + + cmds.move(-10, 0, 8, r=True, os=True, wd=True) + cmds.rotate(90, 0, 0, r=True, os=True, fo=True) + + # Add an extra rotate transform op. In so doing, the capsule prim no + # longer matches the Maya transform stack, so our parent -absolute + # operation will be forced to append Maya fallback transform stack ops + # to the capsule. + capsuleXformable.AddRotateXOp() + self.assertEqual( + capsuleXformable.GetXformOpOrderAttr().Get(), Vt.TokenArray(( + "xformOp:translate", "xformOp:rotateXYZ", "xformOp:rotateX"))) + + capsuleT3d = ufe.Transform3d.transform3d(capsuleItem) + capsuleWorld = capsuleT3d.inclusiveMatrix() + capsuleWorldPre = matrixToList(capsuleWorld) + + # The xform currently has no children. + xformHier = ufe.Hierarchy.hierarchy(xformItem) + xformChildren = xformHier.children() + self.assertEqual(len(xformChildren), 0) + + # Parent the capsule to the xform. + cmds.parent(ufe.PathString.string(capsulePath), + ufe.PathString.string(xformPath)) + + def checkParentDone(): + # The xform now has the capsule as its child. + xformChildren = xformHier.children() + self.assertEqual(len(xformChildren), 1) + self.assertIn('Capsule1', childrenNames(xformChildren)) + + # Confirm that the capsule has not moved in world space. Must + # re-create the prim and its scene item, as its path has changed. + capsulePath = ufe.Path( + [proxyShapePathSegment, + usdUtils.createUfePathSegment('/Xform1/Capsule1')]) + capsulePrim = mayaUsd.ufe.ufePathToPrim( + ufe.PathString.string(capsulePath)) + capsuleXformable = UsdGeom.Xformable(capsulePrim) + capsuleItem = ufe.Hierarchy.createItem(capsulePath) + capsuleT3d = ufe.Transform3d.transform3d(capsuleItem) + capsuleWorld = capsuleT3d.inclusiveMatrix() + assertVectorAlmostEqual( + self, capsuleWorldPre, matrixToList(capsuleWorld)) + + # The capsule's transform ops now have Maya fallback stack ops. A + # scale fallback op is added, even though it's uniform unit. + self.assertEqual( + capsuleXformable.GetXformOpOrderAttr().Get(), Vt.TokenArray(( + "xformOp:translate", "xformOp:rotateXYZ", "xformOp:rotateX", + "xformOp:translate:maya_fallback", + "xformOp:rotateXYZ:maya_fallback", + "xformOp:scale:maya_fallback"))) + + checkParentDone() + + # Undo: the xform no longer has a child, the capsule is still where it + # has always been, and the fallback transform ops are gone. + cmds.undo() + + xformChildren = xformHier.children() + self.assertEqual(len(xformChildren), 0) + + capsulePath = ufe.Path( + [proxyShapePathSegment, usdUtils.createUfePathSegment('/Capsule1')]) + capsulePrim = mayaUsd.ufe.ufePathToPrim( + ufe.PathString.string(capsulePath)) + capsuleXformable = UsdGeom.Xformable(capsulePrim) + capsuleItem = ufe.Hierarchy.createItem(capsulePath) + capsuleT3d = ufe.Transform3d.transform3d(capsuleItem) + capsuleWorld = capsuleT3d.inclusiveMatrix() + assertVectorAlmostEqual( + self, capsuleWorldPre, matrixToList(capsuleWorld)) + self.assertEqual( + capsuleXformable.GetXformOpOrderAttr().Get(), Vt.TokenArray(( + "xformOp:translate", "xformOp:rotateXYZ", "xformOp:rotateX"))) + + # Redo: capsule still hasn't moved, Maya fallback ops are back. + cmds.redo() + + checkParentDone() + + @unittest.skipUnless(mayaUtils.previewReleaseVersion() >= 123, 'Requires Maya fixes only available in Maya Preview Release 123 or later.') + def testParentAbsoluteMultiMatrixOp(self): + """Test parent -absolute on prim with a transform stack with multiple matrix ops.""" + + cmds.file(new=True, force=True) + + # Create a scene with an xform and a capsule. + import mayaUsd_createStageWithNewLayer + + mayaUsd_createStageWithNewLayer.createStageWithNewLayer() + proxyShapePathStr = '|stage1|stageShape1' + stage = mayaUsd.lib.GetPrim(proxyShapePathStr).GetStage() + xformPrim = stage.DefinePrim('/Xform1', 'Xform') + capsulePrim = stage.DefinePrim('/Capsule1', 'Capsule') + xformXformable = UsdGeom.Xformable(xformPrim) + capsuleXformable = UsdGeom.Xformable(capsulePrim) + + proxyShapePathSegment = mayaUtils.createUfePathSegment(proxyShapePathStr) + + # Translate and rotate the xform. + xformPath = ufe.Path([proxyShapePathSegment, + usdUtils.createUfePathSegment('/Xform1')]) + xformItem = ufe.Hierarchy.createItem(xformPath) + + sn = ufe.GlobalSelection.get() + sn.clear() + sn.append(xformItem) + + cmds.move(0, -5, 0, r=True, os=True, wd=True) + cmds.rotate(0, -90, 0, r=True, os=True, fo=True) + + self.assertEqual( + xformXformable.GetXformOpOrderAttr().Get(), Vt.TokenArray(( + "xformOp:translate", "xformOp:rotateXYZ"))) + + sn.clear() + + capsulePath = ufe.Path([proxyShapePathSegment, + usdUtils.createUfePathSegment('/Capsule1')]) + capsuleItem = ufe.Hierarchy.createItem(capsulePath) + + # Add 3 matrix transform ops to the capsule. + # matrix A: tx 5, ry 90 + # matrix B: ty 10, rz 90 + # matrix C: tz 15, rx 90 + op = capsuleXformable.AddTransformOp(opSuffix='A') + matrixValA = Gf.Matrix4d(0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 5, 0, 0, 1) + op.Set(matrixValA) + op = capsuleXformable.AddTransformOp(opSuffix='B') + matrixValB = Gf.Matrix4d(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 10, 0, 1) + op.Set(matrixValB) + op = capsuleXformable.AddTransformOp(opSuffix='C') + matrixValC = Gf.Matrix4d(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 15, 1) + op.Set(matrixValC) + + matrixOps = [ + "xformOp:transform:A", "xformOp:transform:B", "xformOp:transform:C"] + matrixValues = { + "xformOp:transform:A" : matrixValA, + "xformOp:transform:B" : matrixValB, + "xformOp:transform:C" : matrixValC } + + self.assertEqual(capsuleXformable.GetXformOpOrderAttr().Get(), + Vt.TokenArray(matrixOps)) + + # Capture the current world space transform of the capsule. + capsuleT3d = ufe.Transform3d.transform3d(capsuleItem) + capsuleWorldPre = matrixToList(capsuleT3d.inclusiveMatrix()) + + # We will run the parenting test 3 times, targeting each matrix op in + # turn. + for matrixOp in matrixOps: + os.environ['MAYA_USD_MATRIX_XFORM_OP_NAME'] = matrixOp + + # The xform currently has no children. + xformHier = ufe.Hierarchy.hierarchy(xformItem) + xformChildren = xformHier.children() + self.assertEqual(len(xformChildren), 0) + + # Parent the capsule to the xform. + cmds.parent(ufe.PathString.string(capsulePath), + ufe.PathString.string(xformPath)) + + def checkParentDone(): + # The xform now has the capsule as its child. + xformChildren = xformHier.children() + self.assertEqual(len(xformChildren), 1) + self.assertIn('Capsule1', childrenNames(xformChildren)) + + # Confirm that the capsule has not moved in world space. Must + # re-create the prim and its scene item after path change. + capsulePath = ufe.Path( + [proxyShapePathSegment, + usdUtils.createUfePathSegment('/Xform1/Capsule1')]) + capsulePrim = mayaUsd.ufe.ufePathToPrim( + ufe.PathString.string(capsulePath)) + capsuleXformable = UsdGeom.Xformable(capsulePrim) + capsuleItem = ufe.Hierarchy.createItem(capsulePath) + capsuleT3d = ufe.Transform3d.transform3d(capsuleItem) + capsuleWorld = capsuleT3d.inclusiveMatrix() + assertVectorAlmostEqual( + self, capsuleWorldPre, matrixToList(capsuleWorld)) + + # No change in the capsule's transform ops. + self.assertEqual( + capsuleXformable.GetXformOpOrderAttr().Get(), + Vt.TokenArray(matrixOps)) + + # Matrix ops that were not targeted did not change. + for checkMatrixOp in matrixOps: + if checkMatrixOp == matrixOp: + continue + op = UsdGeom.XformOp( + capsulePrim.GetAttribute(checkMatrixOp)) + self.assertEqual( + op.GetOpTransform(mayaUsd.ufe.getTime( + ufe.PathString.string(capsulePath))), + matrixValues[checkMatrixOp]) + + checkParentDone() + + # Undo: the xform no longer has a child, the capsule is still where + # it has always been. + cmds.undo() + + xformChildren = xformHier.children() + self.assertEqual(len(xformChildren), 0) + + capsulePath = ufe.Path( + [proxyShapePathSegment, usdUtils.createUfePathSegment('/Capsule1')]) + capsulePrim = mayaUsd.ufe.ufePathToPrim( + ufe.PathString.string(capsulePath)) + capsuleXformable = UsdGeom.Xformable(capsulePrim) + capsuleItem = ufe.Hierarchy.createItem(capsulePath) + capsuleT3d = ufe.Transform3d.transform3d(capsuleItem) + capsuleWorld = capsuleT3d.inclusiveMatrix() + assertVectorAlmostEqual( + self, capsuleWorldPre, matrixToList(capsuleWorld)) + self.assertEqual( + capsuleXformable.GetXformOpOrderAttr().Get(), + Vt.TokenArray(matrixOps)) + + # Redo: capsule still hasn't moved. + cmds.redo() + + checkParentDone() + + # Go back to initial conditions for next iteration of loop. + cmds.undo() + def testParentToProxyShape(self): # Load a file with a USD hierarchy at least 2-levels deep. From c8e1e83d6a350da65be2a039542a32769836a8ce Mon Sep 17 00:00:00 2001 From: Pierre Tremblay Date: Mon, 22 Feb 2021 11:44:40 -0500 Subject: [PATCH 2/6] Fix pre-flight errors. --- .../ufe/UsdTransform3dFallbackMayaXformStack.cpp | 2 +- test/lib/ufe/testComboCmd.py | 11 +++++++++-- test/lib/ufe/testParentCmd.py | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/mayaUsd/ufe/UsdTransform3dFallbackMayaXformStack.cpp b/lib/mayaUsd/ufe/UsdTransform3dFallbackMayaXformStack.cpp index 7bfe0093a2..9cb01e98f0 100644 --- a/lib/mayaUsd/ufe/UsdTransform3dFallbackMayaXformStack.cpp +++ b/lib/mayaUsd/ufe/UsdTransform3dFallbackMayaXformStack.cpp @@ -218,7 +218,7 @@ Ufe::Transform3d::Ptr createTransform3d(const Ufe::SceneItem::Ptr& item) GfMatrix4d ml { 1 }; if (!UsdGeomXformable::GetLocalTransformation(&ml, mlOps, getTime(item->path()))) { TF_FATAL_ERROR( - "Local transformation computation for item %s failed.", item->path().string()); + "Local transformation computation for item %s failed.", item->path().string().c_str()); } // The Maya fallback transform stack is the last group of transform ops in diff --git a/test/lib/ufe/testComboCmd.py b/test/lib/ufe/testComboCmd.py index b9d216548a..35ef80ed76 100644 --- a/test/lib/ufe/testComboCmd.py +++ b/test/lib/ufe/testComboCmd.py @@ -706,8 +706,15 @@ def testFallback(self): mayaSphereItem = ufe.Hierarchy.createItem(mayaSpherePath) usdSphereItem = ufe.Hierarchy.createItem(usdSpherePath) usdFallbackSphereItem = ufe.Hierarchy.createItem(usdFallbackSpherePath) - usdSphere3d = ufe.Transform3d.transform3d(usdSphereItem) - usdFallbackSphere3d = ufe.Transform3d.transform3d(usdFallbackSphereItem) + # For scene items with fallback transform ops, the transform3d() + # interface considers the complete object (i.e. all transform ops in + # the stack), which is undesirable when setting and getting fallback + # pivot transform ops. To consider only the fallback transform ops, + # use the editTransform3d() interface. For scene items with only a + # Maya transform stack, editTransform3d() and transform3d() are + # equivalent, so arbitrarily choose editTransform3d(). + usdSphere3d = ufe.Transform3d.editTransform3d(usdSphereItem, ufe.EditTransform3dHint()) + usdFallbackSphere3d = ufe.Transform3d.editTransform3d(usdFallbackSphereItem, ufe.EditTransform3dHint()) sn = ufe.GlobalSelection.get() sn.clear() diff --git a/test/lib/ufe/testParentCmd.py b/test/lib/ufe/testParentCmd.py index c91fec8762..0e7867c907 100644 --- a/test/lib/ufe/testParentCmd.py +++ b/test/lib/ufe/testParentCmd.py @@ -508,7 +508,7 @@ def checkParentDone(): checkParentDone() @unittest.skipUnless(mayaUtils.previewReleaseVersion() >= 123, 'Requires Maya fixes only available in Maya Preview Release 123 or later.') - def testParentAbsoluteMultiMatrixOp(self): + def testZParentAbsoluteMultiMatrixOp(self): """Test parent -absolute on prim with a transform stack with multiple matrix ops.""" cmds.file(new=True, force=True) From 5c281bac538ed060735d0fdf44ae3a0f037e275f Mon Sep 17 00:00:00 2001 From: Pierre Tremblay Date: Mon, 22 Feb 2021 14:40:42 -0500 Subject: [PATCH 3/6] Fix more pre-flight errors. --- lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp b/lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp index 972390791e..4350082c11 100644 --- a/lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp +++ b/lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp @@ -86,7 +86,7 @@ GfMatrix4d xformInv( GfMatrix4d m { 1 }; if (!UsdGeomXformable::GetLocalTransformation(&m, ops, getTime(path))) { - TF_FATAL_ERROR("Local transformation computation for item %s failed.", path.string()); + TF_FATAL_ERROR("Local transformation computation for item %s failed.", path.string().c_str()); } return m.GetInverse(); From fe35686aec2f83bdfe74f8aeef980d9bcb9ba259 Mon Sep 17 00:00:00 2001 From: Pierre Tremblay Date: Mon, 22 Feb 2021 15:01:41 -0500 Subject: [PATCH 4/6] Fix clang-format error. --- lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp b/lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp index 4350082c11..56da331fca 100644 --- a/lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp +++ b/lib/mayaUsd/ufe/UsdTransform3dMatrixOp.cpp @@ -86,7 +86,8 @@ GfMatrix4d xformInv( GfMatrix4d m { 1 }; if (!UsdGeomXformable::GetLocalTransformation(&m, ops, getTime(path))) { - TF_FATAL_ERROR("Local transformation computation for item %s failed.", path.string().c_str()); + TF_FATAL_ERROR( + "Local transformation computation for item %s failed.", path.string().c_str()); } return m.GetInverse(); From b31c057f224fe98722792bcb5963d89ef8cfffdb Mon Sep 17 00:00:00 2001 From: Pierre Tremblay Date: Mon, 22 Feb 2021 22:19:34 -0500 Subject: [PATCH 5/6] Restore initial conditions after multi-matrix parent test. --- test/lib/ufe/testParentCmd.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/lib/ufe/testParentCmd.py b/test/lib/ufe/testParentCmd.py index 0e7867c907..9f4112e70c 100644 --- a/test/lib/ufe/testParentCmd.py +++ b/test/lib/ufe/testParentCmd.py @@ -508,7 +508,7 @@ def checkParentDone(): checkParentDone() @unittest.skipUnless(mayaUtils.previewReleaseVersion() >= 123, 'Requires Maya fixes only available in Maya Preview Release 123 or later.') - def testZParentAbsoluteMultiMatrixOp(self): + def testParentAbsoluteMultiMatrixOp(self): """Test parent -absolute on prim with a transform stack with multiple matrix ops.""" cmds.file(new=True, force=True) @@ -657,6 +657,9 @@ def checkParentDone(): # Go back to initial conditions for next iteration of loop. cmds.undo() + # Restore initial conditions. + del os.environ['MAYA_USD_MATRIX_XFORM_OP_NAME'] + def testParentToProxyShape(self): # Load a file with a USD hierarchy at least 2-levels deep. From 3892d756c4287cc7c216fbfc015b3f3ad183a10a Mon Sep 17 00:00:00 2001 From: Pierre Tremblay Date: Tue, 23 Feb 2021 14:34:53 -0500 Subject: [PATCH 6/6] Addressed code review feedback. --- .../UsdTransform3dFallbackMayaXformStack.cpp | 6 ++++ .../ufe/UsdTransform3dSetObjectMatrix.h | 30 +++++++++++++++++++ test/lib/ufe/testParentCmd.py | 18 +++++++++-- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/mayaUsd/ufe/UsdTransform3dFallbackMayaXformStack.cpp b/lib/mayaUsd/ufe/UsdTransform3dFallbackMayaXformStack.cpp index 9cb01e98f0..fc4e0bcd2d 100644 --- a/lib/mayaUsd/ufe/UsdTransform3dFallbackMayaXformStack.cpp +++ b/lib/mayaUsd/ufe/UsdTransform3dFallbackMayaXformStack.cpp @@ -144,6 +144,12 @@ void setXformOpOrder(const UsdGeomXformable& xformable) xformable.SetXformOpOrder(newOrder, resetsXformStack); } +// Create a Ufe::Transform3d interface to edit the Maya fallback transform +// stack. This engine method is used in the implementation of +// createTransform3d() and createEditTransform3d(). To avoid having the caller +// repeat these calls for its own use, the prim's transform ops are returned in +// xformOps, along with an iterator to the first Maya fallback transform op in +// firstFallbackOp. Ufe::Transform3d::Ptr createEditTransform3dImp( const Ufe::SceneItem::Ptr& item, std::vector& xformOps, diff --git a/lib/mayaUsd/ufe/UsdTransform3dSetObjectMatrix.h b/lib/mayaUsd/ufe/UsdTransform3dSetObjectMatrix.h index efa17b460f..f1b9b9f5fd 100644 --- a/lib/mayaUsd/ufe/UsdTransform3dSetObjectMatrix.h +++ b/lib/mayaUsd/ufe/UsdTransform3dSetObjectMatrix.h @@ -58,6 +58,36 @@ namespace ufe { // matrices inv(Ml) and inv(Mr). In the case of the Maya fallback transform // stack, Mr is the identity by definition, as the Maya fallback transform // stack must be the last group of transform ops in the transform stack. +// +// Here is an example given a Maya fallback transform stack: +// +// ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:rotateX", +// "xformOp:translate:maya_fallback", "xformOp:rotateXYZ:maya_fallback", +// "xformOp:scale:maya_fallback"] +// +// Note how there are two rotation transform ops in the original stack, which +// does not match a standard Maya transform stack, and forces the use of a +// fallback Maya transform stack. For fallback Maya transform stacks, Mr is +// always the identity, and Ml is the multiplication of all transform ops +// before the fallback Maya transform stack, i.e. here +// "xformOp:translate" "xformOp:rotateXYZ" "xformOp:rotateX" +// . Mw in this case is the entire fallback Maya transform stack, our target. +// +// Here are three examples given a transform stack with multiple matrix +// transform ops: +// +// ["xformOp:transform:A", "xformOp:transform:B", "xformOp:transform:C"] +// +// If we are targeting matrix transform op Mw == "xformOp:transform:A", then +// Ml is the identity matrix, and Mr is +// "xformOp:transform:B" "xformOp:transform:C". +// +// If we are targeting matrix transform op Mw == "xformOp:transform:B", then +// Ml is "xformOp:transform:A", and Mr is "xformOp:transform:C". +// +// If we are targeting matrix transform op Mw == "xformOp:transform:C", then +// Ml is "xformOp:transform:A" "xformOp:transform:B", and Mr is the identity +// matrix. class MAYAUSD_CORE_PUBLIC UsdTransform3dSetObjectMatrix : public UsdTransform3dBase { diff --git a/test/lib/ufe/testParentCmd.py b/test/lib/ufe/testParentCmd.py index 9f4112e70c..a133e09edf 100644 --- a/test/lib/ufe/testParentCmd.py +++ b/test/lib/ufe/testParentCmd.py @@ -84,6 +84,21 @@ def setUp(self): # Clear selection to start off cmds.select(clear=True) + # Save current 'MAYA_USD_MATRIX_XFORM_OP_NAME' env var, if present. + self.mayaUsdMatrixXformOpName = os.environ.get( + 'MAYA_USD_MATRIX_XFORM_OP_NAME') + + def tearDown(self): + # If there was no 'MAYA_USD_MATRIX_XFORM_OP_NAME' environment variable, + # make sure there is none at the end of the test. + if self.mayaUsdMatrixXformOpName is None: + if 'MAYA_USD_MATRIX_XFORM_OP_NAME' in os.environ: + del os.environ['MAYA_USD_MATRIX_XFORM_OP_NAME'] + else: + # Restore previous value. + os.environ['MAYA_USD_MATRIX_XFORM_OP_NAME'] = \ + self.mayaUsdMatrixXformOpName + def testParentRelative(self): # Create scene items for the cube and the cylinder. shapeSegment = mayaUtils.createUfePathSegment( @@ -657,9 +672,6 @@ def checkParentDone(): # Go back to initial conditions for next iteration of loop. cmds.undo() - # Restore initial conditions. - del os.environ['MAYA_USD_MATRIX_XFORM_OP_NAME'] - def testParentToProxyShape(self): # Load a file with a USD hierarchy at least 2-levels deep.