Blender V5.0
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
174{
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::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Get(stage, skel_path);
308
309 if (!skel_api) {
310 CLOG_WARN(&LOG, "Couldn't get skeleton from path %s", skel_path.GetAsString().c_str());
311 return;
312 }
313
314 /* Use existing animation if possible, otherwise create a new one. */
315 pxr::UsdPrim anim_prim;
316 pxr::UsdSkelAnimation anim;
317 if (skel_api.GetAnimationSource(&anim_prim)) {
318 anim = pxr::UsdSkelAnimation(anim_prim);
319 }
320 else {
321 pxr::SdfPath anim_path = skel_path.AppendChild(usdtokens::Anim);
322 anim = pxr::UsdSkelAnimation::Define(stage, anim_path);
323 }
324
325 if (!anim) {
326 CLOG_WARN(&LOG, "Couldn't get animation under skeleton %s", skel_path.GetAsString().c_str());
327 return;
328 }
329
331
332 /* We are merging blend shape names and weights from multiple
333 * meshes to a single animation. In case of name collisions,
334 * we must generate unique blend shape names for the merged
335 * result. This set keeps track of the unique names that will
336 * be combined on the animation. */
337 Set<std::string> merged_names;
338
339 /* Iterate over all the meshes, generate unique blend shape names in case of name
340 * collisions and set up the information we will need to merge the results. */
341 for (const pxr::SdfPath &mesh_path : mesh_paths) {
342
343 pxr::UsdPrim mesh_prim = stage->GetPrimAtPath(mesh_path);
344 pxr::UsdSkelBindingAPI mesh_skel_api = pxr::UsdSkelBindingAPI::Apply(mesh_prim);
345 if (!mesh_skel_api) {
346 CLOG_WARN(&LOG,
347 "Couldn't apply UsdSkelBindingAPI to mesh prim %s",
348 mesh_path.GetAsString().c_str());
349 continue;
350 }
351
352 /* Get the blend shape names for this mesh. */
353 pxr::UsdAttribute blend_shapes_attr = mesh_skel_api.GetBlendShapesAttr();
354
355 if (!blend_shapes_attr) {
356 continue;
357 }
358
359 pxr::VtTokenArray names;
360 if (!mesh_skel_api.GetBlendShapesAttr().Get(&names)) {
361 continue;
362 }
363
364 /* Ensure the names are unique. */
365 pxr::VtTokenArray unique_names;
366
367 for (const pxr::TfToken &name : names.AsConst()) {
368 std::string unique = add_unique_name(merged_names, name.GetString());
369 unique_names.push_back(pxr::TfToken(unique));
370 }
371
372 /* Set the unique names back on the mesh. */
373 mesh_skel_api.GetBlendShapesAttr().Set(unique_names);
374
375 /* Look up the temporary weights time sample we wrote to the mesh. */
376 const pxr::UsdAttribute temp_weights_attr = pxr::UsdGeomPrimvarsAPI(mesh_prim).GetPrimvar(
378
379 if (!temp_weights_attr) {
380 /* No need to create the animation. Shouldn't usually happen. */
381 return;
382 }
383
384 /* Generate information we will need to merge the weight samples below. */
385 merge_info.append(BlendShapeMergeInfo());
386 merge_info.last().src_blend_shapes = unique_names;
387 merge_info.last().src_weights_attr = temp_weights_attr;
388 }
389
390 if (merged_names.is_empty()) {
391 /* No blend shape names were collected. Shouldn't usually happen. */
392 return;
393 }
394
395 /* Copy the list of name strings to a list of tokens, since we need to work with tokens. */
396 pxr::VtTokenArray skel_blend_shape_names;
397 for (const std::string &name : merged_names) {
398 skel_blend_shape_names.push_back(pxr::TfToken(name));
399 }
400
401 /* Initialize the merge info structs with the list of names on the merged animation. */
402 for (BlendShapeMergeInfo &info : merge_info) {
403 info.init_anim_map(skel_blend_shape_names);
404 }
405
406 /* Set the names on the animation prim. */
407 anim.CreateBlendShapesAttr().Set(skel_blend_shape_names);
408
409 pxr::UsdAttribute dst_weights_attr = anim.CreateBlendShapeWeightsAttr();
410
411 /* Merge the weight time samples. */
412 std::vector<double> times;
413 merge_info.first().src_weights_attr.GetTimeSamples(&times);
414
415 if (times.empty()) {
416 /* Times may be empty if there is only a default value for the weights,
417 * so we read the default. */
418 times.push_back(pxr::UsdTimeCode::Default().GetValue());
419 }
420
421 pxr::VtFloatArray dst_weights;
422
423 for (const double time : times) {
424 for (const BlendShapeMergeInfo &info : merge_info) {
425 pxr::VtFloatArray src_weights;
426 if (info.src_weights_attr.Get(&src_weights, time)) {
427 if (!info.anim_map.Remap(src_weights.AsConst(), &dst_weights)) {
428 CLOG_WARN(&LOG, "Failed remapping blend shape weights");
429 }
430 }
431 }
432 /* Set the merged weights on the animation. */
433 dst_weights_attr.Set(dst_weights, time);
434 }
435}
436
438{
439 if (!obj || !obj->data || obj->type != OB_MESH) {
440 return nullptr;
441 }
442
443 /* If we're exporting blend shapes, we export the unmodified mesh with
444 * the verts in the basis key positions. */
446
447 if (!mesh || !mesh->key || !mesh->key->block.first) {
448 return nullptr;
449 }
450
451 const KeyBlock *basis = reinterpret_cast<KeyBlock *>(mesh->key->block.first);
452
453 if (mesh->verts_num != basis->totelem) {
454 CLOG_WARN(&LOG, "Vertex and shape key element count mismatch for mesh %s", obj->id.name + 2);
455 return nullptr;
456 }
457
458 /* Make a copy of the mesh so we can update the verts to the basis shape. */
459 Mesh *temp_mesh = BKE_mesh_copy_for_eval(*mesh);
460
461 /* Update the verts. */
462 BKE_keyblock_convert_to_mesh(basis, temp_mesh->vert_positions_for_write());
463
464 return temp_mesh;
465}
466
467} // namespace blender::io::usd
void BKE_keyblock_convert_to_mesh(const KeyBlock *kb, blender::MutableSpan< blender::float3 > vert_positions)
Definition key.cc:2186
Mesh * BKE_mesh_copy_for_eval(const Mesh &source)
General operations, lookup, etc. for blender objects.
const Mesh * BKE_object_get_pre_modified_mesh(const Object *object)
#define BLI_assert(a)
Definition BLI_assert.h:46
#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:189
@ KEY_RELATIVE
Object is a sort of wrapper for general info.
@ OB_MESH
BMesh const char void * data
bool contains(const Key &key) const
Definition BLI_set.hh:310
bool add(const Key &key)
Definition BLI_set.hh:248
bool is_empty() const
Definition BLI_set.hh:595
Set(Allocator allocator={}) noexcept
Definition BLI_set.hh:168
void append(const T &value)
const T & last(const int64_t n=0) const
const T & first() const
nullptr float
static ushort indices[]
#define LOG(level)
Definition log.h:97
void remap_blend_shape_anim(pxr::UsdStageRefPtr stage, const pxr::SdfPath &skel_path, const pxr::SdfPathSet &mesh_paths)
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)
std::string make_safe_name(const StringRef name, bool allow_unicode)
Definition usd_utils.cc:18
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)
const char * name
void * data
int totkey
char type
ListBase block
void * first
struct Key * key
int verts_num
i
Definition text_draw.cc:230