USD import: read skeletons.

Added new option to import USD skeletons as Blender
armatures.  Added new USDSkeletonReader class and
updated the mesh import code to optionally create
armature modifiers for meshes bound to skeletons.
Added logic to the mesh reader to allow overriding
the mesh transform to ensure the mesh is aligned
with the authored geom bind transform.
This commit is contained in:
Michael Kowalski 2022-08-30 15:15:42 -04:00
parent 1dfe4938cd
commit 095f016fc0
18 changed files with 1128 additions and 16 deletions

View File

@ -965,6 +965,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
const bool import_meshes = RNA_boolean_get(op->ptr, "import_meshes");
const bool import_blendshapes = RNA_boolean_get(op->ptr, "import_blendshapes");
const bool import_volumes = RNA_boolean_get(op->ptr, "import_volumes");
const bool import_skeletons = RNA_boolean_get(op->ptr, "import_skeletons");
const bool import_subdiv = RNA_boolean_get(op->ptr, "import_subdiv");
@ -1031,6 +1032,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
.import_meshes = import_meshes,
.import_blendshapes = import_blendshapes,
.import_volumes = import_volumes,
.import_skeletons = import_skeletons,
.prim_path_mask = prim_path_mask,
.import_subdiv = import_subdiv,
.import_instance_proxies = import_instance_proxies,
@ -1072,6 +1074,7 @@ static void wm_usd_import_draw(bContext *UNUSED(C), wmOperator *op)
uiItemR(col, ptr, "import_meshes", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_blendshapes", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_volumes", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_skeletons", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "prim_path_mask", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "scale", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "apply_unit_conversion_scale", 0, NULL, ICON_NONE);
@ -1168,6 +1171,7 @@ void WM_OT_usd_import(struct wmOperatorType *ot)
RNA_def_boolean(ot->srna, "import_meshes", true, "Meshes", "");
RNA_def_boolean(ot->srna, "import_blendshapes", true, "Blend Shapes", "");
RNA_def_boolean(ot->srna, "import_volumes", true, "Volumes", "");
RNA_def_boolean(ot->srna, "import_skeletons", true, "Skeletons", "");
RNA_def_boolean(ot->srna,
"import_subdiv",

View File

@ -80,6 +80,7 @@ set(SRC
intern/usd_reader_mesh.cc
intern/usd_reader_nurbs.cc
intern/usd_reader_prim.cc
intern/usd_reader_skeleton.cc
intern/usd_reader_stage.cc
intern/usd_reader_volume.cc
intern/usd_reader_xform.cc
@ -119,6 +120,7 @@ set(SRC
intern/usd_reader_mesh.h
intern/usd_reader_nurbs.h
intern/usd_reader_prim.h
intern/usd_reader_skeleton.h
intern/usd_reader_stage.h
intern/usd_reader_volume.h
intern/usd_reader_xform.h

View File

@ -525,6 +525,10 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo
}
}
if (data->params.import_skeletons) {
archive->process_armature_modifiers();
}
data->import_ok = !data->was_canceled;
*progress = 1.0f;

View File

@ -37,6 +37,7 @@
#include <pxr/usd/usdGeom/mesh.h>
#include <pxr/usd/usdGeom/subset.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
#include <pxr/usd/usdSkel/bindingAPI.h>
#include <iostream>
@ -277,6 +278,10 @@ void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime)
import_blendshapes(bmain, object_, prim_);
}
if (import_params_.import_skeletons) {
import_skel_bindings(bmain, object_, prim_);
}
USDXformReader::read_object_data(bmain, motionSampleTime);
}
@ -955,4 +960,127 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,
return active_mesh;
}
std::string USDMeshReader::get_skeleton_path() const
{
if (!prim_) {
return "";
}
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim_);
if (!skel_api) {
return "";
}
if (pxr::UsdSkelSkeleton skel = skel_api.GetInheritedSkeleton()) {
return skel.GetPath().GetAsString();
}
return "";
}
/* Return a local transform to place the mesh in its world bind position.
* In some cases, the bind transform and prim world transform might be
* be different, in which case we must adjust the local transform
* to ensure the mesh is correctly aligned for bininding. A use
* case where this might be needed is if a skel animation is exported
* from Blender and both the skeleton and mesh are transformed in Create
* or another DCC, without modifying the original mesh bind transform. */
bool USDMeshReader::get_geom_bind_xform_correction(const pxr::GfMatrix4d &bind_xf,
pxr::GfMatrix4d *r_xform,
const float time) const
{
if (!r_xform) {
return false;
}
pxr::GfMatrix4d world_xf = get_world_matrix(prim_, time);
if (pxr::GfIsClose(bind_xf, world_xf, .000000001)) {
/* The world and bind matrices are equal, so we don't
* need to correct the local transfor. Get the transform
* with the standard API. */
pxr::UsdGeomXformable xformable;
if (use_parent_xform_) {
xformable = pxr::UsdGeomXformable(prim_.GetParent());
}
else {
xformable = pxr::UsdGeomXformable(prim_);
}
if (!xformable) {
/* This shouldn't happen. */
*r_xform = pxr::GfMatrix4d(1.0);
return false;
}
bool reset_xform_stack;
return xformable.GetLocalTransformation(r_xform, &reset_xform_stack, time);
}
/* If we got here, then the bind transform and prim
* world transform differ, so we must adjust the local
* transform to ensure the mesh is aligned in the correct
* bind position */
pxr::GfMatrix4d parent_world_xf(1.0);
pxr::UsdPrim parent;
if (use_parent_xform_) {
if (prim_.GetParent()) {
parent = prim_.GetParent().GetParent();
}
}
else {
parent = prim_.GetParent();
}
if (parent) {
parent_world_xf = get_world_matrix(parent, time);
}
pxr::GfMatrix4d corrected_local_xf = bind_xf * parent_world_xf.GetInverse();
*r_xform = corrected_local_xf;
return true;
}
/* Override transform computation to account for the binding
* transformation for skinned meshes. */
bool USDMeshReader::get_local_usd_xform(pxr::GfMatrix4d *r_xform,
bool *r_is_constant,
const float time) const
{
if (!r_xform) {
return false;
}
if (!import_params_.import_skeletons) {
/* Use the standard transform computation, since we are ignoring
* skinning data. */
return USDXformReader::get_local_usd_xform(r_xform, r_is_constant, time);
}
if (pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim_)) {
if (skel_api.GetGeomBindTransformAttr().HasAuthoredValue()) {
pxr::GfMatrix4d bind_xf;
if (skel_api.GetGeomBindTransformAttr().Get(&bind_xf)) {
/* Assume that if a bind transform is defined, then the
* transform is constant. */
if (r_is_constant) {
*r_is_constant = true;
}
return get_geom_bind_xform_correction(bind_xf, r_xform, time);
}
else {
std::cout << "WARNING: couldn't compute geom bind transform for " << prim_.GetPath()
<< std::endl;
}
}
}
return USDXformReader::get_local_usd_xform(r_xform, r_is_constant, time);
}
} // namespace blender::io::usd

View File

@ -55,6 +55,8 @@ class USDMeshReader : public USDGeomReader {
bool topology_changed(const Mesh *existing_mesh, double motionSampleTime) override;
std::string get_skeleton_path() const;
private:
void process_normals_vertex_varying(Mesh *mesh);
void process_normals_face_varying(Mesh *mesh);
@ -76,6 +78,14 @@ class USDMeshReader : public USDGeomReader {
Mesh *mesh,
double motionSampleTime,
bool new_mesh);
bool get_local_usd_xform(pxr::GfMatrix4d *r_xform,
bool *r_is_constant,
const float time) const override;
bool get_geom_bind_xform_correction(const pxr::GfMatrix4d &bind_xf,
pxr::GfMatrix4d *r_xform,
const float time) const;
};
} // namespace blender::io::usd

View File

@ -0,0 +1,373 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
* All rights reserved.
*/
#include "usd_reader_skeleton.h"
#include "usd_skel_convert.h"
#include "BKE_idprop.h"
#include "BKE_action.h"
#include "BKE_armature.h"
#include "BKE_fcurve.h"
#include "BKE_object.h"
#include "BLI_math.h"
#include "BLI_string.h"
#include "BLI_string_utils.h"
#include "DNA_armature_types.h"
#include "DNA_object_types.h"
#include "ED_armature.h"
#include "ED_keyframing.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 {
/* Debugging utility to print the given skeleton's joint paths. */
static void print_joints(pxr::UsdSkelSkeleton &skel,
const double motionSampleTime)
{
if (!skel) {
return;
}
if (pxr::UsdAttribute joints_attr = skel.GetJointsAttr()) {
pxr::VtArray<pxr::TfToken> joints;
if (joints_attr.Get(&joints, motionSampleTime)) {
std::cout << "Num joints " << joints.size() << std::endl;
for (pxr::TfToken &joint : joints) {
pxr::SdfPath joint_path(joint);
std::cout << joint_path << std::endl;
}
}
}
}
/* Code from the Collada importer's AnimationImporter class. */
static FCurve *create_fcurve(int array_index, const char *rna_path)
{
FCurve *fcu = BKE_fcurve_create();
fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED);
fcu->rna_path = BLI_strdupn(rna_path, strlen(rna_path));
fcu->array_index = array_index;
return fcu;
}
/* Code from the Collada importer's AnimationImporter class. */
static void add_bezt(FCurve *fcu,
float frame,
float value,
eBezTriple_Interpolation ipo = BEZT_IPO_LIN)
{
BezTriple bez;
memset(&bez, 0, sizeof(BezTriple));
bez.vec[1][0] = frame;
bez.vec[1][1] = value;
bez.ipo = ipo; /* use default interpolation mode here... */
bez.f1 = bez.f2 = bez.f3 = SELECT;
bez.h1 = bez.h2 = HD_AUTO;
insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS);
BKE_fcurve_handles_recalc(fcu);
}
/* Example code for adding a bone and creating curves for
* animating its translation. */
static void test_add_bone_anim(Main *bmain, Object *obj)
{
if (!obj || !obj->data || !bmain) {
return;
}
bArmature *arm = static_cast<bArmature *>(obj->data);
ED_armature_to_edit(arm);
const char *bone_name = "Bone";
EditBone * bone = ED_armature_ebone_add(arm, bone_name);
/* Blender will cull zero-length bones, so we give
* the bone an arbitrary size. */
float v0[3] = { 0.0f, 0.0f, 0.0f };
float v1[3] = { 0.0f, 1.0f, 0.0f };
copy_v3_v3(bone->head, v0);
copy_v3_v3(bone->tail, v1);
ED_armature_from_edit(bmain, arm);
ED_armature_edit_free(arm);
/* As an arbitrary test, translate the bone in Y for 60 frames. */
bAction *act = ED_id_action_ensure(bmain, (ID *)&obj->id);
bActionGroup *grp = action_groups_add_new(act, bone_name);
const char *rna_path = "pose.bones[\"Bone\"].location";
FCurve *fcu0 = create_fcurve(0, rna_path);
fcu0->totvert = 60;
FCurve *fcu1 = create_fcurve(1, rna_path);
fcu1->totvert = 60;
FCurve *fcu2 = create_fcurve(2, rna_path);
fcu2->totvert = 60;
for (int i = 1; i < 61; ++i) {
add_bezt(fcu0, static_cast<float>(i), 0.0f);
add_bezt(fcu1, static_cast<float>(i), static_cast<float>(i) * 0.1f);
add_bezt(fcu2, static_cast<float>(i), 0.0f);
}
action_groups_add_channel(act, grp, fcu0);
action_groups_add_channel(act, grp, fcu1);
action_groups_add_channel(act, grp, fcu2);
}
bool USDSkeletonReader::valid() const
{
return skel_ && USDXformReader::valid();
}
void USDSkeletonReader::create_object(Main *bmain, const double /* motionSampleTime */)
{
object_ = BKE_object_add_only_object(bmain, OB_ARMATURE, name_.c_str());
bArmature *arm = BKE_armature_add(bmain, name_.c_str());
object_->data = arm;
}
void USDSkeletonReader::read_object_data(Main *bmain, const double motionSampleTime)
{
if (!object_ || !object_->data || !skel_) {
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];
}
}
/* Skeleton-space joint bind transforms. */
pxr::VtMatrix4dArray bind_xforms;
if (!compute_skel_space_bind_transforms(skel_query, bind_xforms, 0.0f)) {
std::cout << "WARNING: couldn't get skeleton space bind xforms for skeleton "
<< skel_.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);
}
USDXformReader::read_object_data(bmain, motionSampleTime);
}
} // namespace blender::io::usd

View File

@ -0,0 +1,45 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
* All rights reserved.
*/
#pragma once
#include "usd.h"
#include "usd_reader_xform.h"
#include <pxr/usd/usdSkel/skeleton.h>
namespace blender::io::usd {
class USDSkeletonReader : public USDXformReader {
private:
pxr::UsdSkelSkeleton skel_;
public:
USDSkeletonReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings)
: USDXformReader(prim, import_params, settings), skel_(prim)
{
}
bool valid() const override;
void create_object(Main *bmain, double motionSampleTime) override;
void read_object_data(Main *bmain, double motionSampleTime) override;
};
} // namespace blender::io::usd

View File

@ -9,6 +9,7 @@
#include "usd_reader_mesh.h"
#include "usd_reader_nurbs.h"
#include "usd_reader_prim.h"
#include "usd_reader_skeleton.h"
#include "usd_reader_volume.h"
#include "usd_reader_xform.h"
@ -19,7 +20,6 @@
#include <pxr/usd/usdGeom/nurbsCurves.h>
#include <pxr/usd/usdGeom/scope.h>
#include <pxr/usd/usdGeom/xform.h>
#include <pxr/usd/usdLux/domeLight.h>
#if PXR_VERSION >= 2111
# include <pxr/usd/usdLux/boundableLightBase.h>
@ -27,12 +27,16 @@
#else
# include <pxr/usd/usdLux/light.h>
#endif
#include <pxr/usd/usdSkel/skeleton.h>
#include <iostream>
#include "BKE_modifier.h"
#include "BLI_sort.hh"
#include "BLI_string.h"
struct Object;
namespace blender::io::usd {
USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage,
@ -85,6 +89,9 @@ USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim
if (params_.import_volumes && prim.IsA<pxr::UsdVolVolume>()) {
return new USDVolumeReader(prim, params_, settings_);
}
if (params_.import_skeletons && prim.IsA<pxr::UsdSkelSkeleton>()) {
return new USDSkeletonReader(prim, params_, settings_);
}
if (prim.IsA<pxr::UsdGeomImageable>()) {
return new USDXformReader(prim, params_, settings_);
}
@ -124,6 +131,9 @@ USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim,
if (prim.IsA<pxr::UsdVolVolume>()) {
return new USDVolumeReader(prim, params_, settings_);
}
if (prim.IsA<pxr::UsdSkelSkeleton>()) {
return new USDSkeletonReader(prim, params_, settings_);
}
if (prim.IsA<pxr::UsdGeomImageable>()) {
return new USDXformReader(prim, params_, settings_);
}
@ -157,6 +167,11 @@ bool USDStageReader::include_by_visibility(const pxr::UsdGeomImageable &imageabl
bool USDStageReader::include_by_purpose(const pxr::UsdGeomImageable &imageable) const
{
if (params_.import_skeletons && imageable.GetPrim().IsA<pxr::UsdSkelSkeleton>()) {
/* Always include skeletons, if requested by the user, regardless of purpose. */
return true;
}
if (params_.import_guide && params_.import_proxy && params_.import_render) {
/* The options allow any purpose, so we trivially include the prim. */
return true;
@ -348,6 +363,41 @@ void USDStageReader::collect_readers(Main *bmain)
}
}
void USDStageReader::process_armature_modifiers() const
{
/* Create armature object map. */
std::map<std::string, Object *> usd_path_to_armature;
for (const USDPrimReader *reader : readers_) {
if (dynamic_cast<const USDSkeletonReader *>(reader) && reader->object()) {
usd_path_to_armature.insert(std::make_pair(reader->prim_path(), reader->object()));
}
}
/* Set armature objects on armature modifiers. */
for (const USDPrimReader *reader : readers_) {
if (!reader->object()) {
/* This should never happen. */
continue;
}
if (const USDMeshReader * mesh_reader = dynamic_cast<const USDMeshReader *>(reader)) {
ModifierData *md = BKE_modifiers_findby_type(reader->object(), eModifierType_Armature);
if (!md) {
continue;
}
ArmatureModifierData *amd = reinterpret_cast<ArmatureModifierData *>(md);
std::string skel_path = mesh_reader->get_skeleton_path();
std::map<std::string, Object *>::const_iterator it = usd_path_to_armature.find(skel_path);
if (it != usd_path_to_armature.end()) {
amd->object = it->second;
}
else {
std::cout << "WARNING: couldn't find armature object for armature modifier for USD prim "
<< reader->prim_path() << " bound to skeleton " << skel_path << std::endl;
}
}
}
}
void USDStageReader::clear_readers()
{
for (USDPrimReader *reader : readers_) {

View File

@ -50,6 +50,8 @@ class USDStageReader {
void collect_readers(struct Main *bmain);
void process_armature_modifiers() const;
bool valid() const;
pxr::UsdStageRefPtr stage()

View File

@ -71,17 +71,14 @@ void USDXformReader::apply_cache_file(CacheFile *cache_file)
id_us_plus(&cache_file->id);
}
void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */,
const float time,
const float scale,
bool *r_is_constant)
bool USDXformReader::get_local_usd_xform(pxr::GfMatrix4d * r_xform,
bool *r_is_constant,
const float time) const
{
if (r_is_constant) {
*r_is_constant = true;
if (!r_xform) {
return false;
}
unit_m4(r_mat);
pxr::UsdGeomXformable xformable;
if (use_parent_xform_) {
@ -93,16 +90,32 @@ void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */,
if (!xformable) {
/* This might happen if the prim is a Scope. */
return;
return false;
}
if (r_is_constant) {
*r_is_constant = !xformable.TransformMightBeTimeVarying();
}
pxr::GfMatrix4d usd_local_xf;
bool reset_xform_stack;
xformable.GetLocalTransformation(&usd_local_xf, &reset_xform_stack, time);
return xformable.GetLocalTransformation(r_xform, &reset_xform_stack, time);
}
void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */,
const float time,
const float scale,
bool *r_is_constant)
{
if (r_is_constant) {
*r_is_constant = true;
}
unit_m4(r_mat);
pxr::GfMatrix4d usd_local_xf;
if (!get_local_usd_xform(&usd_local_xf, r_is_constant, time)) {
return;
}
/* Convert the result to a float matrix. */
pxr::GfMatrix4f mat4f = pxr::GfMatrix4f(usd_local_xf);

View File

@ -9,7 +9,7 @@
namespace blender::io::usd {
class USDXformReader : public USDPrimReader {
private:
protected:
bool use_parent_xform_;
/* Indicates if the created object is the root of a
@ -55,6 +55,10 @@ class USDXformReader : public USDPrimReader {
protected:
/* Returns true if the contained USD prim is the root of a transform hierarchy. */
bool is_root_xform_prim() const;
virtual bool get_local_usd_xform(pxr::GfMatrix4d *r_xform,
bool *r_is_constant,
const float time) const;
};
} // namespace blender::io::usd

View File

@ -24,16 +24,22 @@
#include <pxr/usd/usdSkel/animation.h>
#include <pxr/usd/usdSkel/blendShape.h>
#include <pxr/usd/usdSkel/bindingAPI.h>
#include <pxr/usd/usdSkel/utils.h>
#include "BKE_action.h"
#include "BKE_armature.h"
#include "BKE_deform.h"
#include "BKE_key.h"
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_object_deform.h"
#include "BLI_math_vector.h"
#include "DNA_armature_types.h"
#include "DNA_key_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@ -50,6 +56,8 @@
#include "BKE_fcurve.h"
#include "ED_keyframing.h"
#include "ED_mesh.h"
#include <string>
#include <vector>
@ -69,6 +77,18 @@ FCurve *create_fcurve(int array_index, const char *rna_path)
return fcu;
}
FCurve *create_chan_fcurve(bAction *act,
bActionGroup *grp,
int array_index,
const char *rna_path,
int totvert)
{
FCurve *fcu = create_fcurve(array_index, rna_path);
fcu->totvert = totvert;
action_groups_add_channel(act, grp, fcu);
return fcu;
}
void add_bezt(FCurve *fcu,
float frame,
float value,
@ -85,10 +105,40 @@ void add_bezt(FCurve *fcu,
BKE_fcurve_handles_recalc(fcu);
}
/* Generate dummy curve samples for testing. */
void add_dummy_samples(FCurve *fcu)
{
int totvert = fcu->totvert;
for (int i = 0; i < totvert; ++i) {
add_bezt(fcu, static_cast<float>(i+1), static_cast<float>(i) * 0.1f);
}
}
} // End anonymous namespace.
namespace blender::io::usd {
pxr::GfMatrix4d get_world_matrix(const pxr::UsdPrim &prim, pxr::UsdTimeCode time)
{
pxr::GfMatrix4d local_xf(1.0f);
if (!prim) {
return local_xf;
}
pxr::UsdGeomXformable xformable(prim);
if (xformable) {
bool reset_xform_stack = false;
if (!xformable.GetLocalTransformation(&local_xf, &reset_xform_stack, time)) {
std::cout << "WARNING: couldn't get local xform for prim " << prim.GetPath() << std::endl;
return local_xf;
}
}
return local_xf * get_world_matrix(prim.GetParent(), time);
}
void test_create_shapekeys(Main *bmain, Object *obj)
{
if (!(obj && obj->data && obj->type == OB_MESH)) {
@ -343,4 +393,375 @@ void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim)
}
void create_skeleton_curves(Main *bmain,
Object *obj,
const pxr::UsdSkelSkeletonQuery &skel_query,
const std::map<pxr::TfToken, std::string> &joint_to_bone_map)
{
if (!(bmain && obj && skel_query)) {
return;
}
if (joint_to_bone_map.empty()) {
return;
}
const pxr::UsdSkelAnimQuery &anim_query = skel_query.GetAnimQuery();
if (!anim_query) {
return;
}
std::vector<double> samples;
anim_query.GetJointTransformTimeSamples(&samples);
if (samples.empty()) {
return;
}
size_t num_samples = samples.size();
bAction *act = ED_id_action_ensure(bmain, (ID *)&obj->id);
pxr::VtTokenArray joint_order = skel_query.GetJointOrder();
std::vector<FCurve *> loc_curves;
std::vector<FCurve *> rot_curves;
std::vector<FCurve *> scale_curves;
for (const pxr::TfToken &joint : joint_order) {
std::map<pxr::TfToken, std::string>::const_iterator it = joint_to_bone_map.find(joint);
if (it == joint_to_bone_map.end()) {
/* This joint doesn't correspond to any bone we created.
/* Add null placeholders for the channel curves. */
loc_curves.push_back(nullptr);
loc_curves.push_back(nullptr);
loc_curves.push_back(nullptr);
rot_curves.push_back(nullptr);
rot_curves.push_back(nullptr);
rot_curves.push_back(nullptr);
rot_curves.push_back(nullptr);
scale_curves.push_back(nullptr);
scale_curves.push_back(nullptr);
scale_curves.push_back(nullptr);
continue;
}
bActionGroup *grp = action_groups_add_new(act, it->second.c_str());
/* Add translation curves. */
std::string rna_path = "pose.bones[\"" + it->second + "\"].location";
loc_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples));
loc_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples));
loc_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples));
/* Add rotation curves. */
rna_path = "pose.bones[\"" + it->second + "\"].rotation_quaternion";
rot_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples));
rot_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples));
rot_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples));
rot_curves.push_back(create_chan_fcurve(act, grp, 3, rna_path.c_str(), num_samples));
/* Add scale curves. */
rna_path = "pose.bones[\"" + it->second + "\"].scale";
scale_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples));
scale_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples));
scale_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples));
}
if (loc_curves.size() != joint_order.size() * 3) {
std::cout << "PROGRAMMER ERROR: location curve count mismatch\n";
return;
}
if (rot_curves.size() != joint_order.size() * 4) {
std::cout << "PROGRAMMER ERROR: rotation curve count mismatch\n";
return;
}
if (scale_curves.size() != joint_order.size() * 3) {
std::cout << "PROGRAMMER ERROR: scale curve count mismatch\n";
return;
}
/* Skeleton-space joint bind transforms. */
pxr::VtMatrix4dArray bind_xforms;
if (!compute_skel_space_bind_transforms(skel_query, bind_xforms, 0.0f)) {
std::cout << "WARNING: couldn't get skeleton space bind xforms for skeleton query "
<< skel_query.GetPrim().GetPath() << std::endl;
return;
}
if (bind_xforms.size() != joint_order.size()) {
std::cout << "WARNING: number of bind transforms doesn't match the number of joints\n";
return;
}
const pxr::UsdSkelTopology &skel_topology = skel_query.GetTopology();
/* This will store the inverse of the parent-relative bind xforms. */
pxr::VtMatrix4dArray inv_bind_xforms(bind_xforms.size());
for (int i = 0; i < bind_xforms.size(); ++i) {
int parent_id = skel_topology.GetParent(i);
if (parent_id >= 0) {
/* This is a non-root bone. Compute the transform of the joint
* relative to its parent. */
pxr::GfMatrix4d parent_relative_xf = bind_xforms[i] * bind_xforms[parent_id].GetInverse();
inv_bind_xforms[i] = parent_relative_xf.GetInverse();
} else {
inv_bind_xforms[i] = bind_xforms[i].GetInverse();
}
}
for (double frame : samples) {
pxr::VtMatrix4dArray joint_local_xforms;
if (!skel_query.ComputeJointLocalTransforms(&joint_local_xforms, frame)) {
std::cout << "WARNING: couldn't compute joint local transforms on frame " << frame << std::endl;
continue;
}
if (joint_local_xforms.size() != joint_order.size()) {
std::cout << "WARNING: number of joint local transform entries " << joint_local_xforms.size()
<< " doesn't match the number of joints " << joint_order.size() << std::endl;
continue;
}
for (int i = 0; i < joint_local_xforms.size(); ++i) {
pxr::GfMatrix4d bind_relative_xf = joint_local_xforms[i] * inv_bind_xforms[i];
pxr::GfVec3f t;
pxr::GfQuatf qrot;
pxr::GfVec3h s;
if (!pxr::UsdSkelDecomposeTransform(bind_relative_xf, &t, &qrot, &s)) {
std::cout << "WARNING: error decomposing matrix on frame " << frame << std::endl;
continue;
}
float re = qrot.GetReal();
pxr::GfVec3f im = qrot.GetImaginary();
for (int j = 0; j < 3; ++j) {
int k = 3 * i + j;
if (k >= loc_curves.size()) {
std::cout << "PROGRAMMER ERROR: out of bounds translation curve index." << std::endl;
break;
}
if (FCurve *fcu = loc_curves[k]) {
add_bezt(fcu, frame, t[j]);
}
}
for (int j = 0; j < 4; ++j) {
int k = 4 * i + j;
if (k >= rot_curves.size()) {
std::cout << "PROGRAMMER ERROR: out of bounds rotation curve index." << std::endl;
break;
}
if (FCurve *fcu = rot_curves[k]) {
if (j == 0) {
add_bezt(fcu, frame, re);
}
else {
add_bezt(fcu, frame, im[j - 1]);
}
}
}
for (int j = 0; j < 3; ++j) {
int k = 3 * i + j;
if (k >= scale_curves.size()) {
std::cout << "PROGRAMMER ERROR: out of bounds scale curve index." << std::endl;
break;
}
if (FCurve *fcu = scale_curves[k]) {
add_bezt(fcu, frame, s[j]);
}
}
}
}
}
void import_skel_bindings(Main *bmain, Object *mesh_obj, pxr::UsdPrim prim)
{
if (!(bmain && mesh_obj && prim)) {
return;
}
if (mesh_obj->type != OB_MESH) {
return;
}
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim);
if (!skel_api) {
return;
}
pxr::UsdSkelSkeleton skel = skel_api.GetInheritedSkeleton();
if (!skel) {
return;
}
pxr::VtArray<pxr::TfToken> joints;
if (skel_api.GetJointsAttr().HasAuthoredValue()) {
skel_api.GetJointsAttr().Get(&joints);
}
else if (skel.GetJointsAttr().HasAuthoredValue()) {
skel.GetJointsAttr().Get(&joints);
}
if (joints.empty()) {
return;
}
pxr::UsdGeomPrimvar joint_indices_primvar = skel_api.GetJointIndicesPrimvar();
if (!(joint_indices_primvar && joint_indices_primvar.HasAuthoredValue())) {
return;
}
pxr::UsdGeomPrimvar joint_weights_primvar = skel_api.GetJointWeightsPrimvar();
if (!(joint_weights_primvar && joint_weights_primvar.HasAuthoredValue())) {
return;
}
int joint_indices_elem_size = joint_indices_primvar.GetElementSize();
int joint_weights_elem_size = joint_weights_primvar.GetElementSize();
if (joint_indices_elem_size != joint_weights_elem_size) {
std::cout << "WARNING: joint weights and joint indices element size mismatch." << std::endl;
return;
}
/* The set of unique joint indices referenced in the joint indices
* attribute. */
pxr::VtIntArray joint_indices;
joint_indices_primvar.ComputeFlattened(&joint_indices);
pxr::VtFloatArray joint_weights;
joint_weights_primvar.ComputeFlattened(&joint_weights);
if (joint_indices.empty() || joint_weights.empty()) {
return;
}
if (joint_indices.size() != joint_weights.size()) {
std::cout << "WARNING: joint weights and joint indices size mismatch." << std::endl;
return;
}
Mesh *mesh = static_cast<Mesh *>(mesh_obj->data);
pxr::TfToken interp = joint_weights_primvar.GetInterpolation();
if (interp != pxr::UsdGeomTokens->vertex && interp != pxr::UsdGeomTokens->constant) {
std::cout << "WARNING: unexpected joint weights interpolation type " << interp
<< std::endl;
return;
}
if (interp == pxr::UsdGeomTokens->vertex && joint_weights.size() != mesh->totvert * joint_weights_elem_size) {
std::cout << "WARNING: joint weights of unexpected size for vertex interpolation." << std::endl;
return;
}
if (interp == pxr::UsdGeomTokens->constant && joint_weights.size() != joint_weights_elem_size) {
std::cout << "WARNING: joint weights of unexpected size for constant interpolation."
<< std::endl;
return;
}
std::vector<int> used_indices;
for (int index : joint_indices) {
if (std::find(used_indices.begin(), used_indices.end(), index) == used_indices.end()) {
/* We haven't accounted for this index yet. */
if (index < 0 || index >= joints.size()) {
std::cout << "Out of bound joint index " << index << std::endl;
continue;
}
used_indices.push_back(index);
}
}
if (used_indices.empty()) {
return;
}
if (BKE_object_defgroup_data_create(static_cast<ID *>(mesh_obj->data)) == NULL) {
return;
}
/* Add the armature modifier, if one doesn't exist. */
if (!BKE_modifiers_findby_type(mesh_obj, eModifierType_Armature)) {
ModifierData *md = BKE_modifier_new(eModifierType_Armature);
BLI_addtail(&mesh_obj->modifiers, md);
}
std::vector<bDeformGroup *> joint_def_grps(joints.size(), nullptr);
for (int idx : used_indices) {
std::string joint_name = pxr::SdfPath(joints[idx]).GetName();
if (!BKE_object_defgroup_find_name(mesh_obj, joint_name.c_str())) {
bDeformGroup *def_grp = BKE_object_defgroup_add_name(mesh_obj, joint_name.c_str());
joint_def_grps[idx] = def_grp;
}
}
for (int i = 0; i < mesh->totvert; ++i) {
/* Offset into the weights array, which is
/* always 0 for constant interpolation. */
int offset = 0;
if (interp == pxr::UsdGeomTokens->vertex) {
offset = i * joint_weights_elem_size;
}
for (int j = 0; j < joint_weights_elem_size; ++j) {
int k = offset + j;
float w = joint_weights[k];
if (w < .00001) {
/* No deform group if zero weight. */
continue;
}
int joint_idx = joint_indices[k];
bDeformGroup *def_grp = joint_def_grps[joint_idx];
if (def_grp) {
ED_vgroup_vert_add(mesh_obj, def_grp, i, w, WEIGHT_REPLACE);
}
}
}
}
bool compute_skel_space_bind_transforms(const pxr::UsdSkelSkeletonQuery &skel_query,
pxr::VtMatrix4dArray &out_xforms,
pxr::UsdTimeCode time)
{
if (!skel_query) {
return false;
}
pxr::GfMatrix4d skel_mat_inv = get_world_matrix(skel_query.GetSkeleton().GetPrim(), time).GetInverse();
if (!skel_query.GetJointWorldBindTransforms(&out_xforms)) {
std::cout << "WARNING: couldn't get local xform for prim "
<< skel_query.GetSkeleton().GetPrim().GetPath() << std::endl;
return false;
}
for (int i = 0; i < out_xforms.size(); ++i) {
out_xforms[i] = out_xforms[i] * skel_mat_inv;
}
return true;
}
} // namespace blender::io::usd

View File

@ -19,6 +19,8 @@
#pragma once
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usdSkel/skeletonQuery.h>
#include <map>
struct Main;
struct Object;
@ -34,4 +36,17 @@ void test_create_shapekeys(Main *bmain, Object *shape_obj);
void import_blendshapes(Main *bmain, Object *shape_obj, pxr::UsdPrim prim);
void create_skeleton_curves(Main *bmain,
Object *obj,
const pxr::UsdSkelSkeletonQuery &skel_query,
const std::map<pxr::TfToken, std::string> &joint_to_bone_map);
void import_skel_bindings(Main *bmain, Object *shape_obj, pxr::UsdPrim prim);
bool compute_skel_space_bind_transforms(const pxr::UsdSkelSkeletonQuery &skel_query,
pxr::VtMatrix4dArray &out_xforms,
pxr::UsdTimeCode time);
pxr::GfMatrix4d get_world_matrix(const pxr::UsdPrim &prim, pxr::UsdTimeCode time);
} // namespace blender::io::usd

View File

@ -18,6 +18,7 @@
*/
#include "usd_writer_armature.h"
#include "usd_hierarchy_iterator.h"
#include "usd_writer_transform.h"
#include "BKE_armature.h"
#include "DNA_armature_types.h"
@ -281,7 +282,14 @@ void USDArmatureWriter::do_write(HierarchyContext &context)
if (!this->frame_has_been_written_) {
pxr::GfMatrix4f world_mat(context.matrix_world);
BoneDataBuilder bone_data(world_mat);
/* The context world matrix does not include the unit
* conversion scaling or axis rotation that may be applied
* to root primitives on export, so we must include those,
* if necessary. */
float convert_mat[4][4];
get_export_conversion_matrix(usd_export_context_.export_params, convert_mat);
BoneDataBuilder bone_data(world_mat * pxr::GfMatrix4f(convert_mat));
visit_bones(context.object, &bone_data);

View File

@ -19,6 +19,7 @@
#include "usd_writer_skinned_mesh.h"
#include "usd_hierarchy_iterator.h"
#include "usd_writer_armature.h"
#include "usd_writer_transform.h"
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/matrix4f.h>
@ -144,7 +145,14 @@ void USDSkinnedMeshWriter::do_write(HierarchyContext &context)
if (pxr::UsdAttribute geom_bind_attr = usd_skel_api.CreateGeomBindTransformAttr()) {
pxr::GfMatrix4f mat_world(context.matrix_world);
geom_bind_attr.Set(pxr::GfMatrix4d(mat_world));
/* The context world matrix does not include the unit
* conversion scaling or axis rotation that may be applied
* to root primitives on export, so we must include those,
* if necessary. */
float convert_mat[4][4];
get_export_conversion_matrix(usd_export_context_.export_params, convert_mat);
geom_bind_attr.Set(pxr::GfMatrix4d(mat_world) * pxr::GfMatrix4d(convert_mat));
}
else {
printf("WARNING: couldn't create geom bind transform attribute for skinned mesh %s\n",

View File

@ -23,6 +23,27 @@ static const float UNIT_M4[4][4] = {
{0, 0, 0, 1},
};
/* Returns in r_mat tha unit scaling and axis rotation transforms
* applied to root prims on export. */
void get_export_conversion_matrix(const USDExportParams &params, float r_mat[4][4])
{
unit_m4(r_mat);
if (params.convert_orientation) {
float mrot[3][3];
mat3_from_axis_conversion(
USD_GLOBAL_FORWARD_Y, USD_GLOBAL_UP_Z, params.forward_axis, params.up_axis, mrot);
transpose_m3(mrot);
copy_m4_m3(r_mat, mrot);
}
if (params.convert_to_cm) {
float scale_mat[4][4];
scale_m4_fl(scale_mat, 100.0f);
mul_m4_m4m4(r_mat, scale_mat, r_mat);
}
}
USDTransformWriter::USDTransformWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
{
}
@ -101,7 +122,8 @@ void USDTransformWriter::do_write(HierarchyContext &context)
if (usd_export_context_.export_params.export_transforms) {
float parent_relative_matrix[4][4]; // The object matrix relative to the parent.
// TODO(bjs): This is inefficient checking for every transform. should be moved elsewhere
// TODO(makowalski): This is inefficient checking for every transform and should be moved elsewhere.
// TODO(makowalski): Use get_export_conversion_matrix() here, to avoid duplicating code.
if (should_apply_root_xform(context)) {
float matrix_world[4][4];
copy_m4_m4(matrix_world, context.matrix_world);

View File

@ -10,6 +10,8 @@
namespace blender::io::usd {
void get_export_conversion_matrix(const USDExportParams &params, float r_mat[4][4]);
class USDTransformWriter : public USDAbstractWriter {
private:
std::vector<pxr::UsdGeomXformOp> xformOps_;

View File

@ -138,6 +138,7 @@ struct USDImportParams {
bool import_meshes;
bool import_blendshapes;
bool import_volumes;
bool import_skeletons;
char *prim_path_mask;
bool import_subdiv;
bool import_instance_proxies;