/* 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" /* -------------------------------------------------------------------- */ /** \name Public Context Checks * \{ */ bool ED_space_sequencer_maskedit_mask_poll(bContext *C) { return ED_space_sequencer_maskedit_poll(C); } bool ED_space_sequencer_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 ED_space_sequencer_maskedit_poll(bContext *C) { SpaceSeq *sseq = CTX_wm_space_seq(C); if (sseq) { Scene *scene = CTX_data_scene(C); return ED_space_sequencer_check_show_maskedit(sseq, scene); } return false; } bool ED_space_sequencer_check_show_imbuf(SpaceSeq *sseq) { return (sseq->mainb == SEQ_DRAW_IMG_IMBUF) && ELEM(sseq->view, SEQ_VIEW_PREVIEW, SEQ_VIEW_SEQUENCE_PREVIEW); } bool ED_space_sequencer_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 ED_space_sequencer_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 (!ED_space_sequencer_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_cnew("trimdata"); op->customdata = static_cast(data); data->strip_array = MEM_cnew_array(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_cnew("trimdata"); op->customdata = static_cast(data); data->strip_array = MEM_cnew_array(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 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 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 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 eSeqSplitMethod method = eSeqSplitMethod(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); ED_sequencer_deselect_all(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. */ SeqAnimationBackup 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(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 : ED_sequencer_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(ed->seqbasep->first); strip; strip = static_cast(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(ed->seqbasep->first); while (strip) { SEQ_relations_invalidate_cache_preprocessed(scene, strip); strip = strip->next; } for (strip = static_cast(ed->seqbasep->first); strip; strip = static_cast(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(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(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->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 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 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(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->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 ED_sequencer_deselect_all(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. */ SeqEffectHandle 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_cnew_array(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( 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(thunk); const Strip *strip_a = static_cast(a); const Strip *strip_b = static_cast(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(text_seq.first); strip; strip = strip_next) { TextVars *data = static_cast(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->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; } /** \} */