Skip to content

Commit

Permalink
Merge pull request #2697 from mati-nvidia/mati/usdSkel_docs
Browse files Browse the repository at this point in the history
Improve UsdSkel docs

(Internal change: 2308060)
  • Loading branch information
pixar-oss committed Dec 10, 2023
2 parents 94aafe9 + 94ac833 commit b87a1e9
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 46 deletions.
87 changes: 52 additions & 35 deletions pxr/usd/usdSkel/doxygen/apiIntro.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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<GfMatrix4d>& rootTransformsPerFrame,
const std::vector<VtMatrix4dArray>& jointWorldSpaceTransformsPerFrame,
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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):
Expand All @@ -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])
Expand All @@ -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):
Expand Down Expand Up @@ -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}
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
76 changes: 66 additions & 10 deletions pxr/usd/usdSkel/doxygen/schemas.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand All @@ -218,19 +225,25 @@ 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.
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
Expand Down Expand Up @@ -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 `</MeshA>` 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, `</MeshB>`
specifies a `skel:joints` property that gives an alternate joint ordering.
Using that, the indices of `</MeshB>` 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.
Expand Down Expand Up @@ -738,4 +790,8 @@ that token maps to. So `B = </Mesh/Foo>` and `A = </Mesh/Bar>`. From this,
we find that we our final blend shapes and weights, as pairs, are
`[(</Mesh/Foo>, 0.75), (</Mesh/Bar>, 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.

*/
5 changes: 4 additions & 1 deletion pxr/usd/usdSkel/doxygen/skinnedArm.usda
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
1 change: 1 addition & 0 deletions pxr/usd/usdSkel/overview.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit b87a1e9

Please sign in to comment.