/*! \page UsdSkel_API_Intro API Introduction This page gives a brief introduction to using the UsdSkel API to read skel data encoded in USD. \section UsdSkel_API_SkeletonQuery Querying Skeleton Structure And Animation Given a scene that contains a UsdSkelSkeleton, a consuming application is free to access that primitive and reason about its structure on its own, if it chooses to do so. But UsdSkel also provides a more convenient API for querying data on a Skeleton: UsdSkelSkeletonQuery. A UsdSkelSkeletonQuery is created through a UsdSkelCache, using a UsdSkelSkeleton primtivive, as in the following example: - C++: \code{.cpp} UsdSkelCache skelCache; UsdSkelSkeleton skel = UsdSkelSkeleton.Get((stage, SdfPath("/Path/To/Skel"))); UsdSkelSkeletonQuery skelQuery = skelCache.GetSkelQuery(skel); \endcode - Python: \code{.py} skelCache = UsdSkel.Cache() skel = UsdSkelSkeleton.Get(stage, "/Path/To/Skel"); skelQuery = skelCache.GetSkelQuery(skel) \endcode As with other cache structures in Usd -- UsdGeomXformCache, UsdGeomBBoxCache, etc. -- the UsdSkelCache that was constructed here is meant to be a persistent cache. The cache is thread-safe, and can be re-used for any numbers of primitives. When reading a USD file, it is best to create a single UsdSkelCache to reuse for each new UsdSkelSkeleton that is encountered. A UsdSkelSkeletonQuery provides convenience methods for extracting joint transforms, in a variety of spaces. - C++: \code{.cpp} // Local-space joint transforms VtMatrix4dArray localSpaceXforms; skelQuery.ComputeJointLocalTransforms(&localSpaceXforms, time); // Joint transforms in the space of the Skeleton. VtMatrix4dArray skelSpaceXforms; skelQuery.ComputeJointSkelTransforms(&skelSpaceXforms, time); \endcode - Python: \code{.py} # Local-space joint transforms localSpaceXforms = skelQuery.ComputeJointLocalTransforms(time) # Joint transforms in the space of the Skeleton. skelSpaceXforms = skelQuery.ComputeJointSkelTransforms(time) \endcode Note that this allows the animated transforms of a Skeleton to be exracted without having to deal with some of the more complicated aspects of the Skeleton encoding, such as the inheritance of the _skel:animationSource_ relationship. \subsection UsdSkel_API_JointPathsAndNames Joint Paths and Names Each joint in a Skeleton is identified by a token, which represents the path to a joint in a hierarchy. For example: \code def Skeleton "Skel" { uniform token[] joints = [ "Shoulder", "Shoulder/Elbow", "Shoulder/Elbow/Hand" ] ... } \endcode Each of these tokens can be converted to an _SdfPath_, after which the SdfPath methods can be used to extract different components of the path. For instance, to extract just the _name_ component of each path (`Shoulder/Elbow` -> `Elbow`), do the following: - C++: \code{.cpp} for (size_t i = 0; i < skelQuery.GetJointOrder().size(); ++i) { SdfPath jointPath(skelQuery.GetJointOrder()[i]); std::cout << "Name of joint " << i << " is " << jointPath.GetName() << std::endl; } \endcode - Python: \code{.py} for i,jointToken in enumerate(skelQuery.GetJointOrder()): jointPath = Sdf.Path(jointToken) print "Name of joint", i, "is", jointPath.name \endcode For the schema example above, this code will print: \verbatim Name of joint 0 is Shoulder Name of joint 1 is Elbow Name of joint 2 is Hand \endverbatim It should be noted that, if extracting the _name_ of a joint in this manner, joint names are not guaranteed to be unique. For example, suppose a Skeleton has two arms. That may be encoded as: \code def Skeleton "Skel" { uniform token[] joints = [ "LeftShoulder", "LeftShoulder/Elbow", "LeftShoulder/Elbow/Hand", "RighShoulder", "RightShoulder/Elbow", "RightShoulder/Elbow/Hand" ] ... } \endcode Although the _path_ of every joint is unique, there is no guarantee over the uniqueness of the _name_. This is true of primitives in USD in general: Names need not be globally unique, but rather only need to be unique amongst their sibling primitives. \subsection UsdSkel_API_JointHierarchy Querying the Joint Hierarchy The structure of the joint hierarchy itself can also be queried through the UsdSkelSkeletonQuery. For example, suppose that in an application, every joint is described as a _Joint_ object, which has a pointer to its parent _Joint_. Code for creating a _Joint_ object for every joint in a Skeleton might look something like the following: - C++: \code{.cpp} // The ordered set of Joint objects of an imaginary application. std::vector joints; // GetTopology() returns a UsdSkelTopology object, which describes // the parent<->child relationships. It also gives the numer of joints. size_t numJoints = skelQuery.GetTopology().GetNumJoints(); for (size_t i = 0; i < numJoints; ++i) { std::string name = SdfPath(skelQuery.GetJointOrder()[i]).GetName(); int parent = skelQuery.GetTopology().GetParent(i); if (parent >= 0) { Joint parentJoint = joints[parent]; joints.push_back(Joint(name, parentJoint)); } else { // Root joint joints.push_back(Joint(name)); } } \endcode - Python: \code{.py} # The ordered set of Joint objects of an imaginary application. joints = [] # GetTopology() returns a UsdSkel.Topology object, which describes # the parent<->child relationships. It also gives the number of joints. numJoints = len(skelQuery.GetTopology()) for i in range(numJoints): name = Sdf.Path(skelQuery.GetJointOrder()[i]).name parent = skelQuery.GetTopology().GetParent(i) if parent >= 0: parentJoint = joints[parent] joints.append(Joint(name, parentJoint)) else: joints.append(Joint(name)) \endcode In the above code snippet, indexing into the _joints_ array with the parent index of a joint might appear unsafe, since it might not be clear whether or not the parent joint had been constructed yet. However, it is a schema **requirement** that the set of joints is ordered, with parent joints coming before child joints. That is, linearly iterating through the ordered set of joints on a Skeleton must always describe a top-down hiearchy traversal. When a UsdSkelSkeletonQuery is constructed, its topology is validated, and the resulting query object is only valid if that topology check passes. So given a valid UsdSkelSkeletonQuery, the above code snippet will be safe. To further expand on that point, the _topology_ of a Skeleton may be directly validated as follows: - C++: \code{.cpp} UsdSkelSkeleton skel(skelPrim); VtTokenArray joints; if (skel.GetJointsAttr().Get(&joints)) { UsdSkelTopology topology(joints); std::string whyNot; bool valid = topology.Validate(&whyNot); } \endcode - Python: \code{.py} skel = UsdSkel.Skeleton(skelPrim) joints = skel.GetJointsAttr().Get(joints) if joints: topology = UsdSkel.Topology(joints) valid,whyNot = topology.Validate() \endcode But again, if data is being queried through a UsdSkelSkeletonQuery, this validation occurs _automatically_. \section UsdSkel_API_SkeletonBindings Skeleton Bindings Before applying skinning to primitives, we need to first identify which primitives are skinnable, and which Skeleton affects them. It is also desired that this discovery process helps facilitate data sharing. For instance, suppose a model consists of 1000 individual meshes. In order to skin those meshes on a GPU, we would need to first compute appropriate skinning transforms to upload to the GPU. It would be awfully inefficient to do that for each individual mesh -- I.e., to perform redundant computations, and upload the same set of transforms for each mesh. The following snippet demonstrates how this can be addressed efficiently through UsdSkel. We will show the complete code first, before describing the individual parts in more detail: - C++: \code{.cpp} UsdSkelCache skelCache; // Traverse through the prims on the stage to find where we might // have prims to skin. auto it = stage->Traverse(); for (const UsdPrim& prim : it) { if (prim.IsA()) { it.PruneChilren(); UsdSkelRoot skelRoot(prim); skelCache.Populate(skelRoot); std::vector bindings; skelCache.ComputeSkelBindings(skelRoot, &bindings); // Iterate over the bindings related to this SkelRoot for (const UsdSkelBinding& binding : bindings) { // Get the Skeleton for this binding. UsdSkelQuery skelQuery = skelCache.GetSkelQuery(binding.GetSkeleton()); VtMatrix4dArray skinningXforms; if (skelQuery.ComputeSkinningTransforms(&skinningXforms, time)) { // Iterate over the prims that are skinned by this Skeleton. for (const UsdSkelSkinningQuery& skinningQuery : binding.GetSkinningTargets()) { const UsdPrim& primToSkin = skinningQuery.GetPrim(); // Process prim / apply skinning } } } } } \endcode - Python: \code{.py} skelCache = UsdSkel.Cache() # Traverse through the prims on the stage to find where we might # have prims to skin. it = iter(stage.Traverse()) for prim in it: if prim.IsA(UsdSkel.Root): it.PruneChildren() skelRoot = UsdSkel.Root(prim) skelCache.Populate(skelRoot) bindings = skelCache.ComputeSkelBindings(skelRoot) # Iterate over the bindings related to this SkelRoot for binding in bindings: # Get the Skeleton for this binding. skelQuery = skelCache.GetSkelQuery(binding.GetSkeleton()) skinningXforms = skelQuery.ComputeSkinningTransforms(time) if skinningXforms: # Iterate over the prims that are skinned by this Skeleton. for skinningQuery in binding.GetSkinningTargets(): primToSkin = skinningQuery.GetPrim() # Process prim / apply skinning \endcode The first part of this should be familiar: - C++: \code{.cpp} UsdSkelCache skelCache; \endcode - Python: \code{.py} skelCache = UsdSkel.Cache() \endcode When accessing a UsdSkelSkeletonQuery for a Skeleton, we constructed a UsdSkelCache. Again, in a context where data is being read from USD, this cache is intended to persist and be reused across multiple prims (or multiple stages, for that matter). This code example actually makes for a good example of how the cache can be shared across multiple primitives. - C++: \code{.cpp} auto it = stage->Traverse(); for (const UsdPrim& prim : it) { if (prim.IsA()) { it.PruneChilren(); ... } } \endcode - Python: \code{.py} it = iter(stage.Traverse()) for prim in it: if prim.IsA(UsdSkel.Root): it.PruneChildren() ... \endcode Here, we traverse through the primitives on the stage. When we encounter a UsdSkelRoot primitive, we know that we have discovered a branch of the scene graph that _might_ contain skeletally-posed models. Being able to identify subsets of the scene graph that contain skeletal characters is part of the motivation behind the existence of SkelRoot primitives. It is common in IO contexts to use the SkelRoot as a point for dispatching common computations needed when translating Skeletons. - C++: \code{.cpp} UsdSkelRoot skelRoot(prim); skelCache.Populate(skelRoot); std::vector bindings; skelCache.ComputeSkelBindings(skelRoot, &bindings); \endcode - Python: \code{.py} skelRoot = UsdSkel.Root(prim) skelCache.Populate(skelRoot) bindings = skelCache.ComputeSkelBindings(skelRoot) \endcode A UsdSkelBinding object is a simply a mapping of some Skeleton to a set of skinnable primitives. We can compute those mappings by way of the UsdSkelCache, but must first `Populate()` that section of the scene graph on the cache. What we gain from using this API is that the UsdSkelCache is doing all the work of properly resolving inherited binding properties for us, allowing us to get at the question we're really interested: What prims are we skinning, and with which Skeletons? - C++: \code{.cpp} for (const UsdSkelBinding& binding : bindings) { // Get the Skeleton for this binding. UsdSkelQuery skelQuery = skelCache.GetSkelQuery(binding.GetSkeleton()); ... } \endcode - Python: \code{.py} # Iterate over the bindings related to this SkelRoot for binding in bindings: # Get the Skeleton for this binding. skelQuery = skelCache.GetSkelQuery(binding.GetSkeleton()) ... \endcode There could be any number of Skeletons beneath a SkelRoot. There will be a UsdSkelBinding associated with each uniquely bound Skeleton. So we must iterate over all of them. The binding holds a reference to the Skeleton. As we saw earlier, we can extract a UsdSkelSkeletonQuery from the UsdSkelCache using that Skeleton, which provides a more convenient API for extracting data from the Skeleton. - C++: \code{.cpp} VtMatrix4dArray skinningXforms; if (skelQuery.ComputeSkinningTransforms(&skinningXforms, time)) { ... } \endcode - Python: \code{.py} skinningXforms = skelQuery.ComputeSkinningTransforms(time) if skinningXforms: ... \endcode The skinning transforms have been included at this point only as an example, to emphasize the point that this serves as a common code site at which properties related to a Skeleton can be computed, which are subsequently shared across all of the primitives that are skinned by that Skeleton. - C++: \code{.cpp} // Iterate over the prims that are skinned by this Skeleton. for (const UsdSkelSkinningQuery& skinningQuery : binding.GetSkinningTargets()) { const UsdPrim& primToSkin = skinningQuery.GetPrim(); ... } \endcode - Python: \code{.py} # Iterate over the prims that are skinned by this Skeleton. for skinningQuery in binding.GetSkinningTargets(): primToSkin = skinningQuery.GetPrim() ... \endcode At this point, we have a Skeleton -- or better yet, a UsdSkelSkeletonQuery -- and can traverse over the 'skinning targets', which are the set of primitives that are skinned by that Skeleton. The set of skinned primitives are returned as UsdSkelSkinningQuery objects. Just as UsdSkelSkeletonQuery objects provide convenient API for querying data related to a Skeleton, a UsdSkelSkinningQuery provides convenient API for reading data related to primitives that are skinned, such as joint influences. See the \ref UsdSkel_API_SkinningQuery "skinning query" section for more information. \subsection UsdSkel_API_DiscoveringBindings Discovering Bindings On Skinnable Primitives In the \ref UsdSkel_API_SkeletonBindings section, we explored a top-down traversal of a stage, which allowed us to efficiently associate a Skeleton with multiple prims that are affected by that Skeleton. Sometimes, such top-down traversal patterns are not possible, and we need to discover bindings the other way around: That is, given a primitive, discover the Skeleton that affects it, and begin computing data required to skin it. As with the previous section, we will start with a complete coding example, before breaking down the individual parts. - C++: \code{.cpp} UsdSkelCache skelCache; if (UsdSkelRoot skelRoot = UsdSkelRoot::Find(prim)) { skelCache.Populate(skelRoot); if (UsdSkelSkinningQuery skinningQuery = skelCache.GetSkinningQuery(prim)) { // Find the Skeleton that should affect this prim. UsdSkelSkeleton skel = UsdSkelBindingAPI(prim).GetInheritedSkeleton(); if (UsdSkelSkeletonQuery skelQuery = skelCache.GetSkelQuery(skel)) { // Apply skinning? } } } \endcode - Python: \code{.py} skelCache = UsdSkel.Cache() skelRoot = UsdSkel.Root.Find(prim) if skelRoot: skelCache.Populate(skelRoot) skinningQuery = skelCache.GetSkinningQuery(prim) if skinningQuery: # Find the Skeleton that should affect this prim. skel = UsdSkel.BindingAPI(prim).GetInheritedSkeleton() skelQuery = skelCache.GetSkelQuery(skel); if skelQuery: # Apply skinning? \endcode Once more, the first line should seem familiar: - C++: \code{.cpp} UsdSkelCache skelCache; \endcode - Python: \code{.py} skelCache = UsdSkel.Cache() \endcode As with previous examples, we utilize a UsdSkelCache. Again, we emphasize that such caches should persist, and be shared across multiple prims. - C++: \code{.cpp} if (UsdSkelRoot skelRoot = UsdSkelRoot::Find(prim)) { skelCache.Populate(skelRoot); if (UsdSkelSkinningQuery skinningQuery = skelCache.GetSkinningQuery(prim)) { ... } } \endcode - Python: \code{.py} skelRoot = UsdSkel.Root.Find(prim) if skelRoot: skelCache.Populate(skelRoot) skinningQuery = skelCache.GetSkinningQuery(prim) if skinningQuery: ... \endcode We want to be able to extract a UsdSkelSkinningQuery, which provides useful utilities for working with skinnable primitives. As with UsdSkelSkeletonQuery objects, skinning queries are accessed through the UsdSkelCache. But before they are accessed, we need to `Populate()` the UsdSkelCache with the section of the scene graph that contains the skinnable primitive. Cache population causes the cache to be pre-populated with information about inherited bindings, which is necessary when accessing skinning queries. The SkelRoot that enapsulates a primitive can be found using UsdSkelRoot::Find. If no SkelRoot is found, that means that the primitive is not encapsulated within a SkelRoot, and so any properties on the prim related to skinning should be ignored. - C++: \code{.cpp} if (UsdSkelSkinningQuery skinningQuery = skelCache.GetSkinningQuery(prim)) { ... } \endcode - Python: \code{.py} skinningQuery = skelCache.GetSkinningQuery(prim) if skinningQuery: ... \endcode Having found a SkelRoot and populated the UsdSkelCache, we can access a UsdSkelSkinningQuery object for the primitive that is being skinned. If the resulting UsdSkelSkinningQuery is invalid, that means that either the primitive is not considered to be skinnable, or the skinning properties are malformed in some way. If the latter, appropriate warning messsages will have been posted. - C++: \code{.cpp} // Find the Skeleton that should affect this prim. UsdSkelSkeleton skel = UsdSkelBindingAPI(prim).GetInheritedSkeleton(); if (UsdSkelSkeletonQuery skelQuery = skelCache.GetSkelQuery(skel)) { // Apply skinning? } \endcode - Python: \code{.py} # Find the Skeleton that should affect this prim. skel = UsdSkel.BindingAPI(prim).GetInheritedSkeleton() skelQuery = skelCache.GetSkelQuery(skel); if skelQuery: # Apply skinning? \endcode If we've acquired a valid UsdSkelSkinningQuery, we know that a primitive is a valid candidate for skinning. The next logical step might be to determine which Skeleton affects skinning. UsdSkelBindingAPI::GetInheritedSkeleton can be used to discover the bound Skeleton, based on the inherited _skel:skeleton_ binding properties. As before, once we have a Skeleton, we can get access to a UsdSkelSkeletonQuery to assist value extraction. \subsection UsdSkel_API_SkinningQuery UsdSkelSkinningQuery: Extracting joint influences Coding examples from the previous sections demonstrated how to find skinnable primitives and gain access to a UsdSkelSkinningQuery object for a skinnable primitive. Here we briefly demonstrate some of the basic queries that can be used to extract joint influences from skinning queries: - C++: \code{.cpp} VtIntArray jointIndices; VtFloatArray jointWeights; skinningQuery.ComputeJointInfluences(&jointIndices, &jointWeights); \endcode - Python: \code{.py} influences = skinningQuery.ComputeJointInfluences() if influences: jointIndices,jointWeights = influences \endcode Use UsdSkelSkinningQuery::IsRigidlyDeformed to determine whether or not these arrays represent rigid influences, or varying (per-point) influences. If the skinnable primitive is *not* rigidly deforming, then these arrays store a fixed number of influences per point. The full set of influences for the first point come first, followed by the influences for the second point, and so forth. UsdSkelSkinningQuery::ComputeVaryingJointInfluences returns the number of influences that map to each point. If the skinnable primitive is rigidly deforming, then all of the resulting influences apply to *every point*. Such a deformation can also be applied by altering a primitive's transform -- hence, a rigid deformation. It is up to the client to determine how to deal with rigid influences. Not all applications are capable of dealing with rigid transformations. If that's the case, UsdSkelSkinningQuery::ComputeVaryingJointInfluences can be used instead: - C++: \code{.cpp} VtIntArray jointIndices; VtFloatArray jointWeights; skinningQuery.ComputeVaryingJointInfluences( numPoints, &jointIndices, &jointWeights); \endcode - Python: \code{.py} influences = skinningQuery.ComputeVaryingJointInfluences(numPoints) if influences: jointIndices,jointWeights = influences \endcode When calling UsdSkelSkinningQuery::ComputeVaryingJointInfluences, rigid influences are automatically expanded out to define per-point influences. Another restriction encountered in some applications is that they have a limit on the number of influences that may be specified per point. We do not feel that it is appropriate to enforce such application-specific limitations on the storage encoding, so UsdSkel has defines no limit on the number of influences. However, UsdSkel does provide utility methods to allow influence arrays to be resized, which such applications may use: - C++: \code{.cpp} int numInfluencesPerComponent = skinningQuery.GetNumInfluencesPerComponent(); if (numInfluencesPerComponent > 4) { UsdSkelResizeInfluences(&jointIndices, numInfluencesPerComponent, 4); UsdSkelResizeInfluences(&jointWeights, numInfluencesPerComponent, 4); } \endcode - Python: \code{.py} numInfluencesPerComponent = skinningQuery.GetNumInfluencesPerComponent() if numInfluencesPerComponent > 4: UsdSkel.ResizeInfluences(jointIndices, numInfluencesPerComponent, 4); UsdSkel.ResizeInfluences(jointWeights, numInfluencesPerComponent, 4); \endcode \section UsdSkel_API_BakeSkinning Testing Skinning with UsdSkelBakeSkinning UsdSkel provides a \ref UsdSkelBakeSkinning method that bakes the results of skinning directly into points and transforms, effectively converting skeletally posed primitives into normal geometry caches, with no special skeletal behaviors. UsdSkelBakeSkinning is intended both to serve as a reference implementation for skinning, and to help facilitate testing. \warning UsdSkelBakeSkinning is intended for testing and debugging, and emphasizes correctness over performance. It should not be used in performance-sensitive contexts. Skinning can be baked on a stage as follows: - C++: \code{.cpp} UsdSkelBakeSkinning(stage->Traverse()); stage->Save(); \endcode - Python: \code{.py} UsdSkel.BakeSkinning(stage.Traverse()); stage.Save(); \endcode \section UsdSkel_API_WritingSkels Writing Skeletons The following code demonstarates full USD/UsdSkel API, showing how an animated Skeleton might be authored by a DCC application. It is not meant to be a definitive example on the *right* way to author a Skeleton, but merely serves as a simple example to start from. For simplicity, this example focuses solely on encoding a Skeleton, and does not including bindings for skinnable primitives. As with previous examples, we begin wtih complete code, and then break it down into its component parts. - C++: \code{.cpp} bool WriteAnimatedSkel( const UsdStagePtr& stage, const SdfPath& skelPath, const SdfPathVector& jointPaths, const std::vector& rootTransformsPerFrame, const std::vector& jointWorldSpaceTransformsPerFrame, const std::vector& times, const VtMatrix4dArray& bindTransforms, const VtMatrix4dArray* restTransforms=nullptr) { if (rootTransformsPerFrame.size() != times.size()) return false; if (jointWorldSpaceTransformsPerFrame.size() != times.size()) return false; 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()); return false; } const size_t numJoints = jointPaths.size(); UsdSkelTopology topo(jointPaths); std::string reason; if (!topo.Validate(&reason)) { TF_WARN("Invalid topology: %s", reason.c_str()); return false; } VtTokenArray jointTokens(numJoints); for (size_t i = 0; i < jointPaths.size(); ++i) { jointTokens[i] = TfToken(jointPaths[i].GetString()); } skel.GetJointsAttr().Set(jointTokens); skel.GetBindTransformsAttr().Set(bindTransforms); if (restTransforms && restTransforms->size() == numJoints) { skel.GetRestTransformsAttr().Set(*restTransforms); } UsdAttribute rootTransformAttr = skel.MakeMatrixXform(); for (size_t i = 0; i < times.size(); ++i) { rootTransformAttr.Set(rootTransformsPerFrame[i], times[i]); } UsdSkelAnimation anim = UsdSkelAnimation::Define( stage, skelPath.AppendChild(TfToken("Anim"))); UsdSkelBindingAPI binding = UsdSkelBindingAPI::Apply(skel.GetPrim()); binding.CreateSkeletonRel().SetTargets( SdfPathVector({anim.GetPrim().GetPath()})); anim.GetJointsAttr().Set(jointTokens); // Set root transforms and joint transforms per frame. for (size_t i = 0; i < times.size(); ++i) { const GfMatrix4d& rootTransform = rootTransformsPerFrame[i]; const VtMatrix4dArray& jointWorldSpaceTransforms = jointWorldSpaceTransformsPerFrame[i]; if (jointWorldSpaceTransforms.size() == numJoints) { VtMatrix4dArray jointLocalSpaceTransforms; if (UsdSkelComputeJointLocalTransforms( topo, jointWorldSpaceTransforms, &jointLocalSpaceTransforms, &rootTransform)) { anim.SetTransforms(jointLocalSpaceTransforms, times[i]); } } } // Don't forget to call Save() on the stage! return true; \endcode - Python: \code{.py} def WriteAnimatedSkel(stage, skelPath, jointPaths, rootTransformsPerFrame, jointWorldSpaceTransformsPerFrame, times, bindTransforms, restTransforms=None): if not len(rootTransformsPerFrame) == len(times): return False if not len(jointWorldSpaceTransformsPerFrame) == len(times): 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) return False numJoints = len(jointPaths) topo = UsdSkel.Topology(jointPaths) valid,whyNot = topo.Validate() if not valid: Tf.Warn("Invalid topology: %s"%reason) return False jointTokens = Vt.TokenArray([jointPath.pathString for jointPath in jointPaths]) skel.GetJointsAttr().Set(jointTokens) skel.GetBindTransformsAttr().Set(bindTransforms) if restTransforms and len(restTransforms) == numJoints: skel.GetRestTransformsAttr().Set(restTransforms) rootTransformAttr = skel.MakeMatrixXform() for i,time in enumerate(times): rootTransformAttr.Set(rootTransformsPerFrame[i], time) anim = UsdSkel.Animation.Define(stage, skelPath.AppendChild("Anim")) binding = UsdSkel.BindingAPI.Apply(skel.GetPrim()) binding.CreateSkeletonRel().SetTargets([anim.GetPrim().GetPath()]) anim.GetJointsAttr().Set(jointTokens) for i,time in enumerate(times): rootTransform = rootTransformsPerFrame[i] jointWorldSpaceTransforms = jointWorldSpaceTransformsPerFrame[i] if len(jointWorldSpaceTransforms) == numJoints: jointLocalSpaceTransforms =\ UsdSkel.ComputeJointLocalTransforms( topo, jointWorldSpaceTransforms, rootTransform) if jointLocalSpaceTransforms: anim.SetTransforms(jointLocalSpaceTransforms, time) # Don't forget to call Save() on the stage! return True \endcode - C++: \code{.cpp} if (rootTransformsPerFrame.size() != times.size()) return false; if (jointWorldSpaceTransformsPerFrame.size() != times.size()) return false; if (bindTransforms.size() != jointPaths.size()) return false; \endcode - Python: \code{.py} if not len(rootTransformsPerFrame) == len(times): return False if not len(jointWorldSpaceTransformsPerFrame) == len(times): return False if not len(bindTransforms) == len(jointPaths): return False \endcode For this method, we expect _rootTransformsPerFrame_ to be an array holding a GfMatrix4d for each time in _times_. Similarly, _jointWorldSpaceTransformsPerFrame_ holds a VtMatrix4dArray for each time in _times_, providing the full set of joint transforms for the corresponding time. Finally, the _jointPaths_ input is an array of SdfPath objects giving the path of each joint, and establishing the \ref UsdSkel_JointOrder of the skeleton. The required bindTransforms array must be the same size. A more complete implementation would provide useful warning messages, rather 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()); return false; } \endcode - Python: \code{.py} skel = UsdSkel.Skeleton.Define(stage, skelPath) if not skel: Tf.Warn("Failed defining a Skeleton at <%s>.", skelPath) return False \endcode We start by defining a Skeleton 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. 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. - C++: \code{.cpp} UsdSkelTopology topo(jointPaths); std::string reason; if (!topo.Validate(&reason)) { TF_WARN("Invalid topology: %s", reason.c_str()); return false; } \endcode - Python: \code{.py} topo = UsdSkel.Topology(jointPaths) valid,whyNot = topo.Validate() if not valid: Tf.Warn("Invalid topology: %s"%reason) return False \endcode The input _jointPaths_ specify the topology of the Skeleton. We construct a UsdSkelTopology object at this point primarily for use in subsequent transform computations. But this is also a good point to verify that our topology is valid. - C++: \code{.cpp} VtTokenArray jointTokens(numJoints); for (size_t i = 0; i < jointPaths.size(); ++i) { jointTokens[i] = TfToken(jointPaths[i].GetString()); } skel.GetJointsAttr().Set(jointTokens); \endcode - Python: \code{.py} jointTokens = Vt.TokenArray([jointPath.pathString for jointPath in jointPaths]) skel.GetJointsAttr().Set(jointTokens) \endcode The actual joint topology is stored on the Skeleton primitive as an array of tokens (VtTokenArray). We convert the input paths to tokens, and write the result to the Skeleton. - C++: \code{.cpp} skel.GetBindTransformsAttr().Set(bindTransforms); if (restTransforms && restTransforms->size() == numJoints) { skel.GetRestTransformsAttr().Set(*restTransforms); } \endcode - Python: \code{.py} skel.GetBindTransformsAttr().Set(bindTransforms) if restTransforms and len(restTransforms) == numJoints: skel.GetRestTransformsAttr().Set(restTransforms) \endcode Here we author the _bindTransforms_ property of the Skeleton. The _restTransforms_ property has been treated as optional. But if _restTransforms_ are not authored, then a UsdSkelAnimation must be bound to the Skeleton (which we will do shortly), and that animation must include the full set of joints. See \ref UsdSkel_BindingAPI_Skeletons for more information on binding animations. - C++: \code{.cpp} UsdAttribute rootTransformAttr = skel.MakeMatrixXform(); for (size_t i = 0; i < times.size(); ++i) { rootTransformAttr.Set(rootTransformsPerFrame[i], times[i]); } \endcode - Python: \code{.py} rootTransformAttr = skel.MakeMatrixXform() for i,time in enumerate(times): rootTransformAttr.Set(rootTransformsPerFrame[i], time) \endcode This demonstrates use of UsdGeomXformable API (a base class of UsdSkelSkeleton) for applying transforms on primitives. For this example, the root transform has been written out directly on the Skeleton. In actual production cases, it is not uncommon for the full root transform to instead be set on an ancestor of the Skeleton. - C++: \code{.cpp} UsdSkelAnimation anim = UsdSkelAnimation::Define( stage, skelPath.AppendChild(TfToken("Anim"))); \endcode - Python: \code{.py} anim = UsdSkel.Animation.Define(stage, skelPath.AppendChild("Anim")) \endcode For reasons that are covered in-depth \ref UsdSkel_SkelAnimation "elsewhere", a Skeleton's joint animations are encoded on a separate primitive. Note that *where* on the stage we choose to place the UsdSkelAnimation primitive really only starts to matter when using \ref UsdSkel_Instancing "instancing." In the simple case of encoding a small number of skeletons, adding the animation as a child of the Skeleton, as above, is the most straight-forward approach. But when defining skeletons that take advantage of instancing, a SkelAnimation will not be a descendant of the Skeleton. - C++: \code{.cpp} UsdSkelBindingAPI binding = UsdSkelBindingAPI::Apply(skel.GetPrim()); binding.CreateSkeletonRel().SetTargets( SdfPathVector({anim.GetPrim().GetPath()})); \endcode - Python: \code{.py} binding = UsdSkel.BindingAPI.Apply(skel.GetPrim()) binding.CreateSkeletonRel().SetTargets([anim.GetPrim().GetPath()]) \endcode Here we apply the UsdSkelBindingAPI to the Skeleton, and use it to bind the animation directly to the Skeleton. In more complex \ref UsdSkel_Instancing "instancing" scenarios, we might instead choose to bind the animation to an ancestor of the Skeleton. - C++: \code{.cpp} anim.GetJointsAttr().Set(jointTokens); \endcode - Python: \code{.py} anim.GetJointsAttr().Set(jointTokens) \endcode Previously, we stored _jointTokens_ on the Skeleton, to encode topology. When creating a UsdSkelAnimation, we need to once again store the joint tokens. The _jointTokens_ set on the animation define the \ref UsdSkel_JointOrder of the animation. In this case, the joint order of the animation is identical to that of the Skeleton, but note that that need not be the case (for example, if the Skeleton includes joints for fingers, our animation could exclude the fingers). - C++: \code{.cpp} VtMatrix4dArray jointLocalSpaceTransforms; if (UsdSkelComputeJointLocalTransforms( topo, jointWorldSpaceTransforms, &jointLocalSpaceTransforms, &rootTransform)) { \endcode - Python: \code{.py} jointLocalSpaceTransforms =\ UsdSkel.ComputeJointLocalTransforms( topo, jointWorldSpaceTransforms, rootTransform) \endcode This method has been written to allow joint transforms to be provided in world space, because it is often easier for applications to reliably translate world space transform, rather than local transforms -- for example, because an application may need to skip intermediate joints, or because an application may have weird transform inheritance rules (oddities abound related to inheritance of *scale*!). Previously we created a UsdSkelTopology object, primarily so that it can be used at this point to convert world space joint transforms into local space, as required by the UsdSkelAnimation schema. Of course, if it is easy for an application to provide transforms directly in local space, this conversion would be unnecessary. - C++: \code{.cpp} anim.SetTransforms(jointLocalSpaceTransforms, times[i]); \endcode - Python: \code{.py} anim.SetTransforms(jointLocalSpaceTransforms, time) \endcode Finally, we write joint transforms on the UsdSkelAnimation primitive. */