VSE: delete keyframes from preview
This PR makes it easier to delete strip keyframes from the preview. It works similarly to the 3D viewport and also works with keying sets. Pressing "alt + I" deletes the keyframe on the current frame of the selected strips, when a keyset is active it only deletes the keyframes of the selected keyset. Pull Request: https://projects.blender.org/blender/blender/pulls/140385
This commit is contained in:
committed by
Richard Antalik
parent
72e810a45a
commit
efef8a201a
@@ -3244,6 +3244,7 @@ def km_sequencer_preview(params):
|
||||
("anim.keyframe_insert", {"type": 'I', "value": 'PRESS'}, None),
|
||||
("anim.keying_set_active_set", {"type": 'K', "value": 'PRESS', "shift": True}, None),
|
||||
("anim.keyframe_insert_menu", {"type": 'K', "value": 'PRESS'}, {"properties": [("always_prompt", True)]}),
|
||||
("anim.keyframe_delete_vse", {"type": 'I', "value": 'PRESS', "alt": True}, None),
|
||||
|
||||
*_template_items_context_menu("SEQUENCER_MT_preview_context_menu", params.context_menu_event),
|
||||
])
|
||||
|
||||
@@ -985,6 +985,7 @@ class SEQUENCER_MT_strip_animation(Menu):
|
||||
layout.operator("anim.keyframe_insert", text="Insert Keyframe")
|
||||
layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe with Keying Set").always_prompt = True
|
||||
layout.operator("anim.keying_set_active_set", text="Change Keying Set...")
|
||||
layout.operator("anim.keyframe_delete_vse", text="Delete Keyframes...")
|
||||
|
||||
|
||||
class SEQUENCER_MT_strip_input(Menu):
|
||||
|
||||
@@ -45,6 +45,7 @@ void ANIM_OT_keyframe_delete_by_name(wmOperatorType *ot);
|
||||
void ANIM_OT_keyframe_insert_menu(wmOperatorType *ot);
|
||||
|
||||
void ANIM_OT_keyframe_delete_v3d(wmOperatorType *ot);
|
||||
void ANIM_OT_keyframe_delete_vse(wmOperatorType *ot);
|
||||
void ANIM_OT_keyframe_clear_v3d(wmOperatorType *ot);
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -1374,6 +1374,7 @@ void ED_operatortypes_anim()
|
||||
WM_operatortype_append(ANIM_OT_keyframe_delete);
|
||||
WM_operatortype_append(ANIM_OT_keyframe_insert_menu);
|
||||
WM_operatortype_append(ANIM_OT_keyframe_delete_v3d);
|
||||
WM_operatortype_append(ANIM_OT_keyframe_delete_vse);
|
||||
WM_operatortype_append(ANIM_OT_keyframe_clear_v3d);
|
||||
WM_operatortype_append(ANIM_OT_keyframe_insert_button);
|
||||
WM_operatortype_append(ANIM_OT_keyframe_delete_button);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
@@ -55,6 +56,8 @@
|
||||
#include "ANIM_keyingsets.hh"
|
||||
#include "ANIM_rna.hh"
|
||||
|
||||
#include "SEQ_relations.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
@@ -65,6 +68,7 @@
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_define.hh"
|
||||
#include "RNA_enum_types.hh"
|
||||
#include "RNA_path.hh"
|
||||
#include "RNA_prototypes.hh"
|
||||
|
||||
#include "anim_intern.hh"
|
||||
@@ -604,14 +608,14 @@ static wmOperatorStatus delete_key_using_keying_set(bContext *C, wmOperator *op,
|
||||
int num_channels;
|
||||
const bool confirm = op->flag & OP_IS_INVOKE;
|
||||
|
||||
/* try to delete keyframes for the channels specified by KeyingSet */
|
||||
/* Try to delete keyframes for the channels specified by KeyingSet. */
|
||||
num_channels = blender::animrig::apply_keyingset(
|
||||
C, nullptr, ks, blender::animrig::ModifyKeyMode::DELETE_KEY, cfra);
|
||||
if (G.debug & G_DEBUG) {
|
||||
printf("KeyingSet '%s' - Successfully removed %d Keyframes\n", ks->name, num_channels);
|
||||
}
|
||||
|
||||
/* report failure or do updates? */
|
||||
/* Report failure or do updates? */
|
||||
if (num_channels < 0) {
|
||||
BKE_report(op->reports, RPT_ERROR, "No suitable context info for active keying set");
|
||||
return OPERATOR_CANCELLED;
|
||||
@@ -619,10 +623,14 @@ static wmOperatorStatus delete_key_using_keying_set(bContext *C, wmOperator *op,
|
||||
|
||||
if (num_channels > 0) {
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_REMOVED, nullptr);
|
||||
|
||||
/* VSE notifiers. */
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
||||
WM_event_add_notifier(C, NC_ANIMATION, nullptr);
|
||||
}
|
||||
|
||||
if (confirm) {
|
||||
/* if called by invoke (from the UI), make a note that we've removed keyframes */
|
||||
/* If called by invoke (from the UI), make a note that we've removed keyframes. */
|
||||
if (num_channels > 0) {
|
||||
BKE_reportf(op->reports,
|
||||
RPT_INFO,
|
||||
@@ -811,6 +819,34 @@ void ANIM_OT_keyframe_clear_v3d(wmOperatorType *ot)
|
||||
WM_operator_properties_confirm_or_exec(ot);
|
||||
}
|
||||
|
||||
static blender::Vector<std::string> get_selected_strips_rna_paths(
|
||||
blender::Vector<PointerRNA> &selection)
|
||||
{
|
||||
blender::Vector<std::string> selected_strips_rna_paths;
|
||||
for (PointerRNA &id_ptr : selection) {
|
||||
if (RNA_struct_is_a(id_ptr.type, &RNA_Strip)) {
|
||||
std::optional<std::string> rna_path = RNA_path_from_ID_to_struct(&id_ptr);
|
||||
selected_strips_rna_paths.append(*rna_path);
|
||||
}
|
||||
}
|
||||
return selected_strips_rna_paths;
|
||||
}
|
||||
|
||||
static void invalidate_strip_caches(blender::Vector<PointerRNA> selection, Scene *scene)
|
||||
{
|
||||
for (PointerRNA &id_ptr : selection) {
|
||||
if (RNA_struct_is_a(id_ptr.type, &RNA_Strip)) {
|
||||
::Strip *strip = static_cast<::Strip *>(id_ptr.data);
|
||||
blender::seq::relations_invalidate_cache(scene, strip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool fcurve_belongs_to_strip(const FCurve &fcurve, const std::string &strip_path)
|
||||
{
|
||||
return fcurve.rna_path &&
|
||||
std::strncmp(fcurve.rna_path, strip_path.c_str(), strip_path.length()) == 0;
|
||||
}
|
||||
static bool can_delete_key(FCurve *fcu, Object *ob, ReportList *reports)
|
||||
{
|
||||
/* don't touch protected F-Curves */
|
||||
@@ -853,6 +889,154 @@ static bool can_delete_key(FCurve *fcu, Object *ob, ReportList *reports)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool can_delete_scene_key(FCurve *fcu, Scene *scene, wmOperator *op)
|
||||
{
|
||||
/* Don't touch protected F-Curves. */
|
||||
if (BKE_fcurve_is_protected(fcu)) {
|
||||
BKE_reportf(op->reports,
|
||||
RPT_WARNING,
|
||||
"Not deleting keyframe for locked F-Curve '%s', scene '%s'",
|
||||
fcu->rna_path,
|
||||
scene->id.name + 2);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static wmOperatorStatus delete_key_vse_without_keying_set(bContext *C, wmOperator *op)
|
||||
{
|
||||
using namespace blender::animrig;
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
const float cfra = BKE_scene_frame_get(scene);
|
||||
|
||||
blender::Vector<PointerRNA> selection;
|
||||
blender::Vector<std::string> selected_strips_rna_paths;
|
||||
get_selection(C, &selection);
|
||||
selected_strips_rna_paths = get_selected_strips_rna_paths(selection);
|
||||
|
||||
if (selected_strips_rna_paths.is_empty()) {
|
||||
BKE_reportf(op->reports, RPT_WARNING, "No strips selected");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
const bool confirm = op->flag & OP_IS_INVOKE;
|
||||
if (!scene->adt || !scene->adt->action || (scene->adt->slot_handle == Slot::unassigned)) {
|
||||
BKE_reportf(op->reports, RPT_ERROR, "Scene has no animation data or active action");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
AnimData *adt = scene->adt;
|
||||
bAction *act = adt->action;
|
||||
Action &action = act->wrap();
|
||||
|
||||
const float cfra_unmap = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP);
|
||||
|
||||
blender::VectorSet<std::string> modified_strips;
|
||||
blender::Vector<FCurve *> modified_fcurves;
|
||||
|
||||
foreach_fcurve_in_action_slot(action, adt->slot_handle, [&](FCurve &fcurve) {
|
||||
std::string changed_strip;
|
||||
for (const std::string &strip_path : selected_strips_rna_paths) {
|
||||
if (fcurve_belongs_to_strip(fcurve, strip_path)) {
|
||||
changed_strip = strip_path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!can_delete_scene_key(&fcurve, scene, op) || changed_strip.empty()) {
|
||||
return;
|
||||
}
|
||||
if (blender::animrig::fcurve_delete_keyframe_at_time(&fcurve, cfra_unmap)) {
|
||||
modified_fcurves.append(&fcurve);
|
||||
modified_strips.add(changed_strip);
|
||||
}
|
||||
});
|
||||
|
||||
for (FCurve *fcurve : modified_fcurves) {
|
||||
if (BKE_fcurve_is_empty(fcurve)) {
|
||||
action_fcurve_remove(action, *fcurve);
|
||||
}
|
||||
}
|
||||
|
||||
if (scene->adt->action) {
|
||||
/* The Action might have been unassigned, if it is legacy and the last
|
||||
* F-Curve was removed. */
|
||||
DEG_id_tag_update(&scene->adt->action->id, ID_RECALC_ANIMATION_NO_FLUSH);
|
||||
}
|
||||
|
||||
if (!modified_strips.is_empty()) {
|
||||
/* Key-frames on strips has been moved, so make sure related editors are informed. */
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
||||
WM_event_add_notifier(C, NC_ANIMATION, nullptr);
|
||||
}
|
||||
|
||||
invalidate_strip_caches(selection, scene);
|
||||
|
||||
if (confirm) {
|
||||
/* If called by invoke (from the UI), make a note that we've removed keyframes. */
|
||||
if (modified_strips.is_empty()) {
|
||||
BKE_reportf(op->reports,
|
||||
RPT_WARNING,
|
||||
"No keyframes removed from %ld strip(s)",
|
||||
selected_strips_rna_paths.size());
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
BKE_reportf(op->reports,
|
||||
RPT_INFO,
|
||||
"%ld strip(s) successfully had %ld keyframes removed",
|
||||
modified_strips.size(),
|
||||
modified_fcurves.size());
|
||||
}
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static wmOperatorStatus delete_key_vse_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
KeyingSet *ks = blender::animrig::scene_get_active_keyingset(scene);
|
||||
|
||||
if (ks == nullptr) {
|
||||
return delete_key_vse_without_keying_set(C, op);
|
||||
}
|
||||
|
||||
return delete_key_using_keying_set(C, op, ks);
|
||||
}
|
||||
|
||||
static wmOperatorStatus delete_key_vse_invoke(bContext *C,
|
||||
wmOperator *op,
|
||||
const wmEvent * /*event*/)
|
||||
{
|
||||
if (RNA_boolean_get(op->ptr, "confirm")) {
|
||||
return WM_operator_confirm_ex(C,
|
||||
op,
|
||||
IFACE_("Delete keyframes from selected strips?"),
|
||||
nullptr,
|
||||
IFACE_("Delete"),
|
||||
ALERT_ICON_NONE,
|
||||
false);
|
||||
}
|
||||
return delete_key_vse_exec(C, op);
|
||||
}
|
||||
|
||||
void ANIM_OT_keyframe_delete_vse(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Delete Keyframe";
|
||||
ot->description = "Remove keyframes on current frame for selected strips";
|
||||
ot->idname = "ANIM_OT_keyframe_delete_vse";
|
||||
|
||||
/* callbacks */
|
||||
ot->invoke = delete_key_vse_invoke;
|
||||
ot->exec = delete_key_vse_exec;
|
||||
|
||||
ot->poll = ED_operator_areaactive;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
WM_operator_properties_confirm_or_exec(ot);
|
||||
}
|
||||
|
||||
static wmOperatorStatus delete_key_v3d_without_keying_set(bContext *C, wmOperator *op)
|
||||
{
|
||||
using namespace blender::animrig;
|
||||
|
||||
Reference in New Issue
Block a user