diff options
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/blenkernel/BKE_action.hh | 35 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_armature.hh | 48 | ||||
-rw-r--r-- | source/blender/blenkernel/CMakeLists.txt | 4 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/action_bones.cc | 48 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/armature_pose.cc | 56 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/armature_selection.cc | 78 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/armature_test.cc | 77 | ||||
-rw-r--r-- | source/blender/editors/armature/pose_backup.cc | 51 |
8 files changed, 327 insertions, 70 deletions
diff --git a/source/blender/blenkernel/BKE_action.hh b/source/blender/blenkernel/BKE_action.hh new file mode 100644 index 00000000000..b9f106d367e --- /dev/null +++ b/source/blender/blenkernel/BKE_action.hh @@ -0,0 +1,35 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#pragma once + +/** \file + * \ingroup bke + */ +#ifndef __cplusplus +# error This is a C++ only header. +#endif + +#include "BLI_function_ref.hh" + +struct bAction; +struct FCurve; + +namespace blender::bke { + +using FoundFCurveCallback = blender::FunctionRef<void(FCurve *fcurve, const char *bone_name)>; +void BKE_action_find_fcurves_with_bones(const bAction *action, FoundFCurveCallback callback); + +}; // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_armature.hh b/source/blender/blenkernel/BKE_armature.hh new file mode 100644 index 00000000000..e3f5b528156 --- /dev/null +++ b/source/blender/blenkernel/BKE_armature.hh @@ -0,0 +1,48 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#pragma once + +/** \file + * \ingroup bke + */ +#ifndef __cplusplus +# error This is a C++ only header. +#endif + +#include "BKE_armature.h" + +#include "BLI_function_ref.hh" +#include "BLI_set.hh" + +namespace blender::bke { + +struct SelectedBonesResult { + bool all_bones_selected = true; + bool no_bones_selected = true; +}; + +using SelectedBoneCallback = blender::FunctionRef<void(Bone *bone)>; +SelectedBonesResult BKE_armature_find_selected_bones(const bArmature *armature, + SelectedBoneCallback callback); + +using BoneNameSet = blender::Set<std::string>; +/** + * Return a set of names of the selected bones. An empty set means "ignore bone + * selection", which either means all bones are selected, or none are. + */ +BoneNameSet BKE_armature_find_selected_bone_names(const bArmature *armature); + +}; // namespace blender::bke diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 527996ee46d..e8f31ae72c0 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -69,6 +69,7 @@ set(SRC intern/CCGSubSurf_util.c intern/DerivedMesh.cc intern/action.c + intern/action_bones.cc intern/action_mirror.c intern/addon.c intern/anim_data.c @@ -77,6 +78,7 @@ set(SRC intern/anim_visualization.c intern/appdir.c intern/armature.c + intern/armature_selection.cc intern/armature_deform.c intern/armature_pose.cc intern/armature_update.c @@ -287,6 +289,7 @@ set(SRC BKE_DerivedMesh.h BKE_action.h + BKE_action.hh BKE_addon.h BKE_anim_data.h BKE_anim_path.h @@ -294,6 +297,7 @@ set(SRC BKE_animsys.h BKE_appdir.h BKE_armature.h + BKE_armature.hh BKE_asset.h BKE_attribute.h BKE_attribute_access.hh diff --git a/source/blender/blenkernel/intern/action_bones.cc b/source/blender/blenkernel/intern/action_bones.cc new file mode 100644 index 00000000000..b8d185e6a81 --- /dev/null +++ b/source/blender/blenkernel/intern/action_bones.cc @@ -0,0 +1,48 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup bke + */ + +#include "BKE_action.hh" + +#include "BLI_listbase.h" +#include "BLI_string.h" + +#include "DNA_action_types.h" +#include "DNA_anim_types.h" + +#include "MEM_guardedalloc.h" + +namespace blender::bke { + +void BKE_action_find_fcurves_with_bones(const bAction *action, FoundFCurveCallback callback) +{ + LISTBASE_FOREACH (FCurve *, fcu, &action->curves) { + char *bone_name = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones["); + if (!bone_name) { + continue; + } + callback(fcu, bone_name); + MEM_freeN(bone_name); + } +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/armature_pose.cc b/source/blender/blenkernel/intern/armature_pose.cc index 2b2268c6302..a62a32d9633 100644 --- a/source/blender/blenkernel/intern/armature_pose.cc +++ b/source/blender/blenkernel/intern/armature_pose.cc @@ -23,8 +23,9 @@ * \ingroup bke */ +#include "BKE_action.hh" #include "BKE_animsys.h" -#include "BKE_armature.h" +#include "BKE_armature.hh" #include "BLI_function_ref.hh" #include "BLI_set.hh" @@ -36,14 +37,14 @@ #include "RNA_access.h" +using namespace blender::bke; + namespace { -using BoneNameSet = blender::Set<std::string>; using ActionApplier = blender::FunctionRef<void(PointerRNA *, bAction *, const AnimationEvalContext *)>; /* Forward declarations. */ -BoneNameSet pose_apply_find_selected_bones(const bArmature *armature, const bPose *pose); void pose_apply_disable_fcurves_for_unselected_bones(bAction *action, const BoneNameSet &selected_bone_names); void pose_apply_restore_fcurves(bAction *action); @@ -102,7 +103,7 @@ void pose_apply(struct Object *ob, } const bArmature *armature = (bArmature *)ob->data; - const BoneNameSet selected_bone_names = pose_apply_find_selected_bones(armature, pose); + const BoneNameSet selected_bone_names = BKE_armature_find_selected_bone_names(armature); const bool limit_to_selected_bones = !selected_bone_names.is_empty(); if (limit_to_selected_bones) { @@ -122,30 +123,6 @@ void pose_apply(struct Object *ob, } } -BoneNameSet pose_apply_find_selected_bones(const bArmature *armature, const bPose *pose) -{ - BoneNameSet selected_bone_names; - bool all_bones_selected = true; - bool no_bones_selected = true; - - LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) { - const bool is_selected = PBONE_SELECTED(armature, pchan->bone); - all_bones_selected &= is_selected; - no_bones_selected &= !is_selected; - - if (is_selected) { - /* Bone names are unique, so no need to check for duplicates. */ - selected_bone_names.add_new(pchan->name); - } - } - - /* If no bones are selected, act as if all are. */ - if (all_bones_selected || no_bones_selected) { - return BoneNameSet(); /* An empty set means "ignore bone selection". */ - } - return selected_bone_names; -} - void pose_apply_restore_fcurves(bAction *action) { /* TODO(Sybren): Restore the FCurve flags, instead of just erasing the 'disabled' flag. */ @@ -157,24 +134,13 @@ void pose_apply_restore_fcurves(bAction *action) void pose_apply_disable_fcurves_for_unselected_bones(bAction *action, const BoneNameSet &selected_bone_names) { - LISTBASE_FOREACH (FCurve *, fcu, &action->curves) { - if (!fcu->rna_path || !strstr(fcu->rna_path, "pose.bones[")) { - continue; - } - - /* Get bone name, and check if this bone is selected. */ - char *bone_name = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones["); - if (!bone_name) { - continue; - } - const bool is_selected = selected_bone_names.contains(bone_name); - MEM_freeN(bone_name); - if (is_selected) { - continue; + auto disable_unselected_fcurve = [&](FCurve *fcu, const char *bone_name) { + const bool is_bone_selected = selected_bone_names.contains(bone_name); + if (!is_bone_selected) { + fcu->flag |= FCURVE_DISABLED; } - - fcu->flag |= FCURVE_DISABLED; - } + }; + BKE_action_find_fcurves_with_bones(action, disable_unselected_fcurve); } } // namespace diff --git a/source/blender/blenkernel/intern/armature_selection.cc b/source/blender/blenkernel/intern/armature_selection.cc new file mode 100644 index 00000000000..c7dbc9d05b1 --- /dev/null +++ b/source/blender/blenkernel/intern/armature_selection.cc @@ -0,0 +1,78 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bke + */ + +#include "BKE_armature.hh" + +#include "BLI_listbase.h" + +#include "DNA_action_types.h" +#include "DNA_armature_types.h" + +namespace blender::bke { + +namespace { + +void find_selected_bones__visit_bone(const bArmature *armature, + SelectedBoneCallback callback, + SelectedBonesResult &result, + Bone *bone) +{ + const bool is_selected = PBONE_SELECTED(armature, bone); + result.all_bones_selected &= is_selected; + result.no_bones_selected &= !is_selected; + + if (is_selected) { + callback(bone); + } + + LISTBASE_FOREACH (Bone *, child_bone, &bone->childbase) { + find_selected_bones__visit_bone(armature, callback, result, child_bone); + } +} +} // namespace + +SelectedBonesResult BKE_armature_find_selected_bones(const bArmature *armature, + SelectedBoneCallback callback) +{ + SelectedBonesResult result; + LISTBASE_FOREACH (Bone *, root_bone, &armature->bonebase) { + find_selected_bones__visit_bone(armature, callback, result, root_bone); + } + + return result; +} + +BoneNameSet BKE_armature_find_selected_bone_names(const bArmature *armature) +{ + BoneNameSet selected_bone_names; + + /* Iterate over the selected bones to fill the set of bone names. */ + auto callback = [&](Bone *bone) { selected_bone_names.add(bone->name); }; + SelectedBonesResult result = BKE_armature_find_selected_bones(armature, callback); + + /* If no bones are selected, act as if all are. */ + if (result.all_bones_selected || result.no_bones_selected) { + return BoneNameSet(); + } + + return selected_bone_names; +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/armature_test.cc b/source/blender/blenkernel/intern/armature_test.cc index 589337d9d01..0ad2b58f5d9 100644 --- a/source/blender/blenkernel/intern/armature_test.cc +++ b/source/blender/blenkernel/intern/armature_test.cc @@ -17,10 +17,13 @@ * All rights reserved. */ -#include "BKE_armature.h" +#include "BKE_armature.hh" +#include "BLI_listbase.h" #include "BLI_math.h" +#include "DNA_armature_types.h" + #include "testing/testing.h" namespace blender::bke::tests { @@ -157,4 +160,76 @@ TEST(vec_roll_to_mat3_normalized, Rotationmatrix) } } +class BKE_armature_find_selected_bones_test : public testing::Test { + protected: + bArmature arm; + Bone bone1, bone2, bone3; + + void SetUp() override + { + strcpy(bone1.name, "bone1"); + strcpy(bone2.name, "bone2"); + strcpy(bone3.name, "bone3"); + + arm.bonebase = {NULL, NULL}; + BLI_addtail(&arm.bonebase, &bone1); + BLI_addtail(&arm.bonebase, &bone2); + BLI_addtail(&arm.bonebase, &bone3); + + // Make sure the armature & its bones are visible, to make them selectable. + arm.layer = bone1.layer = bone2.layer = bone3.layer = 1; + } +}; + +TEST_F(BKE_armature_find_selected_bones_test, some_bones_selected) +{ + bone1.flag = BONE_SELECTED; + bone2.flag = 0; + bone3.flag = BONE_SELECTED; + + std::vector<Bone *> seen_bones; + auto callback = [&](Bone *bone) { seen_bones.push_back(bone); }; + + SelectedBonesResult result = BKE_armature_find_selected_bones(&arm, callback); + + ASSERT_EQ(seen_bones.size(), 2) << "Expected 2 selected bones, got " << seen_bones.size(); + EXPECT_EQ(seen_bones[0], &bone1); + EXPECT_EQ(seen_bones[1], &bone3); + + EXPECT_FALSE(result.all_bones_selected); // Bone 2 was not selected. + EXPECT_FALSE(result.no_bones_selected); // Bones 1 and 3 were selected. +} + +TEST_F(BKE_armature_find_selected_bones_test, no_bones_selected) +{ + bone1.flag = bone2.flag = bone3.flag = 0; + + std::vector<Bone *> seen_bones; + auto callback = [&](Bone *bone) { seen_bones.push_back(bone); }; + + SelectedBonesResult result = BKE_armature_find_selected_bones(&arm, callback); + + EXPECT_TRUE(seen_bones.empty()) << "Expected no selected bones, got " << seen_bones.size(); + EXPECT_FALSE(result.all_bones_selected); + EXPECT_TRUE(result.no_bones_selected); +} + +TEST_F(BKE_armature_find_selected_bones_test, all_bones_selected) +{ + bone1.flag = bone2.flag = bone3.flag = BONE_SELECTED; + + std::vector<Bone *> seen_bones; + auto callback = [&](Bone *bone) { seen_bones.push_back(bone); }; + + SelectedBonesResult result = BKE_armature_find_selected_bones(&arm, callback); + + ASSERT_EQ(seen_bones.size(), 3) << "Expected 3 selected bones, got " << seen_bones.size(); + EXPECT_EQ(seen_bones[0], &bone1); + EXPECT_EQ(seen_bones[1], &bone2); + EXPECT_EQ(seen_bones[2], &bone3); + + EXPECT_TRUE(result.all_bones_selected); + EXPECT_FALSE(result.no_bones_selected); +} + } // namespace blender::bke::tests diff --git a/source/blender/editors/armature/pose_backup.cc b/source/blender/editors/armature/pose_backup.cc index 8d53bd8064b..9b2d3e37d98 100644 --- a/source/blender/editors/armature/pose_backup.cc +++ b/source/blender/editors/armature/pose_backup.cc @@ -31,9 +31,12 @@ #include "DNA_object_types.h" #include "BKE_action.h" -#include "BKE_armature.h" +#include "BKE_action.hh" +#include "BKE_armature.hh" #include "BKE_idprop.h" +using namespace blender::bke; + /* simple struct for storing backup info for one pose channel */ typedef struct PoseChannelBackup { struct PoseChannelBackup *next, *prev; @@ -51,21 +54,27 @@ struct PoseBackup { static PoseBackup *pose_backup_create(const Object *ob, const bAction *action, - const bool is_bone_selection_relevant) + const BoneNameSet &selected_bone_names) { ListBase backups = {nullptr, nullptr}; - const bArmature *armature = static_cast<const bArmature *>(ob->data); + const bool is_bone_selection_relevant = !selected_bone_names.is_empty(); + + BoneNameSet backed_up_bone_names; + /* Make a backup of the given pose channel. */ + auto store_animated_pchans = [&](FCurve *, const char *bone_name) { + if (backed_up_bone_names.contains(bone_name)) { + /* Only backup each bone once. */ + return; + } - /* TODO(Sybren): reuse same approach as in `armature_pose.cc` in this function, as that doesn't - * have the assumption that action group names are bone names. */ - LISTBASE_FOREACH (bActionGroup *, agrp, &action->groups) { - bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, agrp->name); + bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name); if (pchan == nullptr) { - continue; + /* FCurve targets non-existent bone. */ + return; } - if (is_bone_selection_relevant && !PBONE_SELECTED(armature, pchan->bone)) { - continue; + if (is_bone_selection_relevant && !selected_bone_names.contains(bone_name)) { + return; } PoseChannelBackup *chan_bak = static_cast<PoseChannelBackup *>( @@ -78,7 +87,11 @@ static PoseBackup *pose_backup_create(const Object *ob, } BLI_addtail(&backups, chan_bak); - } + backed_up_bone_names.add_new(bone_name); + }; + + /* Call `store_animated_pchans()` for each FCurve that targets a bone. */ + BKE_action_find_fcurves_with_bones(action, store_animated_pchans); /* PoseBackup is constructed late, so that the above loop can use stack variables. */ PoseBackup *pose_backup = static_cast<PoseBackup *>(MEM_callocN(sizeof(*pose_backup), __func__)); @@ -89,24 +102,14 @@ static PoseBackup *pose_backup_create(const Object *ob, PoseBackup *ED_pose_backup_create_all_bones(const Object *ob, const bAction *action) { - return pose_backup_create(ob, action, false); + return pose_backup_create(ob, action, BoneNameSet()); } PoseBackup *ED_pose_backup_create_selected_bones(const Object *ob, const bAction *action) { - /* See if bone selection is relevant. */ - bool all_bones_selected = true; - bool no_bones_selected = true; const bArmature *armature = static_cast<const bArmature *>(ob->data); - LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) { - const bool is_selected = PBONE_SELECTED(armature, pchan->bone); - all_bones_selected &= is_selected; - no_bones_selected &= !is_selected; - } - - /* If no bones are selected, act as if all are. */ - const bool is_bone_selection_relevant = !all_bones_selected && !no_bones_selected; - return pose_backup_create(ob, action, is_bone_selection_relevant); + const BoneNameSet selected_bone_names = BKE_armature_find_selected_bone_names(armature); + return pose_backup_create(ob, action, selected_bone_names); } bool ED_pose_backup_is_selection_relevant(const struct PoseBackup *pose_backup) |