WIP: Anim: Add python api to insert keyframes #115814

Draft
Christoph Lendenfeld wants to merge 2 commits from ChrisLend/blender:add_keyframe_insert_api into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.

Inserting keyframes was previously done through operators or directly on the FCurve.
It can be more convenient to add them to the object directly.
The syntax I implement in this PR is
PoseBone.insert_keyframe()
or
Object.insert_keyframe()

TODO

  • implement for objects
  • make sure the UI updates. currently the colors of the channels only change when hovering over them
  • implement functionality to key a single transform channel, e.g. location[0]
Inserting keyframes was previously done through operators or directly on the FCurve. It can be more convenient to add them to the object directly. The syntax I implement in this PR is `PoseBone.insert_keyframe()` or `Object.insert_keyframe()` # TODO * implement for objects * make sure the UI updates. currently the colors of the channels only change when hovering over them * implement functionality to key a single transform channel, e.g. `location[0]`
Christoph Lendenfeld added the
Module
Animation & Rigging
label 2023-12-05 18:49:14 +01:00
Christoph Lendenfeld added 1 commit 2023-12-05 18:49:22 +01:00
Christoph Lendenfeld added 1 commit 2023-12-05 18:53:43 +01:00
beae2a6447 remove channel argument
can use rna_path instead
Member

Thanks for starting this on my (and Alexander's) request!

Just to try to describe my use case more thoroughly (same as Rigify):
My goal as a rigger is to provide animators with IK/FK snapping toggles. This is an operator that does the following:

  • Store the (final, object-space) matrix of some bones
  • Change a custom property in the rig.
  • This property drives constraint influences, causing parents of those bones to move.
  • Restore the bones to their previous final matrices.
  • "Insert keyframes" at these restored transforms.

But the "Insert keyframes" step raises the question of which channels should be keyed?

  • should it use the active keying set or not?
  • should it key if the channel wasn't previously keyed?
  • should it key if auto-keying is off?
  • should it key if the transform channel is locked?
    As well as: What kind of handle/interpolation types to use on the inserted keys?
  • previous or next key of the existing curve?
  • the user preference default?
  • setting passed by the caller? (potentially exposed to the user)

And I think the function would ideally have parameters for all those things, usually defaulting to a user preference when applicable, but allow each setting to be overridden by the caller.
It would also be great if we could insert keys on frames other than the current one, but if that would require the whole scene to be evaluated anyways, then nevermind, we can do that via Python already by changing the current frame.

@angavrilov @dr.sybren might have different/better/more ideas though.

Also, I guess baking over a frame range might be another, slightly separate can of worms, so I won't open that for now.

Thanks for starting this on my (and Alexander's) request! Just to try to describe my use case more thoroughly (same as Rigify): My goal as a rigger is to provide animators with IK/FK snapping toggles. This is an operator that does the following: - Store the (final, object-space) matrix of some bones - Change a custom property in the rig. - This property drives constraint influences, causing parents of those bones to move. - Restore the bones to their previous final matrices. - "Insert keyframes" at these restored transforms. But the "Insert keyframes" step raises the question of which channels should be keyed? - should it use the active keying set or not? - should it key if the channel wasn't previously keyed? - should it key if auto-keying is off? - should it key if the transform channel is locked? As well as: What kind of handle/interpolation types to use on the inserted keys? - previous or next key of the existing curve? - the user preference default? - setting passed by the caller? (potentially exposed to the user) And I think the function would ideally have parameters for all those things, usually defaulting to a user preference when applicable, but allow each setting to be overridden by the caller. It would also be great if we could insert keys on frames other than the current one, but if that would require the whole scene to be evaluated anyways, then nevermind, we can do that via Python already by changing the current frame. @angavrilov @dr.sybren might have different/better/more ideas though. Also, I guess baking over a frame range might be another, slightly separate can of worms, so I won't open that for now.

This seems to miss the point: there are already methods for keyframing a property from the object that holds it.

The problem is that in the interest of python code predictability it does not take almost any user preferences into account. However in some cases you do want to behave similar to keyframing actions done by the user.

  1. You may want to force insertion of the key like with manual keying, and take basic flags into account, like Insert Needed, XYZ to RGB or Cycle Aware.
  2. You may want to prompt insertion of a key under Auto Keyframing rules, including the possibility of doing nothing if e.g. auto key is off, or the property is not in the active keying set. Rigify in this case compromises by adding Available and Replace flags when appropriate, but does not handle keying sets.
  3. You may want to block keyframing certain transform properties based on channel locks (I believe honoring locks is in the plan on how user facing keyframing operations should work?).
  4. I think it would be useful to have a flag (possibly even a user visible tool setting option) to temporarily override the default interpolation type user preference to Constant Interpolation.

I think some of these could be solved just by adding flags like INSERTKEY_USERPREF, INSERTKEY_AUTOKEY (also implying USERPREF), INSERTKEY_CONSTANT with appropriate effects to keyframe_insert.

Edit: I also wonder if integer and boolean properties should always default to Constant in the first place - I think it seldom makes sense to interpolate discrete properties. Rigify forces constant by messing with the fcurve only in one case that involves an integer property.

This seems to miss the point: there are already [methods](https://docs.blender.org/api/master/bpy.types.bpy_struct.html#bpy.types.bpy_struct.keyframe_insert) for keyframing a property from the object that holds it. The problem is that in the interest of python code predictability it does not take almost any user preferences into account. However in some cases you do want to behave similar to keyframing actions done by the user. 1. You may want to force insertion of the key like with manual keying, and take [basic flags](https://projects.blender.org/blender/blender-addons/src/branch/main/rigify/utils/animation.py#L89) into account, like Insert Needed, XYZ to RGB or Cycle Aware. 2. You may want to prompt insertion of a key under Auto Keyframing rules, including the possibility of doing nothing if e.g. auto key is off, or the property is not in the active keying set. Rigify in this case compromises by adding Available and Replace [flags](https://projects.blender.org/blender/blender-addons/src/branch/main/rigify/utils/animation.py#L103) when appropriate, but does not handle keying sets. 3. You may want to block keyframing certain transform properties based on [channel locks](https://projects.blender.org/blender/blender-addons/src/branch/main/rigify/utils/animation.py#L130) (I believe honoring locks is in the plan on how user facing keyframing operations should work?). 4. I think it would be useful to have a flag (possibly even a user visible tool setting option) to temporarily override the default interpolation type user preference to Constant Interpolation. I think some of these could be solved just by adding flags like INSERTKEY_USERPREF, INSERTKEY_AUTOKEY (also implying USERPREF), INSERTKEY_CONSTANT with appropriate effects to `keyframe_insert`. Edit: I also wonder if integer and boolean properties should always default to Constant in the first place - I think it seldom makes sense to interpolate discrete properties. Rigify forces constant by messing with the fcurve only in one case that involves an integer property.
Author
Member

thanks for explaining, I completely misunderstood what was needed based on the explanation in the chat.
But thanks, makes more sense now. I will look into this then.

  • I'll see if I can make the function arguments optional and default to the user setup if they are not provided
  • Passing in the keyframe type is a good idea, but I think this needs a bit of a refactor. But would be good to do anyway
  • Not keying locked channels is on the todo list
thanks for explaining, I completely misunderstood what was needed based on the explanation in the chat. But thanks, makes more sense now. I will look into this then. * I'll see if I can make the function arguments optional and default to the user setup if they are not provided * Passing in the keyframe type is a good idea, but I think this needs a bit of a refactor. But would be good to do anyway * Not keying locked channels is on the todo list
Author
Member

@Mets @angavrilov
getting back to this now and I have the following proposition

query function get_keyframing_options, returns a list of string.
Used to get the flags from the userpreferences/scene. I opted against setting some default in the keyframe_insert function because even the rigify code ignores a flag (Visual Keying). That way you can query the flags and purge certain flags if you don't want them in your specific use case.

add argument to keyframe_insert to determine the inserted key type

add function keyframe_transforms(options: list[str])
This is meant to cover the most basic use case of copy world matrix, change something, paste world_matrix, insert key.
Options you can get from get_keyframing_options

add function keyframe_insert_auto to insert a keyframe under autokeying rules. That one I am not 100% certain about...

Edit:
after looking into the proposed query function get_keyframing_options I realized this is a bit more complicated than anticipated.
The concept of a query function in python doesn't really play well with how the API is structured. Maybe defaults are the way to go

@Mets @angavrilov getting back to this now and I have the following proposition query function `get_keyframing_options`, returns a list of string. Used to get the flags from the userpreferences/scene. I opted against setting some default in the `keyframe_insert` function because even the rigify code ignores a flag (Visual Keying). That way you can query the flags and purge certain flags if you don't want them in your specific use case. add argument to `keyframe_insert` to determine the inserted key type add function `keyframe_transforms(options: list[str])` This is meant to cover the most basic use case of copy world matrix, change something, paste world_matrix, insert key. Options you can get from `get_keyframing_options` add function `keyframe_insert_auto` to insert a keyframe under autokeying rules. That one I am not 100% certain about... Edit: after looking into the proposed query function `get_keyframing_options` I realized this is a bit more complicated than anticipated. The concept of a query function in python doesn't really play well with how the API is structured. Maybe defaults are the way to go
Member

I like the .keyframe_transforms() idea, so that the caller doesn't have to loop over a bunch of transforms and axes and check for rotation mode, that's great. But being able to key on a per-property basis is still useful ofc.


Re Auto-Keying: How about a required kwarg instead of a separate func, like consider_autokey=True?
Re querying settings: How about an optional context param? If passed, it can take all the values from there.

So, you could ignore all user and scene settings if you want, and strictly control how you want to insert keys yourself as an advanced caller who knows what they're doing:
my_pose_bone.keyframe_transforms(context=None, consider_autokey=False, keying_set='LocRotScale', visual_keying=True, ...)

Or you could use all user and scene settings if you want, and put your faith in the user as the naive caller who just wants to go home:
my_pose_bone.keyframe_transforms(context=context)

I like the `.keyframe_transforms()` idea, so that the caller doesn't have to loop over a bunch of transforms and axes and check for rotation mode, that's great. But being able to key on a per-property basis is still useful ofc. ------------------------- Re Auto-Keying: How about a required kwarg instead of a separate func, like `consider_autokey=True`? Re querying settings: How about an optional `context` param? If passed, it can take all the values from there. So, you could ignore all user and scene settings if you want, and strictly control how you want to insert keys yourself as an advanced caller who knows what they're doing: `my_pose_bone.keyframe_transforms(context=None, consider_autokey=False, keying_set='LocRotScale', visual_keying=True, ...)` Or you could use all user and scene settings if you want, and put your faith in the user as the naive caller who just wants to go home: `my_pose_bone.keyframe_transforms(context=context)`
This pull request is marked as a work in progress.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u add_keyframe_insert_api:ChrisLend-add_keyframe_insert_api
git checkout ChrisLend-add_keyframe_insert_api
Sign in to join this conversation.
No reviewers
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
3 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#115814
No description provided.