Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAYA-114992 The true bindPose for a skin cluster comes from it's bindPreMatrix #2270

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
289 changes: 117 additions & 172 deletions lib/usd/translators/jointWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
#include <maya/MDagPath.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnMatrixData.h>
#include <maya/MFnSkinCluster.h>
#include <maya/MFnTransform.h>
#include <maya/MGlobal.h>
#include <maya/MItDag.h>
#include <maya/MMatrix.h>
#include <maya/MPlug.h>
Expand All @@ -55,6 +57,14 @@

#include <vector>

#define CHECK_MSTATUS_AND_CONTINUE(_status) \
{ \
MStatus _maya_status = (_status); \
if (MStatus::kSuccess != _maya_status) { \
continue; \
} \
}

PXR_NAMESPACE_OPEN_SCOPE

PXRUSDMAYA_REGISTER_WRITER(joint, PxrUsdTranslators_JointWriter);
Expand Down Expand Up @@ -106,44 +116,114 @@ static bool _IsTransformNodeAnimated(const MDagPath& dagPath)
/// Gets the world-space rest transform for a single dag path.
static GfMatrix4d _GetJointWorldBindTransform(const MDagPath& dagPath)
{
MFnDagNode dagNode(dagPath);
MMatrix restTransformWorld;
if (UsdMayaUtil::getPlugMatrix(dagNode, "bindPose", &restTransformWorld)) {
return GfMatrix4d(restTransformWorld.matrix);
}
// NOTE: (yliangsiew) Instead of assuming an identity matrix, we check if the joint is linked to
// a corresponding bindPose, and attempt to grab the bind transform matrix there. If it's
// _still_ empty, then we assume identity. This catches odd edge cases where a joint is bound,
// but its `bindPose` attribute is empty and the `bindPose` node stores the actual bind
// transform matrix.
MStatus status;
MPlug plgMsg = dagNode.findPlug(MPxNode::message, false, &status);
if (!status || !plgMsg.isSource()) {
return GfMatrix4d(1);
// In the Maya skin cluster the REAL bindPose data that matters is what
// is stored on the skinCluster node in bindPreMatrix. The dagPose node and
// the bindPose attribute on the joints is not used when doing deformation.
// The values should match up, but someone could edit a scene so they get out of sync.
// Get the bindTransform from the skinCluster.

MFnDagNode dagNode(dagPath);
MStatus status;
MFnDependencyNode dgNode(dagPath.node(), &status);

MPlug plugWorldMatrixParent = dagNode.findPlug("worldMatrix", true, &status);
if (status) {
unsigned int numInstance = plugWorldMatrixParent.numElements(&status);
TF_VERIFY(numInstance < 2 && status); // if the skeleton is instanced in Maya then what?
for (unsigned int instanceIndex = 0; instanceIndex < numInstance; instanceIndex++) {
MPlug plugWorldMatrix = plugWorldMatrixParent.elementByLogicalIndex(instanceIndex);

MPlugArray plgsDest;
plugWorldMatrix.destinations(plgsDest);
GfMatrix4d result;
MObject resultNode;
bool hasResult = false;

for (unsigned int i = 0; i < plgsDest.length(); ++i) {
MPlug plgDest = plgsDest[i];
MObject curNode = plgDest.node();
if (!curNode.hasFn(MFn::kSkinClusterFilter)) {
continue;
}

// We should be connected to a matrix[x] plug.
TF_VERIFY(plgDest.isElement());
unsigned int membersIdx = plgDest.logicalIndex();
MFnDependencyNode fnNode(curNode, &status);
CHECK_MSTATUS_AND_CONTINUE(status);
MPlug plgWorldMatrices = fnNode.findPlug("bindPreMatrix", false, &status);
CHECK_MSTATUS_AND_CONTINUE(status);
MPlug plgBindPreMatrix = plgWorldMatrices.elementByLogicalIndex(membersIdx);
MObject plgBindMatrixData = plgBindPreMatrix.asMObject();
MFnMatrixData fnMatrixData(plgBindMatrixData, &status);
CHECK_MSTATUS_AND_CONTINUE(status);
MMatrix mayaResult = fnMatrixData.matrix().inverse();

if (!hasResult) {
result = GfMatrix4d(mayaResult.matrix);
resultNode = curNode;
hasResult = true;
} else {
GfMatrix4d tempResult = GfMatrix4d(mayaResult.matrix);
if (!GfIsClose(tempResult, result, 1e-6)) {
MFnDependencyNode fnResultNode(resultNode, &status);
CHECK_MSTATUS_AND_CONTINUE(status);
MString errorMsg;
errorMsg += "Joint '";
errorMsg += dgNode.name();
errorMsg += "' has different bind poses. bindPreMatrix values on ";
errorMsg += fnResultNode.name();
errorMsg += " and ";
errorMsg += fnNode.name();
errorMsg += " differ. Using bindPreMatrix from ";
errorMsg += fnResultNode.name();
errorMsg += ".";
MGlobal::displayWarning(errorMsg);
}
}
}

if (hasResult) {
return result;
}
}
}
MPlugArray plgsDest;
plgMsg.destinations(plgsDest);
for (unsigned int i = 0; i < plgsDest.length(); ++i) {
MPlug plgDest = plgsDest[i];
MObject curNode = plgDest.node();
if (!curNode.hasFn(MFn::kDagPose)) {
continue;

// Check if the joint is linked to a bindPose, and attempt to grab the bind transform matrix
// there.
MPlug plgMsg = dagNode.findPlug(MPxNode::message, false, &status);
if (status && plgMsg.isSource()) {
MPlugArray plgsDest;
plgMsg.destinations(plgsDest);
for (unsigned int i = 0; i < plgsDest.length(); ++i) {
MPlug plgDest = plgsDest[i];
MObject curNode = plgDest.node();
if (!curNode.hasFn(MFn::kDagPose)) {
continue;
}

// We should be connected to a members[x] plug.
TF_VERIFY(plgDest.isElement());
unsigned int membersIdx = plgDest.logicalIndex();
MFnDependencyNode fnNode(curNode, &status);
CHECK_MSTATUS_AND_CONTINUE(status);
MPlug plgWorldMatrices = fnNode.findPlug("worldMatrix", false, &status);
CHECK_MSTATUS_AND_CONTINUE(status);
MPlug plgWorldMatrix = plgWorldMatrices.elementByLogicalIndex(membersIdx);
MObject plgWorldMatrixData = plgWorldMatrix.asMObject();
MFnMatrixData fnMatrixData(plgWorldMatrixData, &status);
CHECK_MSTATUS_AND_CONTINUE(status);
MMatrix result = fnMatrixData.matrix();

return GfMatrix4d(result.matrix);
}
}

// NOTE: (yliangsiew) We should be connected to a members[x] plug.
TF_VERIFY(plgDest.isElement());
unsigned int membersIdx = plgDest.logicalIndex();
MFnDependencyNode fnNode(curNode, &status);
CHECK_MSTATUS_AND_RETURN(status, GfMatrix4d(1));
MPlug plgWorldMatrices = fnNode.findPlug("worldMatrix", false, &status);
CHECK_MSTATUS_AND_RETURN(status, GfMatrix4d(1));
MPlug plgWorldMatrix = plgWorldMatrices.elementByLogicalIndex(membersIdx);
MObject plgWorldMatrixData = plgWorldMatrix.asMObject();
MFnMatrixData fnMatrixData(plgWorldMatrixData, &status);
CHECK_MSTATUS_AND_RETURN(status, GfMatrix4d(1));
MMatrix result = fnMatrixData.matrix();

return GfMatrix4d(result.matrix);
// If the dagPose node doesn't have an extra for our joint there could be something useful in
// the bindPose attribute of the joint. Check there.
MMatrix restTransformWorld;
if (UsdMayaUtil::getPlugMatrix(dagNode, "bindPose", &restTransformWorld)) {
return GfMatrix4d(restTransformWorld.matrix);
}

return GfMatrix4d(1);
Expand All @@ -162,95 +242,6 @@ static VtMatrix4dArray _GetJointWorldBindTransforms(
return worldXforms;
}

/// Find a dagPose that holds a bind pose for \p dagPath.
static MObject _FindBindPose(const MDagPath& dagPath)
{
MStatus status;

MFnDependencyNode depNode(dagPath.node(), &status);
CHECK_MSTATUS_AND_RETURN(status, MObject());

MPlug msgPlug = depNode.findPlug("message", &status);

MPlugArray outputs;
msgPlug.connectedTo(outputs, /*asDst*/ false, /*asSrc*/ true, &status);

for (unsigned int i = 0; i < outputs.length(); ++i) {
MObject outputNode = outputs[i].node();

if (outputNode.apiType() == MFn::kDagPose) {

// dagPose nodes have a 'bindPose' bool that determines whether
// or not they represent a bind pose.

MFnDependencyNode poseDep(outputNode, &status);
MPlug bindPosePlug = poseDep.findPlug("bindPose", &status);
if (status) {
if (bindPosePlug.asBool()) {
return outputNode;
}
}

return outputNode;
}
}
return MObject();
}

/// Get the member indices of all objects in \p dagPaths within the
/// members array plug of a dagPose.
/// Returns true only if all \p dagPaths can be mapped to a dagPose member.
static bool _FindDagPoseMembers(
const MFnDependencyNode& dagPoseDep,
const std::vector<MDagPath>& dagPaths,
std::vector<unsigned int>* indices)
{
MStatus status;
MPlug membersPlug = dagPoseDep.findPlug("members", false, &status);
CHECK_MSTATUS_AND_RETURN(status, false);

// Build a map of dagPath->index.
UsdMayaUtil::MObjectHandleUnorderedMap<size_t> pathIndexMap;
for (size_t i = 0; i < dagPaths.size(); ++i) {
pathIndexMap[MObjectHandle(dagPaths[i].node())] = i;
}

MPlugArray inputs;

size_t numDagPaths = dagPaths.size();
indices->clear();
indices->resize(numDagPaths);

std::vector<uint8_t> visitedIndices(numDagPaths, 0);
for (unsigned int i = 0; i < membersPlug.numConnectedElements(); ++i) {

MPlug memberPlug = membersPlug.connectionByPhysicalIndex(i);
memberPlug.connectedTo(inputs, /*asDst*/ true, /*asSrc*/ false);

for (unsigned int j = 0; j < inputs.length(); ++j) {
MObjectHandle connNode(inputs[j].node());
auto it = pathIndexMap.find(connNode);
if (it != pathIndexMap.end()) {
(*indices)[it->second] = memberPlug.logicalIndex();
visitedIndices[it->second] = 1;
}
}
}

// Validate that all of the input dagPaths are members.
for (size_t i = 0; i < visitedIndices.size(); ++i) {
uint8_t visited = visitedIndices[i];
if (visited != 1) {
TF_WARN(
"Node '%s' is not a member of dagPose '%s'.",
MFnDependencyNode(dagPaths[i].node()).name().asChar(),
dagPoseDep.name().asChar());
return false;
}
}
return true;
}

bool _GetLocalTransformForDagPoseMember(
const MFnDependencyNode& dagPoseDep,
unsigned int logicalIndex,
Expand Down Expand Up @@ -286,52 +277,6 @@ bool _GetLocalTransformForDagPoseMember(
return true;
}

/// Get local-space bind transforms to use as rest transforms.
/// The dagPose is expected to hold the local transforms.
static bool _GetJointLocalRestTransformsFromDagPose(
const SdfPath& skelPath,
const MDagPath& rootJoint,
const std::vector<MDagPath>& jointDagPaths,
VtMatrix4dArray* xforms)
{
// Use whatever bindPose the root joint is a member of.
MObject bindPose = _FindBindPose(rootJoint);
if (bindPose.isNull()) {
TF_DEBUG(PXRUSDMAYA_TRANSLATORS)
.Msg(
"%s -- Could not find a dagPose node holding a bind pose: "
"The Skeleton's 'restTransforms' property will be "
"calculated from the 'bindTransforms'.\n",
skelPath.GetText());
return false;
}

MStatus status;
MFnDependencyNode bindPoseDep(bindPose, &status);
CHECK_MSTATUS_AND_RETURN(status, false);

std::vector<unsigned int> memberIndices;
if (!_FindDagPoseMembers(bindPoseDep, jointDagPaths, &memberIndices)) {
return false;
}

xforms->resize(jointDagPaths.size());
for (size_t i = 0; i < xforms->size(); ++i) {
if (!_GetLocalTransformForDagPoseMember(
bindPoseDep, memberIndices[i], xforms->data() + i)) {
TF_WARN(
"%s -- Failed retrieving the local transform of joint '%s' "
"from dagPose '%s': The Skeleton's 'restTransforms' "
"property will be calculated from the 'bindTransforms'.",
skelPath.GetText(),
jointDagPaths[i].fullPathName().asChar(),
bindPoseDep.name().asChar());
return false;
}
}
return true;
}

/// Set local-space rest transform by converting from the world-space bind xforms.
static bool
_GetJointLocalRestTransformsFromBindTransforms(UsdSkelSkeleton& skel, VtMatrix4dArray& restXforms)
Expand Down Expand Up @@ -555,9 +500,9 @@ bool PxrUsdTranslators_JointWriter::_WriteRestState()
UsdMayaWriteUtil::SetAttribute(
_skel.GetBindTransformsAttr(), bindXforms, UsdTimeCode::Default(), _GetSparseValueWriter());

// Create something reasonable for rest transforms
VtMatrix4dArray restXforms;
if (_GetJointLocalRestTransformsFromDagPose(skelPath, GetDagPath(), _joints, &restXforms)
|| _GetJointLocalRestTransformsFromBindTransforms(_skel, restXforms)) {
if (_GetJointLocalRestTransformsFromBindTransforms(_skel, restXforms)) {
UsdMayaWriteUtil::SetAttribute(
_skel.GetRestTransformsAttr(),
restXforms,
Expand Down
8 changes: 4 additions & 4 deletions test/lib/usd/translators/testUsdExportBindTransform.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ def testBindTransformExport(self):
0, 0, 1, 0,
0, 0, 0, 1))

self.assertEqual(bindTransform, Gf.Matrix4d(-0.002290224357008666, -0.0056947280722030105, 0.9999807051930374, 0.0,
0.01567493647243696, 0.9998602653154095, 0.005729941968379492, 0.0,
-0.9998745177471474, 0.01568775712160303, -0.0022006397844859227, 0.0,
-4.625417221923634, 16.113398996722644, 5.401075137997586, 1.0))
self.assertEqual(bindTransform, Gf.Matrix4d(-0.002290224357008621, -0.0056947280722027946, 0.9999807051930374, -5.421010862427522e-20,
0.01567493647243695, 0.9998602653154097, 0.005729941968379504, 0,
-0.9998745177471474, 0.015687757121603033, -0.0022006397844859223, 0,
-4.625417221923633, 16.11339899672265, 5.401075137997587, 1.0000000000000002) )

if __name__ == '__main__':
unittest.main()
27 changes: 0 additions & 27 deletions test/lib/usd/translators/testUsdExportSkeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,34 +280,7 @@ def testSkelForSegfault(self):
for _ in range(5):
cmds.mayaUSDExport(mergeTransformAndShape=True, file=usdFile,
shadingMode='none', exportSkels='auto', selection=True)

def testSkelMissingJointFromDagPose(self):
"""
Check that dagPoses that don't contain all desired joints issue an
appropriate warning
"""
mayaFile = os.path.join(self.inputPath, "UsdExportSkeletonTest", "UsdExportSkeletonBindPoseMissingJoints.ma")
cmds.file(mayaFile, force=True, open=True)

usdFile = os.path.abspath('UsdExportBindPoseMissingJointsTest.usda')

joints = cmds.listRelatives('joint_grp', allDescendents=True, type='joint')
bindMembers = cmds.dagPose('dagPose1', q=1, members=1)
nonBindJoints = [j for j in joints if j not in bindMembers]
self.assertEqual(nonBindJoints, [u'joint4'])

delegate = UsdUtils.CoalescingDiagnosticDelegate()

cmds.select('joint_grp')
cmds.mayaUSDExport(mergeTransformAndShape=True, file=usdFile, shadingMode='none',
exportSkels='auto', selection=True)

messages = delegate.TakeUncoalescedDiagnostics()
warnings = [x.commentary for x in messages if x.diagnosticCode == Tf.TF_DIAGNOSTIC_WARNING_TYPE]
missingJointWarnings = [x for x in warnings if 'is not a member of dagPose' in x]
self.assertEqual(len(missingJointWarnings), 1)
self.assertIn("Node 'joint4' is not a member of dagPose 'dagPose1'",
missingJointWarnings[0])

def testSkelBindPoseSparseIndices(self):
"""
Expand Down