Collections: Operator to duplicate a collection
authorDalai Felinto <dfelinto@gmail.com>
Thu, 1 Feb 2018 23:11:59 +0000 (21:11 -0200)
committerDalai Felinto <dfelinto@gmail.com>
Fri, 2 Feb 2018 14:25:05 +0000 (12:25 -0200)
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().

release/scripts/startup/bl_ui/space_outliner.py
source/blender/blenkernel/BKE_collection.h
source/blender/blenkernel/BKE_layer.h
source/blender/blenkernel/intern/collection.c
source/blender/blenkernel/intern/layer.c
source/blender/editors/space_outliner/outliner_collections.c
source/blender/editors/space_outliner/outliner_intern.h
source/blender/editors/space_outliner/outliner_ops.c
source/blender/editors/space_outliner/outliner_tools.c
source/blender/makesrna/intern/rna_layer.c

index 28644428cf8f8fdffbde5a0d695d3d8ec4aec885..122fe84763da53320feeecbe6ea6ca5c29db1a5f 100644 (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')
index 48b4ff881aee408cf779087eaac472af475da6bc..618e60550b7e7318e9437aac6c697d02a4869cfb 100644 (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);
index f098a7e7468dc4ecc618a4a4230f0f7039c8c8b9..633cb2c60448569991a741ba82546281ffa2775d 100644 (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);
index ca77969ccaae554db68b266883bbed8335b2bf59..729be02bfc70b876006d1ba52bc99fa834f37305 100644 (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)) {
index 4333faea863170f97b08b473100fedac55e35026..9807c6e4f10a093b91e2c19a909cd000386ad78a 100644 (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,27 +354,94 @@ 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;
+               layer_collection_sync_flags(layer_collection_dst, layer_collection_src);
+               layer_collection_dst = layer_collection_dst->next;
+               layer_collection_src = layer_collection_src->next;
+       }
+}
 
-               if (layer_collection_dst->properties != NULL) {
-                       IDP_FreeProperty(layer_collection_dst->properties);
-                       MEM_SAFE_FREE(layer_collection_dst->properties);
+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;
                }
-
-               if (layer_collection_src->properties != NULL) {
-                       layer_collection_dst->properties = IDP_CopyProperty(layer_collection_src->properties);
+               else {
+                       if (layer_collection_sync_if_match(
+                               &layer_collection->layer_collections,
+                               scene_collection_dst,
+                               scene_collection_src))
+                       {
+                               return true;
+                       }
                }
+       }
+       return false;
+}
 
-               layer_collections_sync_flags(&layer_collection_dst->layer_collections,
-                                            &layer_collection_src->layer_collections);
-
-               layer_collection_dst = layer_collection_dst->next;
-               layer_collection_src = layer_collection_src->next;
+/**
+ * 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);
+               }
        }
 }
 
@@ -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--;
index 394a7a80b39ddb73ac95f7825cf0969d541fc6da..89f1240f86d9fd080cb622b04505dbeef01bd9b1 100644 (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;
+}
index 06d06d66326b7e99cac80ae0a1d95be823060d5b..ab10e5a30366587b1ec546a9736f530d346b9f2a 100644 (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);
 
index b5b4fbbf039d165077d743a624afc796cc911794..eb42fe47cbfaedb00e670c8e7038c2b4e823d6d0 100644 (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);
index c4d032e34eed23376f166a4b2884476be3594f03..3ee0d011bbb5e8688cac7a9d3b428952273457b1 100644 (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"},
index 68c4a15e06b8470834b559d391fa281607b68229..6930aa115c750185bb2eebeb772a6dd221b06634 100644 (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);