USD import blendshapes.

Importing USD blendshapes as shapekeys and creating
animation curves for animated blendshape weights.
This commit is contained in:
Michael Kowalski 2022-08-10 23:19:57 -04:00
parent b665ae266d
commit 833df7ebc1
6 changed files with 402 additions and 0 deletions

View File

@ -963,6 +963,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
const bool import_lights = RNA_boolean_get(op->ptr, "import_lights");
const bool import_materials = RNA_boolean_get(op->ptr, "import_materials");
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_subdiv = RNA_boolean_get(op->ptr, "import_subdiv");
@ -1028,6 +1029,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
.import_lights = import_lights,
.import_materials = import_materials,
.import_meshes = import_meshes,
.import_blendshapes = import_blendshapes,
.import_volumes = import_volumes,
.prim_path_mask = prim_path_mask,
.import_subdiv = import_subdiv,
@ -1068,6 +1070,7 @@ static void wm_usd_import_draw(bContext *UNUSED(C), wmOperator *op)
uiItemR(col, ptr, "import_lights", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_materials", 0, NULL, ICON_NONE);
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(box, ptr, "prim_path_mask", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "scale", 0, NULL, ICON_NONE);
@ -1163,6 +1166,7 @@ void WM_OT_usd_import(struct wmOperatorType *ot)
RNA_def_boolean(ot->srna, "import_lights", true, "Lights", "");
RNA_def_boolean(ot->srna, "import_materials", true, "Materials", "");
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,

View File

@ -69,6 +69,7 @@ set(SRC
intern/usd_common.cc
intern/usd_hierarchy_iterator.cc
intern/usd_light_convert.cc
intern/usd_skel_convert.cc
intern/usd_reader_camera.cc
intern/usd_reader_curve.cc
@ -107,6 +108,7 @@ set(SRC
intern/usd_exporter_context.h
intern/usd_hierarchy_iterator.h
intern/usd_light_convert.h
intern/usd_skel_convert.h
intern/usd_reader_camera.h
intern/usd_reader_curve.h

View File

@ -5,6 +5,7 @@
#include "usd_reader_mesh.h"
#include "usd_reader_material.h"
#include "usd_skel_convert.h"
#include "BKE_customdata.h"
#include "BKE_main.h"
@ -272,6 +273,10 @@ void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime)
}
}
if (import_params_.import_blendshapes) {
import_blendshapes(bmain, object_, prim_);
}
USDXformReader::read_object_data(bmain, motionSampleTime);
}

View File

@ -0,0 +1,353 @@
/*
* 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) 2022 NVIDIA Corporation.
* All rights reserved.
*/
#include "usd_skel_convert.h"
#include "usd.h"
#include <pxr/usd/usdSkel/animation.h>
#include <pxr/usd/usdSkel/blendShape.h>
#include <pxr/usd/usdSkel/bindingAPI.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 "BLI_math_vector.h"
#include "DNA_key_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_meta_types.h"
#include "BKE_main.h"
#include "BKE_scene.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "DNA_scene_types.h"
#include "BKE_fcurve.h"
#include "ED_keyframing.h"
#include <string>
#include <vector>
namespace usdtokens {
// Attribute names.
//static const pxr::TfToken color("color", pxr::TfToken::Immortal);
} // namespace usdtokens
namespace {
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;
}
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);
}
} // End anonymous namespace.
namespace blender::io::usd {
void test_create_shapekeys(Main *bmain, Object *obj)
{
if (!(obj && obj->data && obj->type == OB_MESH)) {
return;
}
Mesh *mesh = static_cast<Mesh *>(obj->data);
/* insert key to source mesh */
Key *key = BKE_key_add(bmain, (ID *)mesh);
key->type = KEY_RELATIVE;
mesh->key = key;
/* insert basis key */
KeyBlock *kb = BKE_keyblock_add(key, "Basis");
BKE_keyblock_convert_from_mesh(mesh, key, kb);
kb = BKE_keyblock_add(key, "Key1");
BKE_keyblock_convert_from_mesh(mesh, key, kb);
float offsets[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
BKE_keyblock_update_from_offset(obj, kb, (float(*)[3])&offsets);
bAction *act = ED_id_action_ensure(bmain, (ID *)&key->id);
FCurve *fcu = create_fcurve(0, "key_blocks[\"Key1\"].value");
fcu->totvert = 3;
add_bezt(fcu, 0.f, 0.f);
add_bezt(fcu, 30.f, 1.f);
add_bezt(fcu, 60.f, 0.3f);
BLI_addtail(&act->curves, fcu);
}
void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim)
{
if (!(obj && obj->data && obj->type == OB_MESH && prim)) {
return;
}
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim);
if (!skel_api) {
return;
}
if (!skel_api.GetBlendShapeTargetsRel().HasAuthoredTargets()) {
return;
}
pxr::SdfPathVector targets;
if (!skel_api.GetBlendShapeTargetsRel().GetTargets(&targets)) {
std::cout << "Couldn't get blendshape targets for prim " << prim.GetPath() << std::endl;
return;
}
if (targets.empty()) {
return;
}
if (!skel_api.GetBlendShapesAttr().HasAuthoredValue()) {
return;
}
pxr::VtTokenArray blendshapes;
if (!skel_api.GetBlendShapesAttr().Get(&blendshapes)) {
return;
}
if (blendshapes.empty()) {
return;
}
if (targets.size() != blendshapes.size()) {
std::cout << "Number of blendshapes doesn't match number of blendshape targets for prim " << prim.GetPath() << std::endl;
return;
}
Mesh *mesh = static_cast<Mesh *>(obj->data);
/* insert key to source mesh */
Key *key = BKE_key_add(bmain, (ID *)mesh);
key->type = KEY_RELATIVE;
mesh->key = key;
/* insert basis key */
KeyBlock *kb = BKE_keyblock_add(key, "Basis");
BKE_keyblock_convert_from_mesh(mesh, key, kb);
pxr::UsdStageRefPtr stage = prim.GetStage();
if (!stage) {
return;
}
/* Keep track of the shapkeys we're adding, for
* validation when creating curves later. */
std::set<pxr::TfToken> shapekey_names;
for (int i = 0; i < targets.size(); ++i) {
const pxr::SdfPath &path = targets[i];
pxr::UsdSkelBlendShape blendshape(stage->GetPrimAtPath(path));
if (!blendshape) {
continue;
}
if (!blendshape.GetOffsetsAttr().HasAuthoredValue()) {
continue;
}
pxr::VtVec3fArray offsets;
if (!blendshape.GetOffsetsAttr().Get(&offsets)) {
std::cout << "Couldn't get offsets for blendshape " << path << std::endl;
continue;
}
shapekey_names.insert(blendshapes[i]);
kb = BKE_keyblock_add(key, blendshapes[i].GetString().c_str());
BKE_keyblock_convert_from_mesh(mesh, key, kb);
pxr::VtArray<int> point_indices;
if (blendshape.GetPointIndicesAttr().HasAuthoredValue()) {
blendshape.GetPointIndicesAttr().Get(&point_indices);
}
float *fp = static_cast<float *>(kb->data);
if (point_indices.empty()) {
for (int a = 0; a < kb->totelem; ++a, fp += 3) {
add_v3_v3(fp, offsets[a].data());
}
}
else {
int a = 0;
for (int i : point_indices) {
if (i < 0 || i > kb->totelem) {
std::cout << "out of bounds point index " << i << std::endl;
++a;
continue;
}
add_v3_v3(&fp[3 * i], offsets[a].data());
++a;
}
}
}
if (!skel_api.GetSkeletonRel().HasAuthoredTargets()) {
return;
}
pxr::UsdRelationship skel_rel = skel_api.GetSkeletonRel();
if (!skel_api.GetSkeletonRel().GetTargets(&targets)) {
std::cout << "Couldn't get skeleton targets for prim " << prim.GetPath() << std::endl;
return;
}
if (targets.empty()) {
return;
}
/* TODO(makowalski): do we need to account for multiple skeleton targets? */
pxr::SdfPath skel_path = targets.front();
pxr::UsdSkelSkeleton skel_prim(stage->GetPrimAtPath(skel_path));
if (!skel_prim) {
return;
}
skel_api = pxr::UsdSkelBindingAPI::Apply(skel_prim.GetPrim());
if (!skel_api) {
return;
}
pxr::UsdPrim anim_prim;
if (!skel_api.GetAnimationSource(&anim_prim)) {
return;
}
pxr::UsdSkelAnimation skel_anim(anim_prim);
if (!skel_anim) {
return;
}
if (!skel_anim.GetBlendShapesAttr().HasAuthoredValue()) {
return;
}
pxr::UsdAttribute weights_attr = skel_anim.GetBlendShapeWeightsAttr();
if (!(weights_attr && weights_attr.HasAuthoredValue())) {
return;
}
std::vector<double> times;
if (!weights_attr.GetTimeSamples(&times)) {
return;
}
if (times.empty()) {
return;
}
blendshapes;
if (!skel_anim.GetBlendShapesAttr().Get(&blendshapes)) {
return;
}
if (blendshapes.empty()) {
return;
}
size_t num_samples = times.size();
/* Create the animation and curves. */
bAction *act = ED_id_action_ensure(bmain, (ID *)&key->id);
std::vector<FCurve *> curves;
for (auto blendshape_name : blendshapes) {
if (shapekey_names.find(blendshape_name) == shapekey_names.end()) {
printf("Warning: blendshape %s doesn't match any shapekey name\n",
blendshape_name.GetString().c_str());
}
std::string rna_path = "key_blocks[\"" + blendshape_name.GetString() + "\"].value";
FCurve *fcu = create_fcurve(0, rna_path.c_str());
fcu->totvert = num_samples;
curves.push_back(fcu);
BLI_addtail(&act->curves, fcu);
}
for (double frame : times) {
pxr::VtFloatArray weights;
if (!weights_attr.Get(&weights, frame)) {
std::cout << "Couldn't get blendshape weights for time " << frame << std::endl;
continue;
}
if (weights.size() != curves.size()) {
std::cout << "Programmer error: number of weight samples doesn't match number of shapekey curves for time " << time << std::endl;
continue;
}
for (int wi = 0; wi < weights.size(); ++wi) {
add_bezt(curves[wi], frame, weights[wi]);
}
}
}
} // namespace blender::io::usd

View File

@ -0,0 +1,37 @@
/*
* 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) 2022 NVIDIA Corporation.
* All rights reserved.
*/
#pragma once
#include <pxr/usd/usd/prim.h>
struct Main;
struct Object;
struct Scene;
struct USDExportParams;
struct USDImportParams;
namespace blender::io::usd {
struct ImportSettings;
void test_create_shapekeys(Main *bmain, Object *shape_obj);
void import_blendshapes(Main *bmain, Object *shape_obj, pxr::UsdPrim prim);
} // namespace blender::io::usd

View File

@ -136,6 +136,7 @@ struct USDImportParams {
bool import_lights;
bool import_materials;
bool import_meshes;
bool import_blendshapes;
bool import_volumes;
char *prim_path_mask;
bool import_subdiv;