USD export armatures with shapekeys WIP.
Extended USDSkinnedMeshWriter to support exporting blendshapes.
This commit is contained in:
parent
1770d462cc
commit
b665ae266d
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue