VSE: ability to connect and disconnect strips in the VSE.

Adds the ability to connect and disconnect strips in the VSE.

- Connected strips have an icon indicating their status, and attempting
  to select one connected strip selects all other connected strips in
  that chain.
- If the user attempts to connect a strip that is already connected to
  other strips, that strip will disconnect itself from others before
  connecting to new strips.
- Preview selection also works in bulk if multiple video strips are
  connected together in the timeline.
- When adding new strips from the Add menu or the File Browser, strips
  from the same file are connected by default. There's an option to
  turn this off in Editing > Video Sequencer user preferences.
- It is possible to individually tweak strips/handles and ignore
  connections with Alt+Click.
  - This shortcut overrides the old keymap item for "Linked Handle"
    selection. The property still exists if people want to use that
    shortcut for its old purpose.
- To make sure that connections remain valid even after duplication,
  I've added a condition to `seq_new_fix_links_recursive` that also
  updates connections using the `seq->tmp` var. (A note -- I've updated
  the comment for this field in `DNA_sequence_types.h` because the var
  is only used for duplication now. It was once present in
  `select_more_less_seq__internal` to be used for linked selection but
  is gone now).
  - There are also functions to cut one-way links and make sure that
    all strips are bidirectionally connected after duplicating.

Pull Request: https://projects.blender.org/blender/blender/pulls/124333
This commit is contained in:
John Kiril Swenson
2024-08-22 14:54:42 +02:00
committed by Aras Pranckevicius
parent ce95155b30
commit 715129bf5b
23 changed files with 600 additions and 86 deletions

View File

@@ -2945,6 +2945,8 @@ def km_sequencer(params):
{"properties": [("unselected", True)]}),
("sequencer.lock", {"type": 'H', "value": 'PRESS', "ctrl": True}, None),
("sequencer.unlock", {"type": 'H', "value": 'PRESS', "ctrl": True, "alt": True}, None),
("sequencer.connect", {"type": 'C', "value": 'PRESS', "ctrl": True, "alt": True},
{"properties": [("toggle", True)]}),
("sequencer.reassign_inputs", {"type": 'R', "value": 'PRESS'}, None),
("sequencer.reload", {"type": 'R', "value": 'PRESS', "alt": True}, None),
("sequencer.reload", {"type": 'R', "value": 'PRESS', "shift": True, "alt": True},
@@ -2998,6 +3000,10 @@ def km_sequencer(params):
value=params.select_mouse_value_fallback,
legacy=params.legacy,
),
("sequencer.select", {"type": params.select_mouse, "value": 'PRESS', "alt": True},
{"properties": [("deselect_all", True), ("ignore_connections", True)]}),
("sequencer.select", {"type": params.select_mouse, "value": 'PRESS', "alt": True, "shift": True},
{"properties": [("toggle", True), ("ignore_connections", True)]}),
("sequencer.select_more", {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None),
("sequencer.select_less", {"type": 'NUMPAD_MINUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None),
("sequencer.select_linked_pick", {"type": 'L', "value": 'PRESS'}, None),
@@ -3010,6 +3016,8 @@ def km_sequencer(params):
{"properties": [("tweak", True), ("mode", 'ADD')]}),
("sequencer.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG', "ctrl": True},
{"properties": [("tweak", True), ("mode", 'SUB')]}),
("sequencer.select_box", {"type": params.select_mouse, "value": 'CLICK_DRAG', "alt": True},
{"properties": [("tweak", True), ("ignore_connections", True), ("mode", 'SET')]}),
("sequencer.select_box", {"type": 'B', "value": 'PRESS'}, None),
("sequencer.select_box", {"type": 'B', "value": 'PRESS', "ctrl": True},
{"properties": [("include_handles", True)]}),
@@ -3024,6 +3032,8 @@ def km_sequencer(params):
{"properties": [("view2d_edge_pan", True)]}),
("transform.seq_slide", {"type": params.select_mouse, "value": 'CLICK_DRAG'},
{"properties": [("view2d_edge_pan", True), ("use_restore_handle_selection", True)]}),
("transform.seq_slide", {"type": params.select_mouse, "value": 'CLICK_DRAG', "alt": True},
{"properties": [("view2d_edge_pan", True), ("use_restore_handle_selection", True)]}),
("transform.transform", {"type": 'E', "value": 'PRESS'},
{"properties": [("mode", 'TIME_EXTEND')]}),
("marker.add", {"type": 'M', "value": 'PRESS'}, None),
@@ -5353,10 +5363,12 @@ def _template_sequencer_preview_select(*, type, value, legacy):
{"properties": [(c, True) for c in props]},
) for props, mods in (
(("center",), ("ctrl",)),
(("ignore_connections",), ("alt",)),
# TODO:
# (("enumerate",), ("alt",)),
(("toggle", "center"), ("shift", "ctrl")),
# (("center", "enumerate"), ("ctrl", "alt")),
(("toggle", "ignore_connections"), ("shift", "alt")),
# (("toggle", "enumerate"), ("shift", "alt")),
# (("toggle", "center", "enumerate"), ("shift", "ctrl", "alt")),
)]
@@ -5370,9 +5382,6 @@ def _template_sequencer_timeline_select(*, type, value, legacy):
{"type": type, "value": value, **{m: True for m in mods}},
{"properties": [(c, True) for c in props]},
) for props, mods in (
(("linked_handle",), ("alt",)),
(("linked_handle", "extend"), ("shift", "alt",)),
(("side_of_frame", "linked_time"), ("ctrl",)),
(("side_of_frame", "linked_time", "extend"), ("ctrl", "shift")),
)]
@@ -8860,8 +8869,12 @@ def km_3d_view_tool_sculpt_gpencil_select_lasso(params):
def km_sequencer_editor_tool_generic_select_timeline_rcs(params, fallback):
return [
("sequencer.select_handle", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("sequencer.select_handle", {"type": 'LEFTMOUSE', "value": 'PRESS',
"alt": True}, {"properties": [("ignore_connections", True)]}),
*_template_items_change_frame(params),
# Frame change can be canceled if click happens on strip handle. In such case move the handle.
# Change frame takes precedence over the sequence slide operator. If a
# mouse press happens on a strip handle, it is cancelled, and the sequence
# slide below activates instead.
("transform.seq_slide", {"type": 'LEFTMOUSE', "value": 'PRESS'},
{"properties": [("view2d_edge_pan", True), ("use_restore_handle_selection", True)]}),
]

View File

@@ -1060,6 +1060,10 @@ class SEQUENCER_MT_strip(Menu):
layout.separator()
layout.menu("SEQUENCER_MT_strip_lock_mute")
layout.separator()
layout.operator("sequencer.connect", icon="LINKED").toggle = True
layout.operator("sequencer.disconnect")
layout.separator()
layout.menu("SEQUENCER_MT_strip_input")
@@ -1226,9 +1230,12 @@ class SEQUENCER_MT_context_menu(Menu):
layout.menu("SEQUENCER_MT_color_tag_picker")
layout.separator()
layout.menu("SEQUENCER_MT_strip_lock_mute")
layout.separator()
layout.operator("sequencer.connect", icon="LINKED").toggle = True
layout.operator("sequencer.disconnect")
def draw_retime(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'

View File

@@ -545,6 +545,7 @@ class USERPREF_PT_edit_sequence_editor(EditingPanel, CenterAlignMixIn, Panel):
edit = prefs.edit
layout.prop(edit, "use_sequencer_simplified_tweaking")
layout.prop(edit, "connect_strips_by_default")
class USERPREF_PT_edit_misc(EditingPanel, CenterAlignMixIn, Panel):

View File

@@ -31,7 +31,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 18
#define BLENDER_FILE_SUBVERSION 19
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@@ -1050,6 +1050,9 @@ void blo_do_versions_userdef(UserDef *userdef)
style->tooltip.shadowcolor = 0.0f;
}
}
if (!USER_VERSION_ATLEAST(403, 19)) {
userdef->sequencer_editor_flag |= USER_SEQ_ED_CONNECT_STRIPS_BY_DEFAULT;
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning

View File

@@ -37,6 +37,7 @@
#include "RNA_prototypes.hh"
#include "SEQ_add.hh"
#include "SEQ_connect.hh"
#include "SEQ_effects.hh"
#include "SEQ_proxy.hh"
#include "SEQ_select.hh"
@@ -887,6 +888,11 @@ static void sequencer_add_movie_multiple_strips(bContext *C,
seq_load_apply_generic_options(C, op, seq_sound);
seq_load_apply_generic_options(C, op, seq_movie);
}
if ((U.sequencer_editor_flag & USER_SEQ_ED_CONNECT_STRIPS_BY_DEFAULT)) {
SEQ_connect(seq_movie, seq_sound);
}
r_movie_strips.add(seq_movie);
}
}
@@ -953,6 +959,11 @@ static bool sequencer_add_movie_single_strip(bContext *C,
seq_load_apply_generic_options(C, op, seq_sound);
seq_load_apply_generic_options(C, op, seq_movie);
}
if ((U.sequencer_editor_flag & USER_SEQ_ED_CONNECT_STRIPS_BY_DEFAULT)) {
SEQ_connect(seq_movie, seq_sound);
}
r_movie_strips.add(seq_movie);
return true;

View File

@@ -30,6 +30,7 @@
#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"
@@ -1021,6 +1022,85 @@ void SEQUENCER_OT_unlock(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \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<Sequence *> 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<Sequence *> selected = SEQ_query_selected_strips(active_seqbase);
if (SEQ_disconnect(selected)) {
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
else {
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
* \{ */
@@ -2009,13 +2089,17 @@ static int sequencer_meta_make_exec(bContext *C, wmOperator *op)
* Sequence is moved within the same edit, no need to re-generate the UID. */
LISTBASE_FOREACH_MUTABLE (Sequence *, seq, active_seqbase) {
if (seq != seqm && seq->flag & SELECT) {
BLI_remlink(active_seqbase, seq);
BLI_addtail(&seqm->seqbase, seq);
SEQ_relations_invalidate_cache_preprocessed(scene, seq);
channel_max = max_ii(seq->machine, channel_max);
channel_min = min_ii(seq->machine, channel_min);
meta_start_frame = min_ii(SEQ_time_left_handle_frame_get(scene, seq), meta_start_frame);
meta_end_frame = max_ii(SEQ_time_right_handle_frame_get(scene, seq), meta_end_frame);
blender::VectorSet<Sequence *> related = SEQ_get_connected_strips(seq);
related.add(seq);
for (Sequence *rel : related) {
BLI_remlink(active_seqbase, rel);
BLI_addtail(&seqm->seqbase, rel);
SEQ_relations_invalidate_cache_preprocessed(scene, rel);
channel_max = max_ii(rel->machine, channel_max);
channel_min = min_ii(rel->machine, channel_min);
meta_start_frame = min_ii(SEQ_time_left_handle_frame_get(scene, rel), meta_start_frame);
meta_end_frame = max_ii(SEQ_time_right_handle_frame_get(scene, rel), meta_end_frame);
}
}
}

View File

@@ -104,6 +104,7 @@ struct StripDrawContext {
bool show_strip_color_tag;
bool missing_data_block;
bool missing_media;
bool is_connected;
};
struct TimelineDrawContext {
@@ -227,6 +228,8 @@ void SEQUENCER_OT_mute(wmOperatorType *ot);
void SEQUENCER_OT_unmute(wmOperatorType *ot);
void SEQUENCER_OT_lock(wmOperatorType *ot);
void SEQUENCER_OT_unlock(wmOperatorType *ot);
void SEQUENCER_OT_connect(wmOperatorType *ot);
void SEQUENCER_OT_disconnect(wmOperatorType *ot);
void SEQUENCER_OT_reload(wmOperatorType *ot);
void SEQUENCER_OT_refresh_all(wmOperatorType *ot);
void SEQUENCER_OT_reassign_inputs(wmOperatorType *ot);
@@ -379,6 +382,7 @@ void sequencer_retiming_keys_draw(const TimelineDrawContext *timeline_ctx,
const StripDrawContext &strip_ctx);
void sequencer_retiming_speed_draw(const TimelineDrawContext *timeline_ctx,
const StripDrawContext &strip_ctx);
void realize_fake_keys(const Scene *scene, Sequence *seq);
SeqRetimingKey *try_to_realize_fake_keys(const bContext *C, Sequence *seq, const int mval[2]);
SeqRetimingKey *retiming_mouseover_key_get(const bContext *C, const int mval[2], Sequence **r_seq);
int left_fake_key_frame_get(const bContext *C, const Sequence *seq);

View File

@@ -29,6 +29,8 @@ void sequencer_operatortypes()
WM_operatortype_append(SEQUENCER_OT_unmute);
WM_operatortype_append(SEQUENCER_OT_lock);
WM_operatortype_append(SEQUENCER_OT_unlock);
WM_operatortype_append(SEQUENCER_OT_connect);
WM_operatortype_append(SEQUENCER_OT_disconnect);
WM_operatortype_append(SEQUENCER_OT_reload);
WM_operatortype_append(SEQUENCER_OT_refresh_all);
WM_operatortype_append(SEQUENCER_OT_reassign_inputs);

View File

@@ -20,6 +20,7 @@
#include "ED_select_utils.hh"
#include "ED_sequencer.hh"
#include "SEQ_connect.hh"
#include "SEQ_iterator.hh"
#include "SEQ_relations.hh"
#include "SEQ_retiming.hh"
@@ -778,6 +779,28 @@ static bool select_key(const Editing *ed,
return true;
}
static bool select_connected_keys(const Scene *scene,
const SeqRetimingKey *source,
const Sequence *source_owner)
{
if (!SEQ_is_strip_connected(source_owner)) {
return false;
}
const int frame = SEQ_retiming_key_timeline_frame_get(scene, source_owner, source);
bool changed = false;
blender::VectorSet<Sequence *> connections = SEQ_get_connected_strips(source_owner);
for (Sequence *connection : connections) {
SeqRetimingKey *con_key = SEQ_retiming_key_get_by_timeline_frame(scene, connection, frame);
if (con_key) {
SEQ_retiming_selection_copy(con_key, source);
changed = true;
}
}
return changed;
}
int sequencer_retiming_select_linked_time(bContext *C,
wmOperator *op,
SeqRetimingKey *key,
@@ -791,6 +814,7 @@ int sequencer_retiming_select_linked_time(bContext *C,
}
for (; key <= SEQ_retiming_last_key_get(key_owner); key++) {
select_key(ed, key, false, false);
select_connected_keys(scene, key, key_owner);
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
@@ -812,18 +836,23 @@ int sequencer_retiming_key_select_exec(bContext *C,
const bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others");
const bool toggle = RNA_boolean_get(op->ptr, "toggle");
/* Click on unselected key. */
/* Clicked on an unselected key. */
if (!SEQ_retiming_selection_contains(ed, key) && !toggle) {
select_key(ed, key, false, deselect_all);
select_connected_keys(scene, key, key_owner);
}
/* Clicked on any key, waiting to click release. */
/* Clicked on a key that is already selected, waiting to click release. */
if (wait_to_deselect_others && !toggle) {
return OPERATOR_RUNNING_MODAL;
}
/* Selection after click is released. */
const bool changed = select_key(ed, key, toggle, deselect_all);
/* The key is already selected, but deselect other selected keys after click is released if no
* transform or toggle happened. */
bool changed = select_key(ed, key, toggle, deselect_all);
if (!toggle) {
changed |= select_connected_keys(scene, key, key_owner);
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED;

View File

@@ -30,6 +30,7 @@
#include "UI_view2d.hh"
#include "SEQ_connect.hh"
#include "SEQ_retiming.hh"
#include "SEQ_sequencer.hh"
#include "SEQ_time.hh"
@@ -124,21 +125,40 @@ int right_fake_key_frame_get(const bContext *C, const Sequence *seq)
return min_ii(content_end, SEQ_time_right_handle_frame_get(scene, seq));
}
static bool retiming_fake_key_is_clicked(const bContext *C,
const Sequence *seq,
const int key_timeline_frame,
const int mval[2])
static bool retiming_fake_key_frame_clicked(const bContext *C,
const Sequence *seq,
const int mval[2],
int &r_frame)
{
const Scene *scene = CTX_data_scene(C);
const View2D *v2d = UI_view2d_fromcontext(C);
rctf box = seq_retiming_keys_box_get(CTX_data_scene(C), v2d, seq);
rctf box = seq_retiming_keys_box_get(scene, v2d, seq);
if (!BLI_rctf_isect_pt(&box, mval[0], mval[1])) {
return false;
}
const float key_pos = UI_view2d_view_to_region_x(v2d, key_timeline_frame);
const float distance = fabs(key_pos - mval[0]);
return distance < RETIME_KEY_MOUSEOVER_THRESHOLD;
const int left_frame = left_fake_key_frame_get(C, seq);
const float left_distance = fabs(UI_view2d_view_to_region_x(v2d, left_frame) - mval[0]);
const int right_frame = right_fake_key_frame_get(C, seq);
int right_x = right_frame;
/* `key_x_get()` compensates 1 frame offset of last key, however this can not
* be conveyed via `fake_key` alone. Therefore the same offset must be emulated. */
if (SEQ_time_right_handle_frame_get(scene, seq) >= SEQ_time_content_end_frame_get(scene, seq)) {
right_x += 1;
}
const float right_distance = fabs(UI_view2d_view_to_region_x(v2d, right_x) - mval[0]);
r_frame = (left_distance < right_distance) ? left_frame : right_frame;
return min_ff(left_distance, right_distance) < RETIME_KEY_MOUSEOVER_THRESHOLD;
}
void realize_fake_keys(const Scene *scene, Sequence *seq)
{
SEQ_retiming_data_ensure(seq);
SEQ_retiming_add_key(scene, seq, SEQ_time_left_handle_frame_get(scene, seq));
SEQ_retiming_add_key(scene, seq, SEQ_time_right_handle_frame_get(scene, seq));
}
SeqRetimingKey *try_to_realize_fake_keys(const bContext *C, Sequence *seq, const int mval[2])
@@ -146,31 +166,11 @@ SeqRetimingKey *try_to_realize_fake_keys(const bContext *C, Sequence *seq, const
Scene *scene = CTX_data_scene(C);
SeqRetimingKey *key = nullptr;
if (retiming_fake_key_is_clicked(C, seq, left_fake_key_frame_get(C, seq), mval)) {
SEQ_retiming_data_ensure(seq);
int frame = SEQ_time_left_handle_frame_get(scene, seq);
key = SEQ_retiming_add_key(scene, seq, frame);
int key_frame;
if (retiming_fake_key_frame_clicked(C, seq, mval, key_frame)) {
realize_fake_keys(scene, seq);
key = SEQ_retiming_key_get_by_timeline_frame(scene, seq, key_frame);
}
int right_key_frame = right_fake_key_frame_get(C, seq);
/* `key_x_get()` compensates 1 frame offset of last key, however this can not
* be conveyed via `fake_key` alone. Therefore the same offset must be emulated. */
if (SEQ_time_right_handle_frame_get(scene, seq) >= SEQ_time_content_end_frame_get(scene, seq)) {
right_key_frame += 1;
}
if (retiming_fake_key_is_clicked(C, seq, right_key_frame, mval)) {
SEQ_retiming_data_ensure(seq);
const int frame = SEQ_time_right_handle_frame_get(scene, seq);
key = SEQ_retiming_add_key(scene, seq, frame);
}
/* Ensure both keys are realized so we only change the speed of what is visible in the strip,
* but return only the one that was clicked on. */
if (key != nullptr) {
SEQ_retiming_add_key(scene, seq, SEQ_time_right_handle_frame_get(scene, seq));
SEQ_retiming_add_key(scene, seq, SEQ_time_left_handle_frame_get(scene, seq));
}
return key;
}
@@ -353,13 +353,11 @@ void sequencer_retiming_draw_continuity(const TimelineDrawContext *timeline_ctx,
}
}
static SeqRetimingKey retiming_key_init(const Scene *scene,
const Sequence *seq,
int timeline_frame)
static SeqRetimingKey fake_retiming_key_init(const Scene *scene, const Sequence *seq, int key_x)
{
int sound_offset = SEQ_time_get_rounded_sound_offset(scene, seq);
SeqRetimingKey fake_key;
fake_key.strip_frame_index = (timeline_frame - SEQ_time_start_frame_get(seq) - sound_offset) *
fake_key.strip_frame_index = (key_x - SEQ_time_start_frame_get(seq) - sound_offset) *
SEQ_time_media_playback_rate_factor_get(scene, seq);
fake_key.flag = 0;
return fake_key;
@@ -380,7 +378,7 @@ static bool fake_keys_draw(const TimelineDrawContext *timeline_ctx,
const int left_key_frame = left_fake_key_frame_get(timeline_ctx->C, seq);
if (SEQ_retiming_key_get_by_timeline_frame(scene, seq, left_key_frame) == nullptr) {
SeqRetimingKey fake_key = retiming_key_init(scene, seq, left_key_frame);
SeqRetimingKey fake_key = fake_retiming_key_init(scene, seq, left_key_frame);
retime_key_draw(timeline_ctx, strip_ctx, &fake_key, sh_bindings);
}
@@ -391,7 +389,7 @@ static bool fake_keys_draw(const TimelineDrawContext *timeline_ctx,
if (strip_ctx.right_handle >= SEQ_time_content_end_frame_get(scene, seq)) {
right_key_frame += 1;
}
SeqRetimingKey fake_key = retiming_key_init(scene, seq, right_key_frame);
SeqRetimingKey fake_key = fake_retiming_key_init(scene, seq, right_key_frame);
retime_key_draw(timeline_ctx, strip_ctx, &fake_key, sh_bindings);
}
return true;

View File

@@ -31,6 +31,7 @@
#include "RNA_define.hh"
#include "SEQ_channels.hh"
#include "SEQ_connect.hh"
#include "SEQ_effects.hh"
#include "SEQ_iterator.hh"
#include "SEQ_relations.hh"
@@ -208,8 +209,8 @@ static void select_linked_time_seq(const Scene *scene,
if (left_match && right_match) {
/* Direct match, copy all selection settings. */
seq_dest->flag &= ~(SELECT | SEQ_LEFTSEL | SEQ_RIGHTSEL);
seq_dest->flag |= seq_source->flag & (SELECT | SEQ_LEFTSEL | SEQ_RIGHTSEL);
seq_dest->flag &= ~(SEQ_ALLSEL);
seq_dest->flag |= seq_source->flag & (SEQ_ALLSEL);
recurs_sel_seq(seq_dest);
}
else if (left_match && handle_clicked == SEQ_HANDLE_LEFT) {
@@ -870,6 +871,24 @@ static bool element_already_selected(const StripSelection &selection)
return seq1_already_selected && seq2_already_selected && both_handles_selected;
}
static void sequencer_select_connected_strips(const StripSelection &selection)
{
blender::VectorSet<Sequence *> sources;
sources.add(selection.seq1);
if (selection.seq2) {
sources.add(selection.seq2);
}
for (Sequence *source : sources) {
blender::VectorSet<Sequence *> connections = SEQ_get_connected_strips(source);
for (Sequence *connection : connections) {
/* Copy selection settings exactly for connected strips. */
connection->flag &= ~(SEQ_ALLSEL);
connection->flag |= source->flag & (SEQ_ALLSEL);
}
}
}
static void sequencer_select_strip_impl(const Editing *ed,
Sequence *seq,
const eSeqHandle handle_clicked,
@@ -1162,16 +1181,29 @@ int sequencer_select_exec(bContext *C, wmOperator *op)
/* If no key was found, the mouse cursor may still intersect with a "fake key" that has not been
* realized yet. */
if (seq_key_owner != nullptr && key == nullptr) {
if (seq_key_owner != nullptr && key == nullptr &&
retiming_keys_can_be_displayed(CTX_wm_space_seq(C)) &&
SEQ_retiming_data_is_editable(seq_key_owner))
{
key = try_to_realize_fake_keys(C, seq_key_owner, mouse_co.region);
}
if (key != nullptr && retiming_keys_can_be_displayed(CTX_wm_space_seq(C)) &&
SEQ_retiming_data_is_editable(seq_key_owner))
{
if (key != nullptr) {
if (!was_retiming) {
ED_sequencer_deselect_all(scene);
}
/* Attempt to realize any other connected strips' fake keys. */
if (SEQ_is_strip_connected(seq_key_owner)) {
const int key_frame = SEQ_retiming_key_timeline_frame_get(scene, seq_key_owner, key);
blender::VectorSet<Sequence *> connections = SEQ_get_connected_strips(seq_key_owner);
for (Sequence *connection : connections) {
if (key_frame == left_fake_key_frame_get(C, connection) ||
key_frame == right_fake_key_frame_get(C, connection))
{
realize_fake_keys(scene, connection);
}
}
}
return sequencer_retiming_key_select_exec(C, op, key, seq_key_owner);
}
@@ -1238,10 +1270,11 @@ int sequencer_select_exec(bContext *C, wmOperator *op)
else {
sseq->flag |= SPACE_SEQ_DESELECT_STRIP_HANDLE;
}
const bool ignore_connections = RNA_boolean_get(op->ptr, "ignore_connections");
/* Clicking on already selected element falls on modal operation.
* All strips are deselected on mouse button release unless extend mode is used. */
if (already_selected && wait_to_deselect_others && !toggle) {
if (already_selected && wait_to_deselect_others && !toggle && !ignore_connections) {
return OPERATOR_RUNNING_MODAL;
}
@@ -1271,6 +1304,10 @@ int sequencer_select_exec(bContext *C, wmOperator *op)
sequencer_select_strip_impl(ed, selection.seq2, seq2_handle_clicked, extend, deselect, toggle);
}
if (!ignore_connections) {
sequencer_select_connected_strips(selection);
}
sequencer_select_do_updates(C, scene);
sequencer_select_set_active(scene, selection.seq1);
return OPERATOR_FINISHED;
@@ -1340,6 +1377,13 @@ void SEQUENCER_OT_select(wmOperatorType *ot)
"Side of Frame",
"Select all strips on same side of the current frame as the mouse cursor");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"ignore_connections",
false,
"Ignore Connections",
"Select strips individually whether or not they are connected");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
@@ -1382,7 +1426,6 @@ static int sequencer_select_handle_exec(bContext *C, wmOperator *op)
}
SpaceSeq *sseq = CTX_wm_space_seq(C);
if (element_already_selected(selection)) {
sseq->flag &= ~SPACE_SEQ_DESELECT_STRIP_HANDLE;
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
@@ -1401,6 +1444,11 @@ static int sequencer_select_handle_exec(bContext *C, wmOperator *op)
sequencer_select_strip_impl(ed, selection.seq2, seq2_handle_clicked, false, false, false);
}
const bool ignore_connections = RNA_boolean_get(op->ptr, "ignore_connections");
if (!ignore_connections) {
sequencer_select_connected_strips(selection);
}
sequencer_select_do_updates(C, scene);
sequencer_select_set_active(scene, selection.seq1);
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
@@ -1421,6 +1469,8 @@ static int sequencer_select_handle_invoke(bContext *C, wmOperator *op, const wmE
void SEQUENCER_OT_select_handle(wmOperatorType *ot)
{
PropertyRNA *prop;
/* Identifiers. */
ot->name = "Select Handle";
ot->idname = "SEQUENCER_OT_select_handle";
@@ -1436,6 +1486,13 @@ void SEQUENCER_OT_select_handle(wmOperatorType *ot)
/* Properties. */
WM_operator_properties_generic_select(ot);
prop = RNA_def_boolean(ot->srna,
"ignore_connections",
false,
"Ignore Connections",
"Select strips individually whether or not they are connected");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
@@ -2092,6 +2149,14 @@ static int sequencer_box_select_exec(bContext *C, wmOperator *op)
seq->flag &= ~(SEQ_LEFTSEL | SEQ_RIGHTSEL);
changed = true;
}
const bool ignore_connections = RNA_boolean_get(op->ptr, "ignore_connections");
if (!ignore_connections) {
/* Propagate selection to connected strips. */
StripSelection selection;
selection.seq1 = seq;
sequencer_select_connected_strips(selection);
}
}
}
@@ -2162,6 +2227,13 @@ void SEQUENCER_OT_select_box(wmOperatorType *ot)
prop = RNA_def_boolean(
ot->srna, "include_handles", false, "Select Handles", "Select the strips and their handles");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"ignore_connections",
false,
"Ignore Connections",
"Select strips individually whether or not they are connected");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */

View File

@@ -39,6 +39,7 @@
#include "RNA_prototypes.hh"
#include "SEQ_channels.hh"
#include "SEQ_connect.hh"
#include "SEQ_effects.hh"
#include "SEQ_prefetch.hh"
#include "SEQ_relations.hh"
@@ -71,7 +72,7 @@ using namespace blender::ed::seq;
#define MUTE_ALPHA 120
constexpr float MISSING_ICON_SIZE = 12.0f;
constexpr float ICON_SIZE = 12.0f;
Vector<Sequence *> sequencer_visible_strips_get(const bContext *C)
{
@@ -225,6 +226,7 @@ static StripDrawContext strip_draw_context_get(TimelineDrawContext *ctx, Sequenc
/* Determine if strip (or contents of meta strip) has missing data/media. */
strip_ctx.missing_data_block = !SEQ_sequence_has_valid_data(seq);
strip_ctx.missing_media = media_presence_is_missing(scene, seq);
strip_ctx.is_connected = SEQ_is_strip_connected(seq);
if (seq->type == SEQ_TYPE_META) {
const ListBase *seqbase = &seq->seqbase;
LISTBASE_FOREACH (const Sequence *, sub, seqbase) {
@@ -888,7 +890,7 @@ static void draw_icon_centered(TimelineDrawContext &ctx,
UI_view2d_view_ortho(ctx.v2d);
wmOrtho2_region_pixelspace(ctx.region);
const float icon_size = MISSING_ICON_SIZE * UI_SCALE_FAC;
const float icon_size = ICON_SIZE * UI_SCALE_FAC;
if (BLI_rctf_size_x(&rect) * 1.1f < icon_size * ctx.pixelx ||
BLI_rctf_size_y(&rect) * 1.1f < icon_size * ctx.pixely)
{
@@ -903,7 +905,7 @@ static void draw_icon_centered(TimelineDrawContext &ctx,
const float x_offset = (right - left - icon_size) * 0.5f;
const float y_offset = (top - bottom - icon_size) * 0.5f;
const float inv_scale_fac = (ICON_DEFAULT_HEIGHT / MISSING_ICON_SIZE) * UI_INV_SCALE_FAC;
const float inv_scale_fac = (ICON_DEFAULT_HEIGHT / ICON_SIZE) * UI_INV_SCALE_FAC;
UI_icon_draw_ex(left + x_offset,
bottom + y_offset,
@@ -922,12 +924,13 @@ static void draw_icon_centered(TimelineDrawContext &ctx,
static void draw_strip_icons(TimelineDrawContext *timeline_ctx,
const Vector<StripDrawContext> &strips)
{
const float icon_size_x = MISSING_ICON_SIZE * timeline_ctx->pixelx * UI_SCALE_FAC;
const float icon_size_x = ICON_SIZE * timeline_ctx->pixelx * UI_SCALE_FAC;
for (const StripDrawContext &strip : strips) {
const bool missing_data = strip.missing_data_block;
const bool missing_media = strip.missing_media;
if (!missing_data && !missing_media) {
const bool is_connected = strip.is_connected;
if (!missing_data && !missing_media && !is_connected) {
continue;
}
@@ -951,6 +954,11 @@ static void draw_strip_icons(TimelineDrawContext *timeline_ctx,
if (missing_media) {
rect.xmax = min_ff(strip.right_handle - strip.handle_width, rect.xmin + icon_size_x);
draw_icon_centered(*timeline_ctx, rect, ICON_ERROR, col);
rect.xmin = rect.xmax;
}
if (is_connected) {
rect.xmax = min_ff(strip.right_handle - strip.handle_width, rect.xmin + icon_size_x);
draw_icon_centered(*timeline_ctx, rect, ICON_LINKED, col);
}
}
@@ -1006,12 +1014,17 @@ static void draw_seq_text_overlay(TimelineDrawContext *timeline_ctx,
rect.ymin = !strip_ctx->can_draw_strip_content ? strip_ctx->bottom :
strip_ctx->strip_content_top;
rect.xmin = max_ff(rect.xmin, timeline_ctx->v2d->cur.xmin + text_margin);
int num_icons = 0;
if (strip_ctx->missing_data_block) {
rect.xmin += MISSING_ICON_SIZE * timeline_ctx->pixelx * UI_SCALE_FAC;
num_icons++;
}
if (strip_ctx->missing_media) {
rect.xmin += MISSING_ICON_SIZE * timeline_ctx->pixelx * UI_SCALE_FAC;
num_icons++;
}
if (strip_ctx->is_connected) {
num_icons++;
}
rect.xmin += num_icons * ICON_SIZE * timeline_ctx->pixelx * UI_SCALE_FAC;
rect.xmin = min_ff(rect.xmin, timeline_ctx->v2d->cur.xmax);
CLAMP(rect.xmax, timeline_ctx->v2d->cur.xmin + text_margin, timeline_ctx->v2d->cur.xmax);

View File

@@ -163,7 +163,7 @@ typedef struct SequenceRuntime {
*/
typedef struct Sequence {
struct Sequence *next, *prev;
/** Temp var for copying, and tagging for linked selection. */
/** Temp var for duplication, pointing to the newly duplicated Sequence. */
void *tmp;
/** Needed (to be like ipo), else it will raise libdata warnings, this should never be used. */
void *lib;
@@ -234,6 +234,9 @@ typedef struct Sequence {
ListBase seqbase;
ListBase channels; /* SeqTimelineChannel */
/* List of strip connections (one-way, not bidirectional). */
ListBase connections; /* SeqConnection */
/** The linked "bSound" object. */
struct bSound *sound;
/** Handle to #AUD_SequenceEntry. */
@@ -308,6 +311,11 @@ typedef struct SeqTimelineChannel {
int flag;
} SeqTimelineChannel;
typedef struct SeqConnection {
struct SeqConnection *next, *prev;
Sequence *seq_ref;
} SeqConnection;
typedef struct EditingRuntime {
struct SequenceLookup *sequence_lookup;
MediaPresence *media_presence;

View File

@@ -1597,6 +1597,7 @@ typedef enum eUserpref_SeqProxySetup {
typedef enum eUserpref_SeqEditorFlags {
USER_SEQ_ED_SIMPLE_TWEAKING = (1 << 0),
USER_SEQ_ED_CONNECT_STRIPS_BY_DEFAULT = (1 << 1),
} eUserpref_SeqEditorFlags;
/* Locale Ids. Auto will try to get local from OS. Our default is English though. */

View File

@@ -5753,6 +5753,14 @@ static void rna_def_userdef_edit(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Tweak Handles", "Allows dragging handles without selecting them first");
prop = RNA_def_property(srna, "connect_strips_by_default", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "sequencer_editor_flag", USER_SEQ_ED_CONNECT_STRIPS_BY_DEFAULT);
RNA_def_property_ui_text(
prop,
"Connect Movie Strips by Default",
"Connect newly added movie strips by default if they have multiple channels");
/* duplication linking */
prop = RNA_def_property(srna, "use_duplicate_mesh", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "dupflag", USER_DUP_MESH);

View File

@@ -25,6 +25,7 @@ set(SRC
SEQ_add.hh
SEQ_animation.hh
SEQ_channels.hh
SEQ_connect.hh
SEQ_edit.hh
SEQ_effects.hh
SEQ_iterator.hh
@@ -66,6 +67,7 @@ set(SRC
intern/sequencer.hh
intern/sound.cc
intern/strip_add.cc
intern/strip_connect.cc
intern/strip_edit.cc
intern/strip_relations.cc
intern/strip_retiming.cc

View File

@@ -0,0 +1,54 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup sequencer
*/
#include "BLI_vector_set.hh"
struct Sequence;
struct ListBase;
void SEQ_connections_duplicate(ListBase *connections_dst, ListBase *connections_src);
/**
* Disconnect the strip(s) from any connections with other strips. This function also
* frees the allocated memory as necessary. Returns false if any of the strips were not already
* connected.
*/
bool SEQ_disconnect(Sequence *seq);
bool SEQ_disconnect(blender::VectorSet<Sequence *> &seq_list);
/**
* Ensure that the strip has only bidirectional connections (expected behavior).
*/
void SEQ_cut_one_way_connections(Sequence *seq);
/**
* Connect strips so that they may be selected together. Any connections the
* strips already have will be severed before reconnection.
*/
void SEQ_connect(Sequence *seq1, Sequence *seq2);
void SEQ_connect(blender::VectorSet<Sequence *> &seq_list);
/**
* Returns a list of strips that the `seq` is connected to.
* NOTE: This does not include `seq` itself.
* This list is empty if `seq` is not connected.
*/
blender::VectorSet<Sequence *> SEQ_get_connected_strips(const Sequence *seq);
/**
* Check whether a strip has any connections.
*/
bool SEQ_is_strip_connected(const Sequence *seq);
/**
* Check whether the list of strips are a single connection "group", that is, they are all
* connected to each other and there are no outside connections.
*/
bool SEQ_are_strips_connected_together(blender::VectorSet<Sequence *> &seq_list);

View File

@@ -68,6 +68,7 @@ bool SEQ_retiming_key_is_freeze_frame(const SeqRetimingKey *key);
bool SEQ_retiming_selection_clear(const Editing *ed);
void SEQ_retiming_selection_append(SeqRetimingKey *key);
void SEQ_retiming_selection_remove(SeqRetimingKey *key);
void SEQ_retiming_selection_copy(SeqRetimingKey *dst, const SeqRetimingKey *src);
void SEQ_retiming_remove_multiple_keys(Sequence *seq,
blender::Vector<SeqRetimingKey *> &keys_to_remove);
bool SEQ_retiming_selection_contains(const Editing *ed, const SeqRetimingKey *key);

View File

@@ -32,6 +32,7 @@
#include "IMB_imbuf.hh"
#include "SEQ_channels.hh"
#include "SEQ_connect.hh"
#include "SEQ_edit.hh"
#include "SEQ_effects.hh"
#include "SEQ_iterator.hh"
@@ -209,6 +210,10 @@ static void seq_sequence_free_ex(Scene *scene,
/* free modifiers */
SEQ_modifier_clear(seq);
if (SEQ_is_strip_connected(seq)) {
SEQ_disconnect(seq);
}
/* free cached data used by this strip,
* also invalidate cache for all dependent sequences
*
@@ -324,6 +329,14 @@ static void seq_new_fix_links_recursive(Sequence *seq)
smd->mask_sequence = static_cast<Sequence *>(smd->mask_sequence->tmp);
}
}
if (SEQ_is_strip_connected(seq)) {
LISTBASE_FOREACH (SeqConnection *, con, &seq->connections) {
if (con->seq_ref->tmp) {
con->seq_ref = static_cast<Sequence *>(con->seq_ref->tmp);
}
}
}
}
SequencerToolSettings *SEQ_tool_settings_init()
@@ -525,6 +538,11 @@ static Sequence *seq_dupli(const Scene *scene_src,
SEQ_modifier_list_copy(seqn, seq);
}
if (SEQ_is_strip_connected(seq)) {
BLI_listbase_clear(&seqn->connections);
SEQ_connections_duplicate(&seqn->connections, &seq->connections);
}
if (seq->type == SEQ_TYPE_META) {
seqn->strip->stripdata = nullptr;
@@ -625,6 +643,9 @@ Sequence *SEQ_sequence_dupli_recursive(
/* This does not need to be in recursive call itself, since it is already recursive... */
seq_new_fix_links_recursive(seqn);
if (SEQ_is_strip_connected(seqn)) {
SEQ_cut_one_way_connections(seqn);
}
return seqn;
}
@@ -660,10 +681,16 @@ void SEQ_sequence_base_dupli_recursive(const Scene *scene_src,
return;
}
/* fix modifier linking */
/* Fix effect, modifier, and connected strip links. */
LISTBASE_FOREACH (Sequence *, seq, nseqbase) {
seq_new_fix_links_recursive(seq);
}
/* One-way connections cannot be cut until after all connections are resolved. */
LISTBASE_FOREACH (Sequence *, seq, nseqbase) {
if (SEQ_is_strip_connected(seq)) {
SEQ_cut_one_way_connections(seq);
}
}
}
bool SEQ_valid_strip_channel(Sequence *seq)
@@ -767,6 +794,10 @@ static bool seq_write_data_cb(Sequence *seq, void *userdata)
BLO_write_struct(writer, SeqTimelineChannel, channel);
}
LISTBASE_FOREACH (SeqConnection *, con, &seq->connections) {
BLO_write_struct(writer, SeqConnection, con);
}
if (seq->retiming_keys != nullptr) {
int size = SEQ_retiming_keys_count(seq);
BLO_write_struct_array(writer, SeqRetimingKey, size, seq->retiming_keys);
@@ -885,6 +916,13 @@ static bool seq_read_data_cb(Sequence *seq, void *user_data)
SEQ_modifier_blend_read_data(reader, &seq->modifiers);
BLO_read_struct_list(reader, SeqConnection, &seq->connections);
LISTBASE_FOREACH (SeqConnection *, con, &seq->connections) {
if (con->seq_ref) {
BLO_read_struct(reader, Sequence, &con->seq_ref);
}
}
BLO_read_struct_list(reader, SeqTimelineChannel, &seq->channels);
if (seq->retiming_keys != nullptr) {

View File

@@ -0,0 +1,153 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "BLI_blenlib.h"
#include "DNA_sequence_types.h"
#include "SEQ_connect.hh"
#include "SEQ_time.hh"
static void seq_connections_free(Sequence *seq)
{
if (seq == nullptr) {
return;
}
ListBase *connections = &seq->connections;
LISTBASE_FOREACH_MUTABLE (SeqConnection *, con, connections) {
MEM_delete(con);
}
BLI_listbase_clear(connections);
}
void SEQ_connections_duplicate(ListBase *connections_dst, ListBase *connections_src)
{
LISTBASE_FOREACH (SeqConnection *, con, connections_src) {
SeqConnection *con_duplicate = MEM_cnew<SeqConnection>(__func__, *con);
BLI_addtail(connections_dst, con_duplicate);
}
}
bool SEQ_disconnect(Sequence *seq)
{
if (seq == nullptr || BLI_listbase_is_empty(&seq->connections)) {
return false;
}
/* Remove `SeqConnections` from other strips' `connections` list that point to `seq`. */
LISTBASE_FOREACH (SeqConnection *, con_seq, &seq->connections) {
Sequence *other = con_seq->seq_ref;
LISTBASE_FOREACH_MUTABLE (SeqConnection *, con_other, &other->connections) {
if (con_other->seq_ref == seq) {
BLI_remlink(&other->connections, con_other);
MEM_delete(con_other);
}
}
}
/* Now clear `connections` for `seq` itself.*/
seq_connections_free(seq);
return true;
}
bool SEQ_disconnect(blender::VectorSet<Sequence *> &seq_list)
{
bool changed = false;
for (Sequence *seq : seq_list) {
changed |= SEQ_disconnect(seq);
}
return changed;
}
void SEQ_cut_one_way_connections(Sequence *seq)
{
if (seq == nullptr) {
return;
}
LISTBASE_FOREACH_MUTABLE (SeqConnection *, con_seq, &seq->connections) {
Sequence *other = con_seq->seq_ref;
bool is_one_way = true;
LISTBASE_FOREACH (SeqConnection *, con_other, &other->connections) {
if (con_other->seq_ref == seq) {
/* The `other` sequence has a bidirectional connection with `seq`. */
is_one_way = false;
break;
}
}
if (is_one_way) {
BLI_remlink(&seq->connections, con_seq);
MEM_delete(con_seq);
}
}
}
void SEQ_connect(Sequence *seq1, Sequence *seq2)
{
if (seq1 == nullptr || seq2 == nullptr) {
return;
}
blender::VectorSet<Sequence *> seq_list;
seq_list.add(seq1);
seq_list.add(seq2);
SEQ_connect(seq_list);
}
void SEQ_connect(blender::VectorSet<Sequence *> &seq_list)
{
seq_list.remove_if([&](Sequence *seq) { return seq == nullptr; });
for (Sequence *seq1 : seq_list) {
SEQ_disconnect(seq1);
for (Sequence *seq2 : seq_list) {
if (seq1 == seq2) {
continue;
}
SeqConnection *con = MEM_cnew<SeqConnection>("seqconnection");
con->seq_ref = seq2;
BLI_addtail(&seq1->connections, con);
}
}
}
blender::VectorSet<Sequence *> SEQ_get_connected_strips(const Sequence *seq)
{
blender::VectorSet<Sequence *> connections;
if (seq != nullptr) {
LISTBASE_FOREACH (SeqConnection *, con, &seq->connections) {
connections.add(con->seq_ref);
}
}
return connections;
}
bool SEQ_is_strip_connected(const Sequence *seq)
{
if (seq == nullptr) {
return false;
}
return !BLI_listbase_is_empty(&seq->connections);
}
bool SEQ_are_strips_connected_together(blender::VectorSet<Sequence *> &seq_list)
{
const int expected_connection_num = seq_list.size() - 1;
for (Sequence *seq1 : seq_list) {
blender::VectorSet<Sequence *> connections = SEQ_get_connected_strips(seq1);
int found_connection_num = connections.size();
if (found_connection_num != expected_connection_num) {
return false;
}
for (Sequence *seq2 : connections) {
if (!seq_list.contains(seq2)) {
return false;
}
}
}
return true;
}

View File

@@ -25,6 +25,8 @@
#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"
@@ -305,9 +307,9 @@ static void seq_split_set_left_hold_offset(Main *bmain,
SEQ_time_left_handle_frame_set(scene, seq, timeline_frame);
}
static bool seq_edit_split_effect_intersect_check(const Scene *scene,
const Sequence *seq,
const int timeline_frame)
static bool seq_edit_split_intersect_check(const Scene *scene,
const Sequence *seq,
const int timeline_frame)
{
return timeline_frame > SEQ_time_left_handle_frame_get(scene, seq) &&
timeline_frame < SEQ_time_right_handle_frame_get(scene, seq);
@@ -320,7 +322,7 @@ static void seq_edit_split_handle_strip_offsets(Main *bmain,
const int timeline_frame,
const eSeqSplitMethod method)
{
if (seq_edit_split_effect_intersect_check(scene, right_seq, timeline_frame)) {
if (seq_edit_split_intersect_check(scene, right_seq, timeline_frame)) {
switch (method) {
case SEQ_SPLIT_SOFT:
SEQ_time_left_handle_frame_set(scene, right_seq, timeline_frame);
@@ -331,7 +333,7 @@ static void seq_edit_split_handle_strip_offsets(Main *bmain,
}
}
if (seq_edit_split_effect_intersect_check(scene, left_seq, timeline_frame)) {
if (seq_edit_split_intersect_check(scene, left_seq, timeline_frame)) {
switch (method) {
case SEQ_SPLIT_SOFT:
SEQ_time_right_handle_frame_set(scene, left_seq, timeline_frame);
@@ -349,24 +351,21 @@ static bool seq_edit_split_effect_inputs_intersect(const Scene *scene,
{
bool input_does_intersect = false;
if (seq->seq1) {
input_does_intersect |= seq_edit_split_effect_intersect_check(
scene, seq->seq1, timeline_frame);
input_does_intersect |= seq_edit_split_intersect_check(scene, seq->seq1, timeline_frame);
if ((seq->seq1->type & SEQ_TYPE_EFFECT) != 0) {
input_does_intersect |= seq_edit_split_effect_inputs_intersect(
scene, seq->seq1, timeline_frame);
}
}
if (seq->seq2) {
input_does_intersect |= seq_edit_split_effect_intersect_check(
scene, seq->seq2, timeline_frame);
input_does_intersect |= seq_edit_split_intersect_check(scene, seq->seq2, timeline_frame);
if ((seq->seq1->type & SEQ_TYPE_EFFECT) != 0) {
input_does_intersect |= seq_edit_split_effect_inputs_intersect(
scene, seq->seq2, timeline_frame);
}
}
if (seq->seq3) {
input_does_intersect |= seq_edit_split_effect_intersect_check(
scene, seq->seq3, timeline_frame);
input_does_intersect |= seq_edit_split_intersect_check(scene, seq->seq3, timeline_frame);
if ((seq->seq1->type & SEQ_TYPE_EFFECT) != 0) {
input_does_intersect |= seq_edit_split_effect_inputs_intersect(
scene, seq->seq3, timeline_frame);
@@ -389,7 +388,7 @@ static bool seq_edit_split_operation_permitted_check(const Scene *scene,
if ((seq->type & SEQ_TYPE_EFFECT) == 0) {
continue;
}
if (!seq_edit_split_effect_intersect_check(scene, seq, timeline_frame)) {
if (!seq_edit_split_intersect_check(scene, seq, timeline_frame)) {
continue;
}
if (SEQ_effect_get_num_inputs(seq->type) <= 1) {
@@ -415,11 +414,11 @@ Sequence *SEQ_edit_strip_split(Main *bmain,
const eSeqSplitMethod method,
const char **r_error)
{
if (!seq_edit_split_effect_intersect_check(scene, seq, timeline_frame)) {
if (!seq_edit_split_intersect_check(scene, seq, timeline_frame)) {
return nullptr;
}
/* Whole strip chain must be duplicated in order to preserve relationships. */
/* Whole strip effect chain must be duplicated in order to preserve relationships. */
blender::VectorSet<Sequence *> strips;
strips.add(seq);
SEQ_iterator_set_expand(scene, seqbase, strips, SEQ_query_strip_effect_chain);
@@ -428,6 +427,13 @@ Sequence *SEQ_edit_strip_split(Main *bmain,
return nullptr;
}
/* All connected strips (that are selected and at the cut frame) must also be duplicated. */
blender::VectorSet<Sequence *> connections = SEQ_get_connected_strips(seq);
connections.remove_if([&](Sequence *seq) {
return !(seq->flag & SELECT) || !seq_edit_split_intersect_check(scene, seq, timeline_frame);
});
strips.add_multiple(connections.as_span());
/* Store `F-Curves`, so original ones aren't renamed. */
SeqAnimationBackup animation_backup{};
SEQ_animation_backup_original(scene, &animation_backup);

View File

@@ -1087,6 +1087,12 @@ void SEQ_retiming_selection_remove(SeqRetimingKey *key)
key->flag &= ~SEQ_KEY_SELECTED;
}
void SEQ_retiming_selection_copy(SeqRetimingKey *dst, const SeqRetimingKey *src)
{
SEQ_retiming_selection_remove(dst);
dst->flag |= (src->flag & SEQ_KEY_SELECTED);
}
blender::Map<SeqRetimingKey *, Sequence *> SEQ_retiming_selection_get(const Editing *ed)
{
blender::Map<SeqRetimingKey *, Sequence *> selection;