USD export armatures with shapekeys WIP.

Extended USDSkinnedMeshWriter to support exporting
blendshapes.
This commit is contained in:
Michael Kowalski 2022-08-07 14:07:07 -04:00
parent 1770d462cc
commit b665ae266d
7 changed files with 151 additions and 88 deletions

View File

@ -136,7 +136,7 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const Hierarch
data_writer = new USDSkinnedMeshWriter(usd_export_context);
}
else if (usd_export_context.export_params.export_blendshapes &&
is_blendshape_mesh(context->object)) {
is_blendshape_mesh(context->object)) {
data_writer = new USDBlendShapeMeshWriter(usd_export_context);
}
else {

View File

@ -97,7 +97,6 @@ static void print_blendshape_info(Object *obj)
// BKE_keyblock_element_count_from_shape()
LISTBASE_FOREACH (KeyBlock *, kb, &key->block) {
printf("%s %f %f\n", kb->name, kb->curval, kb->pos);
}
printf("anim pointer %p\n", key->adt);
@ -110,31 +109,34 @@ bool is_blendshape_mesh(Object *obj)
return key && key->totkey > 0 && key->type == KEY_RELATIVE;
}
USDBlendShapeMeshWriter::USDBlendShapeMeshWriter(const USDExporterContext &ctx) : USDMeshWriter(ctx)
USDBlendShapeMeshWriter::USDBlendShapeMeshWriter(const USDExporterContext &ctx)
: USDMeshWriter(ctx)
{
}
void USDBlendShapeMeshWriter::do_write(HierarchyContext &context)
{
if (!this->frame_has_been_written_) {
/* Even though this writer is potentially animated
* because it may have animating blendshape weights,
* the mesh is never animated. Setting the is_animated_
* varialbe to false before writing the mesh prevents
* unwanted time samples for mesh properties from being
* written. Hopefully, there could be nicer way to do this. */
bool prev_is_animated = is_animated_;
is_animated_ = false;
try {
USDGenericMeshWriter::do_write(context);
is_animated_ = prev_is_animated;
}
catch (...) {
is_animated_ = prev_is_animated;
throw;
}
USDGenericMeshWriter::do_write(context);
}
write_blendshape(context);
}
bool USDBlendShapeMeshWriter::is_supported(const HierarchyContext *context) const
{
return is_blendshape_mesh(context->object) && USDGenericMeshWriter::is_supported(context);
}
bool USDBlendShapeMeshWriter::check_is_animated(const HierarchyContext &context) const
{
const Key *key = get_shape_key(context.object);
return key && key->totkey > 0 && key->adt != nullptr;
}
void USDBlendShapeMeshWriter::write_blendshape(HierarchyContext &context) const
{
/* A blendshape writer might be created even if
* there are no blendshapes, so check that blendshapes
* exist before continuting. */
@ -142,17 +144,6 @@ void USDBlendShapeMeshWriter::do_write(HierarchyContext &context)
return;
}
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
pxr::UsdTimeCode timecode = get_export_time_code();
pxr::UsdPrim mesh_prim = stage->GetPrimAtPath(usd_export_context_.usd_path);
if (!mesh_prim.IsValid()) {
printf("WARNING: couldn't get valid mesh prim for blendshape mesh %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
pxr::UsdSkelSkeleton skel = get_skeleton(context);
if (!skel) {
@ -163,7 +154,22 @@ void USDBlendShapeMeshWriter::do_write(HierarchyContext &context)
const Key *key = get_shape_key(context.object);
if (!key) {
printf("WARNING: couldn't get shape key for blendshape mesh prim %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
if (!this->frame_has_been_written_) {
pxr::UsdPrim mesh_prim = usd_export_context_.stage->GetPrimAtPath(
usd_export_context_.usd_path);
if (!mesh_prim.IsValid()) {
printf("WARNING: couldn't get valid mesh prim for blendshape mesh %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
create_blend_shapes(key, mesh_prim, skel);
}
@ -172,18 +178,6 @@ void USDBlendShapeMeshWriter::do_write(HierarchyContext &context)
}
}
bool USDBlendShapeMeshWriter::is_supported(const HierarchyContext *context) const
{
return is_blendshape_mesh(context->object) && USDGenericMeshWriter::is_supported(context);
}
bool USDBlendShapeMeshWriter::check_is_animated(const HierarchyContext & context) const
{
const Key *key = get_shape_key(context.object);
return key && key->totkey > 0 && key->adt != nullptr;
}
void USDBlendShapeMeshWriter::create_blend_shapes(const Key *key,
const pxr::UsdPrim &mesh_prim,
const pxr::UsdSkelSkeleton &skel) const
@ -230,7 +224,8 @@ void USDBlendShapeMeshWriter::create_blend_shapes(const Key *key,
pxr::SdfPath path = usd_export_context_.usd_path.AppendChild(name);
blendshape_paths.push_back(path);
pxr::UsdSkelBlendShape blendshape = usd_export_context_.usd_define_or_over<pxr::UsdSkelBlendShape>(path);
pxr::UsdSkelBlendShape blendshape =
usd_export_context_.usd_define_or_over<pxr::UsdSkelBlendShape>(path);
pxr::UsdAttribute offsets_attr = blendshape.CreateOffsetsAttr();
@ -262,23 +257,19 @@ void USDBlendShapeMeshWriter::create_blend_shapes(const Key *key,
blendshape_attr.Set(blendshape_names);
skel_api.CreateBlendShapeTargetsRel().SetTargets(blendshape_paths);
/* Some DCCs seem to require joint names, indices and weights to
* bind the skeleton. */
pxr::VtTokenArray joints({usdtokens::joint1});
pxr::VtArray<int> joint_indices(basis_totelem, 0);
pxr::VtArray<float> joint_weights(basis_totelem, 1.0f);
/* Some DCCs seem to require joint indices and weights to
* bind the skeleton for blendshapes, so we we create these
* primvars, if needed. */
skel_api.CreateJointsAttr().Set(joints);
skel_api.CreateJointIndicesPrimvar(false, 1).GetAttr().Set(joint_indices);
skel_api.CreateJointWeightsPrimvar(false, 1).GetAttr().Set(joint_weights);
if (!skel_api.GetJointIndicesAttr().HasAuthoredValue()) {
pxr::VtArray<int> joint_indices(basis_totelem, 0);
skel_api.CreateJointIndicesPrimvar(false, 1).GetAttr().Set(joint_indices);
}
/* Initialize the skeleton. */
pxr::VtMatrix4dArray bind_transforms(1, pxr::GfMatrix4d(1.0));
pxr::VtMatrix4dArray rest_transforms(1, pxr::GfMatrix4d(1.0));
skel.CreateBindTransformsAttr().Set(bind_transforms);
skel.GetRestTransformsAttr().Set(rest_transforms);
skel.CreateJointsAttr().Set(joints);
if (!skel_api.GetJointWeightsAttr().HasAuthoredValue()) {
pxr::VtArray<float> joint_weights(basis_totelem, 1.0f);
skel_api.CreateJointWeightsPrimvar(false, 1).GetAttr().Set(joint_weights);
}
/* Create the skeleton animation. */
pxr::SdfPath anim_path = skel.GetPath().AppendChild(usdtokens::Anim);
@ -286,12 +277,6 @@ void USDBlendShapeMeshWriter::create_blend_shapes(const Key *key,
anim_path);
if (anim) {
/* Specify the animation source on the skeleton. */
skel_api = pxr::UsdSkelBindingAPI::Apply(skel.GetPrim());
skel_api.CreateAnimationSourceRel().AddTarget(pxr::SdfPath(usdtokens::Anim));
anim.CreateJointsAttr().Set(joints);
/* Set the blendshape names on the animation. */
pxr::UsdAttribute blendshape_attr = anim.CreateBlendShapesAttr();
blendshape_attr.Set(blendshape_names);
@ -300,7 +285,6 @@ void USDBlendShapeMeshWriter::create_blend_shapes(const Key *key,
pxr::UsdAttribute weights_attr = anim.CreateBlendShapeWeightsAttr();
weights_attr.Set(weights);
}
}
void USDBlendShapeMeshWriter::add_weights_sample(const Key *key,
@ -324,7 +308,25 @@ pxr::UsdSkelSkeleton USDBlendShapeMeshWriter::get_skeleton(const HierarchyContex
pxr::SdfPath skel_path = usd_export_context_.usd_path.GetParentPath().AppendChild(
usdtokens::Skel);
return usd_export_context_.usd_define_or_over<pxr::UsdSkelSkeleton>(skel_path);
pxr::UsdSkelSkeleton skel = usd_export_context_.usd_define_or_over<pxr::UsdSkelSkeleton>(
skel_path);
/* Initialize the skeleton. */
pxr::VtMatrix4dArray bind_transforms(1, pxr::GfMatrix4d(1.0));
pxr::VtMatrix4dArray rest_transforms(1, pxr::GfMatrix4d(1.0));
skel.CreateBindTransformsAttr().Set(bind_transforms);
skel.GetRestTransformsAttr().Set(rest_transforms);
/* Some DCCs seem to require joint names to bind the
* skeleton to blendshapes. */
pxr::VtTokenArray joints({usdtokens::joint1});
skel.CreateJointsAttr().Set(joints);
/* Specify the animation source on the skeleton. */
pxr::UsdSkelBindingAPI skel_api(skel.GetPrim());
skel_api.CreateAnimationSourceRel().AddTarget(pxr::SdfPath(usdtokens::Anim));
return skel;
}
Mesh *USDBlendShapeMeshWriter::get_export_mesh(Object *object_eval, bool &r_needsfree)
@ -352,8 +354,8 @@ Mesh *USDBlendShapeMeshWriter::get_export_mesh(Object *object_eval, bool &r_need
return nullptr;
}
Mesh *temp_mesh = reinterpret_cast<Mesh *>(BKE_id_copy_ex(
nullptr, &src_mesh->id, nullptr, LIB_ID_COPY_LOCALIZE));
Mesh *temp_mesh = reinterpret_cast<Mesh *>(
BKE_id_copy_ex(nullptr, &src_mesh->id, nullptr, LIB_ID_COPY_LOCALIZE));
BKE_keyblock_convert_to_mesh(basis, temp_mesh->mvert, temp_mesh->totvert);
@ -362,6 +364,19 @@ Mesh *USDBlendShapeMeshWriter::get_export_mesh(Object *object_eval, bool &r_need
return temp_mesh;
}
/* Blend shape meshes are never animated, but the blendshape writer itself
* might be animating as it must add time samples to skeletal animations.
* This function ensures that the mesh data is written as non-timesampled.
* This is currently required to work around a bug in Create which causes
* a crash if the blendhshape mesh is timesampled. */
pxr::UsdTimeCode USDBlendShapeMeshWriter::get_mesh_export_time_code() const
{
/* By using the default timecode USD won't even write a single `timeSample` for non-animated
* data. Instead, it writes it as non-timesampled. */
static pxr::UsdTimeCode default_timecode = pxr::UsdTimeCode::Default();
return default_timecode;
}
bool USDBlendShapeMeshWriter::exporting_anim(const Key *shape_key) const
{
return usd_export_context_.export_params.export_animation && shape_key && shape_key->adt;

View File

@ -41,14 +41,17 @@ class USDBlendShapeMeshWriter : public USDMeshWriter {
virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
virtual pxr::UsdTimeCode get_mesh_export_time_code() const override;
virtual pxr::UsdSkelSkeleton get_skeleton(const HierarchyContext &context) const;
void write_blendshape(HierarchyContext &context) const;
void create_blend_shapes(const Key *shape_key,
const pxr::UsdPrim &mesh_prim,
const pxr::UsdSkelSkeleton &skel) const;
void add_weights_sample(const Key *shape_key,
const pxr::UsdSkelSkeleton &skel) const;
void add_weights_sample(const Key *shape_key, const pxr::UsdSkelSkeleton &skel) const;
bool exporting_anim(const Key *shape_key) const;
};

View File

@ -161,6 +161,11 @@ void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
BKE_id_free(nullptr, mesh);
}
pxr::UsdTimeCode USDGenericMeshWriter::get_mesh_export_time_code() const
{
return get_export_time_code();
}
struct USDMeshData {
pxr::VtArray<pxr::GfVec3f> points;
pxr::VtIntArray face_vertex_counts;
@ -461,7 +466,7 @@ void USDGenericMeshWriter::write_face_maps(const Object *ob,
void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
{
pxr::UsdTimeCode timecode = get_export_time_code();
pxr::UsdTimeCode timecode = get_mesh_export_time_code();
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
pxr::UsdGeomMesh usd_mesh =

View File

@ -24,6 +24,11 @@ class USDGenericMeshWriter : public USDAbstractWriter {
virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) = 0;
virtual void free_export_mesh(Mesh *mesh);
/* Get time code for writing mesh properties.
* The default implementation is equivalent to
* calling USDAbstractWriter::get_export_time_code(). */
virtual pxr::UsdTimeCode get_mesh_export_time_code() const;
private:
/* Mapping from material slot number to array of face indices with that material. */
typedef std::map<short, pxr::VtIntArray> MaterialFaceGroups;

View File

@ -70,12 +70,19 @@ static Object *get_armature_obj(Object *obj)
return mod ? mod->object : nullptr;
}
USDSkinnedMeshWriter::USDSkinnedMeshWriter(const USDExporterContext &ctx) : USDMeshWriter(ctx)
USDSkinnedMeshWriter::USDSkinnedMeshWriter(const USDExporterContext &ctx)
: USDBlendShapeMeshWriter(ctx)
{
}
void USDSkinnedMeshWriter::do_write(HierarchyContext &context)
{
if (this->frame_has_been_written_ && usd_export_context_.export_params.export_blendshapes) {
/* Only blendshapes may be animated on skinned meshes. */
write_blendshape(context);
return;
}
Object *arm_obj = get_armature_obj(context.object);
if (!arm_obj) {
@ -126,21 +133,14 @@ void USDSkinnedMeshWriter::do_write(HierarchyContext &context)
return;
}
ID *arm_id = reinterpret_cast<ID *>(arm_obj->data);
std::string skel_path = usd_export_context_.hierarchy_iterator->get_object_export_path(arm_id);
if (skel_path.empty()) {
pxr::SdfPath skel_path = get_skel_path(arm_obj);
if (skel_path.IsEmpty()) {
printf("WARNING: couldn't get USD skeleton path for skinned mesh %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
if (strlen(usd_export_context_.export_params.root_prim_path) != 0) {
skel_path = std::string(usd_export_context_.export_params.root_prim_path) + skel_path;
}
usd_skel_api.CreateSkeletonRel().SetTargets(pxr::SdfPathVector({pxr::SdfPath(skel_path)}));
usd_skel_api.CreateSkeletonRel().SetTargets(pxr::SdfPathVector({skel_path}));
if (pxr::UsdAttribute geom_bind_attr = usd_skel_api.CreateGeomBindTransformAttr()) {
pxr::GfMatrix4f mat_world(context.matrix_world);
@ -173,6 +173,10 @@ void USDSkinnedMeshWriter::do_write(HierarchyContext &context)
if (needs_free) {
free_export_mesh(mesh);
}
if (usd_export_context_.export_params.export_blendshapes) {
write_blendshape(context);
}
}
void USDSkinnedMeshWriter::write_weights(const Object *ob,
@ -303,11 +307,38 @@ bool USDSkinnedMeshWriter::is_supported(const HierarchyContext *context) const
return is_skinned_mesh(context->object) && USDGenericMeshWriter::is_supported(context);
}
bool USDSkinnedMeshWriter::check_is_animated(const HierarchyContext & /*context*/) const
bool USDSkinnedMeshWriter::check_is_animated(const HierarchyContext &context) const
{
/* We assume that skinned meshes are never animated, as the source of
* any animation is the mesh's bound skeleton. */
return false;
return usd_export_context_.export_params.export_blendshapes &&
USDBlendShapeMeshWriter::check_is_animated(context);
}
pxr::SdfPath USDSkinnedMeshWriter::get_skel_path(Object *arm_obj) const
{
ID *arm_id = reinterpret_cast<ID *>(arm_obj->data);
std::string skel_path = usd_export_context_.hierarchy_iterator->get_object_export_path(arm_id);
if (skel_path.empty()) {
return pxr::SdfPath();
}
if (strlen(usd_export_context_.export_params.root_prim_path) != 0) {
skel_path = std::string(usd_export_context_.export_params.root_prim_path) + skel_path;
}
return pxr::SdfPath(skel_path);
}
pxr::UsdSkelSkeleton USDSkinnedMeshWriter::get_skeleton(const HierarchyContext &context) const
{
Object *arm_obj = get_armature_obj(context.object);
if (!arm_obj) {
return pxr::UsdSkelSkeleton();
}
pxr::SdfPath skel_path = get_skel_path(arm_obj);
return usd_export_context_.usd_define_or_over<pxr::UsdSkelSkeleton>(skel_path);
}
} // namespace blender::io::usd

View File

@ -18,7 +18,7 @@
*/
#pragma once
#include "usd_writer_mesh.h"
#include "usd_writer_blendshape_mesh.h"
#include <pxr/usd/usdSkel/bindingAPI.h>
@ -29,7 +29,7 @@ namespace blender::io::usd {
bool is_skinned_mesh(Object *obj);
class USDSkinnedMeshWriter : public USDMeshWriter {
class USDSkinnedMeshWriter : public USDBlendShapeMeshWriter {
public:
USDSkinnedMeshWriter(const USDExporterContext &ctx);
@ -43,6 +43,10 @@ class USDSkinnedMeshWriter : public USDMeshWriter {
const Mesh *mesh,
const pxr::UsdSkelBindingAPI &skel_api,
const std::vector<std::string> &bone_names) const;
pxr::SdfPath get_skel_path(Object *arm_obj) const;
virtual pxr::UsdSkelSkeleton get_skeleton(const HierarchyContext &context) const override;
};
} // namespace blender::io::usd