# Copy-Pasting Keyframes in Action Editor This is a draft for a design for copy-pasting keyframes in the Action editor. The main goal is to add the necessary changes for working with slotted Actions. > [!Note] > > Bug report: > : [#129690: Anim: pasting keyframes in Action editor sends them to the wrong slot](https://projects.blender.org/blender/blender/issues/129690) > > PR that adds the suggested approach to the tech docs: > : [#103: Anim: document slot-aware copy-paste of keyframes in Action editor](https://projects.blender.org/blender/blender-developer-docs/pulls/103) ## The Problem These two don't mix very well: 1. Single interaction, just Ctrl+C / Ctrl+V. Just one option to flip poses. 2. Long list of intended behaviours, depending on what was copied and what is selected when pasting. This list of behaviours can be roughly be broken down into the following categories: 1. Dumb copy-paste of keys **to the same channels**. This can be from one frame to another, between Actions, between blend files, etc. But each key goes to an F-Curve with the same data path and array index as where it was copied from. 2. Copy-paste of keys **between channels**. For example, using rotation keys to scale F-Curves, as reference for further animation. ## Current Use Cases What I (Sybren) was able to figure out of the current code, these use cases are supported: - 1 → 1: just copy. - 1 → N: just copy when the data path matches. - N → 1: for things like copying location XYZ, and pasting to one channel. This should pick up either the X, Y, or Z component, depending on what is being pasted to. - N → N: for copying entire poses (so exact matches), or copying location XYZ to scale XYZ (so only matching on array index). ### Matching Rules When pasting, the copied F-Curves are matched to the target F-Curves, using increasingly accepting rule sets. If a rule set matches at least one curve, the following rule sets are ignored. 1. Match exactly by the F-Curve's data path (like `pose.bones["name"].location`) + array index. The name can be flipped when animating bones and flipping is requested. 2. Match by the animated property name, so only when ending in `location` when the copied data path was `pose.bones["name"].location`. Plus the array index. 3. Match by array index only. ### Exceptions Depending on how many curves were copied from, and how many curves were copied to, there are exceptions to the above: Copied from one curve : array index is ignored Pasting to one curve : data path is ignored ## Pasting Process Blender follows this pseudo-code when pasting: ```python= for target_fcurve in selected_fcurves: # Find a matching F-Curve to paste onto the target: source_fcurve = find_match(copy_buffer, target_fcurve) # If we found one, copy it to the target F-Curve if source_fcurve: copy(source_fcurve, target_fcurve) ``` This means that slots are completely ignored, and that thus all slots receive the copied data (if they have matching F-Curves, which is likely when animating two similarly rigged characters). ## Adding Slotted Actions support Needs good list of use cases. ### Detectable Things These two may interfere with each other, we should pick one: 1. One or more Slot channels is selected in the channel list. 2. The active Object's assigned slot (in the Action editor header). Probably 1. is better, as 2. only works for objects (so wouldn't be able to paste object curves to material slot). **Big question:** how does this interact with the matching rules that are already in Blender? ## Ponderings & Direction It's probably the best to **create multiple paste operators**. Those can then be shown as a choice in a popup (like the Ctrl+P for parenting) or a pie menu, or people can directly bind them to the hotkeys they want. Paste to Same Channels: : Would go **to the same channels** as the copied keys were on. This ignores the selection of the F-Curves. This can thus also be used to start animating something, by pasting into an empty Action. Paste to Selected Channels: : This is the behaviour that's now in Blender, which **takes the channel selection into account** to determine what you want to paste to. This means the F-Curves must already exist and thus be animated to receive pasted data. ### Visibility & Existence Pasting should only happen to *visible* F-Curves. Pasting should only happen to *valid* F-Curves. When pasting from one rig to another, this should skip animation of bones that do not exist in the target rig. ### Selected F-Curves Selection is a bit borken. Selecting a group does _not_ select the F-Curves in it, so it's hard to select all F-Curves in a range. You have to expand all the groups before range-selecting. ## New Behaviour ![2025-01-20-1650-copy paste keyframes behaviour](https://hackmd.io/_uploads/r1D_fl2v1e.jpg) # Animation Tools: Copy-Paste in Action Editor This chapter describes how the copy-pasting of animation keys works in the Action editor. <style> .copy { color:#3af; } .paste { color:#3fa; } </style> Since some parts of the behavior are determined by what was copied, and other parts depend on what is selected when pasting, colors are used to distinguish between those: - <span class="copy">blue</span> indicates information at the time of copying, and - <span class="paste">green</span> indicates information at the time of pasting. Copy-Paste of animation keys in the Action editor works as follows: 1. <span class='copy'>Key selection</span> indicates what gets copied. For copying, the selection state of the channels (F-Curves, slots, etc.) is irrelevant. 2. Pressing Ctrl+C copies those keys, recording the F-Curves and Action slots they came from. 3. <span class='paste'>Channel selection (F-Curves, slots)</span> indicates what gets pasted, and where. For pasting, key selection is irrelevant. Note that pasting should **never**: - **Create new F-Curves**. Only already-existing curves should be pasted into. - **Paste into invisible F-Curves**. Collapsed groups and other summary-like channels also count as "visible". But when "Only Show Selected" is enabled, the animation of non-selected objects/bones should not be touched. ## F-Curve Matching The diagram below describes how target F-Curves (i.e. the ones being pasted into) are chosen, and how they are matched to the copied keyframes. The following shorthand is used: - `R`: RNA data path of the F-Curve (like `pose.bones["Root"].location`) - `I`: Array Index of the F-Curve (like `1` for the Y-location). - `N`: The identifier of the property the F-Curve animates (like `location`). Combinations are concatenated, so `RI` means the combination of (RNA path, array index). ```mermaid flowchart TD classDef copy stroke:#3af; classDef paste stroke:#3fa; classDef choice stroke:#a3f; num_fcurves_selected("How many F-Curves selected to paste into?"):::paste original_ri["RI of copied F-Curve(s)"]:::choice fcurves_copied("How many F-Curves copied?"):::copy slots_copied("Copied from how many slots?"):::copy ri_of_target("Selected F-Curve"):::choice degrade("Degrade (RI, NI, I)<br>matching copied to selected F-Curves"):::choice degrade_within_slot("Degrade (RI, NI, I)<br>but keep within slot<br>of the same name"):::choice num_fcurves_selected -->|None| original_ri num_fcurves_selected -->|Single| fcurves_copied num_fcurves_selected -->|Multiple| slots_copied fcurves_copied -->|Single| ri_of_target fcurves_copied -->|Multiple| degrade slots_copied -->|Single| degrade slots_copied -->|Multiple| degrade_within_slot ``` The "Degrade" behavior works as follows. The first rule that matches anything "wins", and in that case subsequent rules are not visited any more. 1. `RI`: Exact match on (RNA data path, array index). So if you copied from (`pose.bones["Root"].location, 1`) and that is among the selected F-Curves too, this rule matches. 2. `NI`: Match (property name, array index). So in the above example, it would also match if you have `pose.bones["Arm_L"].location` selected. 3. `I`: Match only the array index. So if you have Location X/Y/Z copied, and selected an "Y thing", it will paste the Y location keys there. ## Slot Matching The diagram below describes how the target slot is chosen when pasting. ```mermaid flowchart TD classDef copy stroke:#3af; classDef paste stroke:#3fa; classDef choice stroke:#a3f; any_fcurve_selected(Any F-curve selected?):::paste any_slot_selected(Any slot selected?):::paste copied_from_slots(Copied from slots?):::copy slot_of_target_fcurves["Use slot(s) of<br>selected F-curves"]:::choice all_selected_slots["Duplicate into<br>all selected slots"]:::choice match_by_name["Match slots by name<br>(fallback to active)"]:::choice any_fcurve_selected -->|One or more| slot_of_target_fcurves any_fcurve_selected -->|None| any_slot_selected any_slot_selected -->|One or more| copied_from_slots any_slot_selected -->|None| match_by_name copied_from_slots -->|One slot| all_selected_slots copied_from_slots -->|Multiple slots| match_by_name ```