diff --git a/source/blender/animrig/ANIM_action.hh b/source/blender/animrig/ANIM_action.hh index ceb26571265..f21d12e08b9 100644 --- a/source/blender/animrig/ANIM_action.hh +++ b/source/blender/animrig/ANIM_action.hh @@ -921,6 +921,16 @@ void assert_baklava_phase_1_invariants(const Strip &strip); */ Action *convert_to_layered_action(Main &bmain, const Action &action); +/** + * Deselect the keys of all actions in the Span. Duplicate entries are only visited once. + */ +void deselect_keys_actions(blender::Span actions); + +/** + * Deselect all keys within the action. + */ +void action_deselect_keys(Action &action); + } // namespace blender::animrig /* Wrap functions for the DNA structs. */ diff --git a/source/blender/animrig/CMakeLists.txt b/source/blender/animrig/CMakeLists.txt index 03e456143f9..702d9c000a2 100644 --- a/source/blender/animrig/CMakeLists.txt +++ b/source/blender/animrig/CMakeLists.txt @@ -22,6 +22,7 @@ set(INC_SYS set(SRC intern/action.cc intern/action_runtime.cc + intern/action_selection.cc intern/anim_rna.cc intern/animdata.cc intern/bone_collections.cc diff --git a/source/blender/animrig/intern/action_selection.cc b/source/blender/animrig/intern/action_selection.cc new file mode 100644 index 00000000000..1700b08b255 --- /dev/null +++ b/source/blender/animrig/intern/action_selection.cc @@ -0,0 +1,33 @@ +#include "DNA_action_types.h" +#include "DNA_anim_types.h" + +#include "BLI_listbase.h" +#include "BLI_set.hh" + +#include "BKE_anim_data.hh" +#include "BKE_fcurve.hh" + +#include "ANIM_action.hh" +#include "ANIM_fcurve.hh" + +namespace blender::animrig { + +void action_deselect_keys(Action &action) +{ + for (FCurve *fcu : fcurves_all(action)) { + BKE_fcurve_deselect_all_keys(*fcu); + } +} + +void deselect_keys_actions(Span actions) +{ + Set visited_actions; + for (bAction *action : actions) { + if (!visited_actions.add(action)) { + continue; + } + action_deselect_keys(action->wrap()); + } +} + +} // namespace blender::animrig diff --git a/source/blender/blenkernel/BKE_fcurve.hh b/source/blender/blenkernel/BKE_fcurve.hh index d2a2c4a5d47..cbad8c9eb55 100644 --- a/source/blender/blenkernel/BKE_fcurve.hh +++ b/source/blender/blenkernel/BKE_fcurve.hh @@ -442,6 +442,11 @@ bool BKE_fcurve_is_protected(const FCurve *fcu); */ bool BKE_fcurve_has_selected_control_points(const FCurve *fcu); +/** + * Deselect all keyframes within that FCurve. + */ +void BKE_fcurve_deselect_all_keys(FCurve &fcu); + /** * Checks if the F-Curve has a Cycles modifier with simple settings * that warrant transition smoothing. diff --git a/source/blender/blenkernel/intern/fcurve.cc b/source/blender/blenkernel/intern/fcurve.cc index 41ada77bf54..0f43eef1325 100644 --- a/source/blender/blenkernel/intern/fcurve.cc +++ b/source/blender/blenkernel/intern/fcurve.cc @@ -1018,6 +1018,16 @@ bool BKE_fcurve_has_selected_control_points(const FCurve *fcu) return false; } +void BKE_fcurve_deselect_all_keys(FCurve &fcu) +{ + if (!fcu.bezt) { + return; + } + for (int i = 0; i < fcu.totvert; i++) { + BEZT_DESEL_ALL(&fcu.bezt[i]); + } +} + bool BKE_fcurve_is_keyframable(const FCurve *fcu) { /* F-Curve's keyframes must be "usable" (i.e. visible + have an effect on final result) */ diff --git a/source/blender/editors/animation/anim_deps.cc b/source/blender/editors/animation/anim_deps.cc index 07529110e1b..ef6bb0dbaae 100644 --- a/source/blender/editors/animation/anim_deps.cc +++ b/source/blender/editors/animation/anim_deps.cc @@ -18,15 +18,21 @@ #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_sequence_types.h" +#include "DNA_space_types.h" +#include "DNA_windowmanager_types.h" #include "BLI_blenlib.h" +#include "BLI_set.hh" #include "BLI_utildefines.h" #include "BKE_action.h" #include "BKE_anim_data.hh" +#include "BKE_context.hh" #include "BKE_fcurve.hh" #include "BKE_gpencil_legacy.h" #include "BKE_grease_pencil.hh" +#include "BKE_screen.hh" +#include "BKE_workspace.hh" #include "DEG_depsgraph.hh" @@ -38,6 +44,8 @@ #include "ED_anim_api.hh" +#include "ANIM_action.hh" + /* **************************** depsgraph tagging ******************************** */ void ANIM_list_elem_update(Main *bmain, Scene *scene, bAnimListElem *ale) @@ -460,3 +468,64 @@ void ANIM_animdata_freelist(ListBase *anim_data) BLI_freelistN(anim_data); #endif } + +void ANIM_deselect_keys_in_animation_editors(bContext *C) +{ + using namespace blender; + + wmWindow *ctx_window = CTX_wm_window(C); + ScrArea *ctx_area = CTX_wm_area(C); + ARegion *ctx_region = CTX_wm_region(C); + + Set dna_actions; + LISTBASE_FOREACH (wmWindow *, win, &CTX_wm_manager(C)->windows) { + bScreen *screen = BKE_workspace_active_screen_get(win->workspace_hook); + + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + if (!ELEM(area->spacetype, SPACE_GRAPH, SPACE_ACTION)) { + continue; + } + ARegion *window_region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); + + if (!window_region) { + continue; + } + + CTX_wm_window_set(C, win); + CTX_wm_area_set(C, area); + CTX_wm_region_set(C, window_region); + bAnimContext ac; + if (!ANIM_animdata_get_context(C, &ac)) { + continue; + } + ListBase anim_data = {nullptr, nullptr}; + int filter = 0; + if (ac.spacetype == SPACE_GRAPH) { + SpaceGraph *graph_editor = (SpaceGraph *)ac.sl; + filter = graph_editor->ads->filterflag; + } + else { + BLI_assert(ac.spacetype == SPACE_ACTION); + SpaceAction *action_editor = (SpaceAction *)ac.sl; + filter = action_editor->ads.filterflag; + } + ANIM_animdata_filter( + &ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype)); + LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { + if (!ale->adt || !ale->adt->action) { + continue; + } + dna_actions.add(ale->adt->action); + } + ANIM_animdata_freelist(&anim_data); + } + } + + CTX_wm_window_set(C, ctx_window); + CTX_wm_area_set(C, ctx_area); + CTX_wm_region_set(C, ctx_region); + + for (bAction *dna_action : dna_actions) { + animrig::action_deselect_keys(dna_action->wrap()); + } +} diff --git a/source/blender/editors/animation/keyframing.cc b/source/blender/editors/animation/keyframing.cc index d5ab73f37cb..e6150a805ee 100644 --- a/source/blender/editors/animation/keyframing.cc +++ b/source/blender/editors/animation/keyframing.cc @@ -40,6 +40,7 @@ #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" +#include "ED_anim_api.hh" #include "ED_keyframing.hh" #include "ED_object.hh" #include "ED_screen.hh" @@ -433,6 +434,8 @@ static int insert_key(bContext *C, wmOperator *op) static int insert_key_exec(bContext *C, wmOperator *op) { + ANIM_deselect_keys_in_animation_editors(C); + Scene *scene = CTX_data_scene(C); /* Use the active keying set if there is one. */ const int type = RNA_enum_get(op->ptr, "type"); @@ -469,6 +472,8 @@ void ANIM_OT_keyframe_insert(wmOperatorType *ot) static int keyframe_insert_with_keyingset_exec(bContext *C, wmOperator *op) { + ANIM_deselect_keys_in_animation_editors(C); + Scene *scene = CTX_data_scene(C); KeyingSet *ks = keyingset_get_from_op_with_error(op, op->type->prop, scene); if (ks == nullptr) { @@ -1062,6 +1067,8 @@ static int insert_key_button_exec(bContext *C, wmOperator *op) const std::optional group = default_channel_group_for_path( &ptr, identifier); + ANIM_deselect_keys_in_animation_editors(C); + /* NOTE: `index == -1` is a magic number, meaning either "operate on all * elements" or "not an array property". */ const std::optional array_index = (all || index < 0) ? std::nullopt : diff --git a/source/blender/editors/armature/pose_lib_2.cc b/source/blender/editors/armature/pose_lib_2.cc index 7e1cd335c22..fd1d106c0cc 100644 --- a/source/blender/editors/armature/pose_lib_2.cc +++ b/source/blender/editors/armature/pose_lib_2.cc @@ -45,6 +45,7 @@ #include "ED_screen.hh" #include "ED_util.hh" +#include "ANIM_action.hh" #include "ANIM_bone_collections.hh" #include "ANIM_keyframing.hh" #include "ANIM_keyingsets.hh" @@ -155,6 +156,10 @@ static void poselib_keytag_pose(bContext *C, Scene *scene, PoseBlendData *pbd) ANIM_relative_keyingset_add_source(sources, &pbd->ob->id, &RNA_PoseBone, pchan); } + if (adt->action) { + blender::animrig::action_deselect_keys(adt->action->wrap()); + } + /* Perform actual auto-keying. */ ANIM_apply_keyingset( C, &sources, ks, blender::animrig::ModifyKeyMode::INSERT, float(scene->r.cfra)); diff --git a/source/blender/editors/include/ED_anim_api.hh b/source/blender/editors/include/ED_anim_api.hh index ddd237c49ac..9141c619f65 100644 --- a/source/blender/editors/include/ED_anim_api.hh +++ b/source/blender/editors/include/ED_anim_api.hh @@ -753,6 +753,12 @@ void ANIM_set_active_channel(bAnimContext *ac, */ bool ANIM_is_active_channel(bAnimListElem *ale); +/** + * Deselects the keys displayed within the open animation editors. Depending on the display + * settings of those editors, the keys may not be from an action of the selected objects. + */ +void ANIM_deselect_keys_in_animation_editors(bContext *C); + /* ************************************************ */ /* DRAWING API */ /* `anim_draw.cc` */ diff --git a/source/blender/editors/object/object_transform.cc b/source/blender/editors/object/object_transform.cc index 3b0bf13ceb3..1132bcf67ed 100644 --- a/source/blender/editors/object/object_transform.cc +++ b/source/blender/editors/object/object_transform.cc @@ -67,8 +67,10 @@ #include "WM_api.hh" #include "WM_types.hh" +#include "ANIM_action.hh" #include "ANIM_keyframing.hh" +#include "ED_anim_api.hh" #include "ED_armature.hh" #include "ED_keyframing.hh" #include "ED_mesh.hh" @@ -341,6 +343,10 @@ static int object_clear_transform_generic_exec(bContext *C, /* get KeyingSet to use */ ks = ANIM_get_keyingset_for_autokeying(scene, default_ksName); + if (blender::animrig::is_autokey_on(scene)) { + ANIM_deselect_keys_in_animation_editors(C); + } + for (Object *ob : objects) { if (use_transform_data_origin) { data_xform_container_item_ensure(xds, ob); diff --git a/source/blender/editors/space_action/action_edit.cc b/source/blender/editors/space_action/action_edit.cc index 36d83a8bf26..c15678423fe 100644 --- a/source/blender/editors/space_action/action_edit.cc +++ b/source/blender/editors/space_action/action_edit.cc @@ -975,6 +975,8 @@ static int actkeys_insertkey_exec(bContext *C, wmOperator *op) /* what channels to affect? */ mode = RNA_enum_get(op->ptr, "type"); + ANIM_deselect_keys_in_animation_editors(C); + /* insert keyframes */ insert_action_keys(&ac, mode); diff --git a/source/blender/editors/space_graph/graph_edit.cc b/source/blender/editors/space_graph/graph_edit.cc index 3334abb26b2..9d8e7649e40 100644 --- a/source/blender/editors/space_graph/graph_edit.cc +++ b/source/blender/editors/space_graph/graph_edit.cc @@ -258,6 +258,8 @@ static int graphkeys_insertkey_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + ANIM_deselect_keys_in_animation_editors(C); + /* Which channels to affect? */ mode = eGraphKeys_InsertKey_Types(RNA_enum_get(op->ptr, "type")); diff --git a/source/blender/editors/space_view3d/view3d_snap.cc b/source/blender/editors/space_view3d/view3d_snap.cc index 410e90208ae..b5eb9feb1b1 100644 --- a/source/blender/editors/space_view3d/view3d_snap.cc +++ b/source/blender/editors/space_view3d/view3d_snap.cc @@ -41,12 +41,14 @@ #include "RNA_access.hh" #include "RNA_define.hh" +#include "ED_anim_api.hh" #include "ED_curves.hh" #include "ED_keyframing.hh" #include "ED_object.hh" #include "ED_screen.hh" #include "ED_transverts.hh" +#include "ANIM_action.hh" #include "ANIM_bone_collections.hh" #include "ANIM_keyframing.hh" @@ -190,9 +192,11 @@ static int snap_sel_to_grid_exec(bContext *C, wmOperator *op) /* Build object array. */ Vector objects_eval; + Vector objects_orig; { FOREACH_SELECTED_EDITABLE_OBJECT_BEGIN (view_layer_eval, v3d, ob_eval) { objects_eval.append(ob_eval); + objects_orig.append(DEG_get_original_object(ob_eval)); } FOREACH_SELECTED_EDITABLE_OBJECT_END; } @@ -214,6 +218,10 @@ static int snap_sel_to_grid_exec(bContext *C, wmOperator *op) xds = object::data_xform_container_create(); } + if (blender::animrig::is_autokey_on(scene)) { + ANIM_deselect_keys_in_animation_editors(C); + } + for (Object *ob_eval : objects_eval) { Object *ob = DEG_get_original_object(ob_eval); vec[0] = -ob_eval->object_to_world().location()[0] + @@ -502,6 +510,10 @@ static bool snap_selected_to_location(bContext *C, } } + if (blender::animrig::is_autokey_on(scene)) { + ANIM_deselect_keys_in_animation_editors(C); + } + for (Object *ob : objects) { if (ob->parent && BKE_object_flag_test_recursive(ob->parent, OB_DONE)) { continue; diff --git a/source/blender/editors/transform/transform_convert_armature.cc b/source/blender/editors/transform/transform_convert_armature.cc index 50ab01e2a47..01bd40b2386 100644 --- a/source/blender/editors/transform/transform_convert_armature.cc +++ b/source/blender/editors/transform/transform_convert_armature.cc @@ -25,11 +25,13 @@ #include "BIK_api.h" +#include "ED_anim_api.hh" #include "ED_armature.hh" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" +#include "ANIM_action.hh" #include "ANIM_bone_collections.hh" #include "ANIM_keyframing.hh" #include "ANIM_rna.hh" @@ -1660,6 +1662,18 @@ static void special_aftertrans_update__pose(bContext *C, TransInfo *t) } else { const bool canceled = (t->state == TRANS_CANCEL); + + if (blender::animrig::is_autokey_on(t->scene) && !canceled) { + blender::Vector objects; + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + for (int i = 0; i < tc->data_len; i++) { + const TransData *td = &tc->data[i]; + objects.append(td->ob); + } + } + ANIM_deselect_keys_in_animation_editors(C); + } + GSet *motionpath_updates = BLI_gset_ptr_new("motionpath updates"); FOREACH_TRANS_DATA_CONTAINER (t, tc) { diff --git a/source/blender/editors/transform/transform_convert_object.cc b/source/blender/editors/transform/transform_convert_object.cc index 0802abbde99..da4862eca6c 100644 --- a/source/blender/editors/transform/transform_convert_object.cc +++ b/source/blender/editors/transform/transform_convert_object.cc @@ -21,8 +21,11 @@ #include "BKE_rigidbody.h" #include "BKE_scene.hh" +#include "ANIM_action.hh" #include "ANIM_keyframing.hh" #include "ANIM_rna.hh" + +#include "ED_anim_api.hh" #include "ED_object.hh" #include "DEG_depsgraph_query.hh" @@ -904,6 +907,15 @@ static void special_aftertrans_update__object(bContext *C, TransInfo *t) TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); bool motionpath_update = false; + if (blender::animrig::is_autokey_on(t->scene) && !canceled) { + blender::Vector objects; + for (int i = 0; i < tc->data_len; i++) { + const TransData *td = &tc->data[i]; + objects.append(td->ob); + } + ANIM_deselect_keys_in_animation_editors(C); + } + for (int i = 0; i < tc->data_len; i++) { TransData *td = tc->data + i; ListBase pidlist; diff --git a/source/blender/makesrna/intern/rna_action.cc b/source/blender/makesrna/intern/rna_action.cc index 1dd23d12142..f19b095ee92 100644 --- a/source/blender/makesrna/intern/rna_action.cc +++ b/source/blender/makesrna/intern/rna_action.cc @@ -859,6 +859,12 @@ static void rna_Action_end_frame_set(PointerRNA *ptr, float value) CLAMP_MAX(data->frame_start, data->frame_end); } +static void rna_Action_deselect_keys(bAction *act) +{ + animrig::action_deselect_keys(act->wrap()); + WM_main_add_notifier(NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr); +} + /* Used to check if an action (value pointer) * is suitable to be assigned to the ID-block that is ptr. */ bool rna_Action_id_poll(PointerRNA *ptr, PointerRNA value) @@ -2092,6 +2098,10 @@ static void rna_def_action(BlenderRNA *brna) RNA_def_property_float_funcs(prop, "rna_Action_curve_frame_range_get", nullptr, nullptr); RNA_def_property_clear_flag(prop, PROP_EDITABLE); + FunctionRNA *func = RNA_def_function(srna, "deselect_keys", "rna_Action_deselect_keys"); + RNA_def_function_ui_description( + func, "Deselects all keys of the Action. The selection status of F-Curves is unchanged"); + rna_def_action_legacy(brna, srna); /* API calls */ diff --git a/tests/python/bl_animation_keyframing.py b/tests/python/bl_animation_keyframing.py index 6a85a1a6929..f571f5e4183 100644 --- a/tests/python/bl_animation_keyframing.py +++ b/tests/python/bl_animation_keyframing.py @@ -178,6 +178,19 @@ class InsertKeyTest(AbstractKeyframingTest, unittest.TestCase): _fcurve_paths_match(keyed_object.animation_data.action.fcurves, keyed_rna_paths) bpy.data.objects.remove(keyed_object, do_unlink=True) + def test_key_selection_state(self): + keyed_object = _create_animation_object() + bpy.context.preferences.edit.key_insert_channels = {"LOCATION"} + with bpy.context.temp_override(**_get_view3d_context()): + bpy.ops.anim.keyframe_insert() + bpy.context.scene.frame_set(5) + bpy.ops.anim.keyframe_insert() + + for fcurve in keyed_object.animation_data.action.fcurves: + self.assertEqual(len(fcurve.keyframe_points), 2) + self.assertFalse(fcurve.keyframe_points[0].select_control_point) + self.assertTrue(fcurve.keyframe_points[1].select_control_point) + class VisualKeyingTest(AbstractKeyframingTest, unittest.TestCase): """ Check if visual keying produces the correct keyframe values. """ @@ -368,6 +381,20 @@ class AutoKeyframingTest(AbstractKeyframingTest, unittest.TestCase): expected_paths = [f"{bone_path}.location", f"{bone_path}.rotation_euler", f"{bone_path}.scale"] _fcurve_paths_match(action.fcurves, expected_paths) + def test_key_selection_state(self): + armature_obj = _create_armature() + bpy.ops.object.mode_set(mode='POSE') + bpy.ops.transform.translate(value=(1, 0, 0)) + bpy.context.scene.frame_set(5) + bpy.ops.transform.translate(value=(0, 1, 0)) + bpy.ops.object.mode_set(mode='OBJECT') + + action = armature_obj.animation_data.action + for fcurve in action.fcurves: + self.assertEqual(len(fcurve.keyframe_points), 2) + self.assertFalse(fcurve.keyframe_points[0].select_control_point) + self.assertTrue(fcurve.keyframe_points[1].select_control_point) + class InsertAvailableTest(AbstractKeyframingTest, unittest.TestCase):