Skip to content

Commit

Permalink
Merge pull request #3886 from Autodesk/barbalt/EMSUSD-1464/dual-quate…
Browse files Browse the repository at this point in the history
…rnions

EMSUSD-1464 USD Support for Maya Dual Quaternion skinned shapes
  • Loading branch information
seando-adsk authored Aug 15, 2024
2 parents 668990e + 923d942 commit 8391cd9
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 0 deletions.
20 changes: 20 additions & 0 deletions lib/mayaUsd/fileio/translators/translatorSkel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,26 @@ bool UsdMayaTranslatorSkel::CreateSkinCluster(
std::string skinClusterName = TfStringPrintf("skinCluster_%s", primToSkin.GetName().GetText());
status = dgMod.renameNode(skinCluster, MString(skinClusterName.c_str()));

// Check if the skinning method on the mesh is dualQuaternion (classicLinear is default)
TfToken skinningMethod;
if (skinningQuery
.GetPrim()
#if PXR_VERSION > 2211
.GetAttribute(UsdSkelTokens->primvarsSkelSkinningMethod)
.Get(&skinningMethod)
&& skinningMethod == UsdSkelTokens->dualQuaternion) {
#else
.GetAttribute(TfToken("primvars:skel:skinningMethod"))
.Get(&skinningMethod)
&& skinningMethod == TfToken("dualQuaternion")) {
#endif
MFnSkinCluster skinClusterFn(skinCluster, &status);
if (status == MS::kSuccess) {
MPlug skinMethodPlug = skinClusterFn.findPlug("skinningMethod");
skinMethodPlug.setInt(1); // Dual quaternion
}
}

CHECK_MSTATUS_AND_RETURN(status, false);

MObject groupId = dgMod.createNode(_MayaTokens->groupIdType, &status);
Expand Down
11 changes: 11 additions & 0 deletions lib/mayaUsd/fileio/utils/jointWriteUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,17 @@ MObject UsdMayaJointUtil::writeSkinningData(
// the root joint. Configure this mesh to be bound to the same skel.
bindingAPI.CreateSkeletonRel().SetTargets({ skelPath });

#if PXR_VERSION > 2211
// Get the skinning method from the skin cluster and set it on the mesh prim
MPlug skinMethodPlug = skinCluster.findPlug("skinningMethod");
if (skinMethodPlug.asInt() > 0) {
// Use linear as default skinning method, change to dual quaternion for the others
bindingAPI.GetPrim()
.GetAttribute(UsdSkelTokens->primvarsSkelSkinningMethod)
.Set(UsdSkelTokens->dualQuaternion);
}
#endif

return inMeshObj;
}

Expand Down
113 changes: 113 additions & 0 deletions test/lib/usd/translators/UsdImportSkeleton/skelCubeDq.usda
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#usda 1.0
(
endTimeCode = 30
startTimeCode = 1
timeCodesPerSecond = 24
upAxis = "Y"
)

def SkelRoot "Root" (
kind = "component"
prepend apiSchemas = ["SkelBindingAPI"]
)
{
float3[] extent.timeSamples = {
1: [(-8.881784e-16, -1, 0), (0, 1, 4.440892e-16)],
2: [(-8.8548644e-16, -1, 0), (0, 0.99992824, 0.011980047)],
3: [(-8.77966e-16, -1, 0), (0, 0.9990293, 0.04405114)],
4: [(-8.6628537e-16, -1, 0), (0, 0.99590486, 0.0904073)],
5: [(-8.512596e-16, -1, 0), (0, 0.98941684, 0.14510126)],
6: [(-8.341171e-16, -1, 0), (0, 0.97935027, 0.20217088)],
7: [(-8.1660954e-16, -1, 0), (0, 0.9667337, 0.25578493)],
8: [(-8.0096155e-16, -1, 0), (0, 0.95381016, 0.3004101)],
9: [(-7.8970147e-16, -1, 0), (0, 0.94367975, 0.3308602)],
10: [(-7.8540797e-16, -1, 0), (0, 0.93965006, 0.34213713)],
11: [(-0.008673679, -1, 0), (0, 0.9396149, 0.34212375)],
12: [(-0.032211367, -1, 0), (0, 0.9391626, 0.34195903)],
13: [(-0.06686227, -1, 0), (0, 0.93754745, 0.34137106)],
14: [(-0.10882605, -1, 0), (0, 0.9340696, 0.34010422)],
15: [(-0.15427072, -1, 0), (0, 0.9284015, 0.33804023)],
16: [(-0.19939104, -1, 0), (0, 0.9207824, 0.33526555)],
17: [(-0.24048574, -1, 0), (0, 0.91207445, 0.33209455)],
18: [(-0.2740113, -1, 0), (0, 0.903687, 0.3290404)],
19: [(-0.2965753, -1, 0), (0, 0.8973754, 0.32674214)],
20: [(-0.30484772, -1, 0), (0, 0.89492446, 0.3258497)],
21: [(-0.30484772, -1, 0), (0, 0.89492446, 0.3258497)],
22: [(-0.30484763, -1, 0), (0, 0.8949245, 0.32584968)],
23: [(-0.30484766, -1, 0), (0, 0.8949245, 0.3258497)],
24: [(-0.30484772, -1, 0), (0, 0.89492446, 0.32584974)],
25: [(-0.3048477, -1, 0), (0, 0.8949245, 0.3258497)],
26: [(-0.3048477, -1, 0), (0, 0.8949245, 0.3258497)],
27: [(-0.30484763, -1, 0), (0, 0.8949245, 0.32584968)],
28: [(-0.30484766, -1, 0), (0, 0.8949245, 0.32584968)],
29: [(-0.30484766, -1, 0), (0, 0.8949245, 0.32584968)],
30: [(-0.30484766, -1, 0), (0, 0.8949245, 0.3258497)],
}
float3[] extentsHint.timeSamples = {
1: [(-8.881784e-16, -1, 0), (0, 1, 4.440892e-16)],
2: [(-8.8548644e-16, -1, 0), (0, 0.99992824, 0.011980047)],
3: [(-8.77966e-16, -1, 0), (0, 0.9990293, 0.04405114)],
4: [(-8.6628537e-16, -1, 0), (0, 0.99590486, 0.0904073)],
5: [(-8.512596e-16, -1, 0), (0, 0.98941684, 0.14510126)],
6: [(-8.341171e-16, -1, 0), (0, 0.97935027, 0.20217088)],
7: [(-8.1660954e-16, -1, 0), (0, 0.9667337, 0.25578493)],
8: [(-8.0096155e-16, -1, 0), (0, 0.95381016, 0.3004101)],
9: [(-7.8970147e-16, -1, 0), (0, 0.94367975, 0.3308602)],
10: [(-7.8540797e-16, -1, 0), (0, 0.93965006, 0.34213713)],
11: [(-0.008673679, -1, 0), (0, 0.9396149, 0.34212375)],
12: [(-0.032211367, -1, 0), (0, 0.9391626, 0.34195903)],
13: [(-0.06686227, -1, 0), (0, 0.93754745, 0.34137106)],
14: [(-0.10882605, -1, 0), (0, 0.9340696, 0.34010422)],
15: [(-0.15427072, -1, 0), (0, 0.9284015, 0.33804023)],
16: [(-0.19939104, -1, 0), (0, 0.9207824, 0.33526555)],
17: [(-0.24048574, -1, 0), (0, 0.91207445, 0.33209455)],
18: [(-0.2740113, -1, 0), (0, 0.903687, 0.3290404)],
19: [(-0.2965753, -1, 0), (0, 0.8973754, 0.32674214)],
20: [(-0.30484772, -1, 0), (0, 0.89492446, 0.3258497)],
21: [(-0.30484772, -1, 0), (0, 0.89492446, 0.3258497)],
22: [(-0.30484763, -1, 0), (0, 0.8949245, 0.32584968)],
23: [(-0.30484766, -1, 0), (0, 0.8949245, 0.3258497)],
24: [(-0.30484772, -1, 0), (0, 0.89492446, 0.32584974)],
25: [(-0.3048477, -1, 0), (0, 0.8949245, 0.3258497)],
26: [(-0.3048477, -1, 0), (0, 0.8949245, 0.3258497)],
27: [(-0.30484763, -1, 0), (0, 0.8949245, 0.32584968)],
28: [(-0.30484766, -1, 0), (0, 0.8949245, 0.32584968)],
29: [(-0.30484766, -1, 0), (0, 0.8949245, 0.32584968)],
30: [(-0.30484766, -1, 0), (0, 0.8949245, 0.3258497)],
}
rel skel:animationSource = </Root/Animation>
rel skel:skeleton = </Root/Skeleton>

def Skeleton "Skeleton"
{
uniform matrix4d[] bindTransforms = [( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ), ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (1, 0, 0, 1) ), ( (0, 0, 1, 0), (1, 0, 0, 0), (0, 1, 0, 0), (2, 0, 0, 1) )]
uniform token[] joints = ["joint1", "joint1/joint2", "joint1/joint2/joint3"]
uniform matrix4d[] restTransforms = [( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ), ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (1, 0, 0, 1) ), ( (0, 0, 1, 0), (1, 0, 0, 0), (0, 1, 0, 0), (1, 0, 0, 1) )]
}

def SkelAnimation "Animation"
{
uniform token[] joints = ["joint1", "joint1/joint2", "joint1/joint2/joint3"]
}

def Mesh "Cube" (
prepend apiSchemas = ["SkelBindingAPI"]
)
{
int[] faceVertexCounts = [4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
int[] faceVertexIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
point3f[] points = [(-0.5, -1, 0.5), (0.5, -1, 0.5), (0.5, 0, 0.5), (-0.5, 0, 0.5), (-0.5, 0, 0.5), (0.5, 0, 0.5), (0.5, 1, 0.5), (-0.5, 1, 0.5), (-0.5, 1, 0.5), (0.5, 1, 0.5), (0.5, 1, -0.5), (-0.5, 1, -0.5), (-0.5, 1, -0.5), (0.5, 1, -0.5), (0.5, 0, -0.5), (-0.5, 0, -0.5), (-0.5, 0, -0.5), (0.5, 0, -0.5), (0.5, -1, -0.5), (-0.5, -1, -0.5), (-0.5, -1, -0.5), (0.5, -1, -0.5), (0.5, -1, 0.5), (-0.5, -1, 0.5), (0.5, -1, 0.5), (0.5, -1, -0.5), (0.5, 0, -0.5), (0.5, 0, 0.5), (0.5, 0, 0.5), (0.5, 0, -0.5), (0.5, 1, -0.5), (0.5, 1, 0.5), (-0.5, -1, -0.5), (-0.5, -1, 0.5), (-0.5, 0, 0.5), (-0.5, 0, -0.5), (-0.5, 0, -0.5), (-0.5, 0, 0.5), (-0.5, 1, 0.5), (-0.5, 1, -0.5)]
matrix4d primvars:skel:geomBindTransform = ( (0, 0, 1, 0), (1, 0, 0, 0), (0, 1, 0, 0), (1, 0, 0, 1) )
int[] primvars:skel:jointIndices = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 1, 2, 0, 1, 2, 0, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 1, 2, 0, 1, 2, 0] (
elementSize = 3
interpolation = "vertex"
)
uniform token primvars:skel:skinningMethod = "dualQuaternion"
float[] primvars:skel:jointWeights = [1, 0, 0, 1, 0, 0, 0.5, 0.5, 0, 0.5, 0.5, 0, 0.5, 0.5, 0, 0.5, 0.5, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0.5, 0.5, 0, 0.5, 0.5, 0, 0.5, 0.5, 0, 0.5, 0.5, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0.5, 0.5, 0, 0.5, 0.5, 0, 0.5, 0.5, 0, 0.5, 0.5, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0.5, 0.5, 0, 0.5, 0.5, 0, 0.5, 0.5, 0, 0.5, 0.5, 0, 1, 0, 0, 1, 0, 0] (
elementSize = 3
interpolation = "vertex"
)
uniform token subdivisionScheme = "none"
}
}

25 changes: 25 additions & 0 deletions test/lib/usd/translators/testUsdExportSkeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,31 @@ def testSkelExportWithNewRoot(self):
skeleton = UsdSkel.Skeleton.Get(stage, '/testSkel/joint1')
self.assertTrue(skeleton)

@unittest.skipUnless(Usd.GetVersion() > (0, 22, 11), 'SkinningMethod requires USD version 23 and above')
def testSkelExportSkinningMethod(self):
"""
Test if we correctly exported the proper skinned method for mesh
"""
mayaFile = os.path.join(self.inputPath, "UsdExportSkeletonTest", "UsdExportSkeletonAtSceneRoot.ma")
cmds.file(mayaFile, force=True, open=True)

# change the skinning method, so that it can be tested in usd
skinClusterName="skinCluster1"
selectionList = OM.MSelectionList()
selectionList.add(skinClusterName)
skinCluster = OM.MFnDependencyNode(selectionList.getDependNode(0))
cmds.setAttr("%s.skinningMethod"%skinClusterName, 1) # 1 == dual quaternion

usdFile = os.path.abspath('UsdExportSkinningMethodTest.usda')
cmds.usdExport(mergeTransformAndShape=True, file=usdFile, rootPrimType='xform', exportSkin='auto',
exportSkels="auto", rootPrim="testSkel", defaultPrim="testSkel")

stage = Usd.Stage.Open(usdFile)

meshPrim = stage.GetPrimAtPath('/testSkel/pCube1')
self.assertTrue(meshPrim)
self.assertTrue(meshPrim.GetAttribute("primvars:skel:skinningMethod").Get() == "dualQuaternion")

def testSkelForSegfault(self):
"""
Certain skeletons cause heap corruption and segfaulting when exported multiple times.
Expand Down
23 changes: 23 additions & 0 deletions test/lib/usd/translators/testUsdImportSkeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ def _ValidateSkinClusterRig(self, joints, skinClusterName, groupPartsName,
_ArraysAreClose(cmds.getAttr("%s.geomMatrix"%skinClusterName),
_GfMatrixToList(
usdSkinningQuery.GetGeomBindTransform())))

self.assertEqual(cmds.getAttr("%s.skinningMethod"%skinClusterName), 0)

connections = cmds.listConnections(
"%s.groupId"%groupIdName,
Expand Down Expand Up @@ -274,6 +276,27 @@ def test_SkelImport(self):
meshName=meshPrim.GetName(),
usdSkelQuery=skelQuery,
usdSkinningQuery=skinningQuery)

def test_SkelImportDQ(self):
"""
Tests if skinning method is properly setup when importing a skin mesh.
"""
cmds.file(new=True, force=True)

path = os.path.join(self.inputPath, "UsdImportSkeleton", "skelCubeDq.usda")

cmds.usdImport(file=path, readAnimData=True, primPath="/Root",
shadingMode=[["none", "default"], ])

stage = Usd.Stage.Open(path)
meshPrim = stage.GetPrimAtPath("/Root/Cube")
self.assertTrue(meshPrim)

skinClusterName="skinCluster_{}".format(meshPrim.GetName())
skinCluster = _GetDepNode(skinClusterName)
self.assertEqual(skinCluster.typeName, "skinCluster")
# In maya, Dual Quaternion is the second position on the dropdown, checking if it is properly set here
self.assertEqual(cmds.getAttr("%s.skinningMethod"%skinClusterName), 1)

def test_SkelImportStaticTimeSampledMesh(self):
"""
Expand Down

0 comments on commit 8391cd9

Please sign in to comment.