The "slip strip contents" operator in the VSE now can move the strip keyframes. There is a property to enable keyframe slipping. The property is disabled by default, mainly because animation is often used for fade in/out, which would be annoying if it moved with content. Pull Request: https://projects.blender.org/blender/blender/pulls/136386
222 lines
6.6 KiB
C++
222 lines
6.6 KiB
C++
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup sequencer
|
|
*/
|
|
|
|
#include <cstring>
|
|
|
|
#include "DNA_anim_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_sequence_types.h"
|
|
|
|
#include "BKE_fcurve.hh"
|
|
|
|
#include "BLI_listbase.h"
|
|
|
|
#include "DEG_depsgraph.hh"
|
|
|
|
#include "SEQ_animation.hh"
|
|
|
|
namespace blender::seq {
|
|
|
|
bool animation_keyframes_exist(const Scene *scene)
|
|
{
|
|
return scene->adt != nullptr && scene->adt->action != nullptr &&
|
|
scene->adt->action->wrap().has_keyframes(scene->adt->slot_handle);
|
|
}
|
|
|
|
bool animation_drivers_exist(Scene *scene)
|
|
{
|
|
return scene->adt != nullptr && !BLI_listbase_is_empty(&scene->adt->drivers);
|
|
}
|
|
|
|
bool fcurve_matches(const Strip &strip, const FCurve &fcurve)
|
|
{
|
|
return animrig::fcurve_matches_collection_path(
|
|
fcurve, "sequence_editor.strips_all[", strip.name + 2);
|
|
}
|
|
|
|
void offset_animdata(const Scene *scene, Strip *strip, float ofs)
|
|
{
|
|
if (!animation_keyframes_exist(scene) || ofs == 0.0f) {
|
|
return;
|
|
}
|
|
|
|
Vector<FCurve *> fcurves = animrig::fcurves_in_action_slot_filtered(
|
|
scene->adt->action, scene->adt->slot_handle, [&](const FCurve &fcurve) {
|
|
return fcurve_matches(*strip, fcurve);
|
|
});
|
|
|
|
for (FCurve *fcu : fcurves) {
|
|
uint i;
|
|
if (fcu->bezt) {
|
|
for (i = 0; i < fcu->totvert; i++) {
|
|
BezTriple *bezt = &fcu->bezt[i];
|
|
bezt->vec[0][0] += ofs;
|
|
bezt->vec[1][0] += ofs;
|
|
bezt->vec[2][0] += ofs;
|
|
}
|
|
}
|
|
if (fcu->fpt) {
|
|
for (i = 0; i < fcu->totvert; i++) {
|
|
FPoint *fpt = &fcu->fpt[i];
|
|
fpt->vec[0] += ofs;
|
|
}
|
|
}
|
|
}
|
|
|
|
DEG_id_tag_update(&scene->adt->action->id, ID_RECALC_ANIMATION);
|
|
}
|
|
|
|
void free_animdata(Scene *scene, Strip *strip)
|
|
{
|
|
if (!animation_keyframes_exist(scene)) {
|
|
return;
|
|
}
|
|
|
|
Vector<FCurve *> fcurves = animrig::fcurves_in_action_slot_filtered(
|
|
scene->adt->action, scene->adt->slot_handle, [&](const FCurve &fcurve) {
|
|
return fcurve_matches(*strip, fcurve);
|
|
});
|
|
|
|
animrig::Action &action = scene->adt->action->wrap();
|
|
for (FCurve *fcu : fcurves) {
|
|
action_fcurve_remove(action, *fcu);
|
|
}
|
|
}
|
|
|
|
void animation_backup_original(Scene *scene, AnimationBackup *backup)
|
|
{
|
|
if (animation_keyframes_exist(scene)) {
|
|
animrig::Action &action = scene->adt->action->wrap();
|
|
|
|
assert_baklava_phase_1_invariants(action);
|
|
|
|
if (action.is_action_legacy()) {
|
|
BLI_movelisttolist(&backup->curves, &scene->adt->action->curves);
|
|
}
|
|
else if (animrig::Channelbag *channelbag = animrig::channelbag_for_action_slot(
|
|
action, scene->adt->slot_handle))
|
|
{
|
|
animrig::channelbag_fcurves_move(backup->channelbag, *channelbag);
|
|
}
|
|
}
|
|
|
|
if (animation_drivers_exist(scene)) {
|
|
BLI_movelisttolist(&backup->drivers, &scene->adt->drivers);
|
|
}
|
|
}
|
|
|
|
void animation_restore_original(Scene *scene, AnimationBackup *backup)
|
|
{
|
|
if (!BLI_listbase_is_empty(&backup->curves) || !backup->channelbag.fcurves().is_empty()) {
|
|
BLI_assert(scene->adt != nullptr && scene->adt->action != nullptr);
|
|
|
|
animrig::Action &action = scene->adt->action->wrap();
|
|
|
|
assert_baklava_phase_1_invariants(action);
|
|
|
|
if (action.is_action_legacy()) {
|
|
BLI_movelisttolist(&scene->adt->action->curves, &backup->curves);
|
|
}
|
|
else {
|
|
animrig::Channelbag *channelbag = animrig::channelbag_for_action_slot(
|
|
action, scene->adt->slot_handle);
|
|
/* The channel bag should exist if we got here, because otherwise the
|
|
* backup channel bag would have been empty. */
|
|
BLI_assert(channelbag != nullptr);
|
|
|
|
animrig::channelbag_fcurves_move(*channelbag, backup->channelbag);
|
|
}
|
|
}
|
|
|
|
if (!BLI_listbase_is_empty(&backup->drivers)) {
|
|
BLI_assert(scene->adt != nullptr);
|
|
BLI_movelisttolist(&scene->adt->drivers, &backup->drivers);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Duplicate the animation in `src` that matches items in `strip` into `dst`.
|
|
*/
|
|
static void strip_animation_duplicate(Strip *strip,
|
|
animrig::Action &dst,
|
|
const animrig::slot_handle_t dst_slot_handle,
|
|
AnimationBackup *src)
|
|
{
|
|
if (strip->type == STRIP_TYPE_META) {
|
|
LISTBASE_FOREACH (Strip *, meta_child, &strip->seqbase) {
|
|
strip_animation_duplicate(meta_child, dst, dst_slot_handle, src);
|
|
}
|
|
}
|
|
|
|
Vector<FCurve *> fcurves = {};
|
|
BLI_assert_msg(BLI_listbase_is_empty(&src->curves) || src->channelbag.fcurves().is_empty(),
|
|
"SeqAnimationBackup has fcurves for both legacy and layered actions, which "
|
|
"should never happen.");
|
|
if (BLI_listbase_is_empty(&src->curves)) {
|
|
fcurves = animrig::fcurves_in_span_filtered(
|
|
src->channelbag.fcurves(),
|
|
[&](const FCurve &fcurve) { return fcurve_matches(*strip, fcurve); });
|
|
}
|
|
else {
|
|
fcurves = animrig::fcurves_in_listbase_filtered(
|
|
src->curves, [&](const FCurve &fcurve) { return fcurve_matches(*strip, fcurve); });
|
|
}
|
|
|
|
for (const FCurve *fcu : fcurves) {
|
|
FCurve *fcu_copy = BKE_fcurve_copy(fcu);
|
|
|
|
/* Handling groups properly requires more work, so we ignore them for now.
|
|
*
|
|
* Note that when legacy actions are deprecated, then we can handle channel
|
|
* groups way more easily because we know they're stored in the
|
|
* already-duplicated channelbag in `src`, and we therefore don't have to
|
|
* worry that they might have already been freed. */
|
|
fcu_copy->grp = nullptr;
|
|
|
|
animrig::action_fcurve_attach(dst, dst_slot_handle, *fcu_copy, std::nullopt);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Duplicate the drivers in `src` that matches items in `strip` into `dst`.
|
|
*/
|
|
static void strip_drivers_duplicate(Strip *strip, AnimData *dst, AnimationBackup *src)
|
|
{
|
|
if (strip->type == STRIP_TYPE_META) {
|
|
LISTBASE_FOREACH (Strip *, meta_child, &strip->seqbase) {
|
|
strip_drivers_duplicate(meta_child, dst, src);
|
|
}
|
|
}
|
|
|
|
Vector<FCurve *> fcurves = animrig::fcurves_in_listbase_filtered(
|
|
src->drivers, [&](const FCurve &fcurve) { return fcurve_matches(*strip, fcurve); });
|
|
|
|
for (const FCurve *fcu : fcurves) {
|
|
FCurve *fcu_cpy = BKE_fcurve_copy(fcu);
|
|
BLI_addtail(&dst->drivers, fcu_cpy);
|
|
}
|
|
}
|
|
|
|
void animation_duplicate_backup_to_scene(Scene *scene, Strip *strip, AnimationBackup *backup)
|
|
{
|
|
BLI_assert(scene != nullptr);
|
|
|
|
if (!BLI_listbase_is_empty(&backup->curves) || !backup->channelbag.fcurves().is_empty()) {
|
|
BLI_assert(scene->adt != nullptr);
|
|
BLI_assert(scene->adt->action != nullptr);
|
|
strip_animation_duplicate(strip, scene->adt->action->wrap(), scene->adt->slot_handle, backup);
|
|
}
|
|
|
|
if (!BLI_listbase_is_empty(&backup->drivers)) {
|
|
BLI_assert(scene->adt != nullptr);
|
|
strip_drivers_duplicate(strip, scene->adt, backup);
|
|
}
|
|
}
|
|
|
|
} // namespace blender::seq
|