diff --git a/release/datafiles/userdef/userdef_default.c b/release/datafiles/userdef/userdef_default.c index 3d35b6b75c1..1cc3e7c59cb 100644 --- a/release/datafiles/userdef/userdef_default.c +++ b/release/datafiles/userdef/userdef_default.c @@ -165,6 +165,7 @@ const UserDef U_default = { .glalphaclip = 0.004, .autokey_mode = (AUTOKEY_MODE_NORMAL & ~AUTOKEY_ON), .autokey_flag = AUTOKEY_FLAG_XYZ2RGB, + .key_insert_channels = USER_ANIM_KEY_CHANNEL_LOCATION, .animation_flag = USER_ANIM_HIGH_QUALITY_DRAWING, .text_render = 0, .navigation_mode = VIEW_NAVIGATION_WALK, diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index f1e6ba29397..8271499c6d9 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -4684,7 +4684,7 @@ def km_object_mode(params): ("object.join", {"type": 'J', "value": 'PRESS', "ctrl": True}, None), ("wm.context_toggle", {"type": 'PERIOD', "value": 'PRESS', "ctrl": True}, {"properties": [("data_path", 'tool_settings.use_transform_data_origin')]}), - ("anim.keyframe_insert_menu", {"type": 'I', "value": 'PRESS'}, None), + ("anim.keyframe_insert", {"type": 'I', "value": 'PRESS'}, None), ("anim.keyframe_delete_v3d", {"type": 'I', "value": 'PRESS', "alt": True}, None), ("anim.keying_set_active_set", {"type": 'I', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None), ("collection.create", {"type": 'G', "value": 'PRESS', "ctrl": True}, None), @@ -4823,7 +4823,7 @@ def km_pose(params): op_menu("VIEW3D_MT_bone_collections", {"type": 'M', "value": 'PRESS', "shift": True}), ("armature.move_to_collection", {"type": 'M', "value": 'PRESS'}, None), ("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None), - ("anim.keyframe_insert_menu", {"type": 'I', "value": 'PRESS'}, None), + ("anim.keyframe_insert", {"type": 'I', "value": 'PRESS'}, None), ("anim.keyframe_delete_v3d", {"type": 'I', "value": 'PRESS', "alt": True}, None), ("anim.keying_set_active_set", {"type": 'I', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None), ("pose.push", {"type": 'E', "value": 'PRESS', "ctrl": True}, None), diff --git a/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index ffcda9501d2..a694f2a2363 100644 --- a/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -3225,7 +3225,7 @@ def km_pose(params): ("pose.select_hierarchy", {"type": 'DOWN_ARROW', "value": 'PRESS', "shift": True, "repeat": True}, {"properties": [("direction", 'CHILD'), ("extend", True)]}), ("pose.select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None), - ("anim.keyframe_insert_menu", {"type": 'S', "value": 'PRESS', "shift": True}, None), + ("anim.keyframe_insert", {"type": 'S', "value": 'PRESS', "shift": True}, None), ("anim.keyframe_insert_by_name", {"type": 'S', "value": 'PRESS'}, {"properties": [("type", 'LocRotScale')]}), ("anim.keyframe_insert_by_name", {"type": 'W', "value": 'PRESS', "shift": True}, @@ -3297,7 +3297,7 @@ def km_object_mode(params): {"properties": [("use_global", True), ("confirm", False)]}), ("object.duplicate_move", {"type": 'D', "value": 'PRESS', "ctrl": True}, None), # Keyframing - ("anim.keyframe_insert_menu", {"type": 'S', "value": 'PRESS', "shift": True}, None), + ("anim.keyframe_insert", {"type": 'S', "value": 'PRESS', "shift": True}, None), ("anim.keyframe_insert_by_name", {"type": 'S', "value": 'PRESS'}, {"properties": [("type", 'LocRotScale')]}), ("anim.keyframe_insert_by_name", {"type": 'W', "value": 'PRESS', "shift": True}, diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index e1bd7aff13c..a0d6a86f4b8 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -576,6 +576,8 @@ class USERPREF_PT_animation_keyframes(AnimationPanel, CenterAlignMixIn, Panel): prefs = context.preferences edit = prefs.edit + layout.prop(edit, "key_insert_channels", expand=True) + col = layout.column() col.prop(edit, "use_visual_keying") col.prop(edit, "use_keyframe_insert_needed", text="Only Insert Needed") diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 0a579cda627..d0e71c9aabd 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -2755,7 +2755,8 @@ class VIEW3D_MT_object_animation(Menu): def draw(self, _context): layout = self.layout - layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe...") + layout.operator("anim.keyframe_insert", text="Insert Keyframe") + layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe with Keying Set") layout.operator("anim.keyframe_delete_v3d", text="Delete Keyframes...") layout.operator("anim.keyframe_clear_v3d", text="Clear Keyframes...") layout.operator("anim.keying_set_active_set", text="Change Keying Set...") @@ -3019,7 +3020,8 @@ class VIEW3D_MT_object_context_menu(Menu): layout.separator() - layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe...") + layout.operator("anim.keyframe_insert", text="Insert Keyframe") + layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe with Keying Set") layout.separator() @@ -4177,7 +4179,8 @@ class VIEW3D_MT_pose_context_menu(Menu): layout.operator_context = 'INVOKE_REGION_WIN' - layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe...") + layout.operator("anim.keyframe_insert", text="Insert Keyframe") + layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe with Keying Set") layout.separator() diff --git a/source/blender/animrig/ANIM_keyframing.hh b/source/blender/animrig/ANIM_keyframing.hh index 33d0f16b555..77cc98aaeda 100644 --- a/source/blender/animrig/ANIM_keyframing.hh +++ b/source/blender/animrig/ANIM_keyframing.hh @@ -10,6 +10,9 @@ #pragma once +#include + +#include "BLI_vector.hh" #include "DNA_anim_types.h" #include "ED_transform.hh" #include "RNA_types.hh" @@ -175,4 +178,20 @@ bool autokeyframe_property(bContext *C, /** \} */ +/** + * Insert keys for the given rna_path in the given action. The length of the values Span is + * expected to be the size of the property array. + * \param frame is expected to be in the local time of the action, meaning it has to be NLA mapped + * already. + * \returns The number of keys inserted. + */ +int insert_key_action(Main *bmain, + bAction *action, + PointerRNA *ptr, + const std::string &rna_path, + float frame, + const Span values, + eInsertKeyFlags insert_key_flag, + eBezTriple_KeyframeType key_type); + } // namespace blender::animrig diff --git a/source/blender/animrig/intern/keyframing.cc b/source/blender/animrig/intern/keyframing.cc index ed473b881eb..c66c3128ec3 100644 --- a/source/blender/animrig/intern/keyframing.cc +++ b/source/blender/animrig/intern/keyframing.cc @@ -8,6 +8,7 @@ #include #include +#include #include "ANIM_action.hh" #include "ANIM_animdata.hh" @@ -38,6 +39,7 @@ #include "RNA_access.hh" #include "RNA_define.hh" #include "RNA_path.hh" +#include "RNA_prototypes.h" #include "RNA_types.hh" #include "WM_api.hh" @@ -965,4 +967,40 @@ int clear_keyframe(Main *bmain, return key_count; } +int insert_key_action(Main *bmain, + bAction *action, + PointerRNA *ptr, + const std::string &rna_path, + const float frame, + const Span values, + eInsertKeyFlags insert_key_flag, + eBezTriple_KeyframeType key_type) +{ + BLI_assert(bmain != nullptr); + BLI_assert(action != nullptr); + + std::string group; + if (ptr->type == &RNA_PoseBone) { + bPoseChannel *pose_channel = static_cast(ptr->data); + group = pose_channel->name; + } + else { + group = "Object Transforms"; + } + + int property_array_index = 0; + int inserted_keys = 0; + for (float value : values) { + FCurve *fcurve = action_fcurve_ensure( + bmain, action, group.c_str(), ptr, rna_path.c_str(), property_array_index); + const bool inserted_key = insert_keyframe_value( + fcurve, frame, value, key_type, insert_key_flag); + if (inserted_key) { + inserted_keys++; + } + property_array_index++; + } + return inserted_keys; +} + } // namespace blender::animrig diff --git a/source/blender/blenloader/intern/versioning_userdef.cc b/source/blender/blenloader/intern/versioning_userdef.cc index bbaaeea9a18..287e0cebd7d 100644 --- a/source/blender/blenloader/intern/versioning_userdef.cc +++ b/source/blender/blenloader/intern/versioning_userdef.cc @@ -916,6 +916,9 @@ void blo_do_versions_userdef(UserDef *userdef) */ { /* Keep this block, even when empty. */ + userdef->key_insert_channels = (USER_ANIM_KEY_CHANNEL_LOCATION | + USER_ANIM_KEY_CHANNEL_ROTATION | USER_ANIM_KEY_CHANNEL_SCALE | + USER_ANIM_KEY_CHANNEL_CUSTOM_PROPERTIES); } LISTBASE_FOREACH (bTheme *, btheme, &userdef->themes) { diff --git a/source/blender/editors/animation/keyframing.cc b/source/blender/editors/animation/keyframing.cc index eae1ed976b6..5e9aa0575e8 100644 --- a/source/blender/editors/animation/keyframing.cc +++ b/source/blender/editors/animation/keyframing.cc @@ -15,6 +15,7 @@ #include "BLT_translation.h" +#include "DNA_ID.h" #include "DNA_action_types.h" #include "DNA_anim_types.h" #include "DNA_armature_types.h" @@ -32,6 +33,7 @@ #include "BKE_fcurve.h" #include "BKE_global.h" #include "BKE_idtype.h" +#include "BKE_lib_id.h" #include "BKE_nla.h" #include "BKE_report.h" #include "BKE_scene.h" @@ -48,6 +50,7 @@ #include "ANIM_fcurve.hh" #include "ANIM_keyframing.hh" #include "ANIM_rna.hh" +#include "ANIM_visualkey.hh" #include "UI_interface.hh" #include "UI_resources.hh" @@ -96,6 +99,9 @@ eInsertKeyFlags ANIM_get_keyframing_flags(Scene *scene, const bool use_autokey_m } /* only if including settings from the autokeying mode... */ + /* TODO: The fact that this flag needs to be passed as true is confusing because it is not clear + * why those two flags would be exclusive to autokeying. Refactor flags so they are separate + * between normal keying and autokeying. */ if (use_autokey_mode) { /* keyframing mode - only replace existing keyframes */ if (is_autokey_mode(scene, AUTOKEY_MODE_EDITKEYS)) { @@ -268,21 +274,14 @@ static bool modify_key_op_poll(bContext *C) /* Insert Key Operator ------------------------ */ -static int insert_key_exec(bContext *C, wmOperator *op) +static int insert_key_with_keyingset(bContext *C, wmOperator *op, KeyingSet *ks) { Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); bool ob_edit_mode = false; const float cfra = BKE_scene_frame_get(scene); - int num_channels; const bool confirm = op->flag & OP_IS_INVOKE; - - KeyingSet *ks = keyingset_get_from_op_with_error(op, op->type->prop, scene); - if (ks == nullptr) { - return OPERATOR_CANCELLED; - } - /* exit the edit mode to make sure that those object data properties that have been * updated since the last switching to the edit mode will be keyframed correctly */ @@ -292,7 +291,7 @@ static int insert_key_exec(bContext *C, wmOperator *op) } /* try to insert keyframes for the channels specified by KeyingSet */ - num_channels = ANIM_apply_keyingset(C, nullptr, ks, MODIFYKEY_MODE_INSERT, cfra); + const int num_channels = ANIM_apply_keyingset(C, nullptr, ks, MODIFYKEY_MODE_INSERT, cfra); if (G.debug & G_DEBUG) { BKE_reportf(op->reports, RPT_INFO, @@ -334,15 +333,225 @@ static int insert_key_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static blender::Vector construct_rna_paths(PointerRNA *ptr) +{ + eRotationModes rotation_mode; + IDProperty *properties; + blender::Vector paths; + + if (ptr->type == &RNA_PoseBone) { + bPoseChannel *pchan = static_cast(ptr->data); + rotation_mode = eRotationModes(pchan->rotmode); + properties = pchan->prop; + } + else if (ptr->type == &RNA_Object) { + Object *ob = static_cast(ptr->data); + rotation_mode = eRotationModes(ob->rotmode); + properties = ob->id.properties; + } + else { + /* Pointer type not supported. */ + return paths; + } + + eKeyInsertChannels insert_channel_flags = eKeyInsertChannels(U.key_insert_channels); + if (insert_channel_flags & USER_ANIM_KEY_CHANNEL_LOCATION) { + paths.append("location"); + } + if (insert_channel_flags & USER_ANIM_KEY_CHANNEL_ROTATION) { + switch (rotation_mode) { + case ROT_MODE_QUAT: + paths.append("rotation_quaternion"); + break; + case ROT_MODE_AXISANGLE: + paths.append("rotation_axis_angle"); + break; + case ROT_MODE_XYZ: + case ROT_MODE_XZY: + case ROT_MODE_YXZ: + case ROT_MODE_YZX: + case ROT_MODE_ZXY: + case ROT_MODE_ZYX: + paths.append("rotation_euler"); + default: + break; + } + } + if (insert_channel_flags & USER_ANIM_KEY_CHANNEL_SCALE) { + paths.append("scale"); + } + if (insert_channel_flags & USER_ANIM_KEY_CHANNEL_ROTATION_MODE) { + paths.append("rotation_mode"); + } + if (insert_channel_flags & USER_ANIM_KEY_CHANNEL_CUSTOM_PROPERTIES) { + if (properties) { + LISTBASE_FOREACH (IDProperty *, prop, &properties->data.group) { + std::string name = prop->name; + std::string rna_path = "[\"" + name + "\"]"; + paths.append(rna_path); + } + } + } + return paths; +} + +static blender::Vector get_keyframe_values(PointerRNA *ptr, + PropertyRNA *prop, + const bool visual_key) +{ + using namespace blender; + Vector values; + + if (visual_key && animrig::visualkey_can_use(ptr, prop)) { + /* Visual-keying is only available for object and pchan datablocks, as + * it works by keyframing using a value extracted from the final matrix + * instead of using the kt system to extract a value. + */ + values = animrig::visualkey_get_values(ptr, prop); + } + else { + values = animrig::get_rna_values(ptr, prop); + } + return values; +} + +static void insert_key_rna(PointerRNA *rna_pointer, + const blender::Span rna_paths, + const float scene_frame, + const eInsertKeyFlags insert_key_flags, + const eBezTriple_KeyframeType key_type, + Main *bmain, + ReportList *reports) +{ + using namespace blender; + + ID *id = rna_pointer->owner_id; + bAction *action = ED_id_action_ensure(bmain, id); + if (action == nullptr) { + BKE_reportf(reports, + RPT_ERROR, + "Could not insert keyframe, as this type does not support animation data (ID = " + "%s)", + id->name); + return; + } + + AnimData *adt = BKE_animdata_from_id(id); + const float nla_frame = BKE_nla_tweakedit_remap(adt, scene_frame, NLATIME_CONVERT_UNMAP); + const bool visual_keyframing = insert_key_flags & INSERTKEY_MATRIX; + + int insert_key_count = 0; + for (const std::string &rna_path : rna_paths) { + PointerRNA ptr; + PropertyRNA *prop = nullptr; + const bool path_resolved = RNA_path_resolve_property( + rna_pointer, rna_path.c_str(), &ptr, &prop); + if (!path_resolved) { + BKE_reportf(reports, + RPT_ERROR, + "Could not insert keyframe, as this property does not exist (ID = " + "%s, path = %s)", + id->name, + rna_path.c_str()); + continue; + } + std::string rna_path_id_to_prop = RNA_path_from_ID_to_property(&ptr, prop); + Vector rna_values = get_keyframe_values(&ptr, prop, visual_keyframing); + + insert_key_count += animrig::insert_key_action(bmain, + action, + rna_pointer, + rna_path_id_to_prop, + nla_frame, + rna_values.as_span(), + insert_key_flags, + key_type); + } + + if (insert_key_count == 0) { + BKE_reportf(reports, RPT_ERROR, "Failed to insert any keys"); + } +} + +/* Fill the list with CollectionPointerLink depending on the mode of the context. */ +static bool get_selection(bContext *C, ListBase *r_selection) +{ + const eContextObjectMode context_mode = CTX_data_mode_enum(C); + + switch (context_mode) { + case CTX_MODE_OBJECT: { + CTX_data_selected_objects(C, r_selection); + break; + } + case CTX_MODE_POSE: { + CTX_data_selected_pose_bones(C, r_selection); + break; + } + default: + return false; + } + + return true; +} + +static int insert_key(bContext *C, wmOperator *op) +{ + using namespace blender; + + ListBase selection = {nullptr, nullptr}; + const bool found_selection = get_selection(C, &selection); + if (!found_selection) { + BKE_reportf(op->reports, RPT_ERROR, "Unsupported context mode"); + } + + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + const float scene_frame = BKE_scene_frame_get(scene); + + /* Passing autokey mode as true because that is needed to get the cycle aware keying flag. */ + const bool use_autokey_mode = true; + const eInsertKeyFlags insert_key_flags = ANIM_get_keyframing_flags(scene, use_autokey_mode); + const eBezTriple_KeyframeType key_type = eBezTriple_KeyframeType( + scene->toolsettings->keyframe_type); + + LISTBASE_FOREACH (CollectionPointerLink *, collection_ptr_link, &selection) { + ID *selected_id = collection_ptr_link->ptr.owner_id; + if (!BKE_id_is_editable(bmain, selected_id)) { + BKE_reportf(op->reports, RPT_ERROR, "'%s' is not editable", selected_id->name + 2); + continue; + } + PointerRNA id_ptr = collection_ptr_link->ptr; + Vector rna_paths = construct_rna_paths(&collection_ptr_link->ptr); + + insert_key_rna( + &id_ptr, rna_paths.as_span(), scene_frame, insert_key_flags, key_type, bmain, op->reports); + } + + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_ADDED, nullptr); + BLI_freelistN(&selection); + + return OPERATOR_FINISHED; +} + +static int insert_key_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + /* Use the active keying set if there is one. */ + KeyingSet *ks = ANIM_keyingset_get_from_enum_type(scene, scene->active_keyingset); + if (ks) { + return insert_key_with_keyingset(C, op, ks); + } + return insert_key(C, op); +} + void ANIM_OT_keyframe_insert(wmOperatorType *ot) { - PropertyRNA *prop; - /* identifiers */ ot->name = "Insert Keyframe"; ot->idname = "ANIM_OT_keyframe_insert"; ot->description = - "Insert keyframes on the current frame for all properties in the specified Keying Set"; + "Insert keyframes on the current frame using either the active keying set, or the user " + "preferences if no keying set is active"; /* callbacks */ ot->exec = insert_key_exec; @@ -350,13 +559,16 @@ void ANIM_OT_keyframe_insert(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} - /* keyingset to use (dynamic enum) */ - prop = RNA_def_enum( - ot->srna, "type", rna_enum_dummy_DEFAULT_items, 0, "Keying Set", "The Keying Set to use"); - RNA_def_enum_funcs(prop, ANIM_keying_sets_enum_itemf); - RNA_def_property_flag(prop, PROP_HIDDEN); - ot->prop = prop; +static int keyframe_insert_with_keyingset_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + KeyingSet *ks = keyingset_get_from_op_with_error(op, op->type->prop, scene); + if (ks == nullptr) { + return OPERATOR_CANCELLED; + } + return insert_key_with_keyingset(C, op, ks); } void ANIM_OT_keyframe_insert_by_name(wmOperatorType *ot) @@ -369,7 +581,7 @@ void ANIM_OT_keyframe_insert_by_name(wmOperatorType *ot) ot->description = "Alternate access to 'Insert Keyframe' for keymaps to use"; /* callbacks */ - ot->exec = insert_key_exec; + ot->exec = keyframe_insert_with_keyingset_exec; ot->poll = modify_key_op_poll; /* flags */ @@ -459,7 +671,7 @@ void ANIM_OT_keyframe_insert_menu(wmOperatorType *ot) /* callbacks */ ot->invoke = insert_key_menu_invoke; - ot->exec = insert_key_exec; + ot->exec = keyframe_insert_with_keyingset_exec; ot->poll = ED_operator_areaactive; /* flags */ diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 26d70a20869..b53dcd8cc03 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -955,6 +955,9 @@ typedef struct UserDef { short autokey_mode; /** Flags for autokeying. */ short autokey_flag; + /** Flags for which channels to insert keys at. */ + short key_insert_channels; // eKeyInsertChannels + char _pad15[6]; /** Flags for animation. */ short animation_flag; @@ -1295,6 +1298,14 @@ typedef enum eAutokey_Flag { ANIMRECORD_FLAG_WITHNLA = (1 << 10), } eAutokey_Flag; +typedef enum eKeyInsertChannels { + USER_ANIM_KEY_CHANNEL_LOCATION = (1 << 0), + USER_ANIM_KEY_CHANNEL_ROTATION = (1 << 1), + USER_ANIM_KEY_CHANNEL_SCALE = (1 << 2), + USER_ANIM_KEY_CHANNEL_ROTATION_MODE = (1 << 3), + USER_ANIM_KEY_CHANNEL_CUSTOM_PROPERTIES = (1 << 4), +} eKeyInsertChannels; + /** * Animation flags * #UserDef.animation_flag, used for animation flags that aren't covered by more specific flags diff --git a/source/blender/makesrna/intern/rna_userdef.cc b/source/blender/makesrna/intern/rna_userdef.cc index 9dba218a81c..c41f7fc0c22 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -149,6 +149,15 @@ static const EnumPropertyItem rna_enum_userdef_viewport_aa_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; +static const EnumPropertyItem rna_enum_key_insert_channels[] = { + {USER_ANIM_KEY_CHANNEL_LOCATION, "LOCATION", 0, "Location", ""}, + {USER_ANIM_KEY_CHANNEL_ROTATION, "ROTATION", 0, "Rotation", ""}, + {USER_ANIM_KEY_CHANNEL_SCALE, "SCALE", 0, "Scale", ""}, + {USER_ANIM_KEY_CHANNEL_ROTATION_MODE, "ROTATE_MODE", 0, "Rotation Mode", ""}, + {USER_ANIM_KEY_CHANNEL_CUSTOM_PROPERTIES, "CUSTOM_PROPS", 0, "Custom Properties", ""}, + {0, nullptr, 0, nullptr, nullptr}, +}; + static const EnumPropertyItem rna_enum_preference_gpu_backend_items[] = { {GPU_BACKEND_OPENGL, "OPENGL", 0, "OpenGL", "Use OpenGL backend"}, {GPU_BACKEND_METAL, "METAL", 0, "Metal", "Use Metal backend"}, @@ -5426,6 +5435,14 @@ static void rna_def_userdef_edit(BlenderRNA *brna) "Show warning indicators when transforming objects and bones if auto keying is enabled"); /* keyframing settings */ + prop = RNA_def_property(srna, "key_insert_channels", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, nullptr, "key_insert_channels"); + RNA_def_property_enum_items(prop, rna_enum_key_insert_channels); + RNA_def_property_flag(prop, PROP_ENUM_FLAG); + RNA_def_property_ui_text(prop, + "Default Key Channels", + "Which channels to insert keys at when no keying set is active"); + prop = RNA_def_property(srna, "use_keyframe_insert_needed", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "autokey_flag", AUTOKEY_FLAG_INSERTNEEDED); RNA_def_property_ui_text( diff --git a/tests/python/bl_animation_keyframing.py b/tests/python/bl_animation_keyframing.py index 26acba3689d..af26c39e425 100644 --- a/tests/python/bl_animation_keyframing.py +++ b/tests/python/bl_animation_keyframing.py @@ -51,6 +51,16 @@ def _insert_by_name_test(insert_key: str, expected_paths: list): return match +def _insert_from_user_preference_test(enabled_user_pref_fields: set, expected_paths: list): + keyed_object = _create_animation_object() + bpy.context.preferences.edit.key_insert_channels = enabled_user_pref_fields + with bpy.context.temp_override(**_get_view3d_context()): + bpy.ops.anim.keyframe_insert() + match = _fcurve_paths_match(keyed_object.animation_data.action.fcurves, expected_paths) + bpy.data.objects.remove(keyed_object, do_unlink=True) + return match + + def _get_keying_set(scene, name: str): return scene.keying_sets_all[scene.keying_sets_all.find(name)] @@ -90,6 +100,13 @@ class InsertKeyTest(AbstractKeyframingTest, unittest.TestCase): _insert_with_keying_set_test("Location, Rotation & Scale", ["location", "rotation_euler", "scale"]) ) + def test_insert_from_user_preferences(self): + self.assertTrue(_insert_from_user_preference_test({"LOCATION"}, ["location"])) + self.assertTrue(_insert_from_user_preference_test({"ROTATION"}, ["rotation_euler"])) + self.assertTrue(_insert_from_user_preference_test({"SCALE"}, ["scale"])) + self.assertTrue(_insert_from_user_preference_test( + {"LOCATION", "ROTATION", "SCALE"}, ["location", "rotation_euler", "scale"])) + class VisualKeyingTest(AbstractKeyframingTest, unittest.TestCase): """ Check if visual keying produces the correct keyframe values. """ @@ -130,7 +147,7 @@ class VisualKeyingTest(AbstractKeyframingTest, unittest.TestCase): bpy.data.objects.remove(target, do_unlink=True) bpy.data.objects.remove(constrained, do_unlink=True) - def test_visual_location_user_pref(self): + def test_visual_location_user_pref_override(self): # When enabling the user preference setting, # the normal keying sets behave like their visual keying set counterpart. bpy.context.preferences.edit.use_visual_keying = True @@ -151,6 +168,27 @@ class VisualKeyingTest(AbstractKeyframingTest, unittest.TestCase): bpy.data.objects.remove(constrained, do_unlink=True) bpy.context.preferences.edit.use_visual_keying = False + def test_visual_location_user_pref(self): + target = _create_animation_object() + t_value = 1 + target.location = (t_value, t_value, t_value) + constrained = _create_animation_object() + constraint = constrained.constraints.new("COPY_LOCATION") + constraint.target = target + + bpy.context.preferences.edit.use_visual_keying = True + bpy.context.preferences.edit.key_insert_channels = {"LOCATION"} + + with bpy.context.temp_override(**_get_view3d_context()): + bpy.ops.anim.keyframe_insert() + + for fcurve in constrained.animation_data.action.fcurves: + self.assertEqual(fcurve.keyframe_points[0].co.y, t_value) + + bpy.data.objects.remove(target, do_unlink=True) + bpy.data.objects.remove(constrained, do_unlink=True) + bpy.context.preferences.edit.use_visual_keying = False + class CycleAwareKeyingTest(AbstractKeyframingTest, unittest.TestCase): """ Check if cycle aware keying remaps the keyframes correctly and adds fcurve modifiers. """ @@ -175,8 +213,10 @@ class CycleAwareKeyingTest(AbstractKeyframingTest, unittest.TestCase): bpy.ops.anim.keyframe_insert_by_name(type="Location") # Will be mapped to frame 3. + # This will insert the key based on the user preference settings. + bpy.context.preferences.edit.key_insert_channels = {"LOCATION"} bpy.context.scene.frame_set(22) - bpy.ops.anim.keyframe_insert_by_name(type="Location") + bpy.ops.anim.keyframe_insert() # Will be mapped to frame 9. bpy.context.scene.frame_set(-10)