Blender V5.0
fbx_import_armature.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BKE_action.hh"
10#include "BKE_armature.hh"
11#include "BKE_lib_id.hh"
12#include "BKE_object.hh"
13
14#include "BLI_math_vector.hh"
15
16#include "DNA_object_types.h"
17
18#include "ED_armature.hh"
19
20#include "IO_fbx.hh"
21
23
24namespace blender::io::fbx {
25
28 const ufbx_scene &fbx;
31
39
40 Object *create_armature_for_node(const ufbx_node *node);
41 void create_armature_bones(const ufbx_node *node,
42 Object *arm_obj,
43 const Set<const ufbx_node *> &bone_nodes,
44 EditBone *parent_bone,
45 const ufbx_matrix &parent_mtx,
46 const ufbx_matrix &world_to_arm,
47 const float parent_bone_size);
48 void find_armatures(const ufbx_node *node);
50};
51
53{
54 BLI_assert_msg(node != nullptr, "fbx: node for armature creation should not be null");
55
56 const char *arm_name = get_fbx_name(node->name, "Armature");
57 const char *obj_name = get_fbx_name(node->name, "Armature");
58#ifdef FBX_DEBUG_PRINT
59 fprintf(g_debug_file, "create ARMATURE %s\n", arm_name);
60#endif
61
62 bArmature *arm = BKE_armature_add(&this->bmain, arm_name);
64 obj->dtx |= OB_DRAW_IN_FRONT;
65 obj->data = arm;
66 this->mapping.imported_objects.add(obj);
67 if (!node->is_root) {
68 this->mapping.el_to_object.add(&node->element, obj);
69 if (this->params.use_custom_props) {
70 read_custom_properties(node->props, obj->id, this->params.props_enum_as_string);
71 }
72 node_matrix_to_obj(node, obj, this->mapping);
73
74 /* Record world to fbx node matrix for the armature object. */
75 ufbx_matrix world_to_arm = ufbx_matrix_invert(&node->node_to_world);
76 this->mapping.armature_world_to_arm_node_matrix.add(obj, world_to_arm);
77
78 /* Record world to posed root node matrix. */
79 if (node->bind_pose && node->bind_pose->is_bind_pose) {
80 for (const ufbx_bone_pose &pose : node->bind_pose->bone_poses) {
81 if (pose.bone_node == node) {
82 world_to_arm = ufbx_matrix_invert(&pose.bone_to_world);
83 break;
84 }
85 }
86 }
87 this->mapping.armature_world_to_arm_pose_matrix.add(obj, world_to_arm);
88 }
89 else {
90 /* For armatures created at root, make them have the same rotation/scale
91 * as done by ufbx for all regular nodes. */
92 ufbx_matrix_to_obj(this->mapping.global_conv_matrix, obj);
93 ufbx_matrix world_to_arm = ufbx_matrix_invert(&this->mapping.global_conv_matrix);
94 this->mapping.armature_world_to_arm_pose_matrix.add(obj, world_to_arm);
95 this->mapping.armature_world_to_arm_node_matrix.add(obj, world_to_arm);
96 }
97 return obj;
98}
99
101 Object *arm_obj,
102 const Set<const ufbx_node *> &bone_nodes,
103 EditBone *parent_bone,
104 const ufbx_matrix &parent_mtx,
105 const ufbx_matrix &world_to_arm,
106 const float parent_bone_size)
107{
108 BLI_assert(node != nullptr && !node->is_root);
109 bArmature *arm = static_cast<bArmature *>(arm_obj->data);
110
111 /* Create an EditBone. */
112 std::string name;
113 if (node->is_geometry_transform_helper) {
114 /* Name geometry transform adjustment helpers with parent name and _GeomAdjust suffix. */
115 name = get_fbx_name(node->parent->name, "Bone") + std::string("_GeomAdjust");
116 }
117 else {
118 name = get_fbx_name(node->name, "Bone");
119 }
120 EditBone *bone = ED_armature_ebone_add(arm, name.c_str());
121 this->mapping.node_to_name.add(node, bone->name);
122 this->mapping.node_is_blender_bone.add(node);
123 this->mapping.bone_to_armature.add(node, arm_obj);
124 bone->flag |= BONE_SELECTED;
125 bone->parent = parent_bone;
126 if (node->inherit_mode == UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE) {
128 }
129#ifdef FBX_DEBUG_PRINT
130 fprintf(g_debug_file,
131 "create BONE %s (parent %s) parent_mtx:\n",
132 node->name.data,
133 parent_bone ? parent_bone->name : "");
134 print_matrix(parent_mtx);
135#endif
136
137 ufbx_matrix bone_mtx = this->mapping.get_node_bind_matrix(node);
138 bone_mtx = ufbx_matrix_mul(&world_to_arm, &bone_mtx);
139 bone_mtx.cols[0] = ufbx_vec3_normalize(bone_mtx.cols[0]);
140 bone_mtx.cols[1] = ufbx_vec3_normalize(bone_mtx.cols[1]);
141 bone_mtx.cols[2] = ufbx_vec3_normalize(bone_mtx.cols[2]);
142
143#ifdef FBX_DEBUG_PRINT
144 fprintf(g_debug_file, " bone_mtx:\n");
145 print_matrix(bone_mtx);
146#endif
147
148 /* Calculate bone tail position. */
149 float bone_size = 0.0f;
150 int child_bone_count = 0;
151 for (const ufbx_node *fchild : node->children) {
152 if (!bone_nodes.contains(fchild)) {
153 continue;
154 }
155
156 /* Estimate child position from local transform, but if the child
157 * is skinned/posed then use the posed transform instead. */
158 ufbx_vec3 pos = fchild->local_transform.translation;
159 if (this->mapping.bone_to_bind_matrix.contains(fchild)) {
160 ufbx_matrix local_mtx = this->mapping.calc_local_bind_matrix(fchild, world_to_arm);
161 pos = local_mtx.cols[3];
162 }
163 bone_size += math::length(float3(pos.x, pos.y, pos.z));
164 child_bone_count++;
165 }
166 if (child_bone_count > 0) {
167 bone_size /= child_bone_count;
168 }
169 else {
170 /* This is leaf bone, set length to parent bone length. */
171 bone_size = parent_bone_size;
172 /* If we do not have actual pose/skin matrix for this bone, apply local transform onto parent
173 * matrix. */
174 if (!this->mapping.bone_to_bind_matrix.contains(node)) {
175 ufbx_matrix offset_mtx = ufbx_transform_to_matrix(&node->local_transform);
176 bone_mtx = ufbx_matrix_mul(&parent_mtx, &offset_mtx);
177 bone_mtx.cols[0] = ufbx_vec3_normalize(bone_mtx.cols[0]);
178 bone_mtx.cols[1] = ufbx_vec3_normalize(bone_mtx.cols[1]);
179 bone_mtx.cols[2] = ufbx_vec3_normalize(bone_mtx.cols[2]);
180#ifdef FBX_DEBUG_PRINT
181 fprintf(g_debug_file, " bone_mtx adj for non-posed bones:\n");
182 print_matrix(bone_mtx);
183#endif
184 }
185 }
186 /* Zero length bones are automatically collapsed into their parent when you leave edit mode,
187 * so enforce a minimum length. */
188 bone_size = math::max(bone_size, 0.01f);
189 this->mapping.bone_to_length.add(node, bone_size);
190
191 bone->tail[0] = 0.0f;
192 bone->tail[1] = bone_size;
193 bone->tail[2] = 0.0f;
194 /* Set bone matrix. */
195 float bone_matrix[4][4];
196 matrix_to_m44(bone_mtx, bone_matrix);
197 ED_armature_ebone_from_mat4(bone, bone_matrix);
198
199#ifdef FBX_DEBUG_PRINT
200 fprintf(g_debug_file,
201 " length %.3f head (%.3f %.3f %.3f) tail (%.3f %.3f %.3f)\n",
202 adjf(bone_size),
203 adjf(bone->head[0]),
204 adjf(bone->head[1]),
205 adjf(bone->head[2]),
206 adjf(bone->tail[0]),
207 adjf(bone->tail[1]),
208 adjf(bone->tail[2]));
209#endif
210
211 /* Mark bone as connected to parent if head approximately in the same place as parent tail, in
212 * both rest pose and current pose. */
213 if (parent_bone != nullptr) {
214 float3 self_head_rest(bone->head);
215 float3 par_tail_rest(parent_bone->tail);
216 const float connect_dist = 1.0e-4f;
217 const float connect_dist_sq = connect_dist * connect_dist;
218 float dist_sq_rest = math::distance_squared(self_head_rest, par_tail_rest);
219 if (dist_sq_rest < connect_dist_sq) {
220 /* Bones seem connected in rest pose, now check their current transforms. */
221 ufbx_vec3 self_head_cur_u = node->node_to_world.cols[3];
222 ufbx_vec3 par_tail;
223 par_tail.x = 0;
224 par_tail.y = parent_bone_size;
225 par_tail.z = 0;
226 ufbx_vec3 par_tail_cur_u = ufbx_transform_position(&node->parent->node_to_world, par_tail);
227 float3 self_head_cur(self_head_cur_u.x, self_head_cur_u.y, self_head_cur_u.z);
228 float3 par_tail_cur(par_tail_cur_u.x, par_tail_cur_u.y, par_tail_cur_u.z);
229 float dist_sq_cur = math::distance_squared(self_head_cur, par_tail_cur);
230
231 if (dist_sq_cur < connect_dist_sq) {
232 /* Connected in both cases. */
233 bone->flag |= BONE_CONNECTED;
234 }
235 }
236 }
237
238 /* Recurse into child bones. */
239 for (const ufbx_node *fchild : node->children) {
240 if (!bone_nodes.contains(fchild)) {
241 continue;
242 }
243
244 bool skip_child = false;
245 if (this->params.ignore_leaf_bones) {
246 if (node->children.count == 1 && fchild->children.count == 0 &&
247 !mapping.bone_is_skinned.contains(fchild))
248 {
249 skip_child = true;
250 /* We are skipping this bone, but still record it --
251 * so that later code does not try to create an empty for it. */
252 this->mapping.node_is_blender_bone.add(fchild);
253 }
254 }
255
256 if (!skip_child) {
257 create_armature_bones(fchild, arm_obj, bone_nodes, bone, bone_mtx, world_to_arm, bone_size);
258 }
259 }
260}
261
262/* Need to create armature if we are root bone, or any child is a non-root bone. */
263static bool need_create_armature_for_node(const ufbx_node *node)
264{
265 if (node->bone && node->bone->is_root) {
266 return true;
267 }
268 for (const ufbx_node *fchild : node->children) {
269 if (fchild->bone && !fchild->bone->is_root) {
270 return true;
271 }
272 }
273 return false;
274}
275
276static void find_bones(const ufbx_node *node, Set<const ufbx_node *> &r_bones)
277{
278 if (node->bone != nullptr) {
279 r_bones.add(node);
280 }
281 for (const ufbx_node *child : node->children) {
282 find_bones(child, r_bones);
283 }
284}
285
286static void find_fake_bones(const ufbx_node *root_node,
287 const Set<const ufbx_node *> &bones,
288 Set<const ufbx_node *> &r_fake_bones)
289{
290 for (const ufbx_node *bone_node : bones) {
291 const ufbx_node *node = bone_node->parent;
292 while (!ELEM(node, nullptr, root_node)) {
293 if (node->bone == nullptr) {
294 r_fake_bones.add(node);
295 }
296 node = node->parent;
297 }
298 }
299}
300
301static Set<const ufbx_node *> find_all_bones(const ufbx_node *root_node)
302{
303 /* Find regular FBX bones nodes anywhere under our root armature node. */
305 find_bones(root_node, bones);
306
307 /* There might be non-bone nodes in between, e.g. FBX structure being like:
308 * BoneA -> MeshB -> BoneC -> MeshD. Blender Armature can only contain
309 * bones, so in this case "MeshB" has to have a bone created for it as well.
310 * "Fake bones" are any non-bone FBX nodes in between root armature node
311 * and the actual bone node. */
312 Set<const ufbx_node *> fake_bones;
313 find_fake_bones(root_node, bones, fake_bones);
314 for (const ufbx_node *b : fake_bones) {
315 bones.add(b);
316 }
317 return bones;
318}
319
320void ArmatureImportContext::find_armatures(const ufbx_node *node)
321{
322 const bool needs_arm = need_create_armature_for_node(node);
323 if (needs_arm) {
324 /* Create armature. */
325 Object *arm_obj = this->create_armature_for_node(node);
326 ufbx_matrix world_to_arm = this->mapping.armature_world_to_arm_pose_matrix.lookup_default(
327 arm_obj, ufbx_identity_matrix);
328
329 Set<const ufbx_node *> bone_nodes = find_all_bones(node);
330
331 /* Create bones in edit mode. */
332 bArmature *arm = static_cast<bArmature *>(arm_obj->data);
334 this->mapping.node_to_name.add(node, BKE_id_name(arm_obj->id));
335 for (const ufbx_node *fchild : node->children) {
336 if (bone_nodes.contains(fchild)) {
338 fchild, arm_obj, bone_nodes, nullptr, ufbx_identity_matrix, world_to_arm, 1.0f);
339 }
340 }
341
342 ED_armature_from_edit(&this->bmain, arm);
344
345 /* Setup pose on the object, and custom properties on the bone pose channels. */
346 for (const ufbx_node *fbone : bone_nodes) {
347 if (!this->mapping.node_is_blender_bone.contains(fbone)) {
348 continue; /* Blender bone was not created for it (e.g. root bone in some cases). */
349 }
351 arm_obj->pose, this->mapping.node_to_name.lookup_default(fbone, "").c_str());
352 if (pchan == nullptr) {
353 continue;
354 }
355 read_custom_properties(fbone->props, *pchan, this->params.props_enum_as_string);
356
357 /* For bones that have rest/bind information, put their current transform into
358 * the current pose. */
359 if (this->mapping.bone_to_bind_matrix.contains(fbone)) {
360 ufbx_matrix bind_local_mtx = this->mapping.calc_local_bind_matrix(fbone, world_to_arm);
361 ufbx_matrix bind_local_mtx_inv = ufbx_matrix_invert(&bind_local_mtx);
362 ufbx_transform xform = fbone->local_transform;
363 if (fbone->node_depth <= 1) {
364 ufbx_matrix matrix = ufbx_matrix_mul(&world_to_arm, &fbone->node_to_world);
365 xform = ufbx_matrix_to_transform(&matrix);
366 }
367 ufbx_matrix pose_mtx = calc_bone_pose_matrix(xform, *fbone, bind_local_mtx_inv);
368
369 float pchan_matrix[4][4];
370 matrix_to_m44(pose_mtx, pchan_matrix);
371 BKE_pchan_apply_mat4(pchan, pchan_matrix, false);
372
373#ifdef FBX_DEBUG_PRINT
374 fprintf(g_debug_file, "set POSE matrix of %s matrix_basis:\n", fbone->name.data);
375 print_matrix(pose_mtx);
376#endif
377 }
378 }
379 }
380
381 /* Recurse into children that have not been turned into bones yet. */
382 for (const ufbx_node *fchild : node->children) {
383 if (!this->mapping.node_is_blender_bone.contains(fchild)) {
384 this->find_armatures(fchild);
385 }
386 }
387}
388
390{
391 /* Figure out bind matrices for bone nodes:
392 * - Get them from "pose" objects in FBX that are marked as "bind pose",
393 * - From all "skin deformer" objects in FBX; these override the ones from "poses".
394 * - For all the bone nodes that do not have a matrix yet, record their world matrix
395 * as bind matrix. */
396 for (const ufbx_pose *fpose : this->fbx.poses) {
397 if (!fpose->is_bind_pose) {
398 continue;
399 }
400 for (const ufbx_bone_pose &bone_pose : fpose->bone_poses) {
401 const ufbx_matrix &bind_matrix = bone_pose.bone_to_world;
402 this->mapping.bone_to_bind_matrix.add_overwrite(bone_pose.bone_node, bind_matrix);
403#ifdef FBX_DEBUG_PRINT
404 fprintf(g_debug_file, "bone POSE matrix %s\n", bone_pose.bone_node->name.data);
405 print_matrix(bind_matrix);
406#endif
407 }
408 }
409
410 for (const ufbx_skin_deformer *fskin : this->fbx.skin_deformers) {
411 for (const ufbx_skin_cluster *fbone : fskin->clusters) {
412 const ufbx_matrix &bind_matrix = fbone->bind_to_world;
413 this->mapping.bone_to_bind_matrix.add_overwrite(fbone->bone_node, bind_matrix);
414 this->mapping.bone_is_skinned.add(fbone->bone_node);
415#ifdef FBX_DEBUG_PRINT
416 fprintf(g_debug_file, "bone SKIN matrix %s\n", fbone->bone_node->name.data);
417 print_matrix(bind_matrix);
418#endif
419 }
420 }
421}
422
424 const ufbx_scene &fbx,
425 FbxElementMapping &mapping,
426 const FBXImportParams &params)
427{
428 ArmatureImportContext context(bmain, fbx, params, mapping);
429 context.calc_bone_bind_matrices();
430 context.find_armatures(fbx.root_node);
431}
432
433} // namespace blender::io::fbx
Blender kernel action and pose functionality.
bPoseChannel * BKE_pose_channel_find_name(const bPose *pose, const char *name)
void BKE_pchan_apply_mat4(bPoseChannel *pchan, const float mat[4][4], bool use_compat)
bArmature * BKE_armature_add(Main *bmain, const char *name)
const char * BKE_id_name(const ID &id)
General operations, lookup, etc. for blender objects.
Object * BKE_object_add_only_object(Main *bmain, int type, const char *name) ATTR_RETURNS_NONNULL
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define ELEM(...)
@ BONE_SELECTED
@ BONE_CONNECTED
@ BONE_INHERIT_SCALE_NONE
Object is a sort of wrapper for general info.
@ OB_DRAW_IN_FRONT
@ OB_ARMATURE
EditBone * ED_armature_ebone_add(bArmature *arm, const char *name)
void ED_armature_edit_free(bArmature *arm)
void ED_armature_from_edit(Main *bmain, bArmature *arm)
void ED_armature_ebone_from_mat4(EditBone *ebone, const float mat[4][4])
void ED_armature_to_edit(bArmature *arm)
bool contains(const Key &key) const
Definition BLI_set.hh:310
bool add(const Key &key)
Definition BLI_set.hh:248
uint pos
#define main()
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void ufbx_matrix_to_obj(const ufbx_matrix &mtx, Object *obj)
const char * get_fbx_name(const ufbx_string &name, const char *def)
void read_custom_properties(const ufbx_props &props, ID &id, bool enums_as_strings)
void node_matrix_to_obj(const ufbx_node *node, Object *obj, const FbxElementMapping &mapping)
static bool need_create_armature_for_node(const ufbx_node *node)
void matrix_to_m44(const ufbx_matrix &src, float dst[4][4])
void import_armatures(Main &bmain, const ufbx_scene &fbx, FbxElementMapping &mapping, const FBXImportParams &params)
ufbx_matrix calc_bone_pose_matrix(const ufbx_transform &local_xform, const ufbx_node &node, const ufbx_matrix &local_bind_inv_matrix)
static void find_bones(const ufbx_node *node, Set< const ufbx_node * > &r_bones)
static void find_fake_bones(const ufbx_node *root_node, const Set< const ufbx_node * > &bones, Set< const ufbx_node * > &r_fake_bones)
static Set< const ufbx_node * > find_all_bones(const ufbx_node *root_node)
T length(const VecBase< T, Size > &a)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
T max(const T &a, const T &b)
VecBase< float, 3 > float3
const char * name
char name[64]
float tail[3]
EditBone * parent
char inherit_scale_mode
float head[3]
struct bPose * pose
void create_armature_bones(const ufbx_node *node, Object *arm_obj, const Set< const ufbx_node * > &bone_nodes, EditBone *parent_bone, const ufbx_matrix &parent_mtx, const ufbx_matrix &world_to_arm, const float parent_bone_size)
Object * create_armature_for_node(const ufbx_node *node)
ArmatureImportContext(Main &main, const ufbx_scene &fbx, const FBXImportParams &params, FbxElementMapping &mapping)