VSE: "Duplicate Strips" also duplicates referenced IDs

Duplicating a strip that references an ID like the scene strip would not
duplicate the scene. This is wanted in some workflows.

To align with the rest of Blender, this changes the behavior for how
strips are duplicated:
* `Shift + D` ("duplicate"): Duplicate the strip and also duplicate the
   IDs referenced by the strip. Currently this only affects `Scene`,
   `MovieClip`, and `Mask` strips.
* `Alt + D` ("duplicate linked"): Duplicate the strip, but reference the
  same IDs. This is the current behavior in `main`.

Part of #144063.

Pull Request: https://projects.blender.org/blender/blender/pulls/144138
This commit is contained in:
Falk David
2025-08-11 15:20:35 +02:00
committed by Falk David
parent 38f5e1f763
commit 6eea75e928
10 changed files with 101 additions and 26 deletions

View File

@@ -3053,6 +3053,7 @@ def km_sequencer(params):
{"properties": [("adjust_length", True)]}),
("sequencer.offset_clear", {"type": 'O', "value": 'PRESS', "alt": True}, None),
("sequencer.duplicate_move", {"type": 'D', "value": 'PRESS', "shift": True}, None),
("sequencer.duplicate_move_linked", {"type": 'D', "value": 'PRESS', "alt": True}, None),
("sequencer.retiming_key_delete", {"type": 'X', "value": 'PRESS'}, None),
("sequencer.retiming_key_delete", {"type": 'DEL', "value": 'PRESS'}, None),
("sequencer.delete", {"type": 'X', "value": 'PRESS'}, None),
@@ -3232,6 +3233,7 @@ def km_sequencer_preview(params):
{"properties": [("property", 'ROTATION')]}),
("sequencer.preview_duplicate_move", {"type": 'D', "value": 'PRESS', "shift": True}, None),
("sequencer.preview_duplicate_move_linked", {"type": 'D', "value": 'PRESS', "alt": True}, None),
("sequencer.mute", {"type": 'H', "value": 'PRESS'},
{"properties": [("unselected", False)]}),
("sequencer.mute", {"type": 'H', "value": 'PRESS', "shift": True},

View File

@@ -1170,6 +1170,7 @@ class SEQUENCER_MT_strip(Menu):
layout.operator("sequencer.copy", text="Copy")
layout.operator("sequencer.paste", text="Paste")
layout.operator("sequencer.duplicate_move", text="Duplicate")
layout.operator("sequencer.duplicate_move_linked", text="Duplicate Linked")
layout.separator()
layout.operator("sequencer.delete", text="Delete")

View File

@@ -334,7 +334,8 @@ static void scene_copy_data(Main *bmain,
scene_dst->ed->show_missing_media_flag = scene_src->ed->show_missing_media_flag;
scene_dst->ed->proxy_storage = scene_src->ed->proxy_storage;
STRNCPY(scene_dst->ed->proxy_dir, scene_src->ed->proxy_dir);
blender::seq::seqbase_duplicate_recursive(scene_src,
blender::seq::seqbase_duplicate_recursive(bmain,
scene_src,
scene_dst,
&scene_dst->ed->seqbase,
&scene_src->ed->seqbase,

View File

@@ -168,7 +168,8 @@ static bool sequencer_write_copy_paste_file(Main *bmain_src,
/* Create an empty sequence editor data to store all copied strips. */
scene_dst->ed = MEM_callocN<Editing>(__func__);
scene_dst->ed->seqbasep = &scene_dst->ed->seqbase;
seq::seqbase_duplicate_recursive(scene_src,
seq::seqbase_duplicate_recursive(bmain_src,
scene_src,
scene_dst,
&scene_dst->ed->seqbase,
scene_src->ed->seqbasep,
@@ -480,8 +481,13 @@ wmOperatorStatus sequencer_clipboard_paste_exec(bContext *C, wmOperator *op)
ListBase nseqbase = {nullptr, nullptr};
/* NOTE: seq::seqbase_duplicate_recursive() takes care of generating
* new UIDs for sequences in the new list. */
seq::seqbase_duplicate_recursive(
scene_src, scene_dst, &nseqbase, &scene_src->ed->seqbase, seq::StripDuplicate::Selected, 0);
seq::seqbase_duplicate_recursive(bmain_dst,
scene_src,
scene_dst,
&nseqbase,
&scene_src->ed->seqbase,
seq::StripDuplicate::Selected,
0);
/* BKE_main_merge will copy the scene_src and its action into bmain_dst. Remove them as
* we merge the data from these manually.

View File

@@ -1730,12 +1730,15 @@ void SEQUENCER_OT_split(wmOperatorType *ot)
/** \name Duplicate Strips Operator
* \{ */
static wmOperatorStatus sequencer_add_duplicate_exec(bContext *C, wmOperator * /*op*/)
static wmOperatorStatus sequencer_add_duplicate_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_sequencer_scene(C);
Editing *ed = seq::editing_get(scene);
ARegion *region = CTX_wm_region(C);
const bool linked = RNA_boolean_get(op->ptr, "linked");
if (ed == nullptr) {
return OPERATOR_CANCELLED;
}
@@ -1755,8 +1758,11 @@ static wmOperatorStatus sequencer_add_duplicate_exec(bContext *C, wmOperator * /
}
}
const seq::StripDuplicate dupe_flag = linked ? seq::StripDuplicate::Selected :
(seq::StripDuplicate::Selected |
seq::StripDuplicate::Data);
seq::seqbase_duplicate_recursive(
scene, scene, &duplicated_strips, ed->seqbasep, seq::StripDuplicate::Selected, 0);
bmain, scene, scene, &duplicated_strips, ed->seqbasep, dupe_flag, 0);
deselect_all_strips(scene);
if (duplicated_strips.first == nullptr) {
@@ -1812,6 +1818,8 @@ static wmOperatorStatus sequencer_add_duplicate_exec(bContext *C, wmOperator * /
void SEQUENCER_OT_duplicate(wmOperatorType *ot)
{
PropertyRNA *prop;
/* Identifiers. */
ot->name = "Duplicate Strips";
ot->idname = "SEQUENCER_OT_duplicate";
@@ -1823,6 +1831,13 @@ void SEQUENCER_OT_duplicate(wmOperatorType *ot)
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
prop = RNA_def_boolean(ot->srna,
"linked",
false,
"Linked",
"Duplicate strip but not strip data, linking to the original data");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
@@ -1990,6 +2005,7 @@ void SEQUENCER_OT_offset_clear(wmOperatorType *ot)
static wmOperatorStatus sequencer_separate_images_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_sequencer_scene(C);
Editing *ed = seq::editing_get(scene);
ListBase *seqbase = seq::active_seqbase_get(ed);
@@ -2019,7 +2035,7 @@ static wmOperatorStatus sequencer_separate_images_exec(bContext *C, wmOperator *
se = seq::render_give_stripelem(scene, strip, timeline_frame);
strip_new = seq::strip_duplicate_recursive(
scene, scene, seqbase, strip, seq::StripDuplicate::UniqueName);
bmain, scene, scene, seqbase, strip, seq::StripDuplicate::UniqueName);
strip_new->start = start_ofs;
strip_new->type = STRIP_TYPE_IMAGE;

View File

@@ -157,23 +157,38 @@ void sequencer_keymap(wmKeyConfig *keyconf)
void ED_operatormacros_sequencer()
{
wmOperatorType *ot;
wmOperatorTypeMacro *otmacro;
ot = WM_operatortype_append_macro("SEQUENCER_OT_duplicate_move",
"Duplicate Strips",
"Duplicate selected strips and move them",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "SEQUENCER_OT_duplicate");
WM_operatortype_macro_define(ot, "TRANSFORM_OT_seq_slide");
ot = WM_operatortype_append_macro("SEQUENCER_OT_duplicate_move_linked",
"Duplicate Strips",
"Duplicate selected strips, but not their data, and move them",
OPTYPE_UNDO | OPTYPE_REGISTER);
otmacro = WM_operatortype_macro_define(ot, "SEQUENCER_OT_duplicate");
RNA_boolean_set(otmacro->ptr, "linked", true);
WM_operatortype_macro_define(ot, "TRANSFORM_OT_seq_slide");
ot = WM_operatortype_append_macro("SEQUENCER_OT_preview_duplicate_move",
"Duplicate Strips",
"Duplicate selected strips and move them",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "SEQUENCER_OT_duplicate");
WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
ot = WM_operatortype_append_macro("SEQUENCER_OT_preview_duplicate_move_linked",
"Duplicate Strips",
"Duplicate selected strips, but not their data, and move them",
OPTYPE_UNDO | OPTYPE_REGISTER);
otmacro = WM_operatortype_macro_define(ot, "SEQUENCER_OT_duplicate");
RNA_boolean_set(otmacro->ptr, "linked", true);
WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
ot = WM_operatortype_append_macro("SEQUENCER_OT_retiming_add_freeze_frame_slide",
"Add Freeze Frame And Slide",
"Add freeze frame and move it",

View File

@@ -39,7 +39,11 @@ enum {
enum class StripDuplicate : uint8_t {
/* Note: Technically, the selected strips are duplicated when `All` is not set. */
Selected = 0,
/* Ensure strips have a unique name. */
UniqueName = (1 << 0),
/* Duplicate strips and the IDs they reference. */
Data = (1 << 1),
/* If this is set, duplicate all strips. If not set, duplicate selected strips. */
All = (1 << 3),
};
ENUM_OPERATORS(StripDuplicate, StripDuplicate::All);
@@ -93,12 +97,14 @@ void meta_stack_set(const Scene *scene, Strip *dst);
* \param ed: sequence editor data
*/
Strip *meta_stack_pop(Editing *ed);
Strip *strip_duplicate_recursive(const Scene *scene_src,
Strip *strip_duplicate_recursive(Main *bmain,
const Scene *scene_src,
Scene *scene_dst,
ListBase *new_seq_list,
Strip *strip,
StripDuplicate dupe_flag);
void seqbase_duplicate_recursive(const Scene *scene_src,
void seqbase_duplicate_recursive(Main *bmain,
const Scene *scene_src,
Scene *scene_dst,
ListBase *nseqbase,
const ListBase *seqbase,

View File

@@ -470,7 +470,8 @@ bool proxy_rebuild_context(Main *bmain,
context = MEM_callocN<IndexBuildContext>("strip proxy rebuild context");
strip_new = strip_duplicate_recursive(scene, scene, nullptr, strip, StripDuplicate::Selected);
strip_new = strip_duplicate_recursive(
bmain, scene, scene, nullptr, strip, StripDuplicate::Selected);
context->tc_flags = strip_new->data->proxy->build_tc_flags;
context->size_flags = strip_new->data->proxy->build_size_flags;

View File

@@ -15,6 +15,7 @@
#include "MEM_guardedalloc.h"
#include "DNA_listBase.h"
#include "DNA_mask_types.h"
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "DNA_sound_types.h"
@@ -501,7 +502,8 @@ Strip *meta_stack_pop(Editing *ed)
/** \name Duplicate Functions
* \{ */
static Strip *strip_duplicate(const Scene *scene_src,
static Strip *strip_duplicate(Main *bmain,
const Scene *scene_src,
Scene *scene_dst,
ListBase *new_seq_list,
Strip *strip,
@@ -564,16 +566,34 @@ static Strip *strip_duplicate(const Scene *scene_src,
channels_duplicate(&strip_new->channels, &strip->channels);
}
else if (strip->type == STRIP_TYPE_SCENE) {
if (int(dupe_flag & StripDuplicate::Data) != 0 && strip_new->scene != nullptr) {
Scene *scene_old = strip_new->scene;
strip_new->scene = BKE_scene_duplicate(bmain, scene_old, SCE_COPY_FULL);
}
strip_new->data->stripdata = nullptr;
if (strip->scene_sound) {
strip_new->scene_sound = BKE_sound_scene_add_scene_sound_defaults(scene_dst, strip_new);
}
}
else if (strip->type == STRIP_TYPE_MOVIECLIP) {
/* avoid assert */
if (int(dupe_flag & StripDuplicate::Data) != 0 && strip_new->clip != nullptr) {
MovieClip *clip_old = strip_new->clip;
strip_new->clip = reinterpret_cast<MovieClip *>(
BKE_id_copy(bmain, reinterpret_cast<ID *>(clip_old)));
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT)) {
id_us_min(&strip_new->clip->id);
}
}
}
else if (strip->type == STRIP_TYPE_MASK) {
/* avoid assert */
if (int(dupe_flag & StripDuplicate::Data) != 0 && strip_new->mask != nullptr) {
Mask *mask_old = strip_new->mask;
strip_new->mask = reinterpret_cast<Mask *>(
BKE_id_copy(bmain, reinterpret_cast<ID *>(mask_old)));
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT)) {
id_us_min(&strip_new->mask->id);
}
}
}
else if (strip->type == STRIP_TYPE_MOVIE) {
strip_new->data->stripdata = static_cast<StripElem *>(MEM_dupallocN(strip->data->stripdata));
@@ -627,7 +647,8 @@ static Strip *strip_duplicate(const Scene *scene_src,
return strip_new;
}
static Strip *strip_duplicate_recursive_impl(const Scene *scene_src,
static Strip *strip_duplicate_recursive_impl(Main *bmain,
const Scene *scene_src,
Scene *scene_dst,
ListBase *new_seq_list,
Strip *strip,
@@ -635,17 +656,18 @@ static Strip *strip_duplicate_recursive_impl(const Scene *scene_src,
blender::Map<Strip *, Strip *> &strip_map)
{
Strip *strip_new = strip_duplicate(
scene_src, scene_dst, new_seq_list, strip, dupe_flag, 0, strip_map);
bmain, scene_src, scene_dst, new_seq_list, strip, dupe_flag, 0, strip_map);
if (strip->type == STRIP_TYPE_META) {
LISTBASE_FOREACH (Strip *, s, &strip->seqbase) {
strip_duplicate_recursive_impl(
scene_src, scene_dst, &strip_new->seqbase, s, dupe_flag, strip_map);
bmain, scene_src, scene_dst, &strip_new->seqbase, s, dupe_flag, strip_map);
}
}
return strip_new;
}
Strip *strip_duplicate_recursive(const Scene *scene_src,
Strip *strip_duplicate_recursive(Main *bmain,
const Scene *scene_src,
Scene *scene_dst,
ListBase *new_seq_list,
Strip *strip,
@@ -654,7 +676,7 @@ Strip *strip_duplicate_recursive(const Scene *scene_src,
blender::Map<Strip *, Strip *> strip_map;
Strip *strip_new = strip_duplicate_recursive_impl(
scene_src, scene_dst, new_seq_list, strip, dupe_flag, strip_map);
bmain, scene_src, scene_dst, new_seq_list, strip, dupe_flag, strip_map);
seq_new_fix_links_recursive(strip_new, strip_map);
if (is_strip_connected(strip_new)) {
@@ -664,7 +686,8 @@ Strip *strip_duplicate_recursive(const Scene *scene_src,
return strip_new;
}
static void seqbase_dupli_recursive(const Scene *scene_src,
static void seqbase_dupli_recursive(Main *bmain,
const Scene *scene_src,
Scene *scene_dst,
ListBase *nseqbase,
const ListBase *seqbase,
@@ -678,13 +701,14 @@ static void seqbase_dupli_recursive(const Scene *scene_src,
}
Strip *strip_new = strip_duplicate(
scene_src, scene_dst, nseqbase, strip, dupe_flag, flag, strip_map);
bmain, scene_src, scene_dst, nseqbase, strip, dupe_flag, flag, strip_map);
BLI_assert(strip_new != nullptr);
if (strip->type == STRIP_TYPE_META) {
/* Always include meta all strip children. */
const StripDuplicate dupe_flag_recursive = dupe_flag | StripDuplicate::All;
seqbase_dupli_recursive(scene_src,
seqbase_dupli_recursive(bmain,
scene_src,
scene_dst,
&strip_new->seqbase,
&strip->seqbase,
@@ -695,7 +719,8 @@ static void seqbase_dupli_recursive(const Scene *scene_src,
}
}
void seqbase_duplicate_recursive(const Scene *scene_src,
void seqbase_duplicate_recursive(Main *bmain,
const Scene *scene_src,
Scene *scene_dst,
ListBase *nseqbase,
const ListBase *seqbase,
@@ -704,7 +729,8 @@ void seqbase_duplicate_recursive(const Scene *scene_src,
{
blender::Map<Strip *, Strip *> strip_map;
seqbase_dupli_recursive(scene_src, scene_dst, nseqbase, seqbase, dupe_flag, flag, strip_map);
seqbase_dupli_recursive(
bmain, scene_src, scene_dst, nseqbase, seqbase, dupe_flag, flag, strip_map);
/* Fix effect, modifier, and connected strip links. */
LISTBASE_FOREACH (Strip *, strip, nseqbase) {

View File

@@ -454,7 +454,8 @@ Strip *edit_strip_split(Main *bmain,
/* Duplicate ListBase. */
ListBase right_strips = {nullptr, nullptr};
seqbase_duplicate_recursive(scene, scene, &right_strips, &left_strips, StripDuplicate::All, 0);
seqbase_duplicate_recursive(
bmain, scene, scene, &right_strips, &left_strips, StripDuplicate::All, 0);
Strip *left_strip = static_cast<Strip *>(left_strips.first);
Strip *right_strip = static_cast<Strip *>(right_strips.first);