From 1315991ecbd179bbff8fd08469dda61ec830d72f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 12 Apr 2024 16:27:30 +0200 Subject: [PATCH] Anim: add keytype argument to `keyframe_insert()` RNA function Add an optional keyword argument `keytype` to the `rna_struct.keyframe_insert()` function. This makes it possible to set the new key's type. The code for this was almost all in place, the only thing that was missing was the RNA wrapper, which is what this commit adds. Example: `bpy.context.object.keyframe_insert("location", keytype='JITTER')` There is no backward compatibility issue here, because the argument is optional and defaults to the previously hardcoded value of `KEYFRAME`. Pull Request: https://projects.blender.org/blender/blender/pulls/120578 --- source/blender/python/intern/bpy_rna_anim.cc | 37 +++++++++++++++----- tests/python/bl_animation_fcurves.py | 15 ++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/source/blender/python/intern/bpy_rna_anim.cc b/source/blender/python/intern/bpy_rna_anim.cc index eab83c718a2..83fec149b9d 100644 --- a/source/blender/python/intern/bpy_rna_anim.cc +++ b/source/blender/python/intern/bpy_rna_anim.cc @@ -228,10 +228,13 @@ static int pyrna_struct_keyframe_parse(PointerRNA *ptr, int *r_index, float *r_cfra, const char **r_group_name, - int *r_options) + int *r_options, + eBezTriple_KeyframeType *r_keytype) { - static const char *kwlist[] = {"data_path", "index", "frame", "group", "options", nullptr}; + static const char *kwlist[] = { + "data_path", "index", "frame", "group", "options", "keytype", nullptr}; PyObject *pyoptions = nullptr; + char *keytype_name = nullptr; const char *path; /* NOTE: `parse_str` MUST start with `s|ifsO!`. */ @@ -244,7 +247,8 @@ static int pyrna_struct_keyframe_parse(PointerRNA *ptr, r_cfra, r_group_name, &PySet_Type, - &pyoptions)) + &pyoptions, + &keytype_name)) { return -1; } @@ -269,12 +273,24 @@ static int pyrna_struct_keyframe_parse(PointerRNA *ptr, *r_options |= INSERTKEY_NO_USERPREF; } + if (r_keytype) { + int keytype_as_int = 0; + if (keytype_name && pyrna_enum_value_from_id(rna_enum_beztriple_keyframe_type_items, + keytype_name, + &keytype_as_int, + error_prefix) == -1) + { + return -1; + } + *r_keytype = eBezTriple_KeyframeType(keytype_as_int); + } + return 0; /* success */ } char pyrna_struct_keyframe_insert_doc[] = ".. method:: keyframe_insert(data_path, index=-1, frame=bpy.context.scene.frame_current, " - "group=\"\", options=set())\n" + "group=\"\", options=set(), keytype='KEYFRAME')\n" "\n" " Insert a keyframe on the property given, adding fcurves and animation data when " "necessary.\n" @@ -304,6 +320,9 @@ char pyrna_struct_keyframe_insert_doc[] = " - ``INSERTKEY_CYCLE_AWARE`` Take cyclic extrapolation into account " "(Cycle-Aware Keying option).\n" " :type flag: set\n" + " :arg keytype: Type of the key: 'KEYFRAME', 'BREAKDOWN', 'MOVING_HOLD', 'EXTREME', " + "'JITTER', or 'GENERATED'\n" + " :type keytype: string\n" " :return: Success of keyframe insertion.\n" " :rtype: boolean\n"; PyObject *pyrna_struct_keyframe_insert(BPy_StructRNA *self, PyObject *args, PyObject *kw) @@ -313,7 +332,7 @@ PyObject *pyrna_struct_keyframe_insert(BPy_StructRNA *self, PyObject *args, PyOb int index = -1; float cfra = FLT_MAX; const char *group_name = nullptr; - const char keytype = BEZT_KEYTYPE_KEYFRAME; /* XXX: Expose this as a one-off option... */ + eBezTriple_KeyframeType keytype = BEZT_KEYTYPE_KEYFRAME; int options = 0; PYRNA_STRUCT_CHECK_OBJ(self); @@ -321,13 +340,14 @@ PyObject *pyrna_struct_keyframe_insert(BPy_StructRNA *self, PyObject *args, PyOb if (pyrna_struct_keyframe_parse(&self->ptr, args, kw, - "s|$ifsO!:bpy_struct.keyframe_insert()", + "s|$ifsO!s:bpy_struct.keyframe_insert()", "bpy_struct.keyframe_insert()", &path_full, &index, &cfra, &group_name, - &options) == -1) + &options, + &keytype) == -1) { return nullptr; } @@ -440,12 +460,13 @@ PyObject *pyrna_struct_keyframe_delete(BPy_StructRNA *self, PyObject *args, PyOb if (pyrna_struct_keyframe_parse(&self->ptr, args, kw, - "s|$ifsO!:bpy_struct.keyframe_delete()", + "s|$ifsOs!:bpy_struct.keyframe_delete()", "bpy_struct.keyframe_insert()", &path_full, &index, &cfra, &group_name, + nullptr, nullptr) == -1) { return nullptr; diff --git a/tests/python/bl_animation_fcurves.py b/tests/python/bl_animation_fcurves.py index 94de7e7090d..8a49ad9e54b 100644 --- a/tests/python/bl_animation_fcurves.py +++ b/tests/python/bl_animation_fcurves.py @@ -188,6 +188,21 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase): bpy.ops.object.delete(use_global=False) + def test_keyframe_insert_keytype(self): + key_object = bpy.context.active_object + + # Inserting a key with a specific type should work. + key_object.keyframe_insert("location", keytype='GENERATED') + + # Unsupported/unknown types should be rejected. + with self.assertRaises(ValueError): + key_object.keyframe_insert("rotation_euler", keytype='UNSUPPORTED') + + # Only a single key should have been inserted. + keys = key_object.animation_data.action.fcurves[0].keyframe_points + self.assertEqual(len(keys), 1) + self.assertEqual(keys[0].type, 'GENERATED') + def test_keyframe_insertion_high_frame_number(self): bpy.ops.mesh.primitive_monkey_add() key_count = 100