diff --git a/source/blender/io/fbx/importer/fbx_import.cc b/source/blender/io/fbx/importer/fbx_import.cc index 55746419d9c..c26a9ad2458 100644 --- a/source/blender/io/fbx/importer/fbx_import.cc +++ b/source/blender/io/fbx/importer/fbx_import.cc @@ -155,6 +155,7 @@ void FbxImportContext::import_cameras() } node_matrix_to_obj(node, obj, this->mapping); this->mapping.el_to_object.add(&node->element, obj); + this->mapping.imported_objects.add(obj); } } @@ -206,6 +207,7 @@ void FbxImportContext::import_lights() } node_matrix_to_obj(node, obj, this->mapping); this->mapping.el_to_object.add(&node->element, obj); + this->mapping.imported_objects.add(obj); } } @@ -219,7 +221,9 @@ void FbxImportContext::import_empties() /* Create empties for fbx nodes. */ for (const ufbx_node *node : this->fbx.nodes) { /* Ignore root, bones and nodes for which we have created objects already. */ - if (node->is_root || node->bone || this->mapping.el_to_object.contains(&node->element)) { + if (node->is_root || this->mapping.node_is_blender_bone.contains(node) || + this->mapping.el_to_object.contains(&node->element)) + { continue; } Object *obj = BKE_object_add_only_object(this->bmain, OB_EMPTY, get_fbx_name(node->name)); @@ -232,6 +236,7 @@ void FbxImportContext::import_empties() } node_matrix_to_obj(node, obj, this->mapping); this->mapping.el_to_object.add(&node->element, obj); + this->mapping.imported_objects.add(obj); } } @@ -253,11 +258,6 @@ void FbxImportContext::setup_hierarchy() if (node == nullptr) { continue; } - if (node->bone != nullptr && !node->bone->is_root) { - /* If this node is for a non-root bone, do not try to setup object parenting - * for it (the object for bone bones is whole armature). */ - continue; - } if (node->parent) { Object *obj_par = this->mapping.el_to_object.lookup_default(&node->parent->element, nullptr); if (!ELEM(obj_par, nullptr, item.value)) { @@ -390,14 +390,14 @@ void importer_main(Main *bmain, Scene *scene, ViewLayer *view_layer, const FBXIm ufbx_free_scene(fbx); /* Add objects to collection. */ - for (Object *obj : ctx.mapping.el_to_object.values()) { + for (Object *obj : ctx.mapping.imported_objects) { BKE_collection_object_add(bmain, lc->collection, obj); } /* Select objects, sync layers etc. */ BKE_view_layer_base_deselect_all(scene, view_layer); BKE_view_layer_synced_ensure(scene, view_layer); - for (Object *obj : ctx.mapping.el_to_object.values()) { + for (Object *obj : ctx.mapping.imported_objects) { Base *base = BKE_view_layer_base_find(view_layer, obj); BKE_view_layer_base_select_and_set_active(view_layer, base); diff --git a/source/blender/io/fbx/importer/fbx_import_anim.cc b/source/blender/io/fbx/importer/fbx_import_anim.cc index 913c5cd787d..dda440b9871 100644 --- a/source/blender/io/fbx/importer/fbx_import_anim.cc +++ b/source/blender/io/fbx/importer/fbx_import_anim.cc @@ -129,8 +129,15 @@ static Vector gather_animated_properties(const FbxElementMapp } } else { - /* Animating Object property. */ - Object *obj = mapping.el_to_object.lookup_default(fprop.element, nullptr); + /* Animating Bone/Armature/Object property. */ + const ufbx_node *fnode = ufbx_as_node(fprop.element); + Object *obj = nullptr; + if (fnode) { + obj = mapping.bone_to_armature.lookup_default(fnode, nullptr); + } + if (obj == nullptr) { + obj = mapping.el_to_object.lookup_default(fprop.element, nullptr); + } if (obj == nullptr) { continue; } @@ -199,11 +206,10 @@ static void create_transform_curve_desc(const FbxElementMapping &mapping, { /* For animated bones, prepend bone path to animation curve path. */ std::string rna_prefix; - bool is_bone = false; std::string group_name_str = get_fbx_name(anim.fbx_elem->name); const ufbx_node *fnode = ufbx_as_node(anim.fbx_elem); - if (fnode != nullptr && fnode->bone != nullptr && !fnode->bone->is_root) { - is_bone = true; + const bool is_bone = mapping.node_is_blender_bone.contains(fnode); + if (is_bone) { group_name_str = mapping.node_to_name.lookup_default(fnode, ""); rna_prefix = std::string("pose.bones[\"") + group_name_str + "\"]."; } @@ -252,12 +258,10 @@ static void create_transform_curve_data(const FbxElementMapping &mapping, const float anim_offset, FCurve **curves) { - bool is_bone = false; const ufbx_node *fnode = ufbx_as_node(anim.fbx_elem); ufbx_matrix bone_xform = ufbx_identity_matrix; - if (fnode != nullptr && fnode->bone != nullptr && !fnode->bone->is_root) { - is_bone = true; - + const bool is_bone = mapping.node_is_blender_bone.contains(fnode); + if (is_bone) { /* Bone transform curves need to be transformed to the bind transform * in joint-local space: * - Calculate local space bind matrix: inv(parent_bind) * bind @@ -273,10 +277,8 @@ static void create_transform_curve_data(const FbxElementMapping &mapping, } } - bool found = false; - bone_xform = mapping.calc_local_bind_matrix(fnode, world_to_arm, found); + bone_xform = mapping.calc_local_bind_matrix(fnode, world_to_arm); bone_xform = ufbx_matrix_invert(&bone_xform); - BLI_assert_msg(found, "fbx: did not find bind matrix for bone curve"); } int rot_channels = 3; @@ -338,6 +340,7 @@ static void create_transform_curve_data(const FbxElementMapping &mapping, int64_t scale_index = rot_index + rot_channels; int64_t tot_curves = scale_index + 3; for (int64_t i = 0; i < tot_curves; i++) { + BLI_assert_msg(curves[i], "fbx: animation curve was not created successfully"); BKE_fcurve_bezt_resize(curves[i], sorted_key_times.size()); } diff --git a/source/blender/io/fbx/importer/fbx_import_armature.cc b/source/blender/io/fbx/importer/fbx_import_armature.cc index b51c7d61b6c..c2e5e49a52d 100644 --- a/source/blender/io/fbx/importer/fbx_import_armature.cc +++ b/source/blender/io/fbx/importer/fbx_import_armature.cc @@ -40,7 +40,7 @@ struct ArmatureImportContext { Object *create_armature_for_node(const ufbx_node *node); void create_armature_bones(const ufbx_node *node, Object *arm_obj, - Set &arm_bones, + const Set &bone_nodes, EditBone *parent_bone, const ufbx_matrix &parent_mtx, const ufbx_matrix &world_to_arm, @@ -51,25 +51,20 @@ struct ArmatureImportContext { Object *ArmatureImportContext::create_armature_for_node(const ufbx_node *node) { - Object *obj = nullptr; - if (node != nullptr) { - obj = this->mapping.el_to_object.lookup_default(&node->element, nullptr); - if (obj != nullptr && obj->type == OB_ARMATURE) { - return obj; - } - } + BLI_assert_msg(node != nullptr, "fbx: node for armature creation should not be null"); - const char *arm_name = node ? get_fbx_name(node->name, "Armature") : "Armature"; - const char *obj_name = node ? get_fbx_name(node->name, "Armature") : "Armature"; + const char *arm_name = get_fbx_name(node->name, "Armature"); + const char *obj_name = get_fbx_name(node->name, "Armature"); #ifdef FBX_DEBUG_PRINT fprintf(g_debug_file, "create ARMATURE %s\n", arm_name); #endif bArmature *arm = BKE_armature_add(&this->bmain, arm_name); - obj = BKE_object_add_only_object(&this->bmain, OB_ARMATURE, obj_name); + Object *obj = BKE_object_add_only_object(&this->bmain, OB_ARMATURE, obj_name); obj->dtx |= OB_DRAW_IN_FRONT; obj->data = arm; - if (node != nullptr) { + this->mapping.imported_objects.add(obj); + if (!node->is_root) { this->mapping.el_to_object.add(&node->element, obj); if (this->params.use_custom_props) { read_custom_properties(node->props, obj->id, this->params.props_enum_as_string); @@ -104,55 +99,39 @@ Object *ArmatureImportContext::create_armature_for_node(const ufbx_node *node) void ArmatureImportContext::create_armature_bones(const ufbx_node *node, Object *arm_obj, - Set &arm_bones, + const Set &bone_nodes, EditBone *parent_bone, const ufbx_matrix &parent_mtx, const ufbx_matrix &world_to_arm, const float parent_bone_size) { + BLI_assert(node != nullptr && !node->is_root); bArmature *arm = static_cast(arm_obj->data); - arm_bones.add(node); - /* For all bone nodes, record the whole armature as the owning object. */ - this->mapping.el_to_object.add(&node->element, arm_obj); - - ufbx_matrix bone_mtx = ufbx_identity_matrix; - EditBone *bone = nullptr; - if (node->bone == nullptr || node->bone->is_root) { - /* For root bone, the armature object itself is created, so it will not have the EditBone. */ - this->mapping.node_to_name.add(node, BKE_id_name(arm_obj->id)); + /* Create an EditBone. */ + EditBone *bone = ED_armature_ebone_add(arm, get_fbx_name(node->name, "Bone")); + this->mapping.node_to_name.add(node, bone->name); + this->mapping.node_is_blender_bone.add(node); + this->mapping.bone_to_armature.add(node, arm_obj); + bone->flag |= BONE_SELECTED; + bone->parent = parent_bone; + if (node->inherit_mode == UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE) { + bone->inherit_scale_mode = BONE_INHERIT_SCALE_NONE; } - else { - /* For regular non-root bones, create an EditBone. */ - bone = ED_armature_ebone_add(arm, get_fbx_name(node->name, "Bone")); - this->mapping.node_to_name.add(node, bone->name); - bone->flag |= BONE_SELECTED; - bone->parent = parent_bone; - if (node->inherit_mode == UFBX_INHERIT_MODE_IGNORE_PARENT_SCALE) { - bone->inherit_scale_mode = BONE_INHERIT_SCALE_NONE; - } #ifdef FBX_DEBUG_PRINT - fprintf(g_debug_file, - "create BONE %s (parent %s) parent_mtx:\n", - node->name.data, - parent_bone ? parent_bone->name : ""); - print_matrix(parent_mtx); + fprintf(g_debug_file, + "create BONE %s (parent %s) parent_mtx:\n", + node->name.data, + parent_bone ? parent_bone->name : ""); + print_matrix(parent_mtx); #endif - const ufbx_matrix *bind_mtx = this->mapping.bone_to_bind_matrix.lookup_ptr(node); - BLI_assert_msg(bind_mtx, "fbx: did not find bind matrix for bone"); - if (bind_mtx != nullptr) { - bone_mtx = *bind_mtx; - } - } - + ufbx_matrix bone_mtx = this->mapping.get_node_bind_matrix(node); bone_mtx = ufbx_matrix_mul(&world_to_arm, &bone_mtx); bone_mtx.cols[0] = ufbx_vec3_normalize(bone_mtx.cols[0]); bone_mtx.cols[1] = ufbx_vec3_normalize(bone_mtx.cols[1]); bone_mtx.cols[2] = ufbx_vec3_normalize(bone_mtx.cols[2]); - this->mapping.bone_to_armature.add(node, arm_obj); - #ifdef FBX_DEBUG_PRINT fprintf(g_debug_file, " bone_mtx:\n"); print_matrix(bone_mtx); @@ -162,19 +141,16 @@ void ArmatureImportContext::create_armature_bones(const ufbx_node *node, float bone_size = 0.0f; int child_bone_count = 0; for (const ufbx_node *fchild : node->children) { - if (fchild->attrib_type != UFBX_ELEMENT_BONE) { + if (!bone_nodes.contains(fchild)) { continue; } /* Estimate child position from local transform, but if the child * is skinned/posed then use the posed transform instead. */ ufbx_vec3 pos = fchild->local_transform.translation; - if (this->mapping.bone_has_pose_or_skin_matrix.contains(fchild)) { - bool found; - ufbx_matrix local_mtx = this->mapping.calc_local_bind_matrix(fchild, world_to_arm, found); - if (found) { - pos = local_mtx.cols[3]; - } + if (this->mapping.bone_to_bind_matrix.contains(fchild)) { + ufbx_matrix local_mtx = this->mapping.calc_local_bind_matrix(fchild, world_to_arm); + pos = local_mtx.cols[3]; } bone_size += math::length(float3(pos.x, pos.y, pos.z)); child_bone_count++; @@ -187,7 +163,7 @@ void ArmatureImportContext::create_armature_bones(const ufbx_node *node, bone_size = parent_bone_size; /* If we do not have actual pose/skin matrix for this bone, apply local transform onto parent * matrix. */ - if (!this->mapping.bone_has_pose_or_skin_matrix.contains(node)) { + if (!this->mapping.bone_to_bind_matrix.contains(node)) { ufbx_matrix offset_mtx = ufbx_transform_to_matrix(&node->local_transform); bone_mtx = ufbx_matrix_mul(&parent_mtx, &offset_mtx); bone_mtx.cols[0] = ufbx_vec3_normalize(bone_mtx.cols[0]); @@ -204,34 +180,30 @@ void ArmatureImportContext::create_armature_bones(const ufbx_node *node, bone_size = math::max(bone_size, 0.01f); this->mapping.bone_to_length.add(node, bone_size); - if (bone != nullptr) { - bone->tail[0] = 0.0f; - bone->tail[1] = bone_size; - bone->tail[2] = 0.0f; - /* Set bone matrix. */ - float bone_matrix[4][4]; - matrix_to_m44(bone_mtx, bone_matrix); - ED_armature_ebone_from_mat4(bone, bone_matrix); - } + bone->tail[0] = 0.0f; + bone->tail[1] = bone_size; + bone->tail[2] = 0.0f; + /* Set bone matrix. */ + float bone_matrix[4][4]; + matrix_to_m44(bone_mtx, bone_matrix); + ED_armature_ebone_from_mat4(bone, bone_matrix); #ifdef FBX_DEBUG_PRINT - if (bone != nullptr) { - fprintf(g_debug_file, - " length %.3f head (%.3f %.3f %.3f) tail (%.3f %.3f %.3f)\n", - adjf(bone_size), - adjf(bone->head[0]), - adjf(bone->head[1]), - adjf(bone->head[2]), - adjf(bone->tail[0]), - adjf(bone->tail[1]), - adjf(bone->tail[2])); - } + fprintf(g_debug_file, + " length %.3f head (%.3f %.3f %.3f) tail (%.3f %.3f %.3f)\n", + adjf(bone_size), + adjf(bone->head[0]), + adjf(bone->head[1]), + adjf(bone->head[2]), + adjf(bone->tail[0]), + adjf(bone->tail[1]), + adjf(bone->tail[2])); #endif /* Mark bone as connected to parent if head approximately in the same place as parent tail, in * both rest pose and current pose. */ if (parent_bone != nullptr) { - float3 self_head_rest = bone ? float3(bone->head) : float3(0); + float3 self_head_rest(bone->head); float3 par_tail_rest(parent_bone->tail); const float connect_dist = 1.0e-4f; const float connect_dist_sq = connect_dist * connect_dist; @@ -248,7 +220,7 @@ void ArmatureImportContext::create_armature_bones(const ufbx_node *node, float3 par_tail_cur(par_tail_cur_u.x, par_tail_cur_u.y, par_tail_cur_u.z); float dist_sq_cur = math::distance_squared(self_head_cur, par_tail_cur); - if (dist_sq_cur < connect_dist_sq && bone != nullptr) { + if (dist_sq_cur < connect_dist_sq) { /* Connected in both cases. */ bone->flag |= BONE_CONNECTED; } @@ -257,7 +229,7 @@ void ArmatureImportContext::create_armature_bones(const ufbx_node *node, /* Recurse into child bones. */ for (const ufbx_node *fchild : node->children) { - if (fchild->attrib_type != UFBX_ELEMENT_BONE) { + if (!bone_nodes.contains(fchild)) { continue; } @@ -267,66 +239,106 @@ void ArmatureImportContext::create_armature_bones(const ufbx_node *node, !mapping.bone_is_skinned.contains(fchild)) { skip_child = true; - /* We are skipping this bone, but still record as it would be belonging to - * our armature -- so that later code does not try to create an empty for it. */ - this->mapping.el_to_object.add(&fchild->element, arm_obj); + /* We are skipping this bone, but still record it -- + * so that later code does not try to create an empty for it. */ + this->mapping.node_is_blender_bone.add(fchild); } } if (!skip_child) { - create_armature_bones(fchild, arm_obj, arm_bones, bone, bone_mtx, world_to_arm, bone_size); + create_armature_bones(fchild, arm_obj, bone_nodes, bone, bone_mtx, world_to_arm, bone_size); } } } -void ArmatureImportContext::find_armatures(const ufbx_node *node) +/* Need to create armature if we are root bone, or any child is a non-root bone. */ +static bool need_create_armature_for_node(const ufbx_node *node) { - /* Need to create armature if we are root bone, or any child is a non-root bone. */ - bool needs_arm = false; + if (node->bone && node->bone->is_root) { + return true; + } for (const ufbx_node *fchild : node->children) { if (fchild->bone && !fchild->bone->is_root) { - needs_arm = true; - break; + return true; } } - if (node->bone && node->bone->is_root) { - needs_arm = true; - } + return false; +} - /* Create armature if needed. */ +static void find_bones(const ufbx_node *node, Set &r_bones) +{ + if (node->bone != nullptr) { + r_bones.add(node); + } + for (const ufbx_node *child : node->children) { + find_bones(child, r_bones); + } +} + +static void find_fake_bones(const ufbx_node *root_node, + const Set &bones, + Set &r_fake_bones) +{ + for (const ufbx_node *bone_node : bones) { + const ufbx_node *node = bone_node->parent; + while (node != nullptr && node != root_node) { + if (node->bone == nullptr) { + r_fake_bones.add(node); + } + node = node->parent; + } + } +} + +static Set find_all_bones(const ufbx_node *root_node) +{ + /* Find regular FBX bones nodes anywhere under our root armature node. */ + Set bones; + find_bones(root_node, bones); + + /* There might be non-bone nodes in between, e.g. FBX structure being like: + * BoneA -> MeshB -> BoneC -> MeshD. Blender Armature can only contain + * bones, so in this case "MeshB" has to have a bone created for it as well. + * "Fake bones" are any non-bone FBX nodes in between root armature node + * and the actual bone node. */ + Set fake_bones; + find_fake_bones(root_node, bones, fake_bones); + for (const ufbx_node *b : fake_bones) { + bones.add(b); + } + return bones; +} + +void ArmatureImportContext::find_armatures(const ufbx_node *node) +{ + const bool needs_arm = need_create_armature_for_node(node); if (needs_arm) { - Object *arm_obj = nullptr; - if ((node->bone && node->bone->is_root) || (node->attrib_type == UFBX_ELEMENT_EMPTY)) { - arm_obj = this->create_armature_for_node(node); - } - else { - arm_obj = this->create_armature_for_node(nullptr); - } - - /* Create bones in edit mode. */ + /* Create armature. */ + Object *arm_obj = this->create_armature_for_node(node); ufbx_matrix world_to_arm = this->mapping.armature_world_to_arm_pose_matrix.lookup_default( arm_obj, ufbx_identity_matrix); - Set arm_bones; + Set bone_nodes = find_all_bones(node); + + /* Create bones in edit mode. */ bArmature *arm = static_cast(arm_obj->data); ED_armature_to_edit(arm); - if (node->bone && node->bone->is_root) { - create_armature_bones( - node, arm_obj, arm_bones, nullptr, ufbx_identity_matrix, world_to_arm, 1.0f); - } - else { - for (const ufbx_node *fchild : node->children) { - if (fchild->bone) { - create_armature_bones( - fchild, arm_obj, arm_bones, nullptr, ufbx_identity_matrix, world_to_arm, 1.0f); - } + this->mapping.node_to_name.add(node, BKE_id_name(arm_obj->id)); + for (const ufbx_node *fchild : node->children) { + if (bone_nodes.contains(fchild)) { + create_armature_bones( + fchild, arm_obj, bone_nodes, nullptr, ufbx_identity_matrix, world_to_arm, 1.0f); } } + ED_armature_from_edit(&this->bmain, arm); ED_armature_edit_free(arm); - /* Setup pose on the object, and custom properties on the pose bones. */ - for (const ufbx_node *fbone : arm_bones) { + /* Setup pose on the object, and custom properties on the bone pose channels. */ + for (const ufbx_node *fbone : bone_nodes) { + if (!this->mapping.node_is_blender_bone.contains(fbone)) { + continue; /* Blender bone was not created for it (e.g. root bone in some cases). */ + } bPoseChannel *pchan = BKE_pose_channel_find_name( arm_obj->pose, this->mapping.node_to_name.lookup_default(fbone, "").c_str()); if (pchan == nullptr) { @@ -336,35 +348,31 @@ void ArmatureImportContext::find_armatures(const ufbx_node *node) /* For bones that have rest/bind information, put their current transform into * the current pose. */ - if (this->mapping.bone_has_pose_or_skin_matrix.contains(fbone)) { - bool found; - ufbx_matrix bind_local_mtx = this->mapping.calc_local_bind_matrix( - fbone, world_to_arm, found); - if (found) { - ufbx_matrix bind_local_mtx_inv = ufbx_matrix_invert(&bind_local_mtx); - ufbx_transform xform = fbone->local_transform; - if (fbone->node_depth <= 1) { - ufbx_matrix matrix = ufbx_matrix_mul(&world_to_arm, &fbone->node_to_world); - xform = ufbx_matrix_to_transform(&matrix); - } - ufbx_matrix pose_mtx = calc_bone_pose_matrix(xform, *fbone, bind_local_mtx_inv); + if (this->mapping.bone_to_bind_matrix.contains(fbone)) { + ufbx_matrix bind_local_mtx = this->mapping.calc_local_bind_matrix(fbone, world_to_arm); + ufbx_matrix bind_local_mtx_inv = ufbx_matrix_invert(&bind_local_mtx); + ufbx_transform xform = fbone->local_transform; + if (fbone->node_depth <= 1) { + ufbx_matrix matrix = ufbx_matrix_mul(&world_to_arm, &fbone->node_to_world); + xform = ufbx_matrix_to_transform(&matrix); + } + ufbx_matrix pose_mtx = calc_bone_pose_matrix(xform, *fbone, bind_local_mtx_inv); - float pchan_matrix[4][4]; - matrix_to_m44(pose_mtx, pchan_matrix); - BKE_pchan_apply_mat4(pchan, pchan_matrix, false); + float pchan_matrix[4][4]; + matrix_to_m44(pose_mtx, pchan_matrix); + BKE_pchan_apply_mat4(pchan, pchan_matrix, false); #ifdef FBX_DEBUG_PRINT - fprintf(g_debug_file, "set POSE matrix of %s matrix_basis:\n", fbone->name.data); - print_matrix(pose_mtx); + fprintf(g_debug_file, "set POSE matrix of %s matrix_basis:\n", fbone->name.data); + print_matrix(pose_mtx); #endif - } } } } - /* Recurse into non-bone or root-bone children. */ + /* Recurse into children that have not been turned into bones yet. */ for (const ufbx_node *fchild : node->children) { - if (fchild->bone == nullptr || fchild->bone->is_root) { + if (!this->mapping.node_is_blender_bone.contains(fchild)) { this->find_armatures(fchild); } } @@ -384,7 +392,6 @@ void ArmatureImportContext::calc_bone_bind_matrices() for (const ufbx_bone_pose &bone_pose : fpose->bone_poses) { const ufbx_matrix &bind_matrix = bone_pose.bone_to_world; this->mapping.bone_to_bind_matrix.add_overwrite(bone_pose.bone_node, bind_matrix); - this->mapping.bone_has_pose_or_skin_matrix.add(bone_pose.bone_node); #ifdef FBX_DEBUG_PRINT fprintf(g_debug_file, "bone POSE matrix %s\n", bone_pose.bone_node->name.data); print_matrix(bind_matrix); @@ -396,23 +403,10 @@ void ArmatureImportContext::calc_bone_bind_matrices() for (const ufbx_skin_cluster *fbone : fskin->clusters) { const ufbx_matrix &bind_matrix = fbone->bind_to_world; this->mapping.bone_to_bind_matrix.add_overwrite(fbone->bone_node, bind_matrix); - this->mapping.bone_has_pose_or_skin_matrix.add(fbone->bone_node); this->mapping.bone_is_skinned.add(fbone->bone_node); #ifdef FBX_DEBUG_PRINT fprintf(g_debug_file, "bone SKIN matrix %s\n", fbone->bone_node->name.data); print_matrix(bind_matrix); -#endif - } - } - - for (const ufbx_bone *fbone : this->fbx.bones) { - if (fbone->instances.count != 0) { - const ufbx_node *bone_node = fbone->instances[0]; - const ufbx_matrix &bind_matrix = bone_node->node_to_world; - this->mapping.bone_to_bind_matrix.add(bone_node, bind_matrix); -#ifdef FBX_DEBUG_PRINT - fprintf(g_debug_file, "bone NODE matrix %s\n", bone_node->name.data); - print_matrix(bind_matrix); #endif } } @@ -425,11 +419,6 @@ void import_armatures(Main &bmain, { ArmatureImportContext context(bmain, fbx, params, mapping); context.calc_bone_bind_matrices(); - - /* Create blender armatures at: - * - "Root" bones, - * - Bones with an empty parent, - * - For bones without a parent or a non-empty parent, create an armature above them. */ context.find_armatures(fbx.root_node); } diff --git a/source/blender/io/fbx/importer/fbx_import_mesh.cc b/source/blender/io/fbx/importer/fbx_import_mesh.cc index 308720336fc..b3a0de69dc4 100644 --- a/source/blender/io/fbx/importer/fbx_import_mesh.cc +++ b/source/blender/io/fbx/importer/fbx_import_mesh.cc @@ -21,6 +21,7 @@ #include "BLI_ordered_edge.hh" #include "BLI_string.h" #include "BLI_task.hh" +#include "BLI_vector_set.hh" #include "BLT_translation.hh" @@ -36,15 +37,10 @@ namespace blender::io::fbx { static constexpr const char *temp_custom_normals_name = "fbx_temp_custom_normals"; -static const ufbx_skin_deformer *get_skin_from_mesh(const ufbx_mesh *mesh) +static bool is_skin_deformer_usable(const ufbx_mesh *mesh, const ufbx_skin_deformer *skin) { - if (mesh->skin_deformers.count > 0) { - const ufbx_skin_deformer *skin = mesh->skin_deformers[0]; - if (skin != nullptr && mesh->num_vertices > 0 && skin->vertices.count == mesh->num_vertices) { - return skin; - } - } - return nullptr; + return mesh != nullptr && skin != nullptr && skin->clusters.count > 0 && + mesh->num_vertices > 0 && skin->vertices.count == mesh->num_vertices; } static void import_vertex_positions(const ufbx_mesh *fmesh, Mesh *mesh) @@ -275,34 +271,65 @@ static bool import_normals_into_temp_attribute(const ufbx_mesh *fmesh, return true; } -static void import_skin_vertex_groups(const ufbx_mesh *fmesh, - const ufbx_skin_deformer *skin, +static VectorSet get_skin_bone_name_set(const FbxElementMapping &mapping, + const ufbx_mesh *fmesh) +{ + VectorSet name_set; + for (const ufbx_skin_deformer *skin : fmesh->skin_deformers) { + if (!is_skin_deformer_usable(fmesh, skin)) { + continue; + } + + for (const ufbx_skin_cluster *cluster : skin->clusters) { + if (cluster->num_weights == 0) { + continue; + } + + std::string bone_name = mapping.node_to_name.lookup_default(cluster->bone_node, ""); + name_set.add(bone_name); + } + } + return name_set; +} + +static void import_skin_vertex_groups(const FbxElementMapping &mapping, + const ufbx_mesh *fmesh, Mesh *mesh) { - /* We need to build mapping from cluster indices to non-empty - * cluster indices. */ - Vector skin_cluster_to_nonempty_cluster_index(skin->clusters.count, -1); - int cluster_counter = 0; - for (int i = 0; i < skin->clusters.count; i++) { - if (skin->clusters[i]->num_weights != 0) { - skin_cluster_to_nonempty_cluster_index[i] = cluster_counter; - cluster_counter++; - } + if (fmesh->skin_deformers.count == 0) { + return; + } + + /* A single mesh can be skinned by several armatures, so we need to build bone (vertex group) + * name set, taking all skin deformers into account. */ + VectorSet bone_set = get_skin_bone_name_set(mapping, fmesh); + if (bone_set.is_empty()) { + return; } MutableSpan dverts = mesh->deform_verts_for_write(); - for (int i = 0; i < fmesh->num_vertices; i++) { - const ufbx_skin_vertex &fvertex = skin->vertices[i]; - int num_weights = fvertex.num_weights; - if (num_weights > 0) { - dverts[i].dw = MEM_malloc_arrayN(num_weights, __func__); - dverts[i].totweight = num_weights; - for (int j = 0; j < num_weights; j++) { - const ufbx_skin_weight &fweight = skin->weights[fvertex.weight_begin + j]; - const int bone_index = skin_cluster_to_nonempty_cluster_index[fweight.cluster_index]; - const bool valid = bone_index >= 0; - dverts[i].dw[j].def_nr = valid ? bone_index : 0; - dverts[i].dw[j].weight = valid ? fweight.weight : 0.0f; + + for (const ufbx_skin_deformer *skin : fmesh->skin_deformers) { + if (!is_skin_deformer_usable(fmesh, skin)) { + continue; + } + + for (const ufbx_skin_cluster *cluster : skin->clusters) { + if (cluster->num_weights == 0) { + continue; + } + std::string bone_name = mapping.node_to_name.lookup_default(cluster->bone_node, ""); + const int group_index = bone_set.index_of_try(bone_name); + if (group_index < 0) { + continue; + } + + for (int i = 0; i < cluster->num_weights; i++) { + const int vertex = cluster->vertices[i]; + if (vertex < dverts.size()) { + MDeformWeight *dw = BKE_defvert_ensure_index(&dverts[vertex], group_index); + dw->weight = cluster->weights[i]; + } } } } @@ -445,10 +472,7 @@ void import_meshes(Main &bmain, * data as custom normals after the validation. */ has_custom_normals = import_normals_into_temp_attribute(fmesh, mesh, attributes); } - const ufbx_skin_deformer *skin = get_skin_from_mesh(fmesh); - if (skin != nullptr) { - import_skin_vertex_groups(fmesh, skin, mesh); - } + import_skin_vertex_groups(mapping, fmesh, mesh); /* Validate if needed. */ if (params.validate_meshes) { @@ -481,7 +505,6 @@ void import_meshes(Main &bmain, } const ufbx_mesh *fmesh = fbx.meshes[index]; BLI_assert(fmesh != nullptr); - const ufbx_skin_deformer *skin = get_skin_from_mesh(fmesh); Mesh *mesh_main = static_cast( BKE_object_obdata_add_from_type(&bmain, OB_MESH, get_fbx_name(fmesh->name, "Mesh"))); @@ -509,50 +532,61 @@ void import_meshes(Main &bmain, bool matrix_already_set = false; /* Skinned mesh. */ - if (skin != nullptr && skin->clusters.count > 0) { - Object *parent_to_arm = nullptr; + if (fmesh->skin_deformers.count > 0) { /* Add vertex groups to the object. */ - for (const ufbx_skin_cluster *fcluster : skin->clusters) { - if (fcluster->num_weights == 0) { /* Do not add groups for empty clusters. */ - continue; - } - if (parent_to_arm == nullptr) { - parent_to_arm = mapping.bone_to_armature.lookup_default(fcluster->bone_node, nullptr); - } - std::string bone_name = mapping.node_to_name.lookup_default(fcluster->bone_node, ""); - BKE_object_defgroup_add_name(obj, bone_name.c_str()); + VectorSet bone_set = get_skin_bone_name_set(mapping, fmesh); + for (const std::string &name : bone_set) { + BKE_object_defgroup_add_name(obj, name.c_str()); } - /* Add armature modifier. */ - if (parent_to_arm) { - ModifierData *md = BKE_modifier_new(eModifierType_Armature); - STRNCPY(md->name, BKE_id_name(parent_to_arm->id)); - BLI_addtail(&obj->modifiers, md); - BKE_modifiers_persistent_uid_init(*obj, *md); - ArmatureModifierData *ad = reinterpret_cast(md); - ad->object = parent_to_arm; - obj->parent = parent_to_arm; + /* Add armature modifiers for each skin deformer. */ + for (const ufbx_skin_deformer *skin : fmesh->skin_deformers) { + if (!is_skin_deformer_usable(fmesh, skin)) { + continue; + } + Object *arm_obj = nullptr; + for (const ufbx_skin_cluster *cluster : skin->clusters) { + if (cluster->num_weights == 0) { + continue; + } + arm_obj = mapping.bone_to_armature.lookup_default(cluster->bone_node, nullptr); + if (arm_obj != nullptr) { + break; + } + } + /* Add armature modifier. */ + if (arm_obj != nullptr) { + ModifierData *md = BKE_modifier_new(eModifierType_Armature); + STRNCPY(md->name, BKE_id_name(arm_obj->id)); + BLI_addtail(&obj->modifiers, md); + BKE_modifiers_persistent_uid_init(*obj, *md); + ArmatureModifierData *ad = reinterpret_cast(md); + ad->object = arm_obj; - /* We are setting mesh parent to the armature, so set the matrix that is - * armature-local. Note that the matrix needs to be relative to the FBX - * node matrix (not the root bone pose matrix). */ - ufbx_matrix world_to_arm = mapping.armature_world_to_arm_node_matrix.lookup_default( - parent_to_arm, ufbx_identity_matrix); - ufbx_matrix world_to_arm_pose = mapping.armature_world_to_arm_pose_matrix.lookup_default( - parent_to_arm, ufbx_identity_matrix); + if (!matrix_already_set) { + matrix_already_set = true; + obj->parent = arm_obj; - ufbx_matrix mtx = ufbx_matrix_mul(&world_to_arm, &node->geometry_to_world); - ufbx_matrix_to_obj(mtx, obj); - matrix_already_set = true; + /* We are setting mesh parent to the armature, so set the matrix that is + * armature-local. Note that the matrix needs to be relative to the FBX + * node matrix (not the root bone pose matrix). */ + ufbx_matrix world_to_arm = mapping.armature_world_to_arm_node_matrix.lookup_default( + arm_obj, ufbx_identity_matrix); + ufbx_matrix world_to_arm_pose = mapping.armature_world_to_arm_pose_matrix + .lookup_default(arm_obj, ufbx_identity_matrix); - /* Setup parent inverse matrix of the mesh, to account for the mesh possibly being in - * different bind pose than what the node is at. */ - ufbx_matrix mtx_inv = ufbx_matrix_invert(&mtx); - ufbx_matrix mtx_world = mapping.bone_to_bind_matrix.lookup_default( - node, node->geometry_to_world); - ufbx_matrix mtx_parent_inverse = ufbx_matrix_mul(&mtx_world, &mtx_inv); - mtx_parent_inverse = ufbx_matrix_mul(&world_to_arm_pose, &mtx_parent_inverse); - matrix_to_m44(mtx_parent_inverse, obj->parentinv); + ufbx_matrix mtx = ufbx_matrix_mul(&world_to_arm, &node->geometry_to_world); + ufbx_matrix_to_obj(mtx, obj); + + /* Setup parent inverse matrix of the mesh, to account for the mesh possibly being in + * different bind pose than what the node is at. */ + ufbx_matrix mtx_inv = ufbx_matrix_invert(&mtx); + ufbx_matrix mtx_world = mapping.get_node_bind_matrix(node); + ufbx_matrix mtx_parent_inverse = ufbx_matrix_mul(&mtx_world, &mtx_inv); + mtx_parent_inverse = ufbx_matrix_mul(&world_to_arm_pose, &mtx_parent_inverse); + matrix_to_m44(mtx_parent_inverse, obj->parentinv); + } + } } } @@ -610,6 +644,7 @@ void import_meshes(Main &bmain, node_matrix_to_obj(node, obj, mapping); } mapping.el_to_object.add(&node->element, obj); + mapping.imported_objects.add(obj); } } } diff --git a/source/blender/io/fbx/importer/fbx_import_util.cc b/source/blender/io/fbx/importer/fbx_import_util.cc index 1d05d42ebf0..df1b1d2c7c5 100644 --- a/source/blender/io/fbx/importer/fbx_import_util.cc +++ b/source/blender/io/fbx/importer/fbx_import_util.cc @@ -86,12 +86,20 @@ void node_matrix_to_obj(const ufbx_node *node, Object *obj, const FbxElementMapp /* Handle case of an object parented to a bone: need to set * bone as parent, and make transform be at the end of the bone. */ const ufbx_node *parbone = node->parent; - if (obj->parent == nullptr && parbone && parbone->bone) { + if (obj->parent == nullptr && parbone && mapping.node_is_blender_bone.contains(parbone)) { Object *arm = mapping.bone_to_armature.lookup_default(parbone, nullptr); if (arm != nullptr) { ufbx_matrix offset_mtx = ufbx_identity_matrix; offset_mtx.cols[3].y = -mapping.bone_to_length.lookup_default(parbone, 0.0); - mtx = ufbx_matrix_mul(&offset_mtx, &mtx); + if (mapping.node_is_blender_bone.contains(node)) { + /* The node itself is a "fake bone", in which case parent it to the matching + * fake bone, and matrix is just what puts transform at the bone tail. */ + parbone = node; + mtx = offset_mtx; + } + else { + mtx = ufbx_matrix_mul(&offset_mtx, &mtx); + } obj->parent = arm; obj->partype = PARBONE; diff --git a/source/blender/io/fbx/importer/fbx_import_util.hh b/source/blender/io/fbx/importer/fbx_import_util.hh index 7c27bef65af..4a3f4deac3a 100644 --- a/source/blender/io/fbx/importer/fbx_import_util.hh +++ b/source/blender/io/fbx/importer/fbx_import_util.hh @@ -24,6 +24,7 @@ namespace blender::io::fbx { const char *get_fbx_name(const ufbx_string &name, const char *def = "Untitled"); struct FbxElementMapping { + Set imported_objects; Map el_to_object; Map el_to_shape_key; Map mat_to_material; @@ -35,40 +36,34 @@ struct FbxElementMapping { Map armature_world_to_arm_pose_matrix; Map armature_world_to_arm_node_matrix; + /* Which FBX bone nodes got turned into actual armature bones (not all of them + * always are; in some cases root bone is the armature object itself). */ + Set node_is_blender_bone; + /* Mapping of ufbx node to object name used within blender. If names are too long * or duplicate, they might not match what was in FBX file. */ Map node_to_name; /* Bone node to "bind matrix", i.e. matrix that transforms from bone (in skin bind pose) local - * space to world space. */ + * space to world space. This records bone pose or skin cluster bind matrix (skin cluster taking + * precedence if it exists). */ Map bone_to_bind_matrix; Map bone_to_length; - /* Which bones actually have pose or skin cluster bind matrices in the FBX file (the others - * would just use their world transform). */ - Set bone_has_pose_or_skin_matrix; Set bone_is_skinned; ufbx_matrix global_conv_matrix; - //@TODO: these could be precalculated once - ufbx_matrix calc_local_bind_matrix(const ufbx_node *bone_node, - const ufbx_matrix &world_to_arm, - bool &r_found) const + ufbx_matrix get_node_bind_matrix(const ufbx_node *node) const { - r_found = false; - const ufbx_matrix *bind_mtx = this->bone_to_bind_matrix.lookup_ptr(bone_node); - if (bind_mtx == nullptr) { - return ufbx_identity_matrix; - } - r_found = true; - ufbx_matrix res = *bind_mtx; - - const ufbx_matrix *parent_mtx = nullptr; - if (bone_node->parent != nullptr) { - parent_mtx = this->bone_to_bind_matrix.lookup_ptr(bone_node->parent); - } + return this->bone_to_bind_matrix.lookup_default(node, node->geometry_to_world); + } + ufbx_matrix calc_local_bind_matrix(const ufbx_node *bone_node, + const ufbx_matrix &world_to_arm) const + { + ufbx_matrix res = this->get_node_bind_matrix(bone_node); ufbx_matrix parent_inv_mtx; - if (parent_mtx) { - parent_inv_mtx = ufbx_matrix_invert(parent_mtx); + if (bone_node->parent != nullptr && !bone_node->parent->is_root) { + ufbx_matrix parent_mtx = this->get_node_bind_matrix(bone_node->parent); + parent_inv_mtx = ufbx_matrix_invert(&parent_mtx); } else { parent_inv_mtx = world_to_arm; diff --git a/tests/files/io_tests/fbx/issue45171_double_armature.fbx b/tests/files/io_tests/fbx/issue45171_double_armature.fbx new file mode 100644 index 00000000000..7282bc82807 --- /dev/null +++ b/tests/files/io_tests/fbx/issue45171_double_armature.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb3edde094674ce46c83df72acbecb6aae5eb87586aec72d8890e48dea14b609 +size 28604 diff --git a/tests/files/io_tests/fbx/reference/issue127388_imported_bones_too_large.txt b/tests/files/io_tests/fbx/reference/issue127388_imported_bones_too_large.txt index dfe9a39a55d..b4c433a1853 100644 --- a/tests/files/io_tests/fbx/reference/issue127388_imported_bones_too_large.txt +++ b/tests/files/io_tests/fbx/reference/issue127388_imported_bones_too_large.txt @@ -826,7 +826,7 @@ 0.635 0.405 -0.658 5.409 0.535 -0.845 -0.003 7.713 -0.557 -0.350 -0.753 -5.440 - - bone '(64)attach#1C0#0' h:(0.000, 0.000, 9.000) t:(0.000, 3.025, 9.000) radius h:0.100 t:0.050 + - bone '(64)attach#1C0#0' h:(0.000, 0.000, 9.000) t:(0.000, 1.000, 9.000) radius h:0.100 t:0.050 1.000 0.000 0.000 0.000 0.000 1.000 0.000 0.000 0.000 0.000 1.000 9.000 diff --git a/tests/files/io_tests/fbx/reference/issue45171_double_armature.txt b/tests/files/io_tests/fbx/reference/issue45171_double_armature.txt new file mode 100644 index 00000000000..8eb40e8709c --- /dev/null +++ b/tests/files/io_tests/fbx/reference/issue45171_double_armature.txt @@ -0,0 +1,85 @@ +==== Meshes: 1 +- Mesh 'Cubes' vtx:16 face:12 loop:48 edge:24 + - 1 3 2 0 3 ... 12 13 15 11 9 + - 1/3 2/3 0/2 0/1 3/7 ... 13/15 12/13 12/14 9/13 8/12 + - attr 'position' FLOAT_VECTOR POINT + - (-0.484, -0.484, -0.484) + - (-0.484, -0.484, 0.484) + - (-0.484, 0.484, -0.484) + ... + - (-0.446, -1.275, 1.551) + - (-0.446, -0.307, 0.583) + - (-0.446, -0.307, 1.551) + - attr 'sharp_edge' BOOLEAN EDGE + - 1 1 1 1 1 ... 1 1 1 1 1 + - attr 'custom_normal' INT16_2D CORNER + - (0, 0) + - (0, 0) + - (0, 0) + ... + - (0, 0) + - (0, 0) + - (0, 0) + - vertex groups: + - 1=1.000 + - 1=1.000 + - 1=1.000 + - 1=1.000 + - 1=1.000 + +==== Objects: 5 +- Obj 'Armature' ARMATURE data:'Armature' + - pos 0.000, 0.000, 0.000 + - rot 0.000, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 +- Obj 'Armature.001' ARMATURE data:'Armature.001' + - pos -0.737, -0.813, 0.695 + - rot 0.000, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 +- Obj 'Camera' CAMERA data:'Camera' + - pos 7.481, -6.508, 5.344 + - rot 2.032, -0.011, -2.327 (XYZ) + - scl 1.000, 1.000, 1.000 +- Obj 'Cubes' MESH data:'Cubes' par:'Armature.001' + - pos 0.737, 0.813, -0.695 + - rot 0.000, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 + - 2 vertex groups + - 'Bone_Top' 'Bone_Bottom' + - 2 modifiers + - ARMATURE 'Armature.001' + - ARMATURE 'Armature' +- Obj 'Lamp' LIGHT data:'Lamp' + - pos 4.076, 1.005, 5.904 + - rot -2.491, 0.055, 1.866 (XYZ) + - scl 1.000, 1.000, 1.000 + +==== Cameras: 1 +- Cam 'Camera' PERSP lens:35.0 MILLIMETERS near:0.100 far:100.0 orthosize:7.3 + - fov 0.858 (h 0.858 v 0.503) + - sensor 32.0x18.0 shift 0.000,0.000 + +==== Lights: 1 +- Light 'Lamp' POINT col:(1.000, 1.000, 1.000) energy:1.000 + +==== Armatures: 2 +- Armature 'Armature' 2 bones + - bone 'Bone_Bottom' h:(0.000, 0.000, 0.000) t:(0.000, 0.000, 1.000) radius h:0.100 t:0.050 + 1.000 0.000 0.000 0.000 + 0.000 0.000 -1.000 0.000 + 0.000 1.000 0.000 0.000 + - bone 'Bone_Bottom_end' parent:'Bone_Bottom' h:(0.000, 0.000, 0.000) t:(0.000, 1.000, 0.000) connect radius h:0.100 t:0.050 + 1.000 0.000 0.000 0.000 + 0.000 0.000 -1.000 0.000 + 0.000 1.000 0.000 1.000 + +- Armature 'Armature.001' 2 bones + - bone 'Bone_Top' h:(-0.193, 0.022, 0.372) t:(-0.193, 0.022, 1.372) radius h:0.100 t:0.050 + 1.000 0.000 0.000 -0.193 + 0.000 0.000 -1.000 0.022 + 0.000 1.000 0.000 0.372 + - bone 'Bone_Top_end' parent:'Bone_Top' h:(0.000, 0.000, 0.000) t:(0.000, 1.000, 0.000) connect radius h:0.100 t:0.050 + 1.000 0.000 0.000 -0.193 + 0.000 0.000 -1.000 0.022 + 0.000 1.000 0.000 1.372 + diff --git a/tests/files/io_tests/fbx/reference/issue75277_fbx75_bone_error.txt b/tests/files/io_tests/fbx/reference/issue75277_fbx75_bone_error.txt index f4a3bb1435d..9cf43f7d3b3 100644 --- a/tests/files/io_tests/fbx/reference/issue75277_fbx75_bone_error.txt +++ b/tests/files/io_tests/fbx/reference/issue75277_fbx75_bone_error.txt @@ -10,17 +10,17 @@ - (0.000, 1.086, 0.110) ==== Objects: 2 -- Obj 'Armature' ARMATURE data:'Armature' +- Obj 'rb00_battle_00' ARMATURE data:'rb00_battle_00' - pos 0.000, 0.000, 0.000 - rot 1.571, 0.000, 0.000 (XYZ) - scl 0.010, 0.010, 0.010 -- Obj 'rb00_battle_00' MESH data:'Mesh' +- Obj 'rb00_battle_00.001' MESH data:'Mesh' - pos 0.000, 0.000, 0.000 - rot 1.571, 0.000, 0.000 (XYZ) - scl 0.010, 0.010, 0.010 ==== Armatures: 1 -- Armature 'Armature' 1 bones +- Armature 'rb00_battle_00' 1 bones - bone 'Character1_Reference' h:(0.000, 0.000, 0.000) t:(0.000, 1.000, 0.000) radius h:0.100 t:0.050 1.000 0.000 0.000 0.000 0.000 1.000 0.000 0.000 diff --git a/tests/files/io_tests/fbx/reference/synthetic_mesh_between_bone_nodes.txt b/tests/files/io_tests/fbx/reference/synthetic_mesh_between_bone_nodes.txt new file mode 100644 index 00000000000..7049966ae44 --- /dev/null +++ b/tests/files/io_tests/fbx/reference/synthetic_mesh_between_bone_nodes.txt @@ -0,0 +1,46 @@ +==== Meshes: 2 +- Mesh 'MeshUnderChild' vtx:3 face:1 loop:3 edge:3 + - 0 1 2 + - 0/2 0/1 1/2 + - attr 'position' FLOAT_VECTOR POINT + - (0.000, 0.000, 0.000) + - (1.000, 0.000, 0.000) + - (0.000, 1.000, 0.000) + +- Mesh 'MeshUnderRoot' vtx:3 face:1 loop:3 edge:3 + - 0 1 2 + - 0/2 0/1 1/2 + - attr 'position' FLOAT_VECTOR POINT + - (0.000, 0.000, 0.000) + - (1.000, 0.000, 0.000) + - (0.000, 1.000, 0.000) + +==== Objects: 3 +- Obj 'Armature' ARMATURE data:'Armature' + - pos 0.000, 0.000, 0.000 + - rot 1.571, 0.000, 0.000 (XYZ) + - scl 0.010, 0.010, 0.010 +- Obj 'MeshUnderChild' MESH data:'MeshUnderChild' par:'Armature' par_type:BONE par_bone:'ChildBone' + - pos 0.000, -1.000, -1.000 + - rot 0.000, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 +- Obj 'MeshUnderRoot' MESH data:'MeshUnderRoot' par:'Armature' par_type:BONE par_bone:'MeshUnderRoot' + - pos 0.000, -1.000, 0.000 + - rot 0.000, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 + +==== Armatures: 1 +- Armature 'Armature' 3 bones + - bone 'ChildBone' parent:'MeshUnderRoot' h:(0.000, -2.000, 0.000) t:(0.000, -1.000, 0.000) radius h:0.100 t:0.050 + 1.000 0.000 0.000 0.000 + 0.000 1.000 0.000 1.000 + 0.000 0.000 1.000 3.000 + - bone 'MeshUnderRoot' parent:'RootBone' h:(-1.000, -1.000, 0.000) t:(-1.000, 0.000, 0.000) radius h:0.100 t:0.050 + 1.000 0.000 0.000 0.000 + 0.000 1.000 0.000 2.000 + 0.000 0.000 1.000 3.000 + - bone 'RootBone' h:(1.000, 2.000, 3.000) t:(1.000, 3.000, 3.000) radius h:0.100 t:0.050 + 1.000 0.000 0.000 1.000 + 0.000 1.000 0.000 2.000 + 0.000 0.000 1.000 3.000 + diff --git a/tests/files/io_tests/fbx/reference/synthetic_mesh_between_bone_nodes_animated.txt b/tests/files/io_tests/fbx/reference/synthetic_mesh_between_bone_nodes_animated.txt new file mode 100644 index 00000000000..d42f0d98fa1 --- /dev/null +++ b/tests/files/io_tests/fbx/reference/synthetic_mesh_between_bone_nodes_animated.txt @@ -0,0 +1,113 @@ +==== Meshes: 2 +- Mesh 'MeshUnderChild' vtx:3 face:1 loop:3 edge:3 + - 0 1 2 + - 0/2 0/1 1/2 + - attr 'position' FLOAT_VECTOR POINT + - (0.000, 0.000, 0.000) + - (1.000, 0.000, 0.000) + - (0.000, 1.000, 0.000) + +- Mesh 'MeshUnderRoot' vtx:3 face:1 loop:3 edge:3 + - 0 1 2 + - 0/2 0/1 1/2 + - attr 'position' FLOAT_VECTOR POINT + - (0.000, 0.000, 0.000) + - (1.000, 0.000, 0.000) + - (0.000, 1.000, 0.000) + +==== Objects: 3 +- Obj 'Armature' ARMATURE data:'Armature' + - pos 0.000, 0.000, 0.000 + - rot 1.571, 0.000, 0.000 (XYZ) + - scl 0.010, 0.010, 0.010 + - anim act:AnimStack slot:OBArmature blend:REPLACE drivers:0 +- Obj 'MeshUnderChild' MESH data:'MeshUnderChild' par:'Armature' par_type:BONE par_bone:'ChildBone' + - pos 0.000, -1.000, -1.000 + - rot 0.000, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 +- Obj 'MeshUnderRoot' MESH data:'MeshUnderRoot' par:'Armature' par_type:BONE par_bone:'MeshUnderRoot' + - pos 0.000, -1.000, 0.000 + - rot 0.000, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 + +==== Actions: 1 +- Action 'AnimStack' curverange:(1.0 .. 61.0) layers:1 +- ActionLayer Layer strips:1 + - Keyframe strip channelbags:1 + - Channelbag slot 'OBArmature' curves:30 + - fcu 'pose.bones["ChildBone"].location[0]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'ChildBone' + - (1.000, 0.000) lh:(-9.000, 0.000 AUTO_CLAMPED) rh:(11.000, 0.000 AUTO_CLAMPED) + - (31.000, 0.000) lh:(21.000, 0.000 AUTO_CLAMPED) rh:(41.000, 0.000 AUTO_CLAMPED) + - (61.000, 0.000) lh:(51.000, 0.000 AUTO_CLAMPED) rh:(71.000, 0.000 AUTO_CLAMPED) + - fcu 'pose.bones["ChildBone"].location[1]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'ChildBone' + - (1.000, 0.000) lh:(-9.000, 0.000 AUTO_CLAMPED) rh:(11.000, 0.000 AUTO_CLAMPED) + - (31.000, 0.000) lh:(21.000, 0.000 AUTO_CLAMPED) rh:(41.000, 0.000 AUTO_CLAMPED) + - (61.000, 0.000) lh:(51.000, 0.000 AUTO_CLAMPED) rh:(71.000, 0.000 AUTO_CLAMPED) + - fcu 'pose.bones["ChildBone"].location[2]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'ChildBone' + - (1.000, 0.000) lh:(-9.000, 0.000 AUTO_CLAMPED) rh:(11.000, 0.000 AUTO_CLAMPED) + - (31.000, 0.000) lh:(21.000, 0.000 AUTO_CLAMPED) rh:(41.000, 0.000 AUTO_CLAMPED) + - (61.000, 0.000) lh:(51.000, 0.000 AUTO_CLAMPED) rh:(71.000, 0.000 AUTO_CLAMPED) + - fcu 'pose.bones["ChildBone"].rotation_quaternion[0]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'ChildBone' + - (1.000, 1.000) lh:(-9.000, 1.000 AUTO_CLAMPED) rh:(11.000, 1.000 AUTO_CLAMPED) + - (31.000, 0.966) lh:(21.000, 0.999 AUTO_CLAMPED) rh:(41.000, 0.932 AUTO_CLAMPED) + - (61.000, 0.866) lh:(51.000, 0.866 AUTO_CLAMPED) rh:(71.000, 0.866 AUTO_CLAMPED) + - fcu 'pose.bones["ChildBone"].rotation_quaternion[1]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'ChildBone' + - (1.000, 0.000) lh:(-9.000, 0.000 AUTO_CLAMPED) rh:(11.000, 0.000 AUTO_CLAMPED) + - (31.000, 0.000) lh:(21.000, 0.000 AUTO_CLAMPED) rh:(41.000, 0.000 AUTO_CLAMPED) + - (61.000, 0.000) lh:(51.000, 0.000 AUTO_CLAMPED) rh:(71.000, 0.000 AUTO_CLAMPED) + - fcu 'pose.bones["ChildBone"].rotation_quaternion[2]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'ChildBone' + - (1.000, 0.000) lh:(-9.000, 0.000 AUTO_CLAMPED) rh:(11.000, 0.000 AUTO_CLAMPED) + - (31.000, 0.000) lh:(21.000, 0.000 AUTO_CLAMPED) rh:(41.000, 0.000 AUTO_CLAMPED) + - (61.000, 0.000) lh:(51.000, 0.000 AUTO_CLAMPED) rh:(71.000, 0.000 AUTO_CLAMPED) + - fcu 'pose.bones["ChildBone"].rotation_quaternion[3]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'ChildBone' + - (1.000, 0.000) lh:(-9.000, 0.000 AUTO_CLAMPED) rh:(11.000, 0.000 AUTO_CLAMPED) + - (31.000, 0.259) lh:(21.000, 0.134 AUTO_CLAMPED) rh:(41.000, 0.384 AUTO_CLAMPED) + - (61.000, 0.500) lh:(51.000, 0.500 AUTO_CLAMPED) rh:(71.000, 0.500 AUTO_CLAMPED) + - fcu 'pose.bones["ChildBone"].scale[0]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'ChildBone' + - (1.000, 1.000) lh:(-9.000, 1.000 AUTO_CLAMPED) rh:(11.000, 1.000 AUTO_CLAMPED) + - (31.000, 1.000) lh:(21.000, 1.000 AUTO_CLAMPED) rh:(41.000, 1.000 AUTO_CLAMPED) + - (61.000, 1.000) lh:(51.000, 1.000 AUTO_CLAMPED) rh:(71.000, 1.000 AUTO_CLAMPED) + - fcu 'pose.bones["ChildBone"].scale[1]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'ChildBone' + - (1.000, 1.000) lh:(-9.000, 1.000 AUTO_CLAMPED) rh:(11.000, 1.000 AUTO_CLAMPED) + - (31.000, 1.000) lh:(21.000, 1.000 AUTO_CLAMPED) rh:(41.000, 1.000 AUTO_CLAMPED) + - (61.000, 1.000) lh:(51.000, 1.000 AUTO_CLAMPED) rh:(71.000, 1.000 AUTO_CLAMPED) + - fcu 'pose.bones["ChildBone"].scale[2]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'ChildBone' + - (1.000, 1.000) lh:(-9.000, 1.000 AUTO_CLAMPED) rh:(11.000, 1.000 AUTO_CLAMPED) + - (31.000, 1.000) lh:(21.000, 1.000 AUTO_CLAMPED) rh:(41.000, 1.000 AUTO_CLAMPED) + - (61.000, 1.000) lh:(51.000, 1.000 AUTO_CLAMPED) rh:(71.000, 1.000 AUTO_CLAMPED) + - fcu 'pose.bones["MeshUnderRoot"].location[0]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'MeshUnderRoot' + - (1.000, 0.000) lh:(-9.000, 0.000 AUTO_CLAMPED) rh:(11.000, 0.000 AUTO_CLAMPED) + - (31.000, 0.000) lh:(21.000, 0.000 AUTO_CLAMPED) rh:(41.000, 0.000 AUTO_CLAMPED) + - (61.000, 0.000) lh:(51.000, 0.000 AUTO_CLAMPED) rh:(71.000, 0.000 AUTO_CLAMPED) + - fcu 'pose.bones["MeshUnderRoot"].location[1]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'MeshUnderRoot' + - (1.000, 0.000) lh:(-9.000, 0.000 AUTO_CLAMPED) rh:(11.000, 0.000 AUTO_CLAMPED) + - (31.000, 0.000) lh:(21.000, 0.000 AUTO_CLAMPED) rh:(41.000, 0.000 AUTO_CLAMPED) + - (61.000, 0.000) lh:(51.000, 0.000 AUTO_CLAMPED) rh:(71.000, 0.000 AUTO_CLAMPED) + - fcu 'pose.bones["MeshUnderRoot"].location[2]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'MeshUnderRoot' + - (1.000, 0.000) lh:(-9.000, 0.000 AUTO_CLAMPED) rh:(11.000, 0.000 AUTO_CLAMPED) + - (31.000, 0.000) lh:(21.000, 0.000 AUTO_CLAMPED) rh:(41.000, 0.000 AUTO_CLAMPED) + - (61.000, 0.000) lh:(51.000, 0.000 AUTO_CLAMPED) rh:(71.000, 0.000 AUTO_CLAMPED) + - fcu 'pose.bones["MeshUnderRoot"].rotation_quaternion[0]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'MeshUnderRoot' + - (1.000, 1.000) lh:(-9.000, 1.000 AUTO_CLAMPED) rh:(11.000, 1.000 AUTO_CLAMPED) + - (31.000, 0.866) lh:(21.000, 0.991 AUTO_CLAMPED) rh:(41.000, 0.741 AUTO_CLAMPED) + - (61.000, 0.500) lh:(51.000, 0.500 AUTO_CLAMPED) rh:(71.000, 0.500 AUTO_CLAMPED) + - fcu 'pose.bones["MeshUnderRoot"].rotation_quaternion[1]' smooth:CONT_ACCEL extra:CONSTANT keyframes:3 grp:'MeshUnderRoot' + - (1.000, 0.000) lh:(-9.000, 0.000 AUTO_CLAMPED) rh:(11.000, 0.000 AUTO_CLAMPED) + - (31.000, 0.000) lh:(21.000, 0.000 AUTO_CLAMPED) rh:(41.000, 0.000 AUTO_CLAMPED) + - (61.000, 0.000) lh:(51.000, 0.000 AUTO_CLAMPED) rh:(71.000, 0.000 AUTO_CLAMPED) + +==== Armatures: 1 +- Armature 'Armature' 3 bones + - bone 'ChildBone' parent:'MeshUnderRoot' h:(0.000, -2.000, 0.000) t:(0.000, -1.000, 0.000) radius h:0.100 t:0.050 + 1.000 0.000 0.000 0.000 + 0.000 1.000 0.000 1.000 + 0.000 0.000 1.000 3.000 + - bone 'MeshUnderRoot' parent:'RootBone' h:(-1.000, -1.000, 0.000) t:(-1.000, 0.000, 0.000) radius h:0.100 t:0.050 + 1.000 0.000 0.000 0.000 + 0.000 1.000 0.000 2.000 + 0.000 0.000 1.000 3.000 + - bone 'RootBone' h:(1.000, 2.000, 3.000) t:(1.000, 3.000, 3.000) radius h:0.100 t:0.050 + 1.000 0.000 0.000 1.000 + 0.000 1.000 0.000 2.000 + 0.000 0.000 1.000 3.000 + diff --git a/tests/files/io_tests/fbx/synthetic_mesh_between_bone_nodes.fbx b/tests/files/io_tests/fbx/synthetic_mesh_between_bone_nodes.fbx new file mode 100644 index 00000000000..3f4f6f0b272 --- /dev/null +++ b/tests/files/io_tests/fbx/synthetic_mesh_between_bone_nodes.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fc0e9d1a3a9e524836de733d08c4b24853dc9cdfe64a729ba08f127ba94403d +size 16208 diff --git a/tests/files/io_tests/fbx/synthetic_mesh_between_bone_nodes_animated.fbx b/tests/files/io_tests/fbx/synthetic_mesh_between_bone_nodes_animated.fbx new file mode 100644 index 00000000000..0d90dd2bc4b --- /dev/null +++ b/tests/files/io_tests/fbx/synthetic_mesh_between_bone_nodes_animated.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1950ee13e839cc0ff28dd90711309a513039bb8d14855daab45f224a607212ab +size 27648