Blender V5.0
usd_writer_armature.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8#include "usd_utils.hh"
9
10#include "ANIM_action.hh"
11
12#include "BLI_listbase.h"
13
14#include "BKE_action.hh"
15
16#include "DNA_armature_types.h"
17
18#include <pxr/base/gf/matrix4d.h>
19#include <pxr/base/gf/matrix4f.h>
20#include <pxr/usd/usdGeom/primvarsAPI.h>
21#include <pxr/usd/usdSkel/animation.h>
22#include <pxr/usd/usdSkel/bindingAPI.h>
23#include <pxr/usd/usdSkel/skeleton.h>
24#include <pxr/usd/usdSkel/utils.h>
25
26#include "CLG_log.h"
27static CLG_LogRef LOG = {"io.usd"};
28
34static pxr::GfMatrix4d parent_relative_pose_mat(const bPoseChannel *pchan)
35{
36 /* Note that the float matrix will be returned as GfMatrix4d, because
37 * USD requires doubles. */
38 const pxr::GfMatrix4f pose_mat(pchan->pose_mat);
39
40 if (pchan->parent) {
41 const pxr::GfMatrix4f parent_pose_mat(pchan->parent->pose_mat);
42 const pxr::GfMatrix4f xf = pose_mat * parent_pose_mat.GetInverse();
43 return pxr::GfMatrix4d(xf);
44 }
45
46 /* No parent, so return the pose matrix directly. */
47 return pxr::GfMatrix4d(pose_mat);
48}
49
50/* Initialize the given skeleton and animation from
51 * the given armature object. */
52static void initialize(const Object *obj,
53 pxr::UsdSkelSkeleton &skel,
54 pxr::UsdSkelAnimation &skel_anim,
56 bool allow_unicode)
57{
58 using namespace blender::io::usd;
59
60 pxr::VtTokenArray joints;
61 pxr::VtArray<float> bone_lengths;
62 pxr::VtArray<pxr::GfMatrix4d> bind_xforms;
63 pxr::VtArray<pxr::GfMatrix4d> rest_xforms;
64
65 /* Function to collect the bind and rest transforms from each bone. */
66 auto visitor = [&](const Bone *bone) {
67 if (!bone) {
68 return;
69 }
70
71 if (deform_bones && !deform_bones->contains(bone->name)) {
72 /* If deform_map is passed in, assume we're going deform-only.
73 * Bones not found in the map should be skipped. */
74 return;
75 }
76
77 /* Store Blender bone lengths to facilitate better round-tripping. */
78 bone_lengths.push_back(bone->length);
79
80 joints.push_back(build_usd_joint_path(bone, allow_unicode));
81 const pxr::GfMatrix4f arm_mat(bone->arm_mat);
82 bind_xforms.push_back(pxr::GfMatrix4d(arm_mat));
83
84 /* Set the rest transform to the parent-relative pose matrix, or the parent-relative
85 * armature matrix, if no pose channel exists. */
86 if (const bPoseChannel *pchan = BKE_pose_channel_find_name(obj->pose, bone->name)) {
87 rest_xforms.push_back(parent_relative_pose_mat(pchan));
88 }
89 else if (bone->parent) {
90 pxr::GfMatrix4f parent_arm_mat(bone->parent->arm_mat);
91 const pxr::GfMatrix4f rest_mat = arm_mat * parent_arm_mat.GetInverse();
92 rest_xforms.push_back(pxr::GfMatrix4d(rest_mat));
93 }
94 else {
95 rest_xforms.push_back(pxr::GfMatrix4d(arm_mat));
96 }
97 };
98
99 visit_bones(obj, visitor);
100 skel.GetJointsAttr().Set(joints);
101 skel.GetBindTransformsAttr().Set(bind_xforms);
102 skel.GetRestTransformsAttr().Set(rest_xforms);
103
104 const pxr::UsdPrim skel_prim = skel.GetPrim();
105
106 /* Store the custom bone lengths as just a regular Primvar attached to the Skeleton. */
107 const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(skel_prim);
108 pxr::UsdGeomPrimvar pv_lengths = pv_api.CreatePrimvar(
109 BlenderBoneLengths, pxr::SdfValueTypeNames->FloatArray, pxr::UsdGeomTokens->uniform);
110 pv_lengths.Set(bone_lengths);
111
112 pxr::UsdSkelBindingAPI usd_skel_api = pxr::UsdSkelBindingAPI::Apply(skel_prim);
113
114 if (skel_anim) {
115 usd_skel_api.CreateAnimationSourceRel().SetTargets(
116 pxr::SdfPathVector({pxr::SdfPath(skel_anim.GetPath().GetName())}));
117 create_pose_joints(skel_anim, *obj, deform_bones, allow_unicode);
118 }
119}
120
121namespace blender::io::usd {
122
123/* Add skeleton transform samples from the armature pose channels. */
124static void add_anim_sample(pxr::UsdSkelAnimation &skel_anim,
125 const Object *obj,
126 const pxr::UsdTimeCode time,
128 pxr::UsdUtilsSparseValueWriter &value_writer)
129{
130 if (!(skel_anim && obj && obj->pose)) {
131 return;
132 }
133
134 pxr::VtArray<pxr::GfMatrix4d> xforms;
135
136 const bPose *pose = obj->pose;
137
138 LISTBASE_FOREACH (const bPoseChannel *, pchan, &pose->chanbase) {
139
140 BLI_assert(pchan->bone);
141
142 if (deform_map && !deform_map->contains(pchan->bone->name)) {
143 /* If deform_map is passed in, assume we're going deform-only.
144 * Bones not found in the map should be skipped. */
145 continue;
146 }
147
148 xforms.push_back(parent_relative_pose_mat(pchan));
149 }
150
151 /* Perform the same steps as UsdSkelAnimation::SetTransforms but write data out sparsely. */
152 pxr::VtArray<pxr::GfVec3f> translations;
153 pxr::VtArray<pxr::GfQuatf> rotations;
154 pxr::VtArray<pxr::GfVec3h> scales;
155 if (pxr::UsdSkelDecomposeTransforms(xforms, &translations, &rotations, &scales)) {
156 set_attribute(skel_anim.GetTranslationsAttr(), translations, time, value_writer);
157 set_attribute(skel_anim.GetRotationsAttr(), rotations, time, value_writer);
158 set_attribute(skel_anim.GetScalesAttr(), scales, time, value_writer);
159 }
160 else {
161 CLOG_WARN(&LOG, "Could not decompose skeleton transforms for frame time %f", time.GetValue());
162 }
163}
164
166
168{
169 if (!(context.object && context.object->type == OB_ARMATURE && context.object->data)) {
171 return;
172 }
173
174 /* Create the skeleton. */
175 pxr::UsdStageRefPtr stage = usd_export_context_.stage;
176 pxr::UsdSkelSkeleton skel = pxr::UsdSkelSkeleton::Define(stage, usd_export_context_.usd_path);
177
178 if (!skel) {
179 CLOG_WARN(&LOG,
180 "Couldn't define UsdSkelSkeleton %s",
181 usd_export_context_.usd_path.GetString().c_str());
182 return;
183 }
184
185 pxr::UsdSkelAnimation skel_anim;
186
187 const bool allow_unicode = usd_export_context_.export_params.allow_unicode;
188
189 if (usd_export_context_.export_params.export_animation) {
190 /* Use the action name as the animation name. */
191 const animrig::Action *action = animrig::get_action(context.object->id);
192 const pxr::TfToken anim_name(action ? make_safe_name(action->id.name + 2, allow_unicode) :
193 "Action");
194
195 /* Create the skeleton animation primitive as a child of the skeleton. */
196 pxr::SdfPath anim_path = usd_export_context_.usd_path.AppendChild(anim_name);
197 skel_anim = pxr::UsdSkelAnimation::Define(stage, anim_path);
198
199 if (!skel_anim) {
200 CLOG_WARN(&LOG, "Couldn't define UsdSkelAnimation %s", anim_path.GetString().c_str());
201 return;
202 }
203 }
204
205 Map<StringRef, const Bone *> *deform_map = usd_export_context_.export_params.only_deform_bones ?
206 &deform_map_ :
207 nullptr;
208
209 if (!this->frame_has_been_written_) {
210 init_deform_bones_map(context.object, deform_map);
211 initialize(context.object, skel, skel_anim, deform_map, allow_unicode);
212 }
213
214 if (usd_export_context_.export_params.export_animation) {
216 skel_anim, context.object, get_export_time_code(), deform_map, usd_value_writer_);
217 }
218}
219
221{
222 const Object *obj = context.object;
223
224 if (!(obj && obj->type == OB_ARMATURE)) {
225 return false;
226 }
227
228 return obj->adt != nullptr;
229}
230
231} // namespace blender::io::usd
Functions and classes to work with Actions.
Blender kernel action and pose functionality.
bPoseChannel * BKE_pose_channel_find_name(const bPose *pose, const char *name)
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:189
@ OB_ARMATURE
void initialize()
bool contains(const Key &key) const
Definition BLI_map.hh:353
pxr::UsdTimeCode get_export_time_code() const
pxr::UsdUtilsSparseValueWriter usd_value_writer_
USDAbstractWriter(const USDExporterContext &usd_export_context)
const USDExporterContext usd_export_context_
bool check_is_animated(const HierarchyContext &context) const override
USDArmatureWriter(const USDExporterContext &ctx)
void do_write(HierarchyContext &context) override
#define LOG(level)
Definition log.h:97
Action * get_action(ID &animated_id)
void init_deform_bones_map(const Object *obj, Map< StringRef, const Bone * > *deform_map)
static void add_anim_sample(pxr::UsdSkelAnimation &skel_anim, const Object *obj, const pxr::UsdTimeCode time, const blender::Map< blender::StringRef, const Bone * > *deform_map, pxr::UsdUtilsSparseValueWriter &value_writer)
void create_pose_joints(pxr::UsdSkelAnimation &skel_anim, const Object &obj, const Map< StringRef, const Bone * > *deform_map, bool allow_unicode)
std::string make_safe_name(const StringRef name, bool allow_unicode)
Definition usd_utils.cc:18
const pxr::TfToken BlenderBoneLengths("blender:bone_lengths", pxr::TfToken::Immortal)
pxr::TfToken build_usd_joint_path(const Bone *bone, bool allow_unicode)
void set_attribute(const pxr::UsdAttribute &attr, const USDT value, pxr::UsdTimeCode time, pxr::UsdUtilsSparseValueWriter &value_writer)
static void visit_bones(const Bone *bone, FunctionRef< void(const Bone *)> visitor)
char name[258]
Definition DNA_ID.h:432
struct bPoseChannel * parent
float pose_mat[4][4]
ListBase chanbase
static pxr::GfMatrix4d parent_relative_pose_mat(const bPoseChannel *pchan)