Remove SEQ_ prefix for blender::seq namespace and ED_sequencer for blender::ed::vse namespace Pull Request: https://projects.blender.org/blender/blender/pulls/135560
3568 lines
103 KiB
C++
3568 lines
103 KiB
C++
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup spseq
|
|
*/
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_fileops.h"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math_vector.h"
|
|
#include "BLI_path_utils.hh"
|
|
#include "BLI_string.h"
|
|
#include "BLI_timecode.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "DNA_anim_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_sound_types.h"
|
|
|
|
#include "BKE_context.hh"
|
|
#include "BKE_global.hh"
|
|
#include "BKE_library.hh"
|
|
#include "BKE_main.hh"
|
|
#include "BKE_report.hh"
|
|
#include "BKE_sound.h"
|
|
|
|
#include "SEQ_add.hh"
|
|
#include "SEQ_animation.hh"
|
|
#include "SEQ_channels.hh"
|
|
#include "SEQ_connect.hh"
|
|
#include "SEQ_edit.hh"
|
|
#include "SEQ_effects.hh"
|
|
#include "SEQ_iterator.hh"
|
|
#include "SEQ_prefetch.hh"
|
|
#include "SEQ_relations.hh"
|
|
#include "SEQ_render.hh"
|
|
#include "SEQ_select.hh"
|
|
#include "SEQ_sequencer.hh"
|
|
#include "SEQ_thumbnail_cache.hh"
|
|
#include "SEQ_time.hh"
|
|
#include "SEQ_transform.hh"
|
|
#include "SEQ_utils.hh"
|
|
|
|
#include "ANIM_action_legacy.hh"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "RNA_define.hh"
|
|
#include "RNA_enum_types.hh"
|
|
#include "RNA_prototypes.hh"
|
|
|
|
/* For menu, popup, icons, etc. */
|
|
#include "ED_fileselect.hh"
|
|
#include "ED_numinput.hh"
|
|
#include "ED_scene.hh"
|
|
#include "ED_screen.hh"
|
|
#include "ED_sequencer.hh"
|
|
|
|
#include "UI_interface.hh"
|
|
#include "UI_resources.hh"
|
|
#include "UI_view2d.hh"
|
|
|
|
#include "DEG_depsgraph.hh"
|
|
#include "DEG_depsgraph_build.hh"
|
|
|
|
/* Own include. */
|
|
#include "sequencer_intern.hh"
|
|
|
|
namespace blender::ed::vse {
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Public Context Checks
|
|
* \{ */
|
|
|
|
bool maskedit_mask_poll(bContext *C)
|
|
{
|
|
return maskedit_poll(C);
|
|
}
|
|
|
|
bool check_show_maskedit(SpaceSeq *sseq, Scene *scene)
|
|
{
|
|
if (sseq && sseq->mainb == SEQ_DRAW_IMG_IMBUF) {
|
|
return (seq::active_mask_get(scene) != nullptr);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool maskedit_poll(bContext *C)
|
|
{
|
|
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
|
|
|
if (sseq) {
|
|
Scene *scene = CTX_data_scene(C);
|
|
return check_show_maskedit(sseq, scene);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool check_show_imbuf(SpaceSeq *sseq)
|
|
{
|
|
return (sseq->mainb == SEQ_DRAW_IMG_IMBUF) &&
|
|
ELEM(sseq->view, SEQ_VIEW_PREVIEW, SEQ_VIEW_SEQUENCE_PREVIEW);
|
|
}
|
|
|
|
bool check_show_strip(SpaceSeq *sseq)
|
|
{
|
|
return ELEM(sseq->view, SEQ_VIEW_SEQUENCE, SEQ_VIEW_SEQUENCE_PREVIEW);
|
|
}
|
|
|
|
static bool sequencer_fcurves_targets_color_strip(const FCurve *fcurve)
|
|
{
|
|
if (!BLI_str_startswith(fcurve->rna_path, "sequence_editor.strips_all[\"")) {
|
|
return false;
|
|
}
|
|
|
|
if (!BLI_str_endswith(fcurve->rna_path, "\"].color")) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool has_playback_animation(const SpaceSeq *sseq, const Scene *scene)
|
|
{
|
|
if (sseq->draw_flag & SEQ_DRAW_BACKDROP) {
|
|
return true;
|
|
}
|
|
|
|
if (!scene->adt) {
|
|
return false;
|
|
}
|
|
if (!scene->adt->action) {
|
|
return false;
|
|
}
|
|
|
|
for (FCurve *fcurve : blender::animrig::legacy::fcurves_for_assigned_action(scene->adt)) {
|
|
if (sequencer_fcurves_targets_color_strip(fcurve)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Shared Poll Functions
|
|
* \{ */
|
|
|
|
bool sequencer_edit_poll(bContext *C)
|
|
{
|
|
return (seq::editing_get(CTX_data_scene(C)) != nullptr);
|
|
}
|
|
|
|
bool sequencer_edit_with_channel_region_poll(bContext *C)
|
|
{
|
|
if (!sequencer_edit_poll(C)) {
|
|
return false;
|
|
}
|
|
ARegion *region = CTX_wm_region(C);
|
|
if (!(region && (region->regiontype == RGN_TYPE_CHANNELS))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool sequencer_editing_initialized_and_active(bContext *C)
|
|
{
|
|
return ED_operator_sequencer_active(C) && sequencer_edit_poll(C);
|
|
}
|
|
|
|
#if 0 /* UNUSED */
|
|
bool sequencer_strip_poll(bContext *C)
|
|
{
|
|
Editing *ed;
|
|
return (((ed = seq::editing_get(CTX_data_scene(C))) != nullptr) && (ed->act_seq != nullptr));
|
|
}
|
|
#endif
|
|
|
|
bool sequencer_strip_editable_poll(bContext *C)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
if (!ID_IS_EDITABLE(&scene->id)) {
|
|
return false;
|
|
}
|
|
Editing *ed = seq::editing_get(scene);
|
|
return (ed && (ed->act_seq != nullptr));
|
|
}
|
|
|
|
bool sequencer_strip_has_path_poll(bContext *C)
|
|
{
|
|
Editing *ed;
|
|
Strip *strip;
|
|
return (((ed = seq::editing_get(CTX_data_scene(C))) != nullptr) &&
|
|
((strip = ed->act_seq) != nullptr) && STRIP_HAS_PATH(strip));
|
|
}
|
|
|
|
bool sequencer_view_has_preview_poll(bContext *C)
|
|
{
|
|
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
|
if (sseq == nullptr) {
|
|
return false;
|
|
}
|
|
if (seq::editing_get(CTX_data_scene(C)) == nullptr) {
|
|
return false;
|
|
}
|
|
if (!(ELEM(sseq->view, SEQ_VIEW_PREVIEW, SEQ_VIEW_SEQUENCE_PREVIEW) &&
|
|
(sseq->mainb == SEQ_DRAW_IMG_IMBUF)))
|
|
{
|
|
return false;
|
|
}
|
|
ARegion *region = CTX_wm_region(C);
|
|
if (!(region && region->regiontype == RGN_TYPE_PREVIEW)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool sequencer_view_preview_only_poll(const bContext *C)
|
|
{
|
|
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
|
if (sseq == nullptr) {
|
|
return false;
|
|
}
|
|
if (seq::editing_get(CTX_data_scene(C)) == nullptr) {
|
|
return false;
|
|
}
|
|
if (!(ELEM(sseq->view, SEQ_VIEW_PREVIEW) && (sseq->mainb == SEQ_DRAW_IMG_IMBUF))) {
|
|
return false;
|
|
}
|
|
ARegion *region = CTX_wm_region(C);
|
|
if (!(region && region->regiontype == RGN_TYPE_PREVIEW)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool sequencer_view_strips_poll(bContext *C)
|
|
{
|
|
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
|
if (sseq == nullptr) {
|
|
return false;
|
|
}
|
|
if (!check_show_strip(sseq)) {
|
|
return false;
|
|
}
|
|
ARegion *region = CTX_wm_region(C);
|
|
if (!(region && region->regiontype == RGN_TYPE_WINDOW)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Remove Gaps Operator
|
|
* \{ */
|
|
|
|
static int sequencer_gap_remove_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
const bool do_all = RNA_boolean_get(op->ptr, "all");
|
|
const Editing *ed = seq::editing_get(scene);
|
|
|
|
seq::edit_remove_gaps(scene, ed->seqbasep, scene->r.cfra, do_all);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_gap_remove(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Remove Gaps";
|
|
ot->idname = "SEQUENCER_OT_gap_remove";
|
|
ot->description =
|
|
"Remove gap at current frame to first strip at the right, independent of selection or "
|
|
"locked state of strips";
|
|
|
|
/* Api callbacks. */
|
|
// ot->invoke = sequencer_snap_invoke;
|
|
ot->exec = sequencer_gap_remove_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_boolean(ot->srna, "all", false, "All Gaps", "Do all gaps to right of current frame");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Insert Gaps Operator
|
|
* \{ */
|
|
|
|
static int sequencer_gap_insert_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
const int frames = RNA_int_get(op->ptr, "frames");
|
|
const Editing *ed = seq::editing_get(scene);
|
|
seq::transform_offset_after_frame(scene, ed->seqbasep, frames, scene->r.cfra);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_gap_insert(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Insert Gaps";
|
|
ot->idname = "SEQUENCER_OT_gap_insert";
|
|
ot->description =
|
|
"Insert gap at current frame to first strips at the right, independent of selection or "
|
|
"locked state of strips";
|
|
|
|
/* Api callbacks. */
|
|
// ot->invoke = sequencer_snap_invoke;
|
|
ot->exec = sequencer_gap_insert_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_int(ot->srna,
|
|
"frames",
|
|
10,
|
|
0,
|
|
INT_MAX,
|
|
"Frames",
|
|
"Frames to insert after current strip",
|
|
0,
|
|
1000);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Snap Strips to the Current Frame Operator
|
|
* \{ */
|
|
|
|
static int sequencer_snap_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
|
|
Editing *ed = seq::editing_get(scene);
|
|
ListBase *channels = seq::channels_displayed_get(ed);
|
|
int snap_frame;
|
|
|
|
snap_frame = RNA_int_get(op->ptr, "frame");
|
|
|
|
/* Check meta-strips. */
|
|
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
|
if (strip->flag & SELECT && !seq::transform_is_locked(channels, strip) &&
|
|
seq::transform_sequence_can_be_translated(strip))
|
|
{
|
|
if ((strip->flag & (SEQ_LEFTSEL + SEQ_RIGHTSEL)) == 0) {
|
|
seq::transform_translate_sequence(
|
|
scene, strip, (snap_frame - strip->startofs) - strip->start);
|
|
}
|
|
else {
|
|
if (strip->flag & SEQ_LEFTSEL) {
|
|
seq::time_left_handle_frame_set(scene, strip, snap_frame);
|
|
}
|
|
else { /* SEQ_RIGHTSEL */
|
|
seq::time_right_handle_frame_set(scene, strip, snap_frame);
|
|
}
|
|
}
|
|
|
|
seq::relations_invalidate_cache_composite(scene, strip);
|
|
}
|
|
}
|
|
|
|
/* Test for effects and overlap. */
|
|
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
|
if (strip->flag & SELECT && !seq::transform_is_locked(channels, strip)) {
|
|
strip->flag &= ~SEQ_OVERLAP;
|
|
if (seq::transform_test_overlap(scene, ed->seqbasep, strip)) {
|
|
seq::transform_seqbase_shuffle(ed->seqbasep, strip, scene);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Recalculate bounds of effect strips, offsetting the keyframes if not snapping any handle. */
|
|
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
|
if (strip->type & STRIP_TYPE_EFFECT) {
|
|
const bool either_handle_selected = (strip->flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL)) != 0;
|
|
|
|
if (strip->seq1 && (strip->seq1->flag & SELECT)) {
|
|
if (!either_handle_selected) {
|
|
seq::offset_animdata(
|
|
scene, strip, (snap_frame - seq::time_left_handle_frame_get(scene, strip)));
|
|
}
|
|
}
|
|
else if (strip->seq2 && (strip->seq2->flag & SELECT)) {
|
|
if (!either_handle_selected) {
|
|
seq::offset_animdata(
|
|
scene, strip, (snap_frame - seq::time_left_handle_frame_get(scene, strip)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int sequencer_snap_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
|
|
int snap_frame;
|
|
|
|
snap_frame = scene->r.cfra;
|
|
|
|
RNA_int_set(op->ptr, "frame", snap_frame);
|
|
return sequencer_snap_exec(C, op);
|
|
}
|
|
|
|
void SEQUENCER_OT_snap(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Snap Strips to the Current Frame";
|
|
ot->idname = "SEQUENCER_OT_snap";
|
|
ot->description = "Frame where selected strips will be snapped";
|
|
|
|
/* Api callbacks. */
|
|
ot->invoke = sequencer_snap_invoke;
|
|
ot->exec = sequencer_snap_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_int(ot->srna,
|
|
"frame",
|
|
0,
|
|
INT_MIN,
|
|
INT_MAX,
|
|
"Frame",
|
|
"Frame where selected strips will be snapped",
|
|
INT_MIN,
|
|
INT_MAX);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Trim Strips Operator
|
|
* \{ */
|
|
|
|
struct SlipData {
|
|
float init_mouseloc[2];
|
|
int previous_offset;
|
|
float previous_subframe_offset;
|
|
float subframe_restore;
|
|
Strip **strip_array;
|
|
int num_seq;
|
|
bool slow;
|
|
int slow_offset; /* Offset at the point where offset was turned on. */
|
|
NumInput num_input;
|
|
};
|
|
|
|
static void slip_add_sequences(ListBase *seqbasep, Strip **strip_array)
|
|
{
|
|
int i = 0;
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, seqbasep) {
|
|
if (!(strip->type & STRIP_TYPE_EFFECT) && (strip->flag & SELECT)) {
|
|
strip_array[i] = strip;
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int slip_count_sequences(ListBase *seqbasep)
|
|
{
|
|
int trimmed_sequences = 0;
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, seqbasep) {
|
|
if (!(strip->type & STRIP_TYPE_EFFECT) && (strip->flag & SELECT)) {
|
|
trimmed_sequences++;
|
|
}
|
|
}
|
|
|
|
return trimmed_sequences;
|
|
}
|
|
|
|
static int sequencer_slip_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
SlipData *data;
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
float mouseloc[2];
|
|
int num_seq;
|
|
View2D *v2d = UI_view2d_fromcontext(C);
|
|
|
|
/* Count the amount of elements to trim. */
|
|
num_seq = slip_count_sequences(ed->seqbasep);
|
|
|
|
if (num_seq == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
data = MEM_callocN<SlipData>("trimdata");
|
|
op->customdata = static_cast<void *>(data);
|
|
data->strip_array = MEM_calloc_arrayN<Strip *>(num_seq, "trimdata_strips");
|
|
data->num_seq = num_seq;
|
|
data->previous_offset = 0;
|
|
|
|
initNumInput(&data->num_input);
|
|
data->num_input.idx_max = 0;
|
|
data->num_input.unit_sys = USER_UNIT_NONE;
|
|
data->num_input.unit_type[0] = 0;
|
|
|
|
slip_add_sequences(ed->seqbasep, data->strip_array);
|
|
|
|
UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &mouseloc[0], &mouseloc[1]);
|
|
|
|
copy_v2_v2(data->init_mouseloc, mouseloc);
|
|
|
|
data->slow = false;
|
|
|
|
WM_event_add_modal_handler(C, op);
|
|
|
|
/* Notify so we draw extensions immediately. */
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
static void sequencer_slip_strips(Scene *scene, SlipData *data, int delta, float subframe_delta)
|
|
{
|
|
for (int i = data->num_seq - 1; i >= 0; i--) {
|
|
Strip *strip = data->strip_array[i];
|
|
|
|
ListBase *channels = seq::channels_displayed_get(seq::editing_get(scene));
|
|
if (seq::transform_is_locked(channels, strip)) {
|
|
continue;
|
|
}
|
|
|
|
seq::time_slip_strip(scene, strip, delta, subframe_delta);
|
|
}
|
|
|
|
for (int i = data->num_seq - 1; i >= 0; i--) {
|
|
Strip *strip = data->strip_array[i];
|
|
seq::relations_invalidate_cache_preprocessed(scene, strip);
|
|
}
|
|
}
|
|
|
|
/* Make sure, that each strip contains at least 1 frame of content.
|
|
* Returns clamped offset relative to current strip positions. */
|
|
static int sequencer_slip_apply_limits(const Scene *scene, SlipData *data, int *offset)
|
|
{
|
|
int delta_offset = *offset - data->previous_offset;
|
|
|
|
for (int i = 0; i < data->num_seq; i++) {
|
|
Strip *strip = data->strip_array[i];
|
|
int strip_content_start = seq::time_start_frame_get(strip) + delta_offset;
|
|
int strip_content_end = strip_content_start + strip->len + strip->anim_startofs +
|
|
strip->anim_endofs;
|
|
int diff = 0;
|
|
|
|
if (strip_content_start >= seq::time_right_handle_frame_get(scene, strip)) {
|
|
diff = seq::time_right_handle_frame_get(scene, strip) - strip_content_start - 1;
|
|
}
|
|
|
|
if (strip_content_end <= seq::time_left_handle_frame_get(scene, strip)) {
|
|
diff = seq::time_left_handle_frame_get(scene, strip) - strip_content_end + 1;
|
|
}
|
|
*offset += diff;
|
|
delta_offset += diff;
|
|
}
|
|
data->previous_offset = *offset;
|
|
|
|
return delta_offset;
|
|
}
|
|
|
|
static int sequencer_slip_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
|
|
/* Count the amount of elements to trim. */
|
|
int num_seq = slip_count_sequences(ed->seqbasep);
|
|
|
|
if (num_seq == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
SlipData *data = MEM_callocN<SlipData>("trimdata");
|
|
op->customdata = static_cast<void *>(data);
|
|
data->strip_array = MEM_calloc_arrayN<Strip *>(num_seq, "trimdata_strips");
|
|
data->num_seq = num_seq;
|
|
|
|
slip_add_sequences(ed->seqbasep, data->strip_array);
|
|
|
|
float offset_fl = RNA_float_get(op->ptr, "offset");
|
|
int offset = round_fl_to_int(offset_fl);
|
|
|
|
float subframe_delta = 0.0f;
|
|
if (std::trunc(offset_fl) != offset_fl) {
|
|
/* Only apply subframe offsets if the input is not an integer. */
|
|
subframe_delta = offset_fl - offset;
|
|
}
|
|
|
|
sequencer_slip_apply_limits(scene, data, &offset);
|
|
sequencer_slip_strips(scene, data, offset, subframe_delta);
|
|
|
|
MEM_freeN(data->strip_array);
|
|
MEM_freeN(data);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static void sequencer_slip_update_header(Scene *scene, ScrArea *area, SlipData *data, int offset)
|
|
{
|
|
if (area == nullptr) {
|
|
return;
|
|
}
|
|
|
|
char msg[UI_MAX_DRAW_STR];
|
|
if (hasNumInput(&data->num_input)) {
|
|
char num_str[NUM_STR_REP_LEN];
|
|
outputNumInput(&data->num_input, num_str, scene->unit);
|
|
SNPRINTF(msg, IFACE_("Slip offset: %s"), num_str);
|
|
}
|
|
else {
|
|
SNPRINTF(msg, IFACE_("Slip offset: %d"), offset);
|
|
}
|
|
|
|
ED_area_status_text(area, msg);
|
|
}
|
|
|
|
static void handle_number_input(
|
|
bContext *C, wmOperator *op, ScrArea *area, SlipData *data, Scene *scene)
|
|
{
|
|
float offset_fl;
|
|
applyNumInput(&data->num_input, &offset_fl);
|
|
int offset = round_fl_to_int(offset_fl);
|
|
|
|
const int delta_offset = sequencer_slip_apply_limits(scene, data, &offset);
|
|
sequencer_slip_update_header(scene, area, data, offset);
|
|
|
|
RNA_float_set(op->ptr, "offset", offset_fl);
|
|
|
|
float subframe_delta = 0.0f;
|
|
if (data->subframe_restore != 0.0f) {
|
|
/* Always remove the previous sub-frame adjustments we have potentially made with the mouse
|
|
* input when the user starts entering values by hand.
|
|
*/
|
|
subframe_delta = -data->subframe_restore;
|
|
data->subframe_restore = 0.0f;
|
|
}
|
|
if (std::trunc(offset_fl) != offset_fl) {
|
|
/* Only apply sub-frame offsets if the input is not an integer. */
|
|
subframe_delta = offset_fl - data->previous_subframe_offset - delta_offset;
|
|
data->subframe_restore += subframe_delta;
|
|
}
|
|
data->previous_subframe_offset = offset_fl;
|
|
sequencer_slip_strips(scene, data, delta_offset, subframe_delta);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
}
|
|
|
|
static int sequencer_slip_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
SlipData *data = (SlipData *)op->customdata;
|
|
ScrArea *area = CTX_wm_area(C);
|
|
const bool has_numInput = hasNumInput(&data->num_input);
|
|
bool handled = true;
|
|
|
|
/* Modal numinput active, try to handle numeric inputs. */
|
|
if (event->val == KM_PRESS && has_numInput && handleNumInput(C, &data->num_input, event)) {
|
|
handle_number_input(C, op, area, data, scene);
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
switch (event->type) {
|
|
case MOUSEMOVE: {
|
|
if (!has_numInput) {
|
|
float mouseloc[2];
|
|
int offset;
|
|
float mouse_x;
|
|
View2D *v2d = UI_view2d_fromcontext(C);
|
|
|
|
if (data->slow) {
|
|
mouse_x = event->mval[0] - data->slow_offset;
|
|
mouse_x *= 0.1f;
|
|
mouse_x += data->slow_offset;
|
|
}
|
|
else {
|
|
mouse_x = event->mval[0];
|
|
}
|
|
|
|
/* Choose the side based on which side of the current frame the mouse is. */
|
|
UI_view2d_region_to_view(v2d, mouse_x, 0, &mouseloc[0], &mouseloc[1]);
|
|
float offset_fl = mouseloc[0] - data->init_mouseloc[0];
|
|
offset = offset_fl;
|
|
|
|
const int delta_offset = sequencer_slip_apply_limits(scene, data, &offset);
|
|
sequencer_slip_update_header(scene, area, data, offset);
|
|
|
|
if (!data->slow) {
|
|
RNA_float_set(op->ptr, "offset", offset);
|
|
}
|
|
float subframe_delta = 0.0f;
|
|
if (data->slow) {
|
|
RNA_float_set(op->ptr, "offset", offset_fl);
|
|
subframe_delta = offset_fl - data->previous_subframe_offset - delta_offset;
|
|
data->subframe_restore += subframe_delta;
|
|
}
|
|
else if (data->subframe_restore != 0.0f) {
|
|
/* If we exit slow mode, make sure we undo the fractional adjustments we have done. */
|
|
subframe_delta = -data->subframe_restore;
|
|
data->subframe_restore = 0.0f;
|
|
}
|
|
data->previous_subframe_offset = offset_fl;
|
|
sequencer_slip_strips(scene, data, delta_offset, subframe_delta);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case LEFTMOUSE:
|
|
case EVT_RETKEY:
|
|
case EVT_PADENTER:
|
|
case EVT_SPACEKEY: {
|
|
MEM_freeN(data->strip_array);
|
|
MEM_freeN(data);
|
|
op->customdata = nullptr;
|
|
if (area) {
|
|
ED_area_status_text(area, nullptr);
|
|
}
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
case EVT_ESCKEY:
|
|
case RIGHTMOUSE: {
|
|
int offset = data->previous_offset;
|
|
float subframe_delta = data->subframe_restore;
|
|
sequencer_slip_strips(scene, data, -offset, -subframe_delta);
|
|
|
|
MEM_freeN(data->strip_array);
|
|
MEM_freeN(data);
|
|
op->customdata = nullptr;
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
if (area) {
|
|
ED_area_status_text(area, nullptr);
|
|
}
|
|
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
case EVT_RIGHTSHIFTKEY:
|
|
case EVT_LEFTSHIFTKEY:
|
|
if (!has_numInput) {
|
|
if (event->val == KM_PRESS) {
|
|
data->slow = true;
|
|
data->slow_offset = event->mval[0];
|
|
}
|
|
else if (event->val == KM_RELEASE) {
|
|
data->slow = false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
handled = false;
|
|
break;
|
|
}
|
|
|
|
/* Modal numinput inactive, try to handle numeric inputs. */
|
|
if (!handled && event->val == KM_PRESS && handleNumInput(C, &data->num_input, event)) {
|
|
handle_number_input(C, op, area, data, scene);
|
|
}
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
void SEQUENCER_OT_slip(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Slip Strips";
|
|
ot->idname = "SEQUENCER_OT_slip";
|
|
ot->description = "Slip the contents of selected strips";
|
|
|
|
/* Api callbacks. */
|
|
ot->invoke = sequencer_slip_invoke;
|
|
ot->modal = sequencer_slip_modal;
|
|
ot->exec = sequencer_slip_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
PropertyRNA *prop;
|
|
|
|
prop = RNA_def_float(ot->srna,
|
|
"offset",
|
|
0,
|
|
-FLT_MAX,
|
|
FLT_MAX,
|
|
"Offset",
|
|
"Offset to the data of the strip",
|
|
-FLT_MAX,
|
|
FLT_MAX);
|
|
RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 100, 0);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Mute Strips Operator
|
|
* \{ */
|
|
|
|
static int sequencer_mute_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
ListBase *channels = seq::channels_displayed_get(ed);
|
|
|
|
bool selected;
|
|
|
|
selected = !RNA_boolean_get(op->ptr, "unselected");
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
|
if (!seq::transform_is_locked(channels, strip)) {
|
|
if (selected) {
|
|
if (strip->flag & SELECT) {
|
|
strip->flag |= SEQ_MUTE;
|
|
seq::relations_invalidate_dependent(scene, strip);
|
|
}
|
|
}
|
|
else {
|
|
if ((strip->flag & SELECT) == 0) {
|
|
strip->flag |= SEQ_MUTE;
|
|
seq::relations_invalidate_dependent(scene, strip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_mute(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Mute Strips";
|
|
ot->idname = "SEQUENCER_OT_mute";
|
|
ot->description = "Mute (un)selected strips";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_mute_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_boolean(
|
|
ot->srna, "unselected", false, "Unselected", "Mute unselected rather than selected strips");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Unmute Strips Operator
|
|
* \{ */
|
|
|
|
static int sequencer_unmute_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
ListBase *channels = seq::channels_displayed_get(ed);
|
|
bool selected;
|
|
|
|
selected = !RNA_boolean_get(op->ptr, "unselected");
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
|
if (!seq::transform_is_locked(channels, strip)) {
|
|
if (selected) {
|
|
if (strip->flag & SELECT) {
|
|
strip->flag &= ~SEQ_MUTE;
|
|
seq::relations_invalidate_dependent(scene, strip);
|
|
}
|
|
}
|
|
else {
|
|
if ((strip->flag & SELECT) == 0) {
|
|
strip->flag &= ~SEQ_MUTE;
|
|
seq::relations_invalidate_dependent(scene, strip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_unmute(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Unmute Strips";
|
|
ot->idname = "SEQUENCER_OT_unmute";
|
|
ot->description = "Unmute (un)selected strips";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_unmute_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_boolean(ot->srna,
|
|
"unselected",
|
|
false,
|
|
"Unselected",
|
|
"Unmute unselected rather than selected strips");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Lock Strips Operator
|
|
* \{ */
|
|
|
|
static int sequencer_lock_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
|
if (strip->flag & SELECT) {
|
|
strip->flag |= SEQ_LOCK;
|
|
}
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_lock(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Lock Strips";
|
|
ot->idname = "SEQUENCER_OT_lock";
|
|
ot->description = "Lock strips so they can't be transformed";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_lock_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Unlock Strips Operator
|
|
* \{ */
|
|
|
|
static int sequencer_unlock_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
|
if (strip->flag & SELECT) {
|
|
strip->flag &= ~SEQ_LOCK;
|
|
}
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_unlock(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Unlock Strips";
|
|
ot->idname = "SEQUENCER_OT_unlock";
|
|
ot->description = "Unlock strips so they can be transformed";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_unlock_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Connect Strips Operator
|
|
* \{ */
|
|
|
|
static int sequencer_connect_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
ListBase *active_seqbase = seq::active_seqbase_get(ed);
|
|
|
|
blender::VectorSet<Strip *> selected = seq::query_selected_strips(active_seqbase);
|
|
|
|
if (selected.is_empty()) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
const bool toggle = RNA_boolean_get(op->ptr, "toggle");
|
|
if (toggle && seq::are_strips_connected_together(selected)) {
|
|
seq::disconnect(selected);
|
|
}
|
|
else {
|
|
seq::connect(selected);
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_connect(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Connect Strips";
|
|
ot->idname = "SEQUENCER_OT_connect";
|
|
ot->description = "Link selected strips together for simplified group selection";
|
|
|
|
ot->exec = sequencer_connect_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_boolean(ot->srna, "toggle", true, "Toggle", "Toggle strip connections");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Disconnect Strips Operator
|
|
* \{ */
|
|
|
|
static int sequencer_disconnect_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
ListBase *active_seqbase = seq::active_seqbase_get(ed);
|
|
|
|
blender::VectorSet<Strip *> selected = seq::query_selected_strips(active_seqbase);
|
|
|
|
if (seq::disconnect(selected)) {
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
void SEQUENCER_OT_disconnect(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Disconnect Strips";
|
|
ot->idname = "SEQUENCER_OT_disconnect";
|
|
ot->description = "Unlink selected strips so that they can be selected individually";
|
|
|
|
ot->exec = sequencer_disconnect_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Reload Strips Operator
|
|
* \{ */
|
|
|
|
static int sequencer_reload_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
const bool adjust_length = RNA_boolean_get(op->ptr, "adjust_length");
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
|
if (strip->flag & SELECT) {
|
|
seq::add_reload_new_file(bmain, scene, strip, !adjust_length);
|
|
blender::seq::thumbnail_cache_invalidate_strip(scene, strip);
|
|
|
|
if (adjust_length) {
|
|
if (seq::transform_test_overlap(scene, ed->seqbasep, strip)) {
|
|
seq::transform_seqbase_shuffle(ed->seqbasep, strip, scene);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_reload(wmOperatorType *ot)
|
|
{
|
|
PropertyRNA *prop;
|
|
|
|
/* Identifiers. */
|
|
ot->name = "Reload Strips";
|
|
ot->idname = "SEQUENCER_OT_reload";
|
|
ot->description = "Reload strips in the sequencer";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_reload_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER; /* No undo, the data changed is stored outside 'main'. */
|
|
|
|
prop = RNA_def_boolean(ot->srna,
|
|
"adjust_length",
|
|
false,
|
|
"Adjust Length",
|
|
"Adjust length of strips to their data length");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Refresh Sequencer Operator
|
|
* \{ */
|
|
|
|
static bool sequencer_refresh_all_poll(bContext *C)
|
|
{
|
|
if (G.is_rendering) {
|
|
return false;
|
|
}
|
|
return sequencer_edit_poll(C);
|
|
}
|
|
|
|
static int sequencer_refresh_all_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
|
|
seq::relations_free_imbuf(scene, &ed->seqbase, false);
|
|
blender::seq::media_presence_free(scene);
|
|
blender::seq::thumbnail_cache_clear(scene);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_refresh_all(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Refresh Sequencer";
|
|
ot->idname = "SEQUENCER_OT_refresh_all";
|
|
ot->description = "Refresh the sequencer editor";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_refresh_all_exec;
|
|
ot->poll = sequencer_refresh_all_poll;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Reassign Inputs Operator
|
|
* \{ */
|
|
|
|
bool strip_effect_get_new_inputs(Scene *scene,
|
|
bool ignore_active,
|
|
int num_inputs,
|
|
Strip **r_seq1,
|
|
Strip **r_seq2,
|
|
const char **r_error_str)
|
|
{
|
|
Editing *ed = seq::editing_get(scene);
|
|
Strip *seq1 = nullptr, *seq2 = nullptr;
|
|
|
|
*r_error_str = nullptr;
|
|
|
|
if (num_inputs == 0) {
|
|
*r_seq1 = *r_seq2 = nullptr;
|
|
return true;
|
|
}
|
|
|
|
blender::VectorSet<Strip *> new_inputs = seq::query_selected_strips(ed->seqbasep);
|
|
// Ignore sound strips for now (avoids unnecessary errors when connected strips are
|
|
// selected together, and the intent to operate on strips with video content is clear).
|
|
new_inputs.remove_if([&](Strip *strip) { return strip->type == STRIP_TYPE_SOUND_RAM; });
|
|
|
|
if (ignore_active) {
|
|
// If `ignore_active` is true, this function is being called from the reassign inputs
|
|
// operator, meaning the active strip must be the effect strip to reassign.
|
|
Strip *active_strip = seq::select_active_get(scene);
|
|
new_inputs.remove_if([&](Strip *strip) { return strip == active_strip; });
|
|
}
|
|
|
|
if (new_inputs.size() > 2) {
|
|
*r_error_str = N_("Cannot apply effect to more than 2 sequence strips with video content");
|
|
return false;
|
|
}
|
|
|
|
if (num_inputs == 2) {
|
|
if (new_inputs.size() != 2) {
|
|
*r_error_str = N_("Exactly 2 selected sequence strips with video content are needed");
|
|
return false;
|
|
}
|
|
seq1 = new_inputs[0];
|
|
seq2 = new_inputs[1];
|
|
}
|
|
else if (num_inputs == 1) {
|
|
if (new_inputs.size() != 1) {
|
|
*r_error_str = N_("Exactly one selected sequence strip with video content is needed");
|
|
return false;
|
|
}
|
|
seq1 = new_inputs[0];
|
|
}
|
|
|
|
*r_seq1 = seq1;
|
|
*r_seq2 = seq2;
|
|
|
|
return true;
|
|
}
|
|
|
|
static int sequencer_reassign_inputs_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Strip *seq1, *seq2;
|
|
Strip *active_strip = seq::select_active_get(scene);
|
|
const char *error_msg;
|
|
const int num_inputs = seq::effect_get_num_inputs(active_strip->type);
|
|
|
|
if (num_inputs == 0) {
|
|
BKE_report(op->reports, RPT_ERROR, "Cannot reassign inputs: strip has no inputs");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (!strip_effect_get_new_inputs(scene, true, num_inputs, &seq1, &seq2, &error_msg)) {
|
|
BKE_report(op->reports, RPT_ERROR, error_msg);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
/* Check if reassigning would create recursivity. */
|
|
if (seq::relations_render_loop_check(seq1, active_strip) ||
|
|
seq::relations_render_loop_check(seq2, active_strip))
|
|
{
|
|
BKE_report(op->reports, RPT_ERROR, "Cannot reassign inputs: recursion detected");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
active_strip->seq1 = seq1;
|
|
active_strip->seq2 = seq2;
|
|
|
|
int old_start = active_strip->start;
|
|
|
|
/* Force time position update for reassigned effects.
|
|
* TODO(Richard): This is because internally startdisp is still used, due to poor performance of
|
|
* mapping effect range to inputs. This mapping could be cached though. */
|
|
seq::strip_lookup_invalidate(scene->ed);
|
|
seq::time_left_handle_frame_set(scene, seq1, seq::time_left_handle_frame_get(scene, seq1));
|
|
|
|
seq::relations_invalidate_cache_preprocessed(scene, active_strip);
|
|
seq::offset_animdata(scene, active_strip, (active_strip->start - old_start));
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static bool sequencer_effect_poll(bContext *C)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
|
|
if (ed) {
|
|
Strip *active_strip = seq::select_active_get(scene);
|
|
if (active_strip && (active_strip->type & STRIP_TYPE_EFFECT)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SEQUENCER_OT_reassign_inputs(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Reassign Inputs";
|
|
ot->idname = "SEQUENCER_OT_reassign_inputs";
|
|
ot->description = "Reassign the inputs for the effect strip";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_reassign_inputs_exec;
|
|
ot->poll = sequencer_effect_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Swap Inputs Operator
|
|
* \{ */
|
|
|
|
static int sequencer_swap_inputs_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Strip *active_strip = seq::select_active_get(scene);
|
|
|
|
if (active_strip->seq1 == nullptr || active_strip->seq2 == nullptr) {
|
|
BKE_report(op->reports, RPT_ERROR, "No valid inputs to swap");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
Strip *strip = active_strip->seq1;
|
|
active_strip->seq1 = active_strip->seq2;
|
|
active_strip->seq2 = strip;
|
|
|
|
seq::relations_invalidate_cache_preprocessed(scene, active_strip);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
void SEQUENCER_OT_swap_inputs(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Swap Inputs";
|
|
ot->idname = "SEQUENCER_OT_swap_inputs";
|
|
ot->description = "Swap the two inputs of the effect strip";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_swap_inputs_exec;
|
|
ot->poll = sequencer_effect_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Split Strips Operator
|
|
* \{ */
|
|
|
|
static int mouse_frame_side(View2D *v2d, short mouse_x, int frame)
|
|
{
|
|
int mval[2];
|
|
float mouseloc[2];
|
|
|
|
mval[0] = mouse_x;
|
|
mval[1] = 0;
|
|
|
|
/* Choose the side based on which side of the current frame the mouse is on. */
|
|
UI_view2d_region_to_view(v2d, mval[0], mval[1], &mouseloc[0], &mouseloc[1]);
|
|
|
|
return mouseloc[0] > frame ? seq::SIDE_RIGHT : seq::SIDE_LEFT;
|
|
}
|
|
|
|
static const EnumPropertyItem prop_split_types[] = {
|
|
{seq::SPLIT_SOFT, "SOFT", 0, "Soft", ""},
|
|
{seq::SPLIT_HARD, "HARD", 0, "Hard", ""},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
const EnumPropertyItem prop_side_types[] = {
|
|
{seq::SIDE_MOUSE, "MOUSE", 0, "Mouse Position", ""},
|
|
{seq::SIDE_LEFT, "LEFT", 0, "Left", ""},
|
|
{seq::SIDE_RIGHT, "RIGHT", 0, "Right", ""},
|
|
{seq::SIDE_BOTH, "BOTH", 0, "Both", ""},
|
|
{seq::SIDE_NO_CHANGE, "NO_CHANGE", 0, "No Change", ""},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
/* Get the splitting side for the Split Strips's operator exec() callback. */
|
|
static int sequence_split_side_for_exec_get(wmOperator *op)
|
|
{
|
|
const int split_side = RNA_enum_get(op->ptr, "side");
|
|
|
|
/* The mouse position can not be resolved from the exec() as the mouse coordinate is not
|
|
* accessible. So fall-back to the RIGHT side instead.
|
|
*
|
|
* The SEQ_SIDE_MOUSE is used by the Strip menu, together with the EXEC_DEFAULT operator
|
|
* context in order to have properly resolved shortcut in the menu. */
|
|
if (split_side == seq::SIDE_MOUSE) {
|
|
return seq::SIDE_RIGHT;
|
|
}
|
|
|
|
return split_side;
|
|
}
|
|
|
|
static int sequencer_split_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
bool changed = false;
|
|
bool strip_selected = false;
|
|
|
|
const bool use_cursor_position = RNA_boolean_get(op->ptr, "use_cursor_position");
|
|
|
|
const int split_frame = RNA_struct_property_is_set(op->ptr, "frame") ?
|
|
RNA_int_get(op->ptr, "frame") :
|
|
scene->r.cfra;
|
|
const int split_channel = RNA_int_get(op->ptr, "channel");
|
|
|
|
const seq::eSplitMethod method = seq::eSplitMethod(RNA_enum_get(op->ptr, "type"));
|
|
const int split_side = sequence_split_side_for_exec_get(op);
|
|
const bool ignore_selection = RNA_boolean_get(op->ptr, "ignore_selection");
|
|
|
|
seq::prefetch_stop(scene);
|
|
|
|
LISTBASE_FOREACH_BACKWARD (Strip *, strip, ed->seqbasep) {
|
|
if (use_cursor_position && strip->machine != split_channel) {
|
|
continue;
|
|
}
|
|
|
|
if (ignore_selection || strip->flag & SELECT) {
|
|
const char *error_msg = nullptr;
|
|
if (seq::edit_strip_split(
|
|
bmain, scene, ed->seqbasep, strip, split_frame, method, &error_msg) != nullptr)
|
|
{
|
|
changed = true;
|
|
}
|
|
if (error_msg != nullptr) {
|
|
BKE_report(op->reports, RPT_ERROR, error_msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed) { /* Got new strips? */
|
|
if (ignore_selection) {
|
|
if (use_cursor_position) {
|
|
LISTBASE_FOREACH (Strip *, strip, seq::active_seqbase_get(ed)) {
|
|
if (seq::time_right_handle_frame_get(scene, strip) == split_frame &&
|
|
strip->machine == split_channel)
|
|
{
|
|
strip_selected = strip->flag & STRIP_ALLSEL;
|
|
}
|
|
}
|
|
if (!strip_selected) {
|
|
LISTBASE_FOREACH (Strip *, strip, seq::active_seqbase_get(ed)) {
|
|
if (seq::time_left_handle_frame_get(scene, strip) == split_frame &&
|
|
strip->machine == split_channel)
|
|
{
|
|
strip->flag &= ~STRIP_ALLSEL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (split_side != seq::SIDE_BOTH) {
|
|
LISTBASE_FOREACH (Strip *, strip, seq::active_seqbase_get(ed)) {
|
|
if (split_side == seq::SIDE_LEFT) {
|
|
if (seq::time_left_handle_frame_get(scene, strip) >= split_frame) {
|
|
strip->flag &= ~STRIP_ALLSEL;
|
|
}
|
|
}
|
|
else {
|
|
if (seq::time_right_handle_frame_get(scene, strip) <= split_frame) {
|
|
strip->flag &= ~STRIP_ALLSEL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (changed) {
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
/* Passthrough to selection if used as tool. */
|
|
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
|
|
}
|
|
|
|
static int sequencer_split_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
View2D *v2d = UI_view2d_fromcontext(C);
|
|
|
|
int split_side = RNA_enum_get(op->ptr, "side");
|
|
int split_frame = scene->r.cfra;
|
|
|
|
if (split_side == seq::SIDE_MOUSE) {
|
|
if (ED_operator_sequencer_active(C) && v2d) {
|
|
split_side = mouse_frame_side(v2d, event->mval[0], split_frame);
|
|
}
|
|
else {
|
|
split_side = seq::SIDE_BOTH;
|
|
}
|
|
}
|
|
float mouseloc[2];
|
|
if (v2d) {
|
|
UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &mouseloc[0], &mouseloc[1]);
|
|
if (RNA_boolean_get(op->ptr, "use_cursor_position")) {
|
|
split_frame = mouseloc[0];
|
|
}
|
|
RNA_int_set(op->ptr, "channel", mouseloc[1]);
|
|
}
|
|
RNA_int_set(op->ptr, "frame", split_frame);
|
|
RNA_enum_set(op->ptr, "side", split_side);
|
|
// RNA_enum_set(op->ptr, "type", split_hard);
|
|
|
|
return sequencer_split_exec(C, op);
|
|
}
|
|
|
|
static void sequencer_split_ui(bContext * /*C*/, wmOperator *op)
|
|
{
|
|
uiLayout *layout = op->layout;
|
|
uiLayoutSetPropSep(layout, true);
|
|
uiLayoutSetPropDecorate(layout, false);
|
|
|
|
uiLayout *row = uiLayoutRow(layout, false);
|
|
uiItemR(row, op->ptr, "type", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
|
|
uiItemR(layout, op->ptr, "frame", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(layout, op->ptr, "side", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
|
|
uiItemS(layout);
|
|
|
|
uiItemR(layout, op->ptr, "use_cursor_position", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
if (RNA_boolean_get(op->ptr, "use_cursor_position")) {
|
|
uiItemR(layout, op->ptr, "channel", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
}
|
|
}
|
|
|
|
void SEQUENCER_OT_split(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Split Strips";
|
|
ot->idname = "SEQUENCER_OT_split";
|
|
ot->description = "Split the selected strips in two";
|
|
|
|
/* Api callbacks. */
|
|
ot->invoke = sequencer_split_invoke;
|
|
ot->exec = sequencer_split_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
ot->ui = sequencer_split_ui;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
PropertyRNA *prop;
|
|
RNA_def_int(ot->srna,
|
|
"frame",
|
|
0,
|
|
INT_MIN,
|
|
INT_MAX,
|
|
"Frame",
|
|
"Frame where selected strips will be split",
|
|
INT_MIN,
|
|
INT_MAX);
|
|
RNA_def_int(ot->srna,
|
|
"channel",
|
|
0,
|
|
INT_MIN,
|
|
INT_MAX,
|
|
"Channel",
|
|
"Channel in which strip will be cut",
|
|
INT_MIN,
|
|
INT_MAX);
|
|
RNA_def_enum(ot->srna,
|
|
"type",
|
|
prop_split_types,
|
|
seq::SPLIT_SOFT,
|
|
"Type",
|
|
"The type of split operation to perform on strips");
|
|
|
|
RNA_def_boolean(ot->srna,
|
|
"use_cursor_position",
|
|
false,
|
|
"Use Cursor Position",
|
|
"Split at position of the cursor instead of current frame");
|
|
|
|
prop = RNA_def_enum(ot->srna,
|
|
"side",
|
|
prop_side_types,
|
|
seq::SIDE_MOUSE,
|
|
"Side",
|
|
"The side that remains selected after splitting");
|
|
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
|
|
prop = RNA_def_boolean(
|
|
ot->srna,
|
|
"ignore_selection",
|
|
false,
|
|
"Ignore Selection",
|
|
"Make cut even if strip is not selected preserving selection state after cut");
|
|
|
|
RNA_def_property_flag(prop, PROP_HIDDEN);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Duplicate Strips Operator
|
|
* \{ */
|
|
|
|
static int sequencer_add_duplicate_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
ARegion *region = CTX_wm_region(C);
|
|
|
|
if (ed == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
Strip *active_seq = seq::select_active_get(scene);
|
|
ListBase duplicated_strips = {nullptr, nullptr};
|
|
|
|
seq::sequence_base_dupli_recursive(scene, scene, &duplicated_strips, ed->seqbasep, 0, 0);
|
|
deselect_all_strips(scene);
|
|
|
|
if (duplicated_strips.first == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* Duplicate animation.
|
|
* First backup original curves from scene and duplicate strip curves from backup into scene.
|
|
* This way, when pasted strips are renamed, curves are renamed with them. Finally, restore
|
|
* original curves from backup.
|
|
*/
|
|
seq::AnimationBackup animation_backup = {{nullptr}};
|
|
seq::animation_backup_original(scene, &animation_backup);
|
|
|
|
ListBase *seqbase = seq::active_seqbase_get(seq::editing_get(scene));
|
|
Strip *strip_last = static_cast<Strip *>(seqbase->last);
|
|
|
|
/* Rely on the `duplicated_strips` list being added at the end.
|
|
* Their UIDs has been re-generated by the #SEQ_sequence_base_dupli_recursive(). */
|
|
BLI_movelisttolist(ed->seqbasep, &duplicated_strips);
|
|
|
|
/* Handle duplicated strips: set active, select, ensure unique name and duplicate animation
|
|
* data. */
|
|
for (Strip *strip = strip_last->next; strip; strip = strip->next) {
|
|
if (active_seq != nullptr && STREQ(strip->name, active_seq->name)) {
|
|
seq::select_active_set(scene, strip);
|
|
}
|
|
strip->flag &= ~(SEQ_LEFTSEL + SEQ_RIGHTSEL + SEQ_LOCK);
|
|
strip->flag |= SEQ_IGNORE_CHANNEL_LOCK;
|
|
|
|
seq::animation_duplicate_backup_to_scene(scene, strip, &animation_backup);
|
|
seq::ensure_unique_name(strip, scene);
|
|
}
|
|
|
|
/* Special case for duplicating strips in preview: Do not duplicate sound strips and handle
|
|
* overlap, because strips won't be translated. */
|
|
if (region->regiontype == RGN_TYPE_PREVIEW && sequencer_view_preview_only_poll(C)) {
|
|
for (Strip *strip = strip_last->next; strip; strip = strip->next) {
|
|
if (strip->type == STRIP_TYPE_SOUND_RAM) {
|
|
seq::edit_flag_for_removal(scene, ed->seqbasep, strip);
|
|
}
|
|
}
|
|
seq::edit_remove_flagged_sequences(scene, ed->seqbasep);
|
|
|
|
for (Strip *strip = strip_last->next; strip; strip = strip->next) {
|
|
if (seq::transform_test_overlap(scene, ed->seqbasep, strip)) {
|
|
seq::transform_seqbase_shuffle(ed->seqbasep, strip, scene);
|
|
}
|
|
}
|
|
}
|
|
|
|
seq::animation_restore_original(scene, &animation_backup);
|
|
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
DEG_relations_tag_update(CTX_data_main(C));
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_duplicate(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Duplicate Strips";
|
|
ot->idname = "SEQUENCER_OT_duplicate";
|
|
ot->description = "Duplicate the selected strips";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_add_duplicate_exec;
|
|
ot->poll = ED_operator_sequencer_active;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Erase Strips Operator
|
|
* \{ */
|
|
|
|
static void sequencer_delete_strip_data(bContext *C, Strip *strip)
|
|
{
|
|
if (strip->type != STRIP_TYPE_SCENE) {
|
|
return;
|
|
}
|
|
|
|
Main *bmain = CTX_data_main(C);
|
|
if (strip->scene) {
|
|
if (ED_scene_delete(C, bmain, strip->scene)) {
|
|
WM_event_add_notifier(C, NC_SCENE | NA_REMOVED, strip->scene);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int sequencer_delete_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
ListBase *seqbasep = seq::active_seqbase_get(seq::editing_get(scene));
|
|
const bool delete_data = RNA_boolean_get(op->ptr, "delete_data");
|
|
|
|
if (sequencer_view_has_preview_poll(C) && !sequencer_view_preview_only_poll(C)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
seq::prefetch_stop(scene);
|
|
|
|
for (Strip *strip : selected_strips_from_context(C)) {
|
|
seq::edit_flag_for_removal(scene, seqbasep, strip);
|
|
if (delete_data) {
|
|
sequencer_delete_strip_data(C, strip);
|
|
}
|
|
}
|
|
seq::edit_remove_flagged_sequences(scene, seqbasep);
|
|
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
if (scene->adt && scene->adt->action) {
|
|
DEG_id_tag_update(&scene->adt->action->id, ID_RECALC_ANIMATION_NO_FLUSH);
|
|
}
|
|
DEG_relations_tag_update(bmain);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_ANIMCHAN, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int sequencer_delete_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
ListBase *markers = &scene->markers;
|
|
|
|
if (!BLI_listbase_is_empty(markers)) {
|
|
ARegion *region = CTX_wm_region(C);
|
|
if (region && (region->regiontype == RGN_TYPE_WINDOW)) {
|
|
/* Bounding box of 30 pixels is used for markers shortcuts,
|
|
* prevent conflict with markers shortcuts here. */
|
|
if (event->mval[1] <= 30) {
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
}
|
|
}
|
|
|
|
return sequencer_delete_exec(C, op);
|
|
}
|
|
|
|
void SEQUENCER_OT_delete(wmOperatorType *ot)
|
|
{
|
|
|
|
/* Identifiers. */
|
|
ot->name = "Delete Strips";
|
|
ot->idname = "SEQUENCER_OT_delete";
|
|
ot->description = "Delete selected strips from the sequencer";
|
|
|
|
/* Api callbacks. */
|
|
ot->invoke = sequencer_delete_invoke;
|
|
ot->exec = sequencer_delete_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
ot->prop = RNA_def_boolean(ot->srna,
|
|
"delete_data",
|
|
false,
|
|
"Delete Data",
|
|
"After removing the Strip, delete the associated data also");
|
|
RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Clear Strip Offset Operator
|
|
* \{ */
|
|
|
|
static int sequencer_offset_clear_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
Strip *strip;
|
|
ListBase *channels = seq::channels_displayed_get(seq::editing_get(scene));
|
|
|
|
/* For effects, try to find a replacement input. */
|
|
for (strip = static_cast<Strip *>(ed->seqbasep->first); strip;
|
|
strip = static_cast<Strip *>(strip->next))
|
|
{
|
|
if (seq::transform_is_locked(channels, strip)) {
|
|
continue;
|
|
}
|
|
|
|
if ((strip->type & STRIP_TYPE_EFFECT) == 0 && (strip->flag & SELECT)) {
|
|
strip->startofs = strip->endofs = 0;
|
|
}
|
|
}
|
|
|
|
/* Update lengths, etc. */
|
|
strip = static_cast<Strip *>(ed->seqbasep->first);
|
|
while (strip) {
|
|
seq::relations_invalidate_cache_preprocessed(scene, strip);
|
|
strip = strip->next;
|
|
}
|
|
|
|
for (strip = static_cast<Strip *>(ed->seqbasep->first); strip;
|
|
strip = static_cast<Strip *>(strip->next))
|
|
{
|
|
if ((strip->type & STRIP_TYPE_EFFECT) == 0 && (strip->flag & SELECT)) {
|
|
if (seq::transform_test_overlap(scene, ed->seqbasep, strip)) {
|
|
seq::transform_seqbase_shuffle(ed->seqbasep, strip, scene);
|
|
}
|
|
}
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_offset_clear(wmOperatorType *ot)
|
|
{
|
|
|
|
/* Identifiers. */
|
|
ot->name = "Clear Strip Offset";
|
|
ot->idname = "SEQUENCER_OT_offset_clear";
|
|
ot->description = "Clear strip offsets from the start and end frames";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_offset_clear_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Separate Images Operator
|
|
* \{ */
|
|
|
|
static int sequencer_separate_images_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
ListBase *seqbase = seq::active_seqbase_get(ed);
|
|
|
|
Strip *strip, *strip_new;
|
|
StripData *data_new;
|
|
StripElem *se, *se_new;
|
|
int start_ofs, timeline_frame, frame_end;
|
|
int step = RNA_int_get(op->ptr, "length");
|
|
|
|
strip = static_cast<Strip *>(seqbase->first); /* Poll checks this is valid. */
|
|
|
|
seq::prefetch_stop(scene);
|
|
|
|
while (strip) {
|
|
if ((strip->flag & SELECT) && (strip->type == STRIP_TYPE_IMAGE) && (strip->len > 1)) {
|
|
Strip *strip_next;
|
|
|
|
/* TODO: remove f-curve and assign to split image strips.
|
|
* The old animation system would remove the user of `strip->ipo`. */
|
|
|
|
start_ofs = timeline_frame = seq::time_left_handle_frame_get(scene, strip);
|
|
frame_end = seq::time_right_handle_frame_get(scene, strip);
|
|
|
|
while (timeline_frame < frame_end) {
|
|
/* New strip. */
|
|
se = seq::render_give_stripelem(scene, strip, timeline_frame);
|
|
|
|
strip_new = seq::sequence_dupli_recursive(
|
|
scene, scene, seqbase, strip, STRIP_DUPE_UNIQUE_NAME);
|
|
|
|
strip_new->start = start_ofs;
|
|
strip_new->type = STRIP_TYPE_IMAGE;
|
|
strip_new->len = 1;
|
|
strip_new->flag |= SEQ_SINGLE_FRAME_CONTENT;
|
|
strip_new->endofs = 1 - step;
|
|
|
|
/* New strip. */
|
|
data_new = strip_new->data;
|
|
data_new->us = 1;
|
|
|
|
/* New stripdata, only one element now. */
|
|
/* Note this assume all elements (images) have the same dimension,
|
|
* since we only copy the name here. */
|
|
se_new = static_cast<StripElem *>(MEM_reallocN(data_new->stripdata, sizeof(*se_new)));
|
|
STRNCPY(se_new->filename, se->filename);
|
|
data_new->stripdata = se_new;
|
|
|
|
if (step > 1) {
|
|
strip_new->flag &= ~SEQ_OVERLAP;
|
|
if (seq::transform_test_overlap(scene, seqbase, strip_new)) {
|
|
seq::transform_seqbase_shuffle(seqbase, strip_new, scene);
|
|
}
|
|
}
|
|
|
|
/* XXX, COPY FCURVES */
|
|
|
|
timeline_frame++;
|
|
start_ofs += step;
|
|
}
|
|
|
|
strip_next = static_cast<Strip *>(strip->next);
|
|
seq::edit_flag_for_removal(scene, seqbase, strip);
|
|
strip = strip_next;
|
|
}
|
|
else {
|
|
strip = strip->next;
|
|
}
|
|
}
|
|
|
|
seq::edit_remove_flagged_sequences(scene, seqbase);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int sequencer_separate_images_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
return WM_operator_props_popup_confirm_ex(
|
|
C, op, event, IFACE_("Separate Sequence Images"), IFACE_("Separate"));
|
|
}
|
|
|
|
void SEQUENCER_OT_images_separate(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Separate Images";
|
|
ot->idname = "SEQUENCER_OT_images_separate";
|
|
ot->description = "On image sequence strips, it returns a strip for each image";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_separate_images_exec;
|
|
ot->invoke = sequencer_separate_images_invoke;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_int(ot->srna, "length", 1, 1, INT_MAX, "Length", "Length of each frame", 1, 1000);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Toggle Meta Strip Operator
|
|
* \{ */
|
|
|
|
static int sequencer_meta_toggle_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
Strip *active_strip = seq::select_active_get(scene);
|
|
|
|
seq::prefetch_stop(scene);
|
|
|
|
if (active_strip && active_strip->type == STRIP_TYPE_META && active_strip->flag & SELECT) {
|
|
/* Deselect active meta strip. */
|
|
seq::select_active_set(scene, nullptr);
|
|
seq::meta_stack_set(scene, active_strip);
|
|
}
|
|
else {
|
|
/* Exit meta-strip if possible. */
|
|
if (BLI_listbase_is_empty(&ed->metastack)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* Display parent meta. */
|
|
Strip *meta_parent = seq::meta_stack_pop(ed);
|
|
seq::select_active_set(scene, meta_parent);
|
|
}
|
|
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_meta_toggle(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Toggle Meta Strip";
|
|
ot->idname = "SEQUENCER_OT_meta_toggle";
|
|
ot->description = "Toggle a meta-strip (to edit enclosed strips)";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_meta_toggle_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Make Meta Strip Operator
|
|
* \{ */
|
|
|
|
static int sequencer_meta_make_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
Strip *active_strip = seq::select_active_get(scene);
|
|
ListBase *active_seqbase = seq::active_seqbase_get(ed);
|
|
|
|
blender::VectorSet<Strip *> selected = seq::query_selected_strips(active_seqbase);
|
|
|
|
if (selected.is_empty()) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
seq::prefetch_stop(scene);
|
|
|
|
int channel_max = 1, channel_min = INT_MAX, meta_start_frame = MAXFRAME,
|
|
meta_end_frame = MINFRAME;
|
|
Strip *seqm = seq::sequence_alloc(active_seqbase, 1, 1, STRIP_TYPE_META);
|
|
|
|
/* Remove all selected from main list, and put in meta.
|
|
* Sequence is moved within the same edit, no need to re-generate the UID. */
|
|
blender::VectorSet<Strip *> strips_to_move;
|
|
strips_to_move.add_multiple(selected);
|
|
seq::iterator_set_expand(
|
|
scene, active_seqbase, strips_to_move, seq::query_strip_connected_and_effect_chain);
|
|
|
|
for (Strip *strip : strips_to_move) {
|
|
seq::relations_invalidate_cache_preprocessed(scene, strip);
|
|
BLI_remlink(active_seqbase, strip);
|
|
BLI_addtail(&seqm->seqbase, strip);
|
|
channel_max = max_ii(strip->machine, channel_max);
|
|
channel_min = min_ii(strip->machine, channel_min);
|
|
meta_start_frame = min_ii(seq::time_left_handle_frame_get(scene, strip), meta_start_frame);
|
|
meta_end_frame = max_ii(seq::time_right_handle_frame_get(scene, strip), meta_end_frame);
|
|
}
|
|
|
|
ListBase *channels_cur = seq::channels_displayed_get(ed);
|
|
ListBase *channels_meta = &seqm->channels;
|
|
for (int i = channel_min; i <= channel_max; i++) {
|
|
SeqTimelineChannel *channel_cur = seq::channel_get_by_index(channels_cur, i);
|
|
SeqTimelineChannel *channel_meta = seq::channel_get_by_index(channels_meta, i);
|
|
STRNCPY(channel_meta->name, channel_cur->name);
|
|
channel_meta->flag = channel_cur->flag;
|
|
}
|
|
|
|
seqm->machine = active_strip ? active_strip->machine : channel_max;
|
|
BLI_strncpy(seqm->name + 2, DATA_("MetaStrip"), sizeof(seqm->name) - 2);
|
|
seq::sequence_base_unique_name_recursive(scene, &ed->seqbase, seqm);
|
|
seqm->start = meta_start_frame;
|
|
seqm->len = meta_end_frame - meta_start_frame;
|
|
seq::select_active_set(scene, seqm);
|
|
if (seq::transform_test_overlap(scene, active_seqbase, seqm)) {
|
|
seq::transform_seqbase_shuffle(active_seqbase, seqm, scene);
|
|
}
|
|
|
|
seq::strip_lookup_invalidate(ed);
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_meta_make(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Make Meta Strip";
|
|
ot->idname = "SEQUENCER_OT_meta_make";
|
|
ot->description = "Group selected strips into a meta-strip";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_meta_make_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name UnMeta Strip Operator
|
|
* \{ */
|
|
|
|
static int sequencer_meta_separate_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
Strip *active_strip = seq::select_active_get(scene);
|
|
|
|
if (active_strip == nullptr || active_strip->type != STRIP_TYPE_META) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
seq::prefetch_stop(scene);
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, &active_strip->seqbase) {
|
|
seq::relations_invalidate_cache_preprocessed(scene, strip);
|
|
}
|
|
|
|
/* Remove all selected from meta, and put in main list.
|
|
* Sequence is moved within the same edit, no need to re-generate the UID. */
|
|
BLI_movelisttolist(ed->seqbasep, &active_strip->seqbase);
|
|
BLI_listbase_clear(&active_strip->seqbase);
|
|
|
|
ListBase *active_seqbase = seq::active_seqbase_get(ed);
|
|
seq::edit_flag_for_removal(scene, active_seqbase, active_strip);
|
|
seq::edit_remove_flagged_sequences(scene, active_seqbase);
|
|
|
|
/* Test for effects and overlap. */
|
|
LISTBASE_FOREACH (Strip *, strip, active_seqbase) {
|
|
if (strip->flag & SELECT) {
|
|
strip->flag &= ~SEQ_OVERLAP;
|
|
if (seq::transform_test_overlap(scene, active_seqbase, strip)) {
|
|
seq::transform_seqbase_shuffle(active_seqbase, strip, scene);
|
|
}
|
|
}
|
|
}
|
|
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_meta_separate(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "UnMeta Strip";
|
|
ot->idname = "SEQUENCER_OT_meta_separate";
|
|
ot->description = "Put the contents of a meta-strip back in the sequencer";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_meta_separate_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Jump to Strip Operator
|
|
* \{ */
|
|
|
|
static bool strip_jump_internal(Scene *scene,
|
|
const short side,
|
|
const bool do_skip_mute,
|
|
const bool do_center)
|
|
{
|
|
bool changed = false;
|
|
int timeline_frame = scene->r.cfra;
|
|
int next_frame = seq::time_find_next_prev_edit(
|
|
scene, timeline_frame, side, do_skip_mute, do_center, false);
|
|
|
|
if (next_frame != timeline_frame) {
|
|
scene->r.cfra = next_frame;
|
|
changed = true;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
static bool sequencer_strip_jump_poll(bContext *C)
|
|
{
|
|
/* Prevent changes during render. */
|
|
if (G.is_rendering) {
|
|
return false;
|
|
}
|
|
|
|
return sequencer_edit_poll(C);
|
|
}
|
|
|
|
static int sequencer_strip_jump_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
const bool next = RNA_boolean_get(op->ptr, "next");
|
|
const bool center = RNA_boolean_get(op->ptr, "center");
|
|
|
|
/* Currently do_skip_mute is always true. */
|
|
if (!strip_jump_internal(scene, next ? seq::SIDE_RIGHT : seq::SIDE_LEFT, true, center)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_FRAME_CHANGE);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_strip_jump(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Jump to Strip";
|
|
ot->idname = "SEQUENCER_OT_strip_jump";
|
|
ot->description = "Move frame to previous edit point";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_strip_jump_exec;
|
|
ot->poll = sequencer_strip_jump_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
RNA_def_boolean(ot->srna, "next", true, "Next Strip", "");
|
|
RNA_def_boolean(ot->srna, "center", true, "Use Strip Center", "");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Swap Strip Operator
|
|
* \{ */
|
|
|
|
static const EnumPropertyItem prop_side_lr_types[] = {
|
|
{seq::SIDE_LEFT, "LEFT", 0, "Left", ""},
|
|
{seq::SIDE_RIGHT, "RIGHT", 0, "Right", ""},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
static void swap_sequence(Scene *scene, Strip *seqa, Strip *seqb)
|
|
{
|
|
int gap = seq::time_left_handle_frame_get(scene, seqb) -
|
|
seq::time_right_handle_frame_get(scene, seqa);
|
|
int strip_a_start;
|
|
int strip_b_start;
|
|
|
|
strip_b_start = (seqb->start - seq::time_left_handle_frame_get(scene, seqb)) +
|
|
seq::time_left_handle_frame_get(scene, seqa);
|
|
seq::transform_translate_sequence(scene, seqb, strip_b_start - seqb->start);
|
|
seq::relations_invalidate_cache_preprocessed(scene, seqb);
|
|
|
|
strip_a_start = (seqa->start - seq::time_left_handle_frame_get(scene, seqa)) +
|
|
seq::time_right_handle_frame_get(scene, seqb) + gap;
|
|
seq::transform_translate_sequence(scene, seqa, strip_a_start - seqa->start);
|
|
seq::relations_invalidate_cache_preprocessed(scene, seqa);
|
|
}
|
|
|
|
static Strip *find_next_prev_sequence(Scene *scene, Strip *test, int lr, int sel)
|
|
{
|
|
/* sel: 0==unselected, 1==selected, -1==don't care. */
|
|
Strip *strip, *best_strip = nullptr;
|
|
Editing *ed = seq::editing_get(scene);
|
|
|
|
int dist, best_dist;
|
|
best_dist = MAXFRAME * 2;
|
|
|
|
if (ed == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
strip = static_cast<Strip *>(ed->seqbasep->first);
|
|
while (strip) {
|
|
if ((strip != test) && (test->machine == strip->machine) &&
|
|
((sel == -1) || (sel == (strip->flag & SELECT))))
|
|
{
|
|
dist = MAXFRAME * 2;
|
|
|
|
switch (lr) {
|
|
case seq::SIDE_LEFT:
|
|
if (seq::time_right_handle_frame_get(scene, strip) <=
|
|
seq::time_left_handle_frame_get(scene, test))
|
|
{
|
|
dist = seq::time_right_handle_frame_get(scene, test) -
|
|
seq::time_left_handle_frame_get(scene, strip);
|
|
}
|
|
break;
|
|
case seq::SIDE_RIGHT:
|
|
if (seq::time_left_handle_frame_get(scene, strip) >=
|
|
seq::time_right_handle_frame_get(scene, test))
|
|
{
|
|
dist = seq::time_left_handle_frame_get(scene, strip) -
|
|
seq::time_right_handle_frame_get(scene, test);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (dist == 0) {
|
|
best_strip = strip;
|
|
break;
|
|
}
|
|
if (dist < best_dist) {
|
|
best_dist = dist;
|
|
best_strip = strip;
|
|
}
|
|
}
|
|
strip = static_cast<Strip *>(strip->next);
|
|
}
|
|
return best_strip; /* Can be nullptr. */
|
|
}
|
|
|
|
static bool strip_is_parent(const Strip *par, const Strip *strip)
|
|
{
|
|
return ((par->seq1 == strip) || (par->seq2 == strip));
|
|
}
|
|
|
|
static int sequencer_swap_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
Strip *active_seq = seq::select_active_get(scene);
|
|
ListBase *seqbase = seq::active_seqbase_get(ed);
|
|
Strip *strip;
|
|
int side = RNA_enum_get(op->ptr, "side");
|
|
|
|
if (active_seq == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
strip = find_next_prev_sequence(scene, active_seq, side, -1);
|
|
|
|
if (strip) {
|
|
|
|
/* Disallow effect strips. */
|
|
if (seq::effect_get_num_inputs(strip->type) >= 1 &&
|
|
(strip->effectdata || strip->seq1 || strip->seq2))
|
|
{
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
if ((seq::effect_get_num_inputs(active_seq->type) >= 1) &&
|
|
(active_seq->effectdata || active_seq->seq1 || active_seq->seq2))
|
|
{
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
ListBase *channels = seq::channels_displayed_get(seq::editing_get(scene));
|
|
if (seq::transform_is_locked(channels, strip) ||
|
|
seq::transform_is_locked(channels, active_seq))
|
|
{
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
switch (side) {
|
|
case seq::SIDE_LEFT:
|
|
swap_sequence(scene, strip, active_seq);
|
|
break;
|
|
case seq::SIDE_RIGHT:
|
|
swap_sequence(scene, active_seq, strip);
|
|
break;
|
|
}
|
|
|
|
/* Do this in a new loop since both effects need to be calculated first. */
|
|
LISTBASE_FOREACH (Strip *, iseq, seqbase) {
|
|
if ((iseq->type & STRIP_TYPE_EFFECT) &&
|
|
(strip_is_parent(iseq, active_seq) || strip_is_parent(iseq, strip)))
|
|
{
|
|
/* This may now overlap. */
|
|
if (seq::transform_test_overlap(scene, seqbase, iseq)) {
|
|
seq::transform_seqbase_shuffle(seqbase, iseq, scene);
|
|
}
|
|
}
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
void SEQUENCER_OT_swap(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Swap Strip";
|
|
ot->idname = "SEQUENCER_OT_swap";
|
|
ot->description = "Swap active strip with strip to the right or left";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_swap_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
RNA_def_enum(
|
|
ot->srna, "side", prop_side_lr_types, seq::SIDE_RIGHT, "Side", "Side of the strip to swap");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Set Render Size Operator
|
|
* \{ */
|
|
|
|
static int sequencer_rendersize_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Strip *active_seq = seq::select_active_get(scene);
|
|
StripElem *se = nullptr;
|
|
|
|
if (active_seq == nullptr || active_seq->data == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
switch (active_seq->type) {
|
|
case STRIP_TYPE_IMAGE:
|
|
se = seq::render_give_stripelem(scene, active_seq, scene->r.cfra);
|
|
break;
|
|
case STRIP_TYPE_MOVIE:
|
|
se = active_seq->data->stripdata;
|
|
break;
|
|
default:
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (se == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* Prevent setting the render size if sequence values aren't initialized. */
|
|
if (se->orig_width <= 0 || se->orig_height <= 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
scene->r.xsch = se->orig_width;
|
|
scene->r.ysch = se->orig_height;
|
|
|
|
active_seq->data->transform->scale_x = active_seq->data->transform->scale_y = 1.0f;
|
|
active_seq->data->transform->xofs = active_seq->data->transform->yofs = 0.0f;
|
|
|
|
seq::relations_invalidate_cache_preprocessed(scene, active_seq);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, scene);
|
|
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_rendersize(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Set Render Size";
|
|
ot->idname = "SEQUENCER_OT_rendersize";
|
|
ot->description = "Set render size and aspect from active sequence";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_rendersize_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Copy Operator
|
|
* \{ */
|
|
|
|
void SEQUENCER_OT_copy(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Copy";
|
|
ot->idname = "SEQUENCER_OT_copy";
|
|
ot->description = "Copy the selected strips to the internal clipboard";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_clipboard_copy_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Paste Operator
|
|
* \{ */
|
|
|
|
bool deselect_all_strips(Scene *scene)
|
|
{
|
|
Editing *ed = seq::editing_get(scene);
|
|
bool changed = false;
|
|
|
|
if (ed == nullptr) {
|
|
return changed;
|
|
}
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, seq::active_seqbase_get(ed)) {
|
|
if (strip->flag & STRIP_ALLSEL) {
|
|
strip->flag &= ~STRIP_ALLSEL;
|
|
changed = true;
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
void SEQUENCER_OT_paste(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Paste";
|
|
ot->idname = "SEQUENCER_OT_paste";
|
|
ot->description = "Paste strips from the internal clipboard";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_clipboard_paste_exec;
|
|
ot->poll = ED_operator_sequencer_active;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
PropertyRNA *prop = RNA_def_boolean(
|
|
ot->srna,
|
|
"keep_offset",
|
|
false,
|
|
"Keep Offset",
|
|
"Keep strip offset relative to the current frame when pasting");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Sequencer Swap Data Operator
|
|
* \{ */
|
|
|
|
static int sequencer_swap_data_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Strip *strip_act;
|
|
Strip *strip_other;
|
|
const char *error_msg;
|
|
|
|
if (seq::select_active_get_pair(scene, &strip_act, &strip_other) == false) {
|
|
BKE_report(op->reports, RPT_ERROR, "Please select two strips");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (seq::edit_sequence_swap(scene, strip_act, strip_other, &error_msg) == false) {
|
|
BKE_report(op->reports, RPT_ERROR, error_msg);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (strip_act->scene_sound) {
|
|
BKE_sound_remove_scene_sound(scene, strip_act->scene_sound);
|
|
}
|
|
|
|
if (strip_other->scene_sound) {
|
|
BKE_sound_remove_scene_sound(scene, strip_other->scene_sound);
|
|
}
|
|
|
|
strip_act->scene_sound = nullptr;
|
|
strip_other->scene_sound = nullptr;
|
|
|
|
if (strip_act->sound) {
|
|
BKE_sound_add_scene_sound_defaults(scene, strip_act);
|
|
}
|
|
if (strip_other->sound) {
|
|
BKE_sound_add_scene_sound_defaults(scene, strip_other);
|
|
}
|
|
|
|
seq::relations_invalidate_cache_raw(scene, strip_act);
|
|
seq::relations_invalidate_cache_raw(scene, strip_other);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_swap_data(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Sequencer Swap Data";
|
|
ot->idname = "SEQUENCER_OT_swap_data";
|
|
ot->description = "Swap 2 sequencer strips";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_swap_data_exec;
|
|
ot->poll = ED_operator_sequencer_active;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Change Effect Input Operator
|
|
* \{ */
|
|
|
|
static int sequencer_change_effect_input_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Strip *strip = seq::select_active_get(scene);
|
|
|
|
Strip **strip_1 = &strip->seq1, **strip_2 = &strip->seq2;
|
|
|
|
if (*strip_1 == nullptr || *strip_2 == nullptr) {
|
|
BKE_report(op->reports, RPT_ERROR, "One of the effect inputs is unset, cannot swap");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
std::swap(*strip_1, *strip_2);
|
|
|
|
seq::relations_invalidate_cache_preprocessed(scene, strip);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_change_effect_input(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Change Effect Input";
|
|
ot->idname = "SEQUENCER_OT_change_effect_input";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_change_effect_input_exec;
|
|
ot->poll = sequencer_effect_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Change Effect Type Operator
|
|
* \{ */
|
|
|
|
const EnumPropertyItem sequencer_prop_effect_types[] = {
|
|
{STRIP_TYPE_CROSS, "CROSS", 0, "Crossfade", "Crossfade effect strip type"},
|
|
{STRIP_TYPE_ADD, "ADD", 0, "Add", "Add effect strip type"},
|
|
{STRIP_TYPE_SUB, "SUBTRACT", 0, "Subtract", "Subtract effect strip type"},
|
|
{STRIP_TYPE_ALPHAOVER, "ALPHA_OVER", 0, "Alpha Over", "Alpha Over effect strip type"},
|
|
{STRIP_TYPE_ALPHAUNDER, "ALPHA_UNDER", 0, "Alpha Under", "Alpha Under effect strip type"},
|
|
{STRIP_TYPE_GAMCROSS, "GAMMA_CROSS", 0, "Gamma Cross", "Gamma Cross effect strip type"},
|
|
{STRIP_TYPE_MUL, "MULTIPLY", 0, "Multiply", "Multiply effect strip type"},
|
|
{STRIP_TYPE_WIPE, "WIPE", 0, "Wipe", "Wipe effect strip type"},
|
|
{STRIP_TYPE_GLOW, "GLOW", 0, "Glow", "Glow effect strip type"},
|
|
{STRIP_TYPE_TRANSFORM, "TRANSFORM", 0, "Transform", "Transform effect strip type"},
|
|
{STRIP_TYPE_COLOR, "COLOR", 0, "Color", "Color effect strip type"},
|
|
{STRIP_TYPE_SPEED, "SPEED", 0, "Speed", "Color effect strip type"},
|
|
{STRIP_TYPE_MULTICAM, "MULTICAM", 0, "Multicam Selector", ""},
|
|
{STRIP_TYPE_ADJUSTMENT, "ADJUSTMENT", 0, "Adjustment Layer", ""},
|
|
{STRIP_TYPE_GAUSSIAN_BLUR, "GAUSSIAN_BLUR", 0, "Gaussian Blur", ""},
|
|
{STRIP_TYPE_TEXT, "TEXT", 0, "Text", ""},
|
|
{STRIP_TYPE_COLORMIX, "COLORMIX", 0, "Color Mix", ""},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
static int sequencer_change_effect_type_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Strip *strip = seq::select_active_get(scene);
|
|
const int new_type = RNA_enum_get(op->ptr, "type");
|
|
|
|
/* Free previous effect and init new effect. */
|
|
seq::EffectHandle sh;
|
|
|
|
if ((strip->type & STRIP_TYPE_EFFECT) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* Can someone explain the logic behind only allowing to increase this,
|
|
* copied from 2.4x - campbell */
|
|
if (seq::effect_get_num_inputs(strip->type) < seq::effect_get_num_inputs(new_type)) {
|
|
BKE_report(op->reports, RPT_ERROR, "New effect needs more input strips");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
sh = seq::effect_handle_get(strip);
|
|
sh.free(strip, true);
|
|
|
|
strip->type = new_type;
|
|
|
|
sh = seq::effect_handle_get(strip);
|
|
sh.init(strip);
|
|
|
|
seq::relations_invalidate_cache_preprocessed(scene, strip);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_change_effect_type(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Change Effect Type";
|
|
ot->idname = "SEQUENCER_OT_change_effect_type";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_change_effect_type_exec;
|
|
ot->poll = sequencer_effect_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
ot->prop = RNA_def_enum(ot->srna,
|
|
"type",
|
|
sequencer_prop_effect_types,
|
|
STRIP_TYPE_CROSS,
|
|
"Type",
|
|
"Sequencer effect type");
|
|
RNA_def_property_translation_context(ot->prop, BLT_I18NCONTEXT_ID_SEQUENCE);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Change Data/Files Operator
|
|
* \{ */
|
|
|
|
static int sequencer_change_path_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
Strip *strip = seq::select_active_get(scene);
|
|
const bool is_relative_path = RNA_boolean_get(op->ptr, "relative_path");
|
|
const bool use_placeholders = RNA_boolean_get(op->ptr, "use_placeholders");
|
|
int minext_frameme, numdigits;
|
|
|
|
if (strip->type == STRIP_TYPE_IMAGE) {
|
|
char directory[FILE_MAX];
|
|
int len;
|
|
StripElem *se;
|
|
|
|
/* Need to find min/max frame for placeholders. */
|
|
if (use_placeholders) {
|
|
len = sequencer_image_seq_get_minmax_frame(op, strip->sfra, &minext_frameme, &numdigits);
|
|
}
|
|
else {
|
|
len = RNA_property_collection_length(op->ptr, RNA_struct_find_property(op->ptr, "files"));
|
|
}
|
|
if (len == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
RNA_string_get(op->ptr, "directory", directory);
|
|
if (is_relative_path) {
|
|
/* TODO(@ideasman42): shouldn't this already be relative from the filesel?
|
|
* (as the 'filepath' is) for now just make relative here,
|
|
* but look into changing after 2.60. */
|
|
BLI_path_rel(directory, BKE_main_blendfile_path(bmain));
|
|
}
|
|
STRNCPY(strip->data->dirpath, directory);
|
|
|
|
if (strip->data->stripdata) {
|
|
MEM_freeN(strip->data->stripdata);
|
|
}
|
|
strip->data->stripdata = se = MEM_calloc_arrayN<StripElem>(len, "stripelem");
|
|
|
|
if (use_placeholders) {
|
|
sequencer_image_seq_reserve_frames(op, se, len, minext_frameme, numdigits);
|
|
}
|
|
else {
|
|
RNA_BEGIN (op->ptr, itemptr, "files") {
|
|
char *filename = RNA_string_get_alloc(&itemptr, "name", nullptr, 0, nullptr);
|
|
STRNCPY(se->filename, filename);
|
|
MEM_freeN(filename);
|
|
se++;
|
|
}
|
|
RNA_END;
|
|
}
|
|
|
|
if (len == 1) {
|
|
strip->flag |= SEQ_SINGLE_FRAME_CONTENT;
|
|
}
|
|
else {
|
|
strip->flag &= ~SEQ_SINGLE_FRAME_CONTENT;
|
|
}
|
|
|
|
/* Reset these else we won't see all the images. */
|
|
strip->anim_startofs = strip->anim_endofs = 0;
|
|
|
|
/* Correct start/end frames so we don't move.
|
|
* Important not to set strip->len = len; allow the function to handle it. */
|
|
seq::add_reload_new_file(bmain, scene, strip, true);
|
|
}
|
|
else if (strip->type == STRIP_TYPE_SOUND_RAM) {
|
|
bSound *sound = strip->sound;
|
|
if (sound == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
char filepath[FILE_MAX];
|
|
RNA_string_get(op->ptr, "filepath", filepath);
|
|
STRNCPY(sound->filepath, filepath);
|
|
BKE_sound_load(bmain, sound);
|
|
}
|
|
else {
|
|
/* Lame, set rna filepath. */
|
|
PropertyRNA *prop;
|
|
char filepath[FILE_MAX];
|
|
|
|
PointerRNA strip_ptr = RNA_pointer_create_discrete(&scene->id, &RNA_Strip, strip);
|
|
|
|
RNA_string_get(op->ptr, "filepath", filepath);
|
|
prop = RNA_struct_find_property(&strip_ptr, "filepath");
|
|
RNA_property_string_set(&strip_ptr, prop, filepath);
|
|
RNA_property_update(C, &strip_ptr, prop);
|
|
seq::relations_sequence_free_anim(strip);
|
|
}
|
|
|
|
seq::relations_invalidate_cache_raw(scene, strip);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int sequencer_change_path_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Strip *strip = seq::select_active_get(scene);
|
|
char filepath[FILE_MAX];
|
|
|
|
BLI_path_join(
|
|
filepath, sizeof(filepath), strip->data->dirpath, strip->data->stripdata->filename);
|
|
|
|
RNA_string_set(op->ptr, "directory", strip->data->dirpath);
|
|
RNA_string_set(op->ptr, "filepath", filepath);
|
|
|
|
/* Set default display depending on strip type. */
|
|
if (strip->type == STRIP_TYPE_IMAGE) {
|
|
RNA_boolean_set(op->ptr, "filter_movie", false);
|
|
}
|
|
else {
|
|
RNA_boolean_set(op->ptr, "filter_image", false);
|
|
}
|
|
|
|
WM_event_add_fileselect(C, op);
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
void SEQUENCER_OT_change_path(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Change Data/Files";
|
|
ot->idname = "SEQUENCER_OT_change_path";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_change_path_exec;
|
|
ot->invoke = sequencer_change_path_invoke;
|
|
ot->poll = sequencer_strip_has_path_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
WM_operator_properties_filesel(ot,
|
|
FILE_TYPE_FOLDER,
|
|
FILE_SPECIAL,
|
|
FILE_OPENFILE,
|
|
WM_FILESEL_DIRECTORY | WM_FILESEL_RELPATH | WM_FILESEL_FILEPATH |
|
|
WM_FILESEL_FILES,
|
|
FILE_DEFAULTDISPLAY,
|
|
FILE_SORT_DEFAULT);
|
|
RNA_def_boolean(ot->srna,
|
|
"use_placeholders",
|
|
false,
|
|
"Use Placeholders",
|
|
"Use placeholders for missing frames of the strip");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Change Strip Scene Operator
|
|
* \{ */
|
|
|
|
static bool sequencer_strip_change_scene_poll(bContext *C)
|
|
{
|
|
Editing *ed = seq::editing_get(CTX_data_scene(C));
|
|
if (ed == nullptr) {
|
|
return false;
|
|
}
|
|
Strip *strip = ed->act_seq;
|
|
return ((strip != nullptr) && (strip->type == STRIP_TYPE_SCENE));
|
|
}
|
|
static int sequencer_change_scene_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
Scene *scene_seq = static_cast<Scene *>(
|
|
BLI_findlink(&bmain->scenes, RNA_enum_get(op->ptr, "scene")));
|
|
|
|
if (scene_seq == nullptr) {
|
|
BKE_report(op->reports, RPT_ERROR, "Scene not found");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* Assign new scene. */
|
|
Strip *strip = seq::select_active_get(scene);
|
|
if (strip) {
|
|
strip->scene = scene_seq;
|
|
/* Do a refresh of the sequencer data. */
|
|
seq::relations_invalidate_cache_raw(scene, strip);
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO | ID_RECALC_SEQUENCER_STRIPS);
|
|
DEG_relations_tag_update(bmain);
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SCENEBROWSE, scene);
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int sequencer_change_scene_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
if (!RNA_struct_property_is_set(op->ptr, "scene")) {
|
|
return WM_enum_search_invoke(C, op, event);
|
|
}
|
|
|
|
return sequencer_change_scene_exec(C, op);
|
|
}
|
|
|
|
void SEQUENCER_OT_change_scene(wmOperatorType *ot)
|
|
{
|
|
PropertyRNA *prop;
|
|
|
|
/* Identifiers. */
|
|
ot->name = "Change Scene";
|
|
ot->idname = "SEQUENCER_OT_change_scene";
|
|
ot->description = "Change Scene assigned to Strip";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_change_scene_exec;
|
|
ot->invoke = sequencer_change_scene_invoke;
|
|
ot->poll = sequencer_strip_change_scene_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* Properties. */
|
|
prop = RNA_def_enum(ot->srna, "scene", rna_enum_dummy_NULL_items, 0, "Scene", "");
|
|
RNA_def_enum_funcs(prop, RNA_scene_without_active_itemf);
|
|
RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE);
|
|
ot->prop = prop;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Export Subtitles Operator
|
|
* \{ */
|
|
|
|
/** Comparison function suitable to be used with BLI_listbase_sort(). */
|
|
static int strip_cmp_time_startdisp_channel(void *thunk, const void *a, const void *b)
|
|
{
|
|
const Scene *scene = static_cast<Scene *>(thunk);
|
|
const Strip *strip_a = static_cast<const Strip *>(a);
|
|
const Strip *strip_b = static_cast<const Strip *>(b);
|
|
|
|
int strip_a_start = seq::time_left_handle_frame_get(scene, strip_a);
|
|
int strip_b_start = seq::time_left_handle_frame_get(scene, strip_b);
|
|
|
|
/* If strips have the same start frame favor the one with a higher channel. */
|
|
if (strip_a_start == strip_b_start) {
|
|
return strip_a->machine > strip_b->machine;
|
|
}
|
|
|
|
return (strip_a_start > strip_b_start);
|
|
}
|
|
|
|
static int sequencer_export_subtitles_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent * /*event*/)
|
|
{
|
|
ED_fileselect_ensure_default_filepath(C, op, ".srt");
|
|
|
|
WM_event_add_fileselect(C, op);
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
struct Seq_get_text_cb_data {
|
|
ListBase *text_seq;
|
|
Scene *scene;
|
|
};
|
|
|
|
static bool strip_get_text_strip_cb(Strip *strip, void *user_data)
|
|
{
|
|
Seq_get_text_cb_data *cd = (Seq_get_text_cb_data *)user_data;
|
|
Editing *ed = seq::editing_get(cd->scene);
|
|
ListBase *channels = seq::channels_displayed_get(ed);
|
|
/* Only text strips that are not muted and don't end with negative frame. */
|
|
if ((strip->type == STRIP_TYPE_TEXT) && !seq::render_is_muted(channels, strip) &&
|
|
(seq::time_right_handle_frame_get(cd->scene, strip) > cd->scene->r.sfra))
|
|
{
|
|
BLI_addtail(cd->text_seq, MEM_dupallocN(strip));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int sequencer_export_subtitles_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Strip *strip, *strip_next;
|
|
Editing *ed = seq::editing_get(scene);
|
|
ListBase text_seq = {nullptr};
|
|
int iter = 1; /* Sequence numbers in `.srt` files are 1-indexed. */
|
|
FILE *file;
|
|
char filepath[FILE_MAX];
|
|
|
|
if (!RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
|
|
BKE_report(op->reports, RPT_ERROR, "No filepath given");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
RNA_string_get(op->ptr, "filepath", filepath);
|
|
BLI_path_extension_ensure(filepath, sizeof(filepath), ".srt");
|
|
|
|
/* Avoid File write exceptions. */
|
|
if (!BLI_exists(filepath)) {
|
|
BLI_file_ensure_parent_dir_exists(filepath);
|
|
if (!BLI_file_touch(filepath)) {
|
|
BKE_report(op->reports, RPT_ERROR, "Can't create subtitle file");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
else if (!BLI_file_is_writable(filepath)) {
|
|
BKE_report(op->reports, RPT_ERROR, "Can't overwrite export file");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (ed != nullptr) {
|
|
Seq_get_text_cb_data cb_data = {&text_seq, scene};
|
|
seq::for_each_callback(&ed->seqbase, strip_get_text_strip_cb, &cb_data);
|
|
}
|
|
|
|
if (BLI_listbase_is_empty(&text_seq)) {
|
|
BKE_report(op->reports, RPT_ERROR, "No subtitles (text strips) to export");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
BLI_listbase_sort_r(&text_seq, strip_cmp_time_startdisp_channel, scene);
|
|
|
|
/* Open and write file. */
|
|
file = BLI_fopen(filepath, "w");
|
|
|
|
for (strip = static_cast<Strip *>(text_seq.first); strip; strip = strip_next) {
|
|
TextVars *data = static_cast<TextVars *>(strip->effectdata);
|
|
char timecode_str_start[32];
|
|
char timecode_str_end[32];
|
|
|
|
/* Write time-code relative to start frame of scene. Don't allow negative time-codes. */
|
|
BLI_timecode_string_from_time(
|
|
timecode_str_start,
|
|
sizeof(timecode_str_start),
|
|
-2,
|
|
FRA2TIME(max_ii(seq::time_left_handle_frame_get(scene, strip) - scene->r.sfra, 0)),
|
|
FPS,
|
|
USER_TIMECODE_SUBRIP);
|
|
BLI_timecode_string_from_time(
|
|
timecode_str_end,
|
|
sizeof(timecode_str_end),
|
|
-2,
|
|
FRA2TIME(seq::time_right_handle_frame_get(scene, strip) - scene->r.sfra),
|
|
FPS,
|
|
USER_TIMECODE_SUBRIP);
|
|
|
|
fprintf(
|
|
file, "%d\n%s --> %s\n%s\n\n", iter++, timecode_str_start, timecode_str_end, data->text);
|
|
|
|
strip_next = static_cast<Strip *>(strip->next);
|
|
MEM_freeN(strip);
|
|
}
|
|
|
|
fclose(file);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static bool sequencer_strip_is_text_poll(bContext *C)
|
|
{
|
|
Editing *ed;
|
|
Strip *strip;
|
|
return (((ed = seq::editing_get(CTX_data_scene(C))) != nullptr) &&
|
|
((strip = ed->act_seq) != nullptr) && (strip->type == STRIP_TYPE_TEXT));
|
|
}
|
|
|
|
void SEQUENCER_OT_export_subtitles(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Export Subtitles";
|
|
ot->idname = "SEQUENCER_OT_export_subtitles";
|
|
ot->description = "Export .srt file containing text strips";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_export_subtitles_exec;
|
|
ot->invoke = sequencer_export_subtitles_invoke;
|
|
ot->poll = sequencer_strip_is_text_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
WM_operator_properties_filesel(ot,
|
|
FILE_TYPE_FOLDER,
|
|
FILE_BLENDER,
|
|
FILE_SAVE,
|
|
WM_FILESEL_FILEPATH,
|
|
FILE_DEFAULTDISPLAY,
|
|
FILE_SORT_DEFAULT);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Set Range to Strips Operator
|
|
* \{ */
|
|
|
|
static int sequencer_set_range_to_strips_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
|
|
int sfra = MAXFRAME;
|
|
int efra = -MAXFRAME;
|
|
bool selected = false;
|
|
const bool preview = RNA_boolean_get(op->ptr, "preview");
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
|
if (strip->flag & SELECT) {
|
|
selected = true;
|
|
sfra = min_ii(sfra, seq::time_left_handle_frame_get(scene, strip));
|
|
/* Offset of -1 is needed because in the sequencer every frame has width.
|
|
* Range from 1 to 1 is drawn as range 1 to 2, because 1 frame long strip starts at frame 1
|
|
* and ends at frame 2. See #106480. */
|
|
efra = max_ii(efra, seq::time_right_handle_frame_get(scene, strip) - 1);
|
|
}
|
|
}
|
|
|
|
if (!selected) {
|
|
BKE_report(op->reports, RPT_WARNING, "Select one or more strips");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
if (efra < 0) {
|
|
BKE_report(op->reports, RPT_ERROR, "Can't set a negative range");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (preview) {
|
|
scene->r.flag |= SCER_PRV_RANGE;
|
|
scene->r.psfra = max_ii(0, sfra);
|
|
scene->r.pefra = efra;
|
|
}
|
|
else {
|
|
scene->r.flag &= ~SCER_PRV_RANGE;
|
|
scene->r.sfra = max_ii(0, sfra);
|
|
scene->r.efra = efra;
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_FRAME_RANGE, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_set_range_to_strips(wmOperatorType *ot)
|
|
{
|
|
PropertyRNA *prop;
|
|
|
|
/* Identifiers. */
|
|
ot->name = "Set Range to Strips";
|
|
ot->idname = "SEQUENCER_OT_set_range_to_strips";
|
|
ot->description = "Set the frame range to the selected strips start and end";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_set_range_to_strips_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
prop = RNA_def_boolean(ot->srna, "preview", false, "Preview", "Set the preview range instead");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Clear Strip Transform Operator
|
|
* \{ */
|
|
|
|
enum {
|
|
STRIP_TRANSFORM_POSITION,
|
|
STRIP_TRANSFORM_SCALE,
|
|
STRIP_TRANSFORM_ROTATION,
|
|
STRIP_TRANSFORM_ALL,
|
|
};
|
|
|
|
static const EnumPropertyItem transform_reset_properties[] = {
|
|
{STRIP_TRANSFORM_POSITION, "POSITION", 0, "Position", "Reset strip transform location"},
|
|
{STRIP_TRANSFORM_SCALE, "SCALE", 0, "Scale", "Reset strip transform scale"},
|
|
{STRIP_TRANSFORM_ROTATION, "ROTATION", 0, "Rotation", "Reset strip transform rotation"},
|
|
{STRIP_TRANSFORM_ALL, "ALL", 0, "All", "Reset strip transform location, scale and rotation"},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
static int sequencer_strip_transform_clear_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
const Editing *ed = seq::editing_get(scene);
|
|
const int property = RNA_enum_get(op->ptr, "property");
|
|
|
|
const bool use_autokeyframe = blender::animrig::is_autokey_on(scene);
|
|
const bool only_when_keyed = blender::animrig::is_keying_flag(scene,
|
|
AUTOKEY_FLAG_INSERTAVAILABLE);
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
|
if (strip->flag & SELECT && strip->type != STRIP_TYPE_SOUND_RAM) {
|
|
StripTransform *transform = strip->data->transform;
|
|
PropertyRNA *prop;
|
|
PointerRNA ptr = RNA_pointer_create_discrete(&scene->id, &RNA_StripTransform, transform);
|
|
switch (property) {
|
|
case STRIP_TRANSFORM_POSITION:
|
|
transform->xofs = 0;
|
|
transform->yofs = 0;
|
|
if (use_autokeyframe) {
|
|
prop = RNA_struct_find_property(&ptr, "offset_x");
|
|
blender::animrig::autokeyframe_property(
|
|
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
|
|
prop = RNA_struct_find_property(&ptr, "offset_y");
|
|
blender::animrig::autokeyframe_property(
|
|
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
|
|
}
|
|
break;
|
|
case STRIP_TRANSFORM_SCALE:
|
|
transform->scale_x = 1.0f;
|
|
transform->scale_y = 1.0f;
|
|
if (use_autokeyframe) {
|
|
prop = RNA_struct_find_property(&ptr, "scale_x");
|
|
blender::animrig::autokeyframe_property(
|
|
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
|
|
prop = RNA_struct_find_property(&ptr, "scale_y");
|
|
blender::animrig::autokeyframe_property(
|
|
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
|
|
}
|
|
break;
|
|
case STRIP_TRANSFORM_ROTATION:
|
|
transform->rotation = 0.0f;
|
|
if (use_autokeyframe) {
|
|
prop = RNA_struct_find_property(&ptr, "rotation");
|
|
blender::animrig::autokeyframe_property(
|
|
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
|
|
}
|
|
break;
|
|
case STRIP_TRANSFORM_ALL:
|
|
transform->xofs = 0;
|
|
transform->yofs = 0;
|
|
transform->scale_x = 1.0f;
|
|
transform->scale_y = 1.0f;
|
|
transform->rotation = 0.0f;
|
|
if (use_autokeyframe) {
|
|
prop = RNA_struct_find_property(&ptr, "offset_x");
|
|
blender::animrig::autokeyframe_property(
|
|
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
|
|
prop = RNA_struct_find_property(&ptr, "offset_y");
|
|
blender::animrig::autokeyframe_property(
|
|
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
|
|
prop = RNA_struct_find_property(&ptr, "scale_x");
|
|
blender::animrig::autokeyframe_property(
|
|
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
|
|
prop = RNA_struct_find_property(&ptr, "scale_y");
|
|
blender::animrig::autokeyframe_property(
|
|
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
|
|
prop = RNA_struct_find_property(&ptr, "rotation");
|
|
blender::animrig::autokeyframe_property(
|
|
C, scene, &ptr, prop, -1, scene->r.cfra, only_when_keyed);
|
|
}
|
|
break;
|
|
}
|
|
seq::relations_invalidate_cache_preprocessed(scene, strip);
|
|
}
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_strip_transform_clear(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Clear Strip Transform";
|
|
ot->idname = "SEQUENCER_OT_strip_transform_clear";
|
|
ot->description = "Reset image transformation to default value";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_strip_transform_clear_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
ot->prop = RNA_def_enum(ot->srna,
|
|
"property",
|
|
transform_reset_properties,
|
|
STRIP_TRANSFORM_ALL,
|
|
"Property",
|
|
"Strip transform property to be reset");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Transform Set Fit Operator
|
|
* \{ */
|
|
|
|
static const EnumPropertyItem scale_fit_methods[] = {
|
|
{SEQ_SCALE_TO_FIT, "FIT", 0, "Scale to Fit", "Scale image so fits in preview"},
|
|
{SEQ_SCALE_TO_FILL, "FILL", 0, "Scale to Fill", "Scale image so it fills preview completely"},
|
|
{SEQ_STRETCH_TO_FILL, "STRETCH", 0, "Stretch to Fill", "Stretch image so it fills preview"},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
static int sequencer_strip_transform_fit_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
const Editing *ed = seq::editing_get(scene);
|
|
const eSeqImageFitMethod fit_method = eSeqImageFitMethod(RNA_enum_get(op->ptr, "fit_method"));
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
|
if (strip->flag & SELECT && strip->type != STRIP_TYPE_SOUND_RAM) {
|
|
const int timeline_frame = scene->r.cfra;
|
|
StripElem *strip_elem = seq::render_give_stripelem(scene, strip, timeline_frame);
|
|
|
|
if (strip_elem == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
seq::set_scale_to_fit(strip,
|
|
strip_elem->orig_width,
|
|
strip_elem->orig_height,
|
|
scene->r.xsch,
|
|
scene->r.ysch,
|
|
fit_method);
|
|
seq::relations_invalidate_cache_preprocessed(scene, strip);
|
|
}
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SEQUENCER_OT_strip_transform_fit(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Strip Transform Set Fit";
|
|
ot->idname = "SEQUENCER_OT_strip_transform_fit";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_strip_transform_fit_exec;
|
|
ot->poll = sequencer_edit_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
ot->prop = RNA_def_enum(ot->srna,
|
|
"fit_method",
|
|
scale_fit_methods,
|
|
SEQ_SCALE_TO_FIT,
|
|
"Fit Method",
|
|
"Scale fit fit_method");
|
|
}
|
|
|
|
static int sequencer_strip_color_tag_set_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
const Editing *ed = seq::editing_get(scene);
|
|
const short color_tag = RNA_enum_get(op->ptr, "color");
|
|
|
|
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
|
if (strip->flag & SELECT) {
|
|
strip->color_tag = color_tag;
|
|
}
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static bool sequencer_strip_color_tag_set_poll(bContext *C)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
if (scene == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
Editing *ed = seq::editing_get(scene);
|
|
if (ed == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
Strip *act_seq = ed->act_seq;
|
|
return act_seq != nullptr;
|
|
}
|
|
|
|
void SEQUENCER_OT_strip_color_tag_set(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Set Color Tag";
|
|
ot->idname = "SEQUENCER_OT_strip_color_tag_set";
|
|
ot->description = "Set a color tag for the selected strips";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sequencer_strip_color_tag_set_exec;
|
|
ot->poll = sequencer_strip_color_tag_set_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_enum(ot->srna, "color", rna_enum_strip_color_items, STRIP_COLOR_NONE, "Color Tag", "");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Set 2D Cursor Operator
|
|
* \{ */
|
|
|
|
static int sequencer_set_2d_cursor_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
|
|
|
float cursor_pixel[2];
|
|
RNA_float_get_array(op->ptr, "location", cursor_pixel);
|
|
|
|
blender::float2 cursor_region = seq::image_preview_unit_from_px(scene, cursor_pixel);
|
|
copy_v2_v2(sseq->cursor, cursor_region);
|
|
|
|
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);
|
|
|
|
/* Use pass-through to allow click-drag to transform the cursor. */
|
|
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
|
|
}
|
|
|
|
static int sequencer_set_2d_cursor_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
ARegion *region = CTX_wm_region(C);
|
|
float cursor_pixel[2];
|
|
UI_view2d_region_to_view(
|
|
®ion->v2d, event->mval[0], event->mval[1], &cursor_pixel[0], &cursor_pixel[1]);
|
|
|
|
RNA_float_set_array(op->ptr, "location", cursor_pixel);
|
|
|
|
return sequencer_set_2d_cursor_exec(C, op);
|
|
}
|
|
|
|
void SEQUENCER_OT_cursor_set(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Set 2D Cursor";
|
|
ot->description = "Set 2D cursor location";
|
|
ot->idname = "SEQUENCER_OT_cursor_set";
|
|
|
|
/* api callbacks */
|
|
ot->exec = sequencer_set_2d_cursor_exec;
|
|
ot->invoke = sequencer_set_2d_cursor_invoke;
|
|
ot->poll = sequencer_view_has_preview_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* properties */
|
|
RNA_def_float_vector(ot->srna,
|
|
"location",
|
|
2,
|
|
nullptr,
|
|
-FLT_MAX,
|
|
FLT_MAX,
|
|
"Location",
|
|
"Cursor location in normalized preview coordinates",
|
|
-10.0f,
|
|
10.0f);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Update scene strip frame range
|
|
* \{ */
|
|
|
|
static int sequencer_scene_frame_range_update_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Editing *ed = seq::editing_get(scene);
|
|
Strip *strip = ed->act_seq;
|
|
|
|
const int old_start = seq::time_left_handle_frame_get(scene, strip);
|
|
const int old_end = seq::time_right_handle_frame_get(scene, strip);
|
|
|
|
Scene *target_scene = strip->scene;
|
|
|
|
strip->len = target_scene->r.efra - target_scene->r.sfra + 1;
|
|
seq::time_left_handle_frame_set(scene, strip, old_start);
|
|
seq::time_right_handle_frame_set(scene, strip, old_end);
|
|
|
|
seq::relations_invalidate_cache_raw(scene, strip);
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO | ID_RECALC_SEQUENCER_STRIPS);
|
|
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static bool sequencer_scene_frame_range_update_poll(bContext *C)
|
|
{
|
|
Editing *ed = seq::editing_get(CTX_data_scene(C));
|
|
return (ed != nullptr && ed->act_seq != nullptr && (ed->act_seq->type & STRIP_TYPE_SCENE) != 0);
|
|
}
|
|
|
|
void SEQUENCER_OT_scene_frame_range_update(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Update Scene Frame Range";
|
|
ot->description = "Update frame range of scene strip";
|
|
ot->idname = "SEQUENCER_OT_scene_frame_range_update";
|
|
|
|
/* api callbacks */
|
|
ot->exec = sequencer_scene_frame_range_update_exec;
|
|
ot->poll = sequencer_scene_frame_range_update_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
} // namespace blender::ed::vse
|