Anim: Insert keyframes without keying sets #113504

Merged
Christoph Lendenfeld merged 59 commits from ChrisLend/blender:keying_sets_rework into main 2023-11-21 15:38:09 +01:00
12 changed files with 375 additions and 29 deletions

View File

@ -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,

View File

@ -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),

View File

@ -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},

View File

@ -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")

View File

@ -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()

View File

@ -10,6 +10,9 @@
#pragma once
#include <string>
#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<float> values,
eInsertKeyFlags insert_key_flag,
eBezTriple_KeyframeType key_type);
} // namespace blender::animrig

View File

@ -8,6 +8,7 @@
#include <cfloat>
#include <cmath>
#include <string>
#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<float> 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<bPoseChannel *>(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

View File

@ -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) {

View File

@ -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<std::string> construct_rna_paths(PointerRNA *ptr)
{
eRotationModes rotation_mode;
IDProperty *properties;
blender::Vector<std::string> paths;
if (ptr->type == &RNA_PoseBone) {
bPoseChannel *pchan = static_cast<bPoseChannel *>(ptr->data);
rotation_mode = eRotationModes(pchan->rotmode);
properties = pchan->prop;
}
else if (ptr->type == &RNA_Object) {
Object *ob = static_cast<Object *>(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<float> get_keyframe_values(PointerRNA *ptr,
PropertyRNA *prop,
const bool visual_key)
{
using namespace blender;
Vector<float> 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<std::string> 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<float> 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);
nathanvegdahl marked this conversation as resolved Outdated

Maybe add a TODO here to investigate why that's the case? Because that seems pretty odd, and is perhaps a sign of something else that also needs some spring cleaning.

Maybe add a TODO here to investigate why that's the case? Because that seems pretty odd, and is perhaps a sign of something else that also needs some spring cleaning.

would the TODO make more sense if I added it in the ANIM_get_keyframing_flags function itself?
by adding it below the current comment its a bit far away from where the actual issue is, and might get missed when the function is refactored/

would the TODO make more sense if I added it in the `ANIM_get_keyframing_flags` function itself? by adding it below the current comment its a bit far away from where the actual issue is, and might get missed when the function is refactored/

Oh! Yeah, that would make a lot more sense. Good call.

Oh! Yeah, that would make a lot more sense. Good call.
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<std::string> 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";
nathanvegdahl marked this conversation as resolved Outdated

Typo: "using the either the" -> "using either the"

Typo: "using the either the" -> "using either the"
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 */

View File

@ -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

View File

@ -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(

View File

@ -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)