Blender V4.3
usd_blend_shape_utils.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 NVIDIA Corporation. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6#include "usd_utils.hh"
7
8#include <pxr/usd/usdGeom/primvarsAPI.h>
9#include <pxr/usd/usdSkel/animMapper.h>
10#include <pxr/usd/usdSkel/animation.h>
11#include <pxr/usd/usdSkel/bindingAPI.h>
12#include <pxr/usd/usdSkel/blendShape.h>
13
14#include "DNA_key_types.h"
15#include "DNA_mesh_types.h"
16
17#include "BKE_key.hh"
18#include "BKE_mesh.hh"
19#include "BKE_object.hh"
20#include "DNA_object_types.h"
21
22#include "BLI_assert.h"
23#include "BLI_listbase.h"
24#include "BLI_math_vector.h"
25#include "BLI_set.hh"
26#include "BLI_vector.hh"
27
28#include <string>
29#include <vector>
30
31#include "CLG_log.h"
32static CLG_LogRef LOG = {"io.usd"};
33
34namespace usdtokens {
35static const pxr::TfToken Anim("Anim", pxr::TfToken::Immortal);
36static const pxr::TfToken joint1("joint1", pxr::TfToken::Immortal);
37static const pxr::TfToken Skel("Skel", pxr::TfToken::Immortal);
38} // namespace usdtokens
39
40namespace {
41
42/* Helper struct to facilitate merging blend shape weights time
43 * samples from multiple meshes to a single skeleton animation. */
44struct BlendShapeMergeInfo {
45 pxr::VtTokenArray src_blend_shapes;
46 pxr::UsdAttribute src_weights_attr;
47 /* Remap blend shape weight array from the
48 * source order to the destination order. */
49 pxr::UsdSkelAnimMapper anim_map;
50
51 void init_anim_map(const pxr::VtTokenArray &dst_blend_shapes)
52 {
53 anim_map = pxr::UsdSkelAnimMapper(src_blend_shapes, dst_blend_shapes);
54 }
55};
56
57/* Helper function to avoid name collisions when merging blend shape names from
58 * multiple meshes to a single skeleton.
59 *
60 * Attempt to add the given name to the 'names' set as a unique entry, modifying
61 * the name with a numerical suffix if necessary, and return the unique name that
62 * was added to the set. */
63std::string add_unique_name(blender::Set<std::string> &names, const std::string &name)
64{
65 std::string unique_name = name;
66 int suffix = 2;
67 while (names.contains(unique_name)) {
68 unique_name = name + std::to_string(suffix++);
69 }
70 names.add(unique_name);
71 return unique_name;
72}
73
74} // namespace
75
76namespace blender::io::usd {
77
78pxr::TfToken TempBlendShapeWeightsPrimvarName("temp:weights", pxr::TfToken::Immortal);
79
80void ensure_blend_shape_skeleton(pxr::UsdStageRefPtr stage, pxr::UsdPrim &mesh_prim)
81{
82 if (!stage || !mesh_prim) {
83 return;
84 }
85
86 pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(mesh_prim);
87
88 if (!skel_api) {
90 "Couldn't apply UsdSkelBindingAPI to mesh prim %s",
91 mesh_prim.GetPath().GetAsString().c_str());
92 return;
93 }
94
95 pxr::UsdSkelSkeleton skel;
96 if (!skel_api.GetSkeleton(&skel)) {
97 pxr::SdfPath skel_path = mesh_prim.GetParent().GetPath().AppendChild(usdtokens::Skel);
98 skel = pxr::UsdSkelSkeleton::Define(stage, skel_path);
99
100 if (!skel) {
101 CLOG_WARN(&LOG,
102 "Couldn't find or create skeleton bound to mesh prim %s",
103 mesh_prim.GetPath().GetAsString().c_str());
104 return;
105 }
106
107 skel_api.CreateSkeletonRel().AddTarget(skel.GetPath());
108
109 /* Initialize the skeleton. */
110 pxr::VtMatrix4dArray bind_transforms(1, pxr::GfMatrix4d(1.0));
111 pxr::VtMatrix4dArray rest_transforms(1, pxr::GfMatrix4d(1.0));
112 skel.CreateBindTransformsAttr().Set(bind_transforms);
113 skel.GetRestTransformsAttr().Set(rest_transforms);
114
115 /* Some DCCs seem to require joint names to bind the
116 * skeleton to blend-shapes. */
117 pxr::VtTokenArray joints({usdtokens::joint1});
118 skel.CreateJointsAttr().Set(joints);
119 }
120
121 pxr::UsdAttribute temp_weights_attr = pxr::UsdGeomPrimvarsAPI(mesh_prim).GetPrimvar(
123
124 if (!temp_weights_attr) {
125 /* No need to create the animation. */
126 return;
127 }
128
129 pxr::SdfPath anim_path = skel.GetPath().AppendChild(usdtokens::Anim);
130 pxr::UsdSkelAnimation anim = pxr::UsdSkelAnimation::Define(stage, anim_path);
131
132 if (!anim) {
133 CLOG_WARN(&LOG, "Couldn't define animation at path %s", anim_path.GetAsString().c_str());
134 return;
135 }
136
137 pxr::VtTokenArray blendshape_names;
138 skel_api.GetBlendShapesAttr().Get(&blendshape_names);
139 anim.CreateBlendShapesAttr().Set(blendshape_names);
140
141 std::vector<double> times;
142 temp_weights_attr.GetTimeSamples(&times);
143
144 pxr::UsdAttribute anim_weights_attr = anim.CreateBlendShapeWeightsAttr();
145
146 pxr::VtFloatArray weights;
147 for (const double time : times) {
148 if (temp_weights_attr.Get(&weights, time)) {
149 anim_weights_attr.Set(weights, time);
150 }
151 }
152
153 /* Next, set the animation source on the skeleton. */
154
155 skel_api = pxr::UsdSkelBindingAPI::Apply(skel.GetPrim());
156
157 if (!skel_api) {
158 CLOG_WARN(&LOG,
159 "Couldn't apply UsdSkelBindingAPI to skeleton prim %s",
160 skel.GetPath().GetAsString().c_str());
161 return;
162 }
163
164 if (!skel_api.CreateAnimationSourceRel().AddTarget(pxr::SdfPath(usdtokens::Anim))) {
165 CLOG_WARN(&LOG,
166 "Couldn't set animation source on skeleton %s",
167 skel.GetPath().GetAsString().c_str());
168 }
169
170 pxr::UsdGeomPrimvarsAPI(mesh_prim).RemovePrimvar(TempBlendShapeWeightsPrimvarName);
171}
172
173const Key *get_mesh_shape_key(const Object *obj)
174{
175 BLI_assert(obj);
176
177 if (!obj->data || obj->type != OB_MESH) {
178 return nullptr;
179 }
180
181 const Mesh *mesh = static_cast<const Mesh *>(obj->data);
182
183 return mesh->key;
184}
185
187{
188 const Key *key = get_mesh_shape_key(obj);
189 return key && key->totkey > 0 && key->type == KEY_RELATIVE;
190}
191
192void create_blend_shapes(pxr::UsdStageRefPtr stage,
193 const Object *obj,
194 const pxr::UsdPrim &mesh_prim,
195 bool allow_unicode)
196{
197 const Key *key = get_mesh_shape_key(obj);
198
199 if (!(key && mesh_prim)) {
200 return;
201 }
202
203 pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(mesh_prim);
204
205 if (!skel_api) {
206 CLOG_WARN(&LOG,
207 "Couldn't apply UsdSkelBindingAPI to mesh prim %s",
208 mesh_prim.GetPath().GetAsString().c_str());
209 return;
210 }
211
212 pxr::VtTokenArray blendshape_names;
213 std::vector<pxr::SdfPath> blendshape_paths;
214
215 /* Get the basis, which we'll use to calculate offsets. */
216 KeyBlock *basis_key = static_cast<KeyBlock *>(key->block.first);
217
218 if (!basis_key) {
219 return;
220 }
221
222 int basis_totelem = basis_key->totelem;
223
224 LISTBASE_FOREACH (KeyBlock *, kb, &key->block) {
225 if (!kb) {
226 continue;
227 }
228
229 if (kb == basis_key) {
230 /* Skip the basis. */
231 continue;
232 }
233
234 pxr::TfToken name(make_safe_name(kb->name, allow_unicode));
235 blendshape_names.push_back(name);
236
237 pxr::SdfPath path = mesh_prim.GetPath().AppendChild(name);
238 blendshape_paths.push_back(path);
239
240 pxr::UsdSkelBlendShape blendshape = pxr::UsdSkelBlendShape::Define(stage, path);
241
242 pxr::UsdAttribute offsets_attr = blendshape.CreateOffsetsAttr();
243
244 /* Some applications, like Houdini, don't render blend shapes unless the point
245 * indices are set, so we always create this attribute, even when every index
246 * is included. */
247 pxr::UsdAttribute point_indices_attr = blendshape.CreatePointIndicesAttr();
248
249 pxr::VtVec3fArray offsets(kb->totelem);
250 pxr::VtIntArray indices(kb->totelem);
251 std::iota(indices.begin(), indices.end(), 0);
252
253 const float(*fp)[3] = static_cast<float(*)[3]>(kb->data);
254
255 const float(*basis_fp)[3] = static_cast<float(*)[3]>(basis_key->data);
256
257 for (int i = 0; i < kb->totelem; ++i) {
258 /* Subtract the key positions from the
259 * basis positions to get the offsets. */
260 sub_v3_v3v3(offsets[i].data(), fp[i], basis_fp[i]);
261 }
262
263 offsets_attr.Set(offsets);
264 point_indices_attr.Set(indices);
265 }
266
267 /* Set the blend-shape names and targets on the shape. */
268 pxr::UsdAttribute blendshape_attr = skel_api.CreateBlendShapesAttr();
269 blendshape_attr.Set(blendshape_names);
270 skel_api.CreateBlendShapeTargetsRel().SetTargets(blendshape_paths);
271
272 /* Some DCCs seem to require joint indices and weights to
273 * bind the skeleton for blend-shapes, so we create these primvars, if needed. */
274
275 if (!skel_api.GetJointIndicesAttr().HasAuthoredValue()) {
276 pxr::VtArray<int> joint_indices(basis_totelem, 0);
277 skel_api.CreateJointIndicesPrimvar(false, 1).GetAttr().Set(joint_indices);
278 }
279
280 if (!skel_api.GetJointWeightsAttr().HasAuthoredValue()) {
281 pxr::VtArray<float> joint_weights(basis_totelem, 1.0f);
282 skel_api.CreateJointWeightsPrimvar(false, 1).GetAttr().Set(joint_weights);
283 }
284}
285
286pxr::VtFloatArray get_blendshape_weights(const Key *key)
287{
288 BLI_assert(key);
289
290 pxr::VtFloatArray weights;
291
292 LISTBASE_FOREACH (KeyBlock *, kb, &key->block) {
293 if (kb == key->block.first) {
294 /* Skip the first key, which is the basis. */
295 continue;
296 }
297 weights.push_back(kb->curval);
298 }
299
300 return weights;
301}
302
303void remap_blend_shape_anim(pxr::UsdStageRefPtr stage,
304 const pxr::SdfPath &skel_path,
305 const pxr::SdfPathSet &mesh_paths)
306{
307 pxr::UsdSkelSkeleton skel = pxr::UsdSkelSkeleton::Get(stage, skel_path);
308
309 if (!skel) {
310 CLOG_WARN(&LOG, "Couldn't get skeleton from path %s", skel_path.GetAsString().c_str());
311 return;
312 }
313
314 /* Create the animation. */
315 pxr::SdfPath anim_path = skel_path.AppendChild(usdtokens::Anim);
316 const pxr::UsdSkelAnimation anim = pxr::UsdSkelAnimation::Define(stage, anim_path);
317
318 if (!anim) {
319 CLOG_WARN(&LOG, "Couldn't define animation at path %s", anim_path.GetAsString().c_str());
320 return;
321 }
322
324
325 /* We are merging blend shape names and weights from multiple
326 * meshes to a single animation. In case of name collisions,
327 * we must generate unique blend shape names for the merged
328 * result. This set keeps track of the unique names that will
329 * be combined on the animation. */
330 Set<std::string> merged_names;
331
332 /* Iterate over all the meshes, generate unique blend shape names in case of name
333 * collisions and set up the information we will need to merge the results. */
334 for (const pxr::SdfPath &mesh_path : mesh_paths) {
335
336 pxr::UsdPrim mesh_prim = stage->GetPrimAtPath(mesh_path);
337 pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(mesh_prim);
338 if (!skel_api) {
339 CLOG_WARN(&LOG,
340 "Couldn't apply UsdSkelBindingAPI to mesh prim %s",
341 mesh_path.GetAsString().c_str());
342 continue;
343 }
344
345 /* Get the blend shape names for this mesh. */
346 pxr::UsdAttribute blend_shapes_attr = skel_api.GetBlendShapesAttr();
347
348 if (!blend_shapes_attr) {
349 continue;
350 }
351
352 pxr::VtTokenArray names;
353 if (!skel_api.GetBlendShapesAttr().Get(&names)) {
354 continue;
355 }
356
357 /* Ensure the names are unique. */
358 pxr::VtTokenArray unique_names;
359
360 for (pxr::TfToken &name : names) {
361 std::string unique = add_unique_name(merged_names, name.GetString());
362 unique_names.push_back(pxr::TfToken(unique));
363 }
364
365 /* Set the unique names back on the mesh. */
366 skel_api.GetBlendShapesAttr().Set(unique_names);
367
368 /* Look up the temporary weights time sample we wrote to the mesh. */
369 pxr::UsdAttribute temp_weights_attr = pxr::UsdGeomPrimvarsAPI(mesh_prim).GetPrimvar(
371
372 if (!temp_weights_attr) {
373 /* No need to create the animation. Shouldn't usually happen. */
374 return;
375 }
376
377 /* Generate information we will need to merge the weight samples below. */
378 merge_info.append(BlendShapeMergeInfo());
379 merge_info.last().src_blend_shapes = unique_names;
380 merge_info.last().src_weights_attr = temp_weights_attr;
381 }
382
383 if (merged_names.is_empty()) {
384 /* No blend shape names were collected. Shouldn't usually happen. */
385 return;
386 }
387
388 /* Copy the list of name strings to a list of tokens, since we need to work with tokens. */
389 pxr::VtTokenArray skel_blend_shape_names;
390 for (const std::string &name : merged_names) {
391 skel_blend_shape_names.push_back(pxr::TfToken(name));
392 }
393
394 /* Initialize the merge info structs with the list of names on the merged animation. */
395 for (BlendShapeMergeInfo &info : merge_info) {
396 info.init_anim_map(skel_blend_shape_names);
397 }
398
399 /* Set the names on the animation prim. */
400 anim.CreateBlendShapesAttr().Set(skel_blend_shape_names);
401
402 pxr::UsdAttribute dst_weights_attr = anim.CreateBlendShapeWeightsAttr();
403
404 /* Merge the weight time samples. */
405 std::vector<double> times;
406 merge_info.first().src_weights_attr.GetTimeSamples(&times);
407
408 if (times.empty()) {
409 /* Times may be empty if there is only a default value for the weights,
410 * so we read the default. */
411 times.push_back(pxr::UsdTimeCode::Default().GetValue());
412 }
413
414 pxr::VtFloatArray dst_weights;
415
416 for (const double time : times) {
417 for (const BlendShapeMergeInfo &info : merge_info) {
418 pxr::VtFloatArray src_weights;
419 if (info.src_weights_attr.Get(&src_weights, time)) {
420 if (!info.anim_map.Remap(src_weights, &dst_weights)) {
421 CLOG_WARN(&LOG, "Failed remapping blend shape weights");
422 }
423 }
424 }
425 /* Set the merged weights on the animation. */
426 dst_weights_attr.Set(dst_weights, time);
427 }
428}
429
431{
432 if (!obj || !obj->data || obj->type != OB_MESH) {
433 return nullptr;
434 }
435
436 /* If we're exporting blend shapes, we export the unmodified mesh with
437 * the verts in the basis key positions. */
438 const Mesh *mesh = BKE_object_get_pre_modified_mesh(obj);
439
440 if (!mesh || !mesh->key || !mesh->key->block.first) {
441 return nullptr;
442 }
443
444 KeyBlock *basis = reinterpret_cast<KeyBlock *>(mesh->key->block.first);
445
446 if (mesh->verts_num != basis->totelem) {
447 CLOG_WARN(&LOG, "Vertex and shape key element count mismatch for mesh %s", obj->id.name + 2);
448 return nullptr;
449 }
450
451 /* Make a copy of the mesh so we can update the verts to the basis shape. */
452 Mesh *temp_mesh = BKE_mesh_copy_for_eval(*mesh);
453
454 /* Update the verts. */
456 basis,
457 reinterpret_cast<float(*)[3]>(temp_mesh->vert_positions_for_write().data()),
458 temp_mesh->verts_num);
459
460 return temp_mesh;
461}
462
463} // namespace blender::io::usd
void BKE_keyblock_convert_to_mesh(const KeyBlock *kb, float(*vert_positions)[3], int totvert)
Definition key.cc:2202
Mesh * BKE_mesh_copy_for_eval(const Mesh &source)
General operations, lookup, etc. for blender objects.
Mesh * BKE_object_get_pre_modified_mesh(const Object *object)
#define BLI_assert(a)
Definition BLI_assert.h:50
#define LISTBASE_FOREACH(type, var, list)
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
@ KEY_RELATIVE
Object is a sort of wrapper for general info.
@ OB_MESH
bool contains(const Key &key) const
Definition BLI_set.hh:291
bool add(const Key &key)
Definition BLI_set.hh:248
bool is_empty() const
Definition BLI_set.hh:572
void append(const T &value)
const T & last(const int64_t n=0) const
const T & first() const
EvaluationStage stage
Definition deg_eval.cc:83
draw_view in_light_buf[] float
static ushort indices[]
#define LOG(severity)
Definition log.h:33
void remap_blend_shape_anim(pxr::UsdStageRefPtr stage, const pxr::SdfPath &skel_path, const pxr::SdfPathSet &mesh_paths)
std::string make_safe_name(const std::string &name, bool allow_unicode)
Definition usd_utils.cc:16
void create_blend_shapes(pxr::UsdStageRefPtr stage, const Object *obj, const pxr::UsdPrim &mesh_prim, bool allow_unicode)
const Key * get_mesh_shape_key(const Object *obj)
void ensure_blend_shape_skeleton(pxr::UsdStageRefPtr stage, pxr::UsdPrim &mesh_prim)
pxr::TfToken TempBlendShapeWeightsPrimvarName
pxr::VtFloatArray get_blendshape_weights(const Key *key)
Mesh * get_shape_key_basis_mesh(Object *obj)
bool is_mesh_with_shape_keys(const Object *obj)
static const pxr::TfToken Skel("Skel", pxr::TfToken::Immortal)
static const pxr::TfToken joint1("joint1", pxr::TfToken::Immortal)
static const pxr::TfToken Anim("Anim", pxr::TfToken::Immortal)
static void unique_name(bNode *node)
char name[66]
Definition DNA_ID.h:425
void * data
int totkey
char type
ListBase block
void * first
struct Key * key
int verts_num
static CLG_LogRef LOG