USD: Skeleton and blend shape import #110912
@ -14,16 +14,10 @@
|
||||
#include "DNA_armature_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "ED_armature.h"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
|
||||
#include <pxr/pxr.h>
|
||||
#include <pxr/usd/usdSkel/cache.h>
|
||||
#include <pxr/usd/usdSkel/skeletonQuery.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace blender::io::usd {
|
||||
@ -47,208 +41,7 @@ void USDSkeletonReader::read_object_data(Main *bmain, const double motionSampleT
|
||||
return;
|
||||
}
|
||||
|
||||
pxr::UsdSkelCache skel_cache;
|
||||
pxr::UsdSkelSkeletonQuery skel_query = skel_cache.GetSkelQuery(skel_);
|
||||
|
||||
if (!skel_query.IsValid()) {
|
||||
std::cout << "WARNING: couldn't query skeleton " << skel_.GetPath() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
const pxr::UsdSkelTopology &skel_topology = skel_query.GetTopology();
|
||||
|
||||
pxr::VtTokenArray joint_order = skel_query.GetJointOrder();
|
||||
|
||||
if (joint_order.size() != skel_topology.size()) {
|
||||
std::cout << "WARNING: skel topology and joint order size mismatch\n";
|
||||
return;
|
||||
}
|
||||
|
||||
bArmature *arm = static_cast<bArmature *>(object_->data);
|
||||
|
||||
ED_armature_to_edit(arm);
|
||||
|
||||
/* The bones we create, stored in the skeleton's joint order. */
|
||||
std::vector<EditBone *> edit_bones;
|
||||
|
||||
size_t num_joints = skel_topology.GetNumJoints();
|
||||
|
||||
/* Keep track of the bones we create for each joint. */
|
||||
std::map<pxr::TfToken, std::string> joint_to_bone_map;
|
||||
|
||||
/* Create the bones. */
|
||||
for (const pxr::TfToken &joint : joint_order) {
|
||||
std::string name = pxr::SdfPath(joint).GetName();
|
||||
EditBone * bone = ED_armature_ebone_add(arm, name.c_str());
|
||||
if (!bone) {
|
||||
std::cout << "WARNING: couldn't add bone for joint " << joint << std::endl;
|
||||
edit_bones.push_back(nullptr);
|
||||
continue;
|
||||
}
|
||||
joint_to_bone_map.insert(std::make_pair(joint, bone->name));
|
||||
edit_bones.push_back(bone);
|
||||
}
|
||||
|
||||
/* Sanity check: we should have created a bone for each joint. */
|
||||
|
||||
if (edit_bones.size() != num_joints) {
|
||||
std::cout << "WARNING: mismatch in bone and joint counts for skeleton " << skel_.GetPath() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Record the child bone indices per parent bone. */
|
||||
std::vector<std::vector<int>> child_bones(num_joints);
|
||||
|
||||
/* Set bone parenting. */
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
int parent_idx = skel_topology.GetParent(i);
|
||||
if (parent_idx < 0) {
|
||||
continue;
|
||||
}
|
||||
if (parent_idx >= edit_bones.size()) {
|
||||
std::cout << "WARNING: out of bounds parent index for bone " << pxr::SdfPath(joint_order[i])
|
||||
<< " for skeleton " << skel_.GetPath() << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
child_bones[parent_idx].push_back(i);
|
||||
if (edit_bones[i] && edit_bones[parent_idx]) {
|
||||
edit_bones[i]->parent = edit_bones[parent_idx];
|
||||
}
|
||||
}
|
||||
|
||||
/* Joint bind transforms. */
|
||||
pxr::VtMatrix4dArray bind_xforms;
|
||||
if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) {
|
||||
std::cout << "WARNING: couldn't get world bind transforms for skeleton "
|
||||
<< skel_query.GetSkeleton().GetPrim().GetPath() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (bind_xforms.size() != num_joints) {
|
||||
std::cout << "WARNING: mismatch in local space rest xforms and joint counts for skeleton " << skel_.GetPath() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if any bone natrices have negative determinants,
|
||||
* indicating negative scales, possibly due to mirroring
|
||||
* operations. Such matrices can't be propery converted
|
||||
* to Blender's axis/roll bone representation (see
|
||||
* https://developer.blender.org/T82930). If we detect
|
||||
* such matrices, we will flag an error and won't try
|
||||
* to import the animation, since the rotations would
|
||||
* be incorrect in such cases. Unfortunately, the Pixar
|
||||
* UsdSkel examples of the "HumanFemale" suffer from
|
||||
* this issue. */
|
||||
bool negative_determinant = false;
|
||||
|
||||
/* Set bone rest transforms. */
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
EditBone *ebone = edit_bones[i];
|
||||
|
||||
if (!ebone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::GfMatrix4f mat(bind_xforms[i]);
|
||||
|
||||
float mat4[4][4];
|
||||
mat.Get(mat4);
|
||||
|
||||
pxr::GfVec3f head(0.0f, 0.0f, 0.0f);
|
||||
pxr::GfVec3f tail(0.0f, 1.0f, 0.0f);
|
||||
|
||||
copy_v3_v3(ebone->head, head.data());
|
||||
copy_v3_v3(ebone->tail, tail.data());
|
||||
|
||||
ED_armature_ebone_from_mat4(ebone, mat4);
|
||||
|
||||
if (mat.GetDeterminant() < 0.0) {
|
||||
negative_determinant = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool valid_skeleton = true;
|
||||
if (negative_determinant) {
|
||||
valid_skeleton = false;
|
||||
WM_reportf(RPT_WARNING,
|
||||
"USD Skeleton Import: bone matrices with negative determinants detected in prim %s."
|
||||
"Such matrices may indicate negative scales, possibly due to mirroring operations, "
|
||||
"and can't currently be converted to Blender's bone representation. "
|
||||
"The skeletal animation won't be imported", prim_.GetPath().GetAsString().c_str());
|
||||
}
|
||||
|
||||
/* Scale bones to account for separation between parents and
|
||||
* children, so that the bone size is in proportion with the
|
||||
* overall skeleton hierarchy. USD skeletons are composed of
|
||||
* joints which we imperfectly represent as bones. */
|
||||
|
||||
float avg_len_scale = 0;
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
|
||||
/* If the bone has any children, scale its length
|
||||
* by the distance between this bone's head
|
||||
* and the average head location of its children. */
|
||||
|
||||
if (child_bones[i].empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EditBone *parent = edit_bones[i];
|
||||
if (!parent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::GfVec3f avg_child_head(0);
|
||||
for (int j : child_bones[i]) {
|
||||
EditBone *child = edit_bones[j];
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
pxr::GfVec3f child_head(child->head);
|
||||
avg_child_head += child_head;
|
||||
}
|
||||
|
||||
avg_child_head /= child_bones[i].size();
|
||||
|
||||
pxr::GfVec3f parent_head(parent->head);
|
||||
pxr::GfVec3f parent_tail(parent->tail);
|
||||
|
||||
float new_len = (avg_child_head - parent_head).GetLength();
|
||||
|
||||
/* Be sure not to scale by zero. */
|
||||
if (new_len > .00001) {
|
||||
parent_tail = parent_head + (parent_tail - parent_head).GetNormalized() * new_len;
|
||||
copy_v3_v3(parent->tail, parent_tail.data());
|
||||
avg_len_scale += new_len;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scale terminal bones by the average length scale. */
|
||||
avg_len_scale /= num_joints;
|
||||
|
||||
if (avg_len_scale > .00001) {
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
if (!child_bones[i].empty()) {
|
||||
continue;
|
||||
}
|
||||
EditBone *bone = edit_bones[i];
|
||||
if (!bone) {
|
||||
continue;
|
||||
}
|
||||
pxr::GfVec3f head(bone->head);
|
||||
pxr::GfVec3f tail(bone->tail);
|
||||
tail = head + (tail - head).GetNormalized() * avg_len_scale;
|
||||
copy_v3_v3(bone->tail, tail.data());
|
||||
}
|
||||
}
|
||||
|
||||
ED_armature_from_edit(bmain, arm);
|
||||
ED_armature_edit_free(arm);
|
||||
|
||||
if (valid_skeleton) {
|
||||
create_skeleton_curves(bmain, object_, skel_query, joint_to_bone_map);
|
||||
}
|
||||
import_skeleton(bmain, object_, skel_);
|
||||
|
||||
USDXformReader::read_object_data(bmain, motionSampleTime);
|
||||
}
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include <pxr/usd/usdSkel/animation.h>
|
||||
#include <pxr/usd/usdSkel/blendShape.h>
|
||||
#include <pxr/usd/usdSkel/bindingAPI.h>
|
||||
#include <pxr/usd/usdSkel/cache.h>
|
||||
#include <pxr/usd/usdSkel/skeletonQuery.h>
|
||||
#include <pxr/usd/usdSkel/utils.h>
|
||||
|
||||
#include "DNA_anim_types.h"
|
||||
@ -33,9 +35,12 @@
|
||||
|
||||
#include "BLI_math_vector.h"
|
||||
|
||||
#include "ED_armature.h"
|
||||
#include "ED_keyframing.h"
|
||||
#include "ED_mesh.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -313,6 +318,215 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim)
|
||||
|
||||
}
|
||||
|
||||
void import_skeleton(Main *bmain, Object *obj, const pxr::UsdSkelSkeleton &skel)
|
||||
{
|
||||
pxr::UsdSkelCache skel_cache;
|
||||
pxr::UsdSkelSkeletonQuery skel_query = skel_cache.GetSkelQuery(skel);
|
||||
|
||||
if (!skel_query.IsValid()) {
|
||||
std::cout << "WARNING: couldn't query skeleton " << skel.GetPath() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
const pxr::UsdSkelTopology &skel_topology = skel_query.GetTopology();
|
||||
|
||||
pxr::VtTokenArray joint_order = skel_query.GetJointOrder();
|
||||
|
||||
if (joint_order.size() != skel_topology.size()) {
|
||||
std::cout << "WARNING: skel topology and joint order size mismatch\n";
|
||||
return;
|
||||
}
|
||||
|
||||
bArmature *arm = static_cast<bArmature *>(obj->data);
|
||||
|
||||
ED_armature_to_edit(arm);
|
||||
|
||||
/* The bones we create, stored in the skeleton's joint order. */
|
||||
std::vector<EditBone *> edit_bones;
|
||||
|
||||
size_t num_joints = skel_topology.GetNumJoints();
|
||||
|
||||
/* Keep track of the bones we create for each joint. */
|
||||
std::map<pxr::TfToken, std::string> joint_to_bone_map;
|
||||
|
||||
/* Create the bones. */
|
||||
for (const pxr::TfToken &joint : joint_order) {
|
||||
std::string name = pxr::SdfPath(joint).GetName();
|
||||
EditBone *bone = ED_armature_ebone_add(arm, name.c_str());
|
||||
if (!bone) {
|
||||
std::cout << "WARNING: couldn't add bone for joint " << joint << std::endl;
|
||||
edit_bones.push_back(nullptr);
|
||||
continue;
|
||||
}
|
||||
joint_to_bone_map.insert(std::make_pair(joint, bone->name));
|
||||
edit_bones.push_back(bone);
|
||||
}
|
||||
|
||||
/* Sanity check: we should have created a bone for each joint. */
|
||||
|
||||
if (edit_bones.size() != num_joints) {
|
||||
std::cout << "WARNING: mismatch in bone and joint counts for skeleton " << skel.GetPath()
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Record the child bone indices per parent bone. */
|
||||
std::vector<std::vector<int>> child_bones(num_joints);
|
||||
|
||||
/* Set bone parenting. */
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
int parent_idx = skel_topology.GetParent(i);
|
||||
if (parent_idx < 0) {
|
||||
continue;
|
||||
}
|
||||
if (parent_idx >= edit_bones.size()) {
|
||||
std::cout << "WARNING: out of bounds parent index for bone " << pxr::SdfPath(joint_order[i])
|
||||
<< " for skeleton " << skel.GetPath() << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
child_bones[parent_idx].push_back(i);
|
||||
if (edit_bones[i] && edit_bones[parent_idx]) {
|
||||
edit_bones[i]->parent = edit_bones[parent_idx];
|
||||
}
|
||||
}
|
||||
|
||||
/* Joint bind transforms. */
|
||||
pxr::VtMatrix4dArray bind_xforms;
|
||||
if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) {
|
||||
std::cout << "WARNING: couldn't get world bind transforms for skeleton "
|
||||
<< skel_query.GetSkeleton().GetPrim().GetPath() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (bind_xforms.size() != num_joints) {
|
||||
std::cout << "WARNING: mismatch in local space rest xforms and joint counts for skeleton "
|
||||
<< skel.GetPath() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if any bone natrices have negative determinants,
|
||||
* indicating negative scales, possibly due to mirroring
|
||||
makowalski marked this conversation as resolved
Outdated
|
||||
* operations. Such matrices can't be propery converted
|
||||
* to Blender's axis/roll bone representation (see
|
||||
* https://developer.blender.org/T82930). If we detect
|
||||
* such matrices, we will flag an error and won't try
|
||||
* to import the animation, since the rotations would
|
||||
* be incorrect in such cases. Unfortunately, the Pixar
|
||||
* UsdSkel examples of the "HumanFemale" suffer from
|
||||
* this issue. */
|
||||
bool negative_determinant = false;
|
||||
|
||||
/* Set bone rest transforms. */
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
EditBone *ebone = edit_bones[i];
|
||||
|
||||
if (!ebone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::GfMatrix4f mat(bind_xforms[i]);
|
||||
|
||||
float mat4[4][4];
|
||||
mat.Get(mat4);
|
||||
|
||||
pxr::GfVec3f head(0.0f, 0.0f, 0.0f);
|
||||
pxr::GfVec3f tail(0.0f, 1.0f, 0.0f);
|
||||
|
||||
copy_v3_v3(ebone->head, head.data());
|
||||
copy_v3_v3(ebone->tail, tail.data());
|
||||
|
||||
ED_armature_ebone_from_mat4(ebone, mat4);
|
||||
|
||||
if (mat.GetDeterminant() < 0.0) {
|
||||
negative_determinant = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool valid_skeleton = true;
|
||||
if (negative_determinant) {
|
||||
valid_skeleton = false;
|
||||
WM_reportf(RPT_WARNING,
|
||||
"USD Skeleton Import: bone matrices with negative determinants detected in prim %s."
|
||||
"Such matrices may indicate negative scales, possibly due to mirroring operations, "
|
||||
"and can't currently be converted to Blender's bone representation. "
|
||||
"The skeletal animation won't be imported",
|
||||
skel.GetPath().GetAsString().c_str());
|
||||
}
|
||||
|
||||
/* Scale bones to account for separation between parents and
|
||||
* children, so that the bone size is in proportion with the
|
||||
* overall skeleton hierarchy. USD skeletons are composed of
|
||||
* joints which we imperfectly represent as bones. */
|
||||
|
||||
float avg_len_scale = 0;
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
|
||||
/* If the bone has any children, scale its length
|
||||
* by the distance between this bone's head
|
||||
* and the average head location of its children. */
|
||||
|
||||
if (child_bones[i].empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EditBone *parent = edit_bones[i];
|
||||
if (!parent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::GfVec3f avg_child_head(0);
|
||||
for (int j : child_bones[i]) {
|
||||
EditBone *child = edit_bones[j];
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
pxr::GfVec3f child_head(child->head);
|
||||
avg_child_head += child_head;
|
||||
}
|
||||
|
||||
avg_child_head /= child_bones[i].size();
|
||||
|
||||
pxr::GfVec3f parent_head(parent->head);
|
||||
pxr::GfVec3f parent_tail(parent->tail);
|
||||
|
||||
float new_len = (avg_child_head - parent_head).GetLength();
|
||||
|
||||
/* Be sure not to scale by zero. */
|
||||
if (new_len > .00001) {
|
||||
parent_tail = parent_head + (parent_tail - parent_head).GetNormalized() * new_len;
|
||||
copy_v3_v3(parent->tail, parent_tail.data());
|
||||
avg_len_scale += new_len;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scale terminal bones by the average length scale. */
|
||||
avg_len_scale /= num_joints;
|
||||
|
||||
if (avg_len_scale > .00001) {
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
if (!child_bones[i].empty()) {
|
||||
continue;
|
||||
}
|
||||
EditBone *bone = edit_bones[i];
|
||||
if (!bone) {
|
||||
continue;
|
||||
}
|
||||
pxr::GfVec3f head(bone->head);
|
||||
pxr::GfVec3f tail(bone->tail);
|
||||
tail = head + (tail - head).GetNormalized() * avg_len_scale;
|
||||
copy_v3_v3(bone->tail, tail.data());
|
||||
}
|
||||
}
|
||||
|
||||
ED_armature_from_edit(bmain, arm);
|
||||
ED_armature_edit_free(arm);
|
||||
|
||||
if (valid_skeleton) {
|
||||
create_skeleton_curves(bmain, obj, skel_query, joint_to_bone_map);
|
||||
}
|
||||
}
|
||||
|
||||
void create_skeleton_curves(Main *bmain,
|
||||
Object *obj,
|
||||
const pxr::UsdSkelSkeletonQuery &skel_query,
|
||||
|
@ -19,6 +19,8 @@ struct ImportSettings;
|
||||
|
||||
void import_blendshapes(Main *bmain, Object *shape_obj, pxr::UsdPrim prim);
|
||||
|
||||
void import_skeleton(Main *bmain, Object *obj, const pxr::UsdSkelSkeleton &skel);
|
||||
|
||||
void create_skeleton_curves(Main *bmain,
|
||||
Object *obj,
|
||||
const pxr::UsdSkelSkeletonQuery &skel_query,
|
||||
|
Loading…
Reference in New Issue
Block a user
picky empty line