Collections: Operator to duplicate a collection

When duplicating a layer collection directly linked to the view layer we copy
the collection and link it.

For all the not directly linked layer collectionns, we try to sync the layer
collection flags, overrides, ...

Also we make sure the new collection is right after the original collection.

We also expose this in RNA, via collection.duplicate().
This commit is contained in:
Dalai Felinto 2018-02-01 21:11:59 -02:00
parent a4d2b102f3
commit c7c070c2ec
Notes: blender-bot 2023-02-14 06:13:31 +01:00
Referenced by issue #53985, New duplication system falls short of a mechanism to hide to be duplicated objects
10 changed files with 348 additions and 19 deletions

View File

@ -195,6 +195,7 @@ class OUTLINER_MT_context_scene_collection(Menu):
layout = self.layout
layout.operator("outliner.collection_nested_new", text="New Collection", icon='NEW')
layout.operator("outliner.collection_duplicate", text="Duplicate Collection")
layout.operator("outliner.collection_delete_selected", text="Delete Collections", icon='X')
layout.separator()
layout.operator("outliner.collection_objects_add", text="Add Selected", icon='ZOOMIN')

View File

@ -49,6 +49,7 @@ struct SceneCollection *BKE_collection_add(
struct ID *owner_id, struct SceneCollection *sc_parent, const int type, const char *name);
bool BKE_collection_remove(struct ID *owner_id, struct SceneCollection *sc);
void BKE_collection_copy_data(struct SceneCollection *sc_dst, struct SceneCollection *sc_src, const int flag);
struct SceneCollection *BKE_collection_duplicate(struct ID *owner_id, struct SceneCollection *scene_collection);
struct SceneCollection *BKE_collection_master(const struct ID *owner_id);
void BKE_collection_rename(const struct Scene *scene, struct SceneCollection *sc, const char *name);
void BKE_collection_master_free(struct ID *owner_id, const bool do_id_user);

View File

@ -78,11 +78,18 @@ struct Base *BKE_view_layer_base_find(struct ViewLayer *view_layer, struct Objec
void BKE_view_layer_base_deselect_all(struct ViewLayer *view_layer);
void BKE_view_layer_base_select(struct ViewLayer *view_layer, struct Base *selbase);
void BKE_layer_collection_sync_flags(
struct ID *owner_id,
struct SceneCollection *scene_collection_dst,
struct SceneCollection *scene_collection_src);
void BKE_view_layer_copy_data(
struct ViewLayer *view_layer_dst, struct ViewLayer *view_layer_src,
struct SceneCollection *mc_dst, struct SceneCollection *mc_src,
const int flag);
struct LayerCollection *BKE_layer_collection_duplicate(struct ID *owner_id, struct LayerCollection *layer_collection);
void BKE_layer_collection_free(struct ViewLayer *view_layer, struct LayerCollection *lc);
struct LayerCollection *BKE_layer_collection_get_active(struct ViewLayer *view_layer);

View File

@ -68,10 +68,9 @@ static SceneCollection *collection_master_from_id(const ID *owner_id)
}
/**
* Add a collection to a collection ListBase and syncronize all render layers
* The ListBase is NULL when the collection is to be added to the master collection
* Add a new collection, but don't handle syncing with layer collections
*/
SceneCollection *BKE_collection_add(ID *owner_id, SceneCollection *sc_parent, const int type, const char *name_custom)
static SceneCollection *collection_add(ID *owner_id, SceneCollection *sc_parent, const int type, const char *name_custom)
{
SceneCollection *sc_master = collection_master_from_id(owner_id);
SceneCollection *sc = MEM_callocN(sizeof(SceneCollection), "New Collection");
@ -99,8 +98,6 @@ SceneCollection *BKE_collection_add(ID *owner_id, SceneCollection *sc_parent, co
BLI_addtail(&sc_parent->scene_collections, sc);
BKE_collection_rename((Scene *)owner_id, sc, name);
BKE_layer_sync_new_scene_collection(owner_id, sc_parent, sc);
if (name != name_custom) {
MEM_freeN((char *)name);
}
@ -108,6 +105,17 @@ SceneCollection *BKE_collection_add(ID *owner_id, SceneCollection *sc_parent, co
return sc;
}
/**
* Add a collection to a collection ListBase and syncronize all render layers
* The ListBase is NULL when the collection is to be added to the master collection
*/
SceneCollection *BKE_collection_add(ID *owner_id, SceneCollection *sc_parent, const int type, const char *name_custom)
{
SceneCollection *scene_collection = collection_add(owner_id, sc_parent, type, name_custom);
BKE_layer_sync_new_scene_collection(owner_id, sc_parent, scene_collection);
return scene_collection;
}
/**
* Free the collection items recursively
*/
@ -270,6 +278,43 @@ void BKE_collection_copy_data(SceneCollection *sc_dst, SceneCollection *sc_src,
}
}
/**
* Makes a shallow copy of a SceneCollection
*
* Add a new collection in the same level as the old one, copy any nested collections
* but link the objects to the new collection (as oppose to copy them).
*/
SceneCollection *BKE_collection_duplicate(ID *owner_id, SceneCollection *scene_collection)
{
SceneCollection *scene_collection_master = BKE_collection_master(owner_id);
SceneCollection *scene_collection_parent = find_collection_parent(scene_collection, scene_collection_master);
/* It's not allowed to copy the master collection. */
if (scene_collection_master == scene_collection) {
return NULL;
}
SceneCollection *scene_collection_new = collection_add(
owner_id,
scene_collection_parent,
scene_collection->type,
scene_collection->name);
if (scene_collection_new != scene_collection->next) {
BLI_remlink(&scene_collection_parent->scene_collections, scene_collection_new);
BLI_insertlinkafter(&scene_collection_parent->scene_collections, scene_collection, scene_collection_new);
}
BKE_collection_copy_data(scene_collection_new, scene_collection, 0);
BKE_layer_sync_new_scene_collection(owner_id, scene_collection_parent, scene_collection_new);
/* Make sure every linked instance of the new collection has the same values (flags, overrides, ...) as the
* corresponding original collection. */
BKE_layer_collection_sync_flags(owner_id, scene_collection_new, scene_collection);
return scene_collection_new;
}
static SceneCollection *master_collection_from_id(const ID *owner_id)
{
switch (GS(owner_id->name)) {

View File

@ -61,6 +61,7 @@
/* prototype */
struct EngineSettingsCB_Type;
static void layer_collections_sync_flags(ListBase *layer_collections_dst, const ListBase *layer_collections_src);
static void layer_collection_free(ViewLayer *view_layer, LayerCollection *lc);
static void layer_collection_objects_populate(ViewLayer *view_layer, LayerCollection *lc, ListBase *objects);
static LayerCollection *layer_collection_add(ViewLayer *view_layer, LayerCollection *parent, SceneCollection *sc);
@ -353,30 +354,97 @@ static SceneCollection *scene_collection_from_new_tree(
return NULL;
}
static void layer_collection_sync_flags(
LayerCollection *layer_collection_dst,
const LayerCollection *layer_collection_src)
{
layer_collection_dst->flag = layer_collection_src->flag;
if (layer_collection_dst->properties != NULL) {
IDP_FreeProperty(layer_collection_dst->properties);
MEM_SAFE_FREE(layer_collection_dst->properties);
}
if (layer_collection_src->properties != NULL) {
layer_collection_dst->properties = IDP_CopyProperty(layer_collection_src->properties);
}
layer_collections_sync_flags(&layer_collection_dst->layer_collections,
&layer_collection_src->layer_collections);
}
static void layer_collections_sync_flags(ListBase *layer_collections_dst, const ListBase *layer_collections_src)
{
LayerCollection *layer_collection_dst = (LayerCollection *)layer_collections_dst->first;
const LayerCollection *layer_collection_src = (const LayerCollection *)layer_collections_src->first;
while (layer_collection_dst != NULL) {
layer_collection_dst->flag = layer_collection_src->flag;
if (layer_collection_dst->properties != NULL) {
IDP_FreeProperty(layer_collection_dst->properties);
MEM_SAFE_FREE(layer_collection_dst->properties);
}
if (layer_collection_src->properties != NULL) {
layer_collection_dst->properties = IDP_CopyProperty(layer_collection_src->properties);
}
layer_collections_sync_flags(&layer_collection_dst->layer_collections,
&layer_collection_src->layer_collections);
layer_collection_sync_flags(layer_collection_dst, layer_collection_src);
layer_collection_dst = layer_collection_dst->next;
layer_collection_src = layer_collection_src->next;
}
}
static bool layer_collection_sync_if_match(
ListBase *lb,
const SceneCollection *scene_collection_dst,
const SceneCollection *scene_collection_src)
{
for (LayerCollection *layer_collection = lb->first;
layer_collection;
layer_collection = layer_collection->next)
{
if (layer_collection->scene_collection == scene_collection_src) {
LayerCollection *layer_collection_dst =
BLI_findptr(
lb,
scene_collection_dst,
offsetof(LayerCollection, scene_collection));
if (layer_collection_dst != NULL) {
layer_collection_sync_flags(layer_collection_dst, layer_collection);
}
return true;
}
else {
if (layer_collection_sync_if_match(
&layer_collection->layer_collections,
scene_collection_dst,
scene_collection_src))
{
return true;
}
}
}
return false;
}
/**
* Sync sibling collections across all view layers
*
* Make sure every linked instance of \a scene_collection_dst has the same values
* (flags, overrides, ...) as the corresponding scene_collection_src.
*
* \note expect scene_collection_dst to be scene_collection_src->next, and it also
* expects both collections to have the same ammount of sub-collections.
*/
void BKE_layer_collection_sync_flags(
ID *owner_id,
SceneCollection *scene_collection_dst,
SceneCollection *scene_collection_src)
{
for (ViewLayer *view_layer = BKE_view_layer_first_from_id(owner_id); view_layer; view_layer = view_layer->next) {
for (LayerCollection *layer_collection = view_layer->layer_collections.first;
layer_collection;
layer_collection = layer_collection->next)
{
layer_collection_sync_if_match(
&layer_collection->layer_collections,
scene_collection_dst,
scene_collection_src);
}
}
}
/* recreate the LayerCollection tree */
static void layer_collections_recreate(
ViewLayer *view_layer_dst, ListBase *lb_src, SceneCollection *mc_dst, SceneCollection *mc_src)
@ -438,6 +506,78 @@ void BKE_view_layer_copy_data(
}
}
/**
* Find and return the ListBase of LayerCollection that has \a lc_child as one of its directly
* nested LayerCollection.
*
* \param lb_parent Initial ListBase of LayerCollection to look into recursively
* usually the view layer's collection list
*/
static ListBase *find_layer_collection_parent_list_base(ListBase *lb_parent, const LayerCollection *lc_child)
{
for (LayerCollection *lc_nested = lb_parent->first; lc_nested; lc_nested = lc_nested->next) {
if (lc_nested == lc_child) {
return lb_parent;
}
ListBase *found = find_layer_collection_parent_list_base(&lc_nested->layer_collections, lc_child);
if (found != NULL) {
return found;
}
}
return NULL;
}
/**
* Makes a shallow copy of a LayerCollection
*
* Add a new collection in the same level as the old one (linking if necessary),
* and copy all the collection data across them.
*/
struct LayerCollection *BKE_layer_collection_duplicate(struct ID *owner_id, struct LayerCollection *layer_collection)
{
SceneCollection *scene_collection, *scene_collection_new;
scene_collection = layer_collection->scene_collection;
scene_collection_new = BKE_collection_duplicate(owner_id, scene_collection);
LayerCollection *layer_collection_new = NULL;
/* If the original layer_collection was directly linked to the view layer
we need to link the new scene collection here as well. */
for (ViewLayer *view_layer = BKE_view_layer_first_from_id(owner_id); view_layer; view_layer = view_layer->next) {
if (BLI_findindex(&view_layer->layer_collections, layer_collection) != -1) {
layer_collection_new = BKE_collection_link(view_layer, scene_collection_new);
layer_collection_sync_flags(layer_collection_new, layer_collection);
if (layer_collection_new != layer_collection->next) {
BLI_remlink(&view_layer->layer_collections, layer_collection_new);
BLI_insertlinkafter(&view_layer->layer_collections, layer_collection, layer_collection_new);
}
break;
}
}
/* Otherwise just try to find the corresponding layer collection to return it back. */
if (layer_collection_new == NULL) {
for (ViewLayer *view_layer = BKE_view_layer_first_from_id(owner_id); view_layer; view_layer = view_layer->next) {
ListBase *layer_collections_parent;
layer_collections_parent = find_layer_collection_parent_list_base(
&view_layer->layer_collections,
layer_collection);
if (layer_collections_parent != NULL) {
layer_collection_new = BLI_findptr(
layer_collections_parent,
scene_collection_new,
offsetof(LayerCollection, scene_collection));
break;
}
}
}
return layer_collection_new;
}
static void view_layer_object_base_unref(ViewLayer *view_layer, Base *base)
{
base->refcount--;

View File

@ -91,6 +91,12 @@ static int view_layer_editor_poll(bContext *C)
return (so != NULL) && (so->outlinevis == SO_VIEW_LAYER);
}
static int outliner_either_collection_editor_poll(bContext *C)
{
SpaceOops *so = CTX_wm_space_outliner(C);
return (so != NULL) && (ELEM(so->outlinevis, SO_VIEW_LAYER, SO_COLLECTIONS));
}
/* -------------------------------------------------------------------- */
/* collection manager operators */
@ -819,3 +825,77 @@ void OUTLINER_OT_collection_objects_select(wmOperatorType *ot)
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
struct CollectionDuplicateData {
TreeElement *te;
};
static TreeTraversalAction outliner_find_first_selected_collection(TreeElement *te, void *customdata)
{
struct CollectionDuplicateData *data = customdata;
TreeStoreElem *tselem = TREESTORE(te);
switch (tselem->type) {
case TSE_LAYER_COLLECTION:
case TSE_SCENE_COLLECTION:
data->te = te;
return TRAVERSE_BREAK;
case TSE_LAYER_COLLECTION_BASE:
default:
return TRAVERSE_CONTINUE;
}
}
static TreeElement *outliner_active_collection(bContext *C)
{
SpaceOops *soops = CTX_wm_space_outliner(C);
struct CollectionDuplicateData data = {
.te = NULL,
};
outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_first_selected_collection, &data);
return data.te;
}
static int collection_duplicate_exec(bContext *C, wmOperator *op)
{
SpaceOops *soops = CTX_wm_space_outliner(C);
TreeElement *te = outliner_active_collection(C);
BLI_assert(te != NULL);
if (BKE_collection_master(TREESTORE(te)->id) == outliner_scene_collection_from_tree_element(te)) {
BKE_report(op->reports, RPT_ERROR, "You can't duplicate the master collection");
return OPERATOR_CANCELLED;
}
switch (soops->outlinevis) {
case SO_COLLECTIONS:
BKE_collection_duplicate(TREESTORE(te)->id, (SceneCollection *)te->directdata);
break;
case SO_VIEW_LAYER:
case SO_GROUPS:
BKE_layer_collection_duplicate(TREESTORE(te)->id, (LayerCollection *)te->directdata);
break;
}
DEG_relations_tag_update(CTX_data_main(C));
WM_main_add_notifier(NC_SCENE | ND_LAYER, CTX_data_scene(C));
return OPERATOR_FINISHED;
}
void OUTLINER_OT_collection_duplicate(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Duplicate Collection";
ot->idname = "OUTLINER_OT_collection_duplicate";
ot->description = "Duplicate collection";
/* api callbacks */
ot->exec = collection_duplicate_exec;
ot->poll = outliner_either_collection_editor_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

View File

@ -344,6 +344,7 @@ void OUTLINER_OT_collection_toggle(struct wmOperatorType *ot);
void OUTLINER_OT_collection_link(struct wmOperatorType *ot);
void OUTLINER_OT_collection_unlink(struct wmOperatorType *ot);
void OUTLINER_OT_collection_new(struct wmOperatorType *ot);
void OUTLINER_OT_collection_duplicate(struct wmOperatorType *ot);
void OUTLINER_OT_collection_objects_remove(struct wmOperatorType *ot);
void OUTLINER_OT_collection_objects_select(struct wmOperatorType *ot);

View File

@ -474,6 +474,7 @@ void outliner_operatortypes(void)
WM_operatortype_append(OUTLINER_OT_collection_link);
WM_operatortype_append(OUTLINER_OT_collection_unlink);
WM_operatortype_append(OUTLINER_OT_collection_new);
WM_operatortype_append(OUTLINER_OT_collection_duplicate);
WM_operatortype_append(OUTLINER_OT_collection_nested_new);
WM_operatortype_append(OUTLINER_OT_collection_delete_selected);

View File

@ -677,6 +677,7 @@ typedef enum eOutliner_PropCollectionOps {
OL_COLLECTION_OP_OBJECTS_REMOVE,
OL_COLLECTION_OP_OBJECTS_SELECT,
OL_COLLECTION_OP_COLLECTION_NEW,
OL_COLLECTION_OP_COLLECTION_COPY,
OL_COLLECTION_OP_COLLECTION_DEL,
OL_COLLECTION_OP_COLLECTION_UNLINK,
OL_COLLECTION_OP_GROUP_CREATE,
@ -875,6 +876,11 @@ static void collection_cb(int event, TreeElement *te, TreeStoreElem *UNUSED(tsel
}
WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene);
}
else if (event == OL_COLLECTION_OP_COLLECTION_COPY) {
BKE_layer_collection_duplicate(id, lc);
DEG_relations_tag_update(CTX_data_main(C));
WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene);
}
else if (event == OL_COLLECTION_OP_COLLECTION_UNLINK) {
ViewLayer *view_layer = CTX_data_view_layer(C);
@ -1851,6 +1857,7 @@ static EnumPropertyItem prop_collection_op_types[] = {
{OL_COLLECTION_OP_OBJECTS_REMOVE, "OBJECTS_REMOVE", ICON_X, "Remove Selected", "Remove selected objects from collection"},
{OL_COLLECTION_OP_OBJECTS_SELECT, "OBJECTS_SELECT", ICON_RESTRICT_SELECT_OFF, "Select Objects", "Selected collection objects"},
{OL_COLLECTION_OP_COLLECTION_NEW, "COLLECTION_NEW", ICON_NEW, "New Collection", "Add a new nested collection"},
{OL_COLLECTION_OP_COLLECTION_COPY, "COLLECTION_DUPLI", ICON_NONE, "Duplicate Collection", "Duplicate the collection"},
{OL_COLLECTION_OP_COLLECTION_UNLINK, "COLLECTION_UNLINK", ICON_UNLINKED, "Unlink", "Unlink collection"},
{OL_COLLECTION_OP_COLLECTION_DEL, "COLLECTION_DEL", ICON_X, "Delete Collection", "Delete the collection"},
{OL_COLLECTION_OP_GROUP_CREATE, "GROUP_CREATE", ICON_GROUP, "Create Group", "Turn the collection into a group collection"},

View File

@ -141,6 +141,23 @@ static int rna_SceneCollection_move_into(ID *id, SceneCollection *sc_src, Main *
return 1;
}
static SceneCollection *rna_SceneCollection_duplicate(
ID *id, SceneCollection *scene_collection, Main *bmain, bContext *C, ReportList *reports)
{
if (scene_collection == BKE_collection_master(id)) {
BKE_report(reports, RPT_ERROR, "The master collection can't be duplicated");
return NULL;
}
SceneCollection *scene_collection_new = BKE_collection_duplicate(id, scene_collection);
DEG_relations_tag_update(bmain);
/* Don't use id here, since the layer collection may come from a group. */
WM_event_add_notifier(C, NC_SCENE | ND_LAYER, CTX_data_scene(C));
return scene_collection_new;
}
static SceneCollection *rna_SceneCollection_new(
ID *id, SceneCollection *sc_parent, Main *bmain, const char *name)
{
@ -717,6 +734,23 @@ static Group *rna_LayerCollection_create_group(
return group;
}
static LayerCollection *rna_LayerCollection_duplicate(
ID *id, LayerCollection *layer_collection, Main *bmain, bContext *C, ReportList *reports)
{
if (layer_collection->scene_collection == BKE_collection_master(id)) {
BKE_report(reports, RPT_ERROR, "The master collection can't be duplicated");
return NULL;
}
LayerCollection *layer_collection_new = BKE_layer_collection_duplicate(id, layer_collection);
DEG_relations_tag_update(bmain);
/* Don't use id here, since the layer collection may come from a group. */
WM_event_add_notifier(C, NC_SCENE | ND_LAYER, CTX_data_scene(C));
return layer_collection_new;
}
static int rna_LayerCollections_active_collection_index_get(PointerRNA *ptr)
{
ViewLayer *view_layer = (ViewLayer *)ptr->data;
@ -1084,6 +1118,12 @@ static void rna_def_scene_collection(BlenderRNA *brna)
parm = RNA_def_pointer(func, "sc_dst", "SceneCollection", "Collection", "Collection to insert into");
parm = RNA_def_boolean(func, "result", false, "Result", "Whether the operation succeded");
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "duplicate", "rna_SceneCollection_duplicate");
RNA_def_function_ui_description(func, "Create a copy of the collection");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
parm = RNA_def_pointer(func, "result", "SceneCollection", "", "Newly created collection");
RNA_def_function_return(func, parm);
}
static void rna_def_layer_collection_override(BlenderRNA *brna)
@ -1998,6 +2038,12 @@ static void rna_def_layer_collection(BlenderRNA *brna)
parm = RNA_def_pointer(func, "result", "Group", "", "Newly created Group");
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "duplicate", "rna_LayerCollection_duplicate");
RNA_def_function_ui_description(func, "Create a copy of the collection");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
parm = RNA_def_pointer(func, "result", "LayerCollection", "", "Newly created collection");
RNA_def_function_return(func, parm);
/* Flags */
prop = RNA_def_property(srna, "selectable", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", COLLECTION_SELECTABLE);