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:
committed by
Aras Pranckevicius
parent
ce95155b30
commit
715129bf5b
@@ -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)]}),
|
||||
]
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
54
source/blender/sequencer/SEQ_connect.hh
Normal file
54
source/blender/sequencer/SEQ_connect.hh
Normal 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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
153
source/blender/sequencer/intern/strip_connect.cc
Normal file
153
source/blender/sequencer/intern/strip_connect.cc
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user