diff --git a/pxr/usd/usdSkel/doxygen/apiIntro.dox b/pxr/usd/usdSkel/doxygen/apiIntro.dox index d6811dc3f4..283fa757b3 100644 --- a/pxr/usd/usdSkel/doxygen/apiIntro.dox +++ b/pxr/usd/usdSkel/doxygen/apiIntro.dox @@ -744,10 +744,12 @@ down into its component parts. - C++: \code{.cpp} +using namespace pxr; + bool WriteAnimatedSkel( const UsdStagePtr& stage, - const SdfPath& skelPath, + const SdfPath& skelRootPath, const SdfPathVector& jointPaths, const std::vector& rootTransformsPerFrame, const std::vector& jointWorldSpaceTransformsPerFrame, @@ -762,12 +764,15 @@ WriteAnimatedSkel( if (bindTransforms.size() != jointPaths.size()) return false; - UsdSkelSkeleton skel = UsdSkelSkeleton::Define(stage, skelPath); - if (!skel) { - TF_WARN("Failed creating a Skeleton prim at <%s>.", skelPath.GetText()); + UsdSkelRoot skelRoot = UsdSkelRoot::Define(stage, skelRootPath); + if (!skelRoot) { + TF_WARN("Failed creating a Skeleton prim at <%s>.", skelRootPath.GetText()); return false; } + UsdSkelSkeleton skel = UsdSkelSkeleton::Define(stage, skelRootPath.AppendChild(TfToken("Skel"))); + + const size_t numJoints = jointPaths.size(); UsdSkelTopology topo(jointPaths); @@ -795,10 +800,10 @@ WriteAnimatedSkel( } UsdSkelAnimation anim = UsdSkelAnimation::Define( - stage, skelPath.AppendChild(TfToken("Anim"))); + stage, skel.GetPath().AppendChild(TfToken("Anim"))); UsdSkelBindingAPI binding = UsdSkelBindingAPI::Apply(skel.GetPrim()); - binding.CreateSkeletonRel().SetTargets( + binding.CreateAnimationSourceRel().SetTargets( SdfPathVector({anim.GetPrim().GetPath()})); anim.GetJointsAttr().Set(jointTokens); @@ -824,11 +829,14 @@ WriteAnimatedSkel( // Don't forget to call Save() on the stage! return true; +} \endcode - Python: \code{.py} -def WriteAnimatedSkel(stage, skelPath, jointPaths, +from pxr import UsdSkel, Vt, Tf + +def WriteAnimatedSkel(stage, skelRootPath, jointPaths, rootTransformsPerFrame, jointWorldSpaceTransformsPerFrame, times, bindTransforms, restTransforms=None): @@ -838,18 +846,20 @@ def WriteAnimatedSkel(stage, skelPath, jointPaths, return False if not len(bindTransforms) == len(jointPaths): return False - - skel = UsdSkel.Skeleton.Define(stage, skelPath) - if not skel: - Tf.Warn("Failed defining a Skeleton at <%s>.", skelPath) + + skelRoot = UsdSkel.Root.Define(stage, skelRootPath) + if not skelRoot: + Tf.Warn("Failed defining a Skeleton at <%s>.", skelRootPath) return False - + + skel = UsdSkel.Skeleton.Define(stage, skelRootPath.AppendChild("Skel")) + numJoints = len(jointPaths) topo = UsdSkel.Topology(jointPaths) - valid,whyNot = topo.Validate() + valid, reason = topo.Validate() if not valid: - Tf.Warn("Invalid topology: %s"%reason) + Tf.Warn("Invalid topology: %s" % reason) return False jointTokens = Vt.TokenArray([jointPath.pathString for jointPath in jointPaths]) @@ -861,14 +871,14 @@ def WriteAnimatedSkel(stage, skelPath, jointPaths, skel.GetRestTransformsAttr().Set(restTransforms) rootTransformAttr = skel.MakeMatrixXform() - for i,time in enumerate(times): + for i, time in enumerate(times): rootTransformAttr.Set(rootTransformsPerFrame[i], time) - - anim = UsdSkel.Animation.Define(stage, skelPath.AppendChild("Anim")) - + + anim = UsdSkel.Animation.Define(stage, skel.GetPath().AppendChild("Anim")) + binding = UsdSkel.BindingAPI.Apply(skel.GetPrim()) - binding.CreateSkeletonRel().SetTargets([anim.GetPrim().GetPath()]) - + binding.CreateAnimationSourceRel().SetTargets([anim.GetPrim().GetPath()]) + anim.GetJointsAttr().Set(jointTokens) for i,time in enumerate(times): @@ -924,30 +934,37 @@ than simply returning _false_. - C++: \code{.cpp} - UsdSkelSkeleton skel = UsdSkelSkeleton::Define(stage, skelPath); - if (!skel) { - TF_WARN("Failed creating a Skeleton prim at <%s>.", skelPath.GetText()); + UsdSkelRoot skelRoot = UsdSkelRoot::Define(stage, skelRootPath) + if (!skelRoot) { + TF_WARN("Failed creating a Skeleton prim at <%s>.", skelRootPath.GetText()); return false; } + + UsdSkelSkeleton skel = UsdSkelSkeleton::Define(stage, skelRootPath.AppendChild(TfToken("Skel"))); \endcode - Python: \code{.py} - skel = UsdSkel.Skeleton.Define(stage, skelPath) - if not skel: - Tf.Warn("Failed defining a Skeleton at <%s>.", skelPath) + skelRoot = UsdSkel.Root.Define(stage, skelRootPath) + if not skelRoot: + Tf.Warn("Failed defining a Skeleton at <%s>.", skelRootPath) return False + + skel = UsdSkel.Skeleton.Define(stage, skelRootPath.AppendChild("Skel")) \endcode -We start by defining a Skeleton primitive on the stage at the given path. +We start by defining a SkelRoot primitive on the stage at the given path. It is good practice to check that the resulting prim is valid. Some reasons why we may be unable to create the prim include: -- The provided _skelPath_ is not a valid, absolute prim path. -- An ancestor of the prim at _skelPath_ is already inactive on the stage. +- The provided _skelRootPath_ is not a valid, absolute prim path. +- An ancestor of the prim at _skelRootPath_ is already inactive on the stage. It is not possible to acquire a UsdPrim for a descendant of an inactive prim. A more complete implementation would likely at least validate that the - _skelPath_ is not invalid. + _skelRootPath_ is not invalid. + +Subsequently, this snippet also creates a Skeleton primitive as a child of the +SkelRoot primitive. - C++: \code{.cpp} @@ -962,9 +979,9 @@ A more complete implementation would likely at least validate that the - Python: \code{.py} topo = UsdSkel.Topology(jointPaths) - valid,whyNot = topo.Validate() + valid, reason = topo.Validate() if not valid: - Tf.Warn("Invalid topology: %s"%reason) + Tf.Warn("Invalid topology: %s" % reason) return False \endcode @@ -1048,7 +1065,7 @@ transform to instead be set on an ancestor of the Skeleton. anim = UsdSkel.Animation.Define(stage, skelPath.AppendChild("Anim")) \endcode -For reasons that are covered in-depth \ref UsdSkel_SkelAnimation "elsewhere", +For reasons that are covered in-depth in the \ref UsdSkel_SkelAnimation "Skel Animation Schema documentation", a Skeleton's joint animations are encoded on a separate primitive. Note that *where* on the stage we choose to place the UsdSkelAnimation primitive @@ -1061,14 +1078,14 @@ not be a descendant of the Skeleton. - C++: \code{.cpp} UsdSkelBindingAPI binding = UsdSkelBindingAPI::Apply(skel.GetPrim()); - binding.CreateSkeletonRel().SetTargets( + binding.CreateAnimationSourceRel().SetTargets( SdfPathVector({anim.GetPrim().GetPath()})); \endcode - Python: \code{.py} binding = UsdSkel.BindingAPI.Apply(skel.GetPrim()) - binding.CreateSkeletonRel().SetTargets([anim.GetPrim().GetPath()]) + binding.CreateAnimationSourceRel().SetTargets([anim.GetPrim().GetPath()]) \endcode Here we apply the UsdSkelBindingAPI to the Skeleton, and use it to bind the diff --git a/pxr/usd/usdSkel/doxygen/schemas.dox b/pxr/usd/usdSkel/doxygen/schemas.dox index 122fd30058..f229d53d37 100644 --- a/pxr/usd/usdSkel/doxygen/schemas.dox +++ b/pxr/usd/usdSkel/doxygen/schemas.dox @@ -76,16 +76,20 @@ name and order joints in vectorized data. For example: \code def SkelAnimation "Anim" { - uniform token[] = ["A", "A/B"] + uniform token[] joints = ["A/B", "A"] } def Skeleton "Skel" { - uniform token[] = ["A/B", "A"] + uniform token[] joints = ["A", "A/B"] } \endcode Note that in both example primitives above, the given paths do not reference any real primitives. Also note that each primitive has its own joint ordering, -and that those orders need not be identical. +and that those orders need not be identical. _Skeleton_ has a strict rule that +the targets of the _joints_ attribute are required to be authored such that all +parent joints come before any of their children in the array. See \ref UsdSkel_JointHierarchy +"Skeleton Schema: Joint Hierarchy" for more information. _SkelAnimation_ does +not have this ancestral order restriction. The purpose of encoding orderings in this manner is to allow for the creation of **self-contained** assets. For example, it is possible to construct @@ -207,8 +211,11 @@ converting transforms to and from this component form. Joint data is stored in arrays, using the \ref UsdSkel_JointOrder "joint order" specified by the _joints_ attribute. This ordering may be -different from the Skeletons that the animation maps to, and may also -only identify a sparse subset of the joints in a skeleton. When an animation +different from the Skeletons that the animation maps to, may also +only identify a sparse subset of the joints in a skeleton, and is not +required to follow a strict \ref UsdSkel_JointHierarchy +"ancestral ordering" like the _joints_ attribute of the +Skeleton schema (i.e. child joints can be listed before parent joints). When an animation provides sparse data, fallback values are taken from the rest pose on the UsdSkelSkeleton primitive to which they apply. @@ -218,11 +225,17 @@ as the authored _joints_ array. The effect of a skel animation prim may also be directly nullified by either deactivating the primitive, or by blocking the component attributes. +\subsection UsdSkel_SkelAnimation_Blendshapes Skel Animation Schema: Blend Shape Animation + In addition to providing joint animations, a SkelAnimation may also provide blend shape weight animations. Blend shape weights are specified in a vectorized -form using the _blendShapeWeights_ attribute. The _blendShapes_ attribute holds -a token array which, for every element authored in _blendShapeWeights_, -identifies which blend shape each weight value applies to. +form using the _blendShapeWeights_ attribute. By creating timesampled +data for the _blendShapeWeights_ attribute, you can create blend shape animations. +The _blendShapes_ attribute holds a token array which, for every element authored in _blendShapeWeights_, +identifies which blend shape each weight value applies to. Note that the +_blendShapes_ attribute should only encode a default value (i.e. timesampled data +will not be respected). It defines a single array of blend shape tokens for all the blend +shapes that partake in that SkelAnimation. The point of this encoding is to decouple the blendshape weight animation from the description of how that animation maps to different skinnable shapes. @@ -230,7 +243,7 @@ Refer to the \ref UsdSkel_BindingAPI_BlendShapes 'BindingAPI: Blend Shapes' documentation for information on how these weights are mapped to skinnable primitives. -\section UsdSkel_SkelAnimation_Binding Skel Animation Schema: Binding to Skeletons +\subsection UsdSkel_SkelAnimation_Binding Skel Animation Schema: Binding to Skeletons A _SkelAnimation_ is made to affect a _Skeleton_ by *binding* the animation to the skeleton, using the _skel:animationSource_ property of UsdSkelBindingAPI @@ -530,12 +543,51 @@ using the UsdSkelBindingAPI. The _jointIndices_ primvar provides an array giving the joint index of an influence, while the _jointWeights_ primvar provides a weight value corresponding to each of those indices. +For example: + +\code +def Skeleton "Skel" { + uniform token[] joints = ["A", "A/B", "A/B/C"] +} +def "MeshA" ( + prepend apiSchemas = ["SkelBindingAPI"] +) +{ + int[] primvars:skel:jointIndices = [1, 0] ( + elementSize = 1 + interpolation = "vertex" + ) + float[] primvars:skel:jointWeights = [1, 1] ( + elementSize = 1 + interpolation = "vertex" + ) +} + +def "MeshB" ( + prepend apiSchemas = ["SkelBindingAPI"] +) +{ + uniform token[] joints = ["A/B/C", "A/B"] + int[] primvars:skel:jointIndices = [0,1] ( + elementSize = 1 + interpolation = "vertex" + ) + float[] primvars:skel:jointWeights = [1,1] ( + elementSize = 1 + interpolation = "vertex" + ) +} +\endcode + Above, since `` does not specify a _skel:joints_ ordering of its own, the joint indices refer to the ordering of the bound Skeleton, and so the joint indices refer to joints `A/B` and `A`, respectively. However, `` specifies a `skel:joints` property that gives an alternate joint ordering. Using that, the indices of `` refer to joints `A/B/C` and `A/B`, -in that order. +in that order. Note that the joints specified for _skel:joints_ on the bound +prims can be a subset of the joints defined on the bound Skeleton. Also, +_skel:joints_ does not need to adhere to the parents before children ordering +mentioned in \ref UsdSkel_JointHierarchy "joint hierarchy." In the common case, the joint influence primvars are configured with _vertex_ interpolation, and define a fixed number of contiguous influences per point. @@ -738,4 +790,8 @@ that token maps to. So `B = ` and `A = `. From this, we find that we our final blend shapes and weights, as pairs, are `[(, 0.75), (, 1.0)`. +For more information about animating blend shapes, please refer to the +\ref UsdSkel_SkelAnimation_Blendshapes "Skel Animation Schema: Blend Shape Animation" and +\ref UsdSkel_BlendShape "Blend Shape Schema" documentation. + */ diff --git a/pxr/usd/usdSkel/doxygen/skinnedArm.usda b/pxr/usd/usdSkel/doxygen/skinnedArm.usda index b5c469baab..7c4a8a8774 100644 --- a/pxr/usd/usdSkel/doxygen/skinnedArm.usda +++ b/pxr/usd/usdSkel/doxygen/skinnedArm.usda @@ -12,7 +12,10 @@ def SkelRoot "Model" ( prepend apiSchemas = ["SkelBindingAPI"] ) { - def Skeleton "Skel" { + def Skeleton "Skel" ( + prepend apiSchemas = ["SkelBindingAPI"] + ) + { uniform token[] joints = ["Shoulder", "Shoulder/Elbow", "Shoulder/Elbow/Hand"] uniform matrix4d[] bindTransforms = [ ((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)), diff --git a/pxr/usd/usdSkel/overview.dox b/pxr/usd/usdSkel/overview.dox index 32021cff12..98043378fb 100644 --- a/pxr/usd/usdSkel/overview.dox +++ b/pxr/usd/usdSkel/overview.dox @@ -47,6 +47,7 @@ an introduction key schemas and API concepts. - \ref UsdSkel_Skeleton - \ref UsdSkel_JointHierarchy - \ref UsdSkel_SkelAnimation + - \ref UsdSkel_SkelAnimation_Blendshapes - \ref UsdSkel_SkelAnimation_Binding - \ref UsdSkel_BlendShape - \ref UsdSkel_BindingAPI