Blender V4.3
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
7
8#include "BKE_action.hh"
9
10#include "DNA_armature_types.h"
11
12#include <pxr/base/gf/matrix4d.h>
13#include <pxr/base/gf/matrix4f.h>
14#include <pxr/usd/usdGeom/primvarsAPI.h>
15#include <pxr/usd/usdSkel/animation.h>
16#include <pxr/usd/usdSkel/bindingAPI.h>
17#include <pxr/usd/usdSkel/skeleton.h>
18
19#include "CLG_log.h"
20static CLG_LogRef LOG = {"io.usd"};
21
22namespace usdtokens {
23static const pxr::TfToken Anim("Anim", pxr::TfToken::Immortal);
24} // namespace usdtokens
25
31static pxr::GfMatrix4d parent_relative_pose_mat(const bPoseChannel *pchan)
32{
33 /* Note that the float matrix will be returned as GfMatrix4d, because
34 * USD requires doubles. */
35 const pxr::GfMatrix4f pose_mat(pchan->pose_mat);
36
37 if (pchan->parent) {
38 const pxr::GfMatrix4f parent_pose_mat(pchan->parent->pose_mat);
39 const pxr::GfMatrix4f xf = pose_mat * parent_pose_mat.GetInverse();
40 return pxr::GfMatrix4d(xf);
41 }
42
43 /* No parent, so return the pose matrix directly. */
44 return pxr::GfMatrix4d(pose_mat);
45}
46
47/* Initialize the given skeleton and animation from
48 * the given armature object. */
49static void initialize(const Object *obj,
50 pxr::UsdSkelSkeleton &skel,
51 pxr::UsdSkelAnimation &skel_anim,
53 bool allow_unicode)
54{
55 using namespace blender::io::usd;
56
57 pxr::VtTokenArray joints;
58 pxr::VtArray<float> bone_lengths;
59 pxr::VtArray<pxr::GfMatrix4d> bind_xforms;
60 pxr::VtArray<pxr::GfMatrix4d> rest_xforms;
61
62 /* Function to collect the bind and rest transforms from each bone. */
63 auto visitor = [&](const Bone *bone) {
64 if (!bone) {
65 return;
66 }
67
68 if (deform_bones && !deform_bones->contains(bone->name)) {
69 /* If deform_map is passed in, assume we're going deform-only.
70 * Bones not found in the map should be skipped. */
71 return;
72 }
73
74 /* Store Blender bone lengths to facilitate better round-tripping. */
75 bone_lengths.push_back(bone->length);
76
77 joints.push_back(build_usd_joint_path(bone, allow_unicode));
78 const pxr::GfMatrix4f arm_mat(bone->arm_mat);
79 bind_xforms.push_back(pxr::GfMatrix4d(arm_mat));
80
81 /* Set the rest transform to the parent-relative pose matrix, or the parent-relative
82 * armature matrix, if no pose channel exists. */
83 if (const bPoseChannel *pchan = BKE_pose_channel_find_name(obj->pose, bone->name)) {
84 rest_xforms.push_back(parent_relative_pose_mat(pchan));
85 }
86 else if (bone->parent) {
87 pxr::GfMatrix4f parent_arm_mat(bone->parent->arm_mat);
88 const pxr::GfMatrix4f rest_mat = arm_mat * parent_arm_mat.GetInverse();
89 rest_xforms.push_back(pxr::GfMatrix4d(rest_mat));
90 }
91 else {
92 rest_xforms.push_back(pxr::GfMatrix4d(arm_mat));
93 }
94 };
95
96 visit_bones(obj, visitor);
97 skel.GetJointsAttr().Set(joints);
98 skel.GetBindTransformsAttr().Set(bind_xforms);
99 skel.GetRestTransformsAttr().Set(rest_xforms);
100
101 const pxr::UsdPrim skel_prim = skel.GetPrim();
102
103 /* Store the custom bone lengths as just a regular Primvar attached to the Skeleton. */
104 const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(skel_prim);
105 pxr::UsdGeomPrimvar pv_lengths = pv_api.CreatePrimvar(
106 BlenderBoneLengths, pxr::SdfValueTypeNames->FloatArray, pxr::UsdGeomTokens->uniform);
107 pv_lengths.Set(bone_lengths);
108
109 pxr::UsdSkelBindingAPI usd_skel_api = pxr::UsdSkelBindingAPI::Apply(skel_prim);
110
111 if (skel_anim) {
112 usd_skel_api.CreateAnimationSourceRel().SetTargets(
113 pxr::SdfPathVector({pxr::SdfPath(usdtokens::Anim)}));
114 create_pose_joints(skel_anim, *obj, deform_bones, allow_unicode);
115 }
116}
117
118/* Add skeleton transform samples from the armature pose channels. */
119static void add_anim_sample(pxr::UsdSkelAnimation &skel_anim,
120 const Object *obj,
121 const pxr::UsdTimeCode time,
123{
124 if (!(skel_anim && obj && obj->pose)) {
125 return;
126 }
127
128 pxr::VtArray<pxr::GfMatrix4d> xforms;
129
130 const bPose *pose = obj->pose;
131
132 LISTBASE_FOREACH (const bPoseChannel *, pchan, &pose->chanbase) {
133
134 BLI_assert(pchan->bone);
135
136 if (deform_map && !deform_map->contains(pchan->bone->name)) {
137 /* If deform_map is passed in, assume we're going deform-only.
138 * Bones not found in the map should be skipped. */
139 continue;
140 }
141
142 xforms.push_back(parent_relative_pose_mat(pchan));
143 }
144
145 skel_anim.SetTransforms(xforms, time);
146}
147
148namespace blender::io::usd {
149
151
153{
154 if (!(context.object && context.object->type == OB_ARMATURE && context.object->data)) {
156 return;
157 }
158
159 /* Create the skeleton. */
160 pxr::UsdStageRefPtr stage = usd_export_context_.stage;
161 pxr::UsdSkelSkeleton skel = pxr::UsdSkelSkeleton::Define(stage, usd_export_context_.usd_path);
162
163 if (!skel) {
164 CLOG_WARN(&LOG,
165 "Couldn't define UsdSkelSkeleton %s",
166 usd_export_context_.usd_path.GetString().c_str());
167 return;
168 }
169
170 pxr::UsdSkelAnimation skel_anim;
171
173 /* Create the skeleton animation primitive as a child of the skeleton. */
174 pxr::SdfPath anim_path = usd_export_context_.usd_path.AppendChild(usdtokens::Anim);
175 skel_anim = pxr::UsdSkelAnimation::Define(stage, anim_path);
176
177 if (!skel_anim) {
178 CLOG_WARN(&LOG, "Couldn't define UsdSkelAnimation %s", anim_path.GetString().c_str());
179 return;
180 }
181 }
182
183 const bool allow_unicode = usd_export_context_.export_params.allow_unicode;
185 &deform_map_ :
186 nullptr;
187
188 if (!this->frame_has_been_written_) {
189 init_deform_bones_map(context.object, deform_map);
190 initialize(context.object, skel, skel_anim, deform_map, allow_unicode);
191 }
192
194 add_anim_sample(skel_anim, context.object, get_export_time_code(), deform_map);
195 }
196}
197
199{
200 const Object *obj = context.object;
201
202 if (!(obj && obj->type == OB_ARMATURE)) {
203 return false;
204 }
205
206 return obj->adt != nullptr;
207}
208
209} // namespace blender::io::usd
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:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define LISTBASE_FOREACH(type, var, list)
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
@ OB_ARMATURE
void initialize()
bool contains(const Key &key) const
Definition BLI_map.hh:329
pxr::UsdTimeCode get_export_time_code() const
const USDExporterContext usd_export_context_
virtual bool check_is_animated(const HierarchyContext &context) const override
USDArmatureWriter(const USDExporterContext &ctx)
virtual void do_write(HierarchyContext &context) override
EvaluationStage stage
Definition deg_eval.cc:83
#define LOG(severity)
Definition log.h:33
void init_deform_bones_map(const Object *obj, Map< StringRef, const Bone * > *deform_map)
static const pxr::TfToken Anim("Anim", pxr::TfToken::Immortal)
struct bPose * pose
struct AnimData * adt
struct bPoseChannel * parent
float pose_mat[4][4]
ListBase chanbase
static pxr::GfMatrix4d parent_relative_pose_mat(const bPoseChannel *pchan)
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)
static CLG_LogRef LOG