Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/space_outliner/outliner_collections.c
| Show All 18 Lines | |||||
| * | * | ||||
| * ***** END GPL LICENSE BLOCK ***** | * ***** END GPL LICENSE BLOCK ***** | ||||
| */ | */ | ||||
| /** \file blender/editors/space_outliner/outliner_collections.c | /** \file blender/editors/space_outliner/outliner_collections.c | ||||
| * \ingroup spoutliner | * \ingroup spoutliner | ||||
| */ | */ | ||||
| #include <string.h> | |||||
| #include "BLI_utildefines.h" | #include "BLI_utildefines.h" | ||||
| #include "BLI_listbase.h" | #include "BLI_listbase.h" | ||||
| #include "DNA_group_types.h" | |||||
| #include "DNA_object_types.h" | |||||
| #include "BKE_context.h" | #include "BKE_context.h" | ||||
| #include "BKE_collection.h" | #include "BKE_collection.h" | ||||
| #include "BKE_layer.h" | #include "BKE_layer.h" | ||||
| #include "BKE_main.h" | #include "BKE_main.h" | ||||
| #include "BKE_report.h" | #include "BKE_report.h" | ||||
| #include "DEG_depsgraph.h" | #include "DEG_depsgraph.h" | ||||
| #include "DEG_depsgraph_build.h" | #include "DEG_depsgraph_build.h" | ||||
| #include "DNA_group_types.h" | #include "ED_object.h" | ||||
| #include "DNA_object_types.h" | |||||
| #include "ED_screen.h" | #include "ED_screen.h" | ||||
| #include "WM_api.h" | #include "WM_api.h" | ||||
| #include "WM_types.h" | #include "WM_types.h" | ||||
| #include "RNA_access.h" | #include "RNA_access.h" | ||||
| #include "RNA_define.h" | #include "RNA_define.h" | ||||
| #include "RNA_enum_types.h" | #include "RNA_enum_types.h" | ||||
| #include "UI_resources.h" | #include "UI_resources.h" | ||||
| #include "outliner_intern.h" /* own include */ | #include "outliner_intern.h" /* own include */ | ||||
| /* Prototypes. */ | |||||
| static int collection_delete_exec(struct bContext *C, struct wmOperator *op); | |||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| static LayerCollection *outliner_collection_active(bContext *C) | bool outliner_is_collection_tree_element(const TreeElement *te) | ||||
| { | |||||
| return CTX_data_layer_collection(C); | |||||
| } | |||||
| SceneCollection *outliner_scene_collection_from_tree_element(TreeElement *te) | |||||
| { | { | ||||
| TreeStoreElem *tselem = TREESTORE(te); | TreeStoreElem *tselem = TREESTORE(te); | ||||
| if (tselem->type == TSE_SCENE_COLLECTION) { | if (!tselem) { | ||||
| return te->directdata; | return false; | ||||
| } | |||||
| else if (tselem->type == TSE_LAYER_COLLECTION) { | |||||
| LayerCollection *lc = te->directdata; | |||||
| return lc->scene_collection; | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| /* -------------------------------------------------------------------- */ | |||||
| /* Poll functions. */ | |||||
| static int collections_editor_poll(bContext *C) | |||||
| { | |||||
| SpaceOops *so = CTX_wm_space_outliner(C); | |||||
| return (so != NULL) && (so->outlinevis == SO_COLLECTIONS); | |||||
| } | |||||
| static int outliner_objects_collection_poll(bContext *C) | |||||
| { | |||||
| SpaceOops *so = CTX_wm_space_outliner(C); | |||||
| if (so == NULL) { | |||||
| return 0; | |||||
| } | |||||
| /* Groups don't support filtering. */ | |||||
| if ((so->outlinevis != SO_GROUPS) && (so->filter & SO_FILTER_NO_COLLECTION)) { | |||||
| return 0; | |||||
| } | |||||
| return ELEM(so->outlinevis, SO_COLLECTIONS, SO_GROUPS); | |||||
| } | |||||
| /* -------------------------------------------------------------------- */ | |||||
| /* collection manager operators */ | |||||
| /** | |||||
| * Recursively get the collection for a given index | |||||
| */ | |||||
| static SceneCollection *scene_collection_from_index(ListBase *lb, const int number, int *i) | |||||
| { | |||||
| for (SceneCollection *sc = lb->first; sc; sc = sc->next) { | |||||
| if (*i == number) { | |||||
| return sc; | |||||
| } | |||||
| (*i)++; | |||||
| SceneCollection *sc_nested = scene_collection_from_index(&sc->scene_collections, number, i); | |||||
| if (sc_nested) { | |||||
| return sc_nested; | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| typedef struct TreeElementFindData { | |||||
| SceneCollection *collection; | |||||
| TreeElement *r_result_te; | |||||
| } TreeElementFindData; | |||||
| static TreeTraversalAction tree_element_find_by_scene_collection_cb(TreeElement *te, void *customdata) | |||||
| { | |||||
| TreeElementFindData *data = customdata; | |||||
| const SceneCollection *current_element_sc = outliner_scene_collection_from_tree_element(te); | |||||
| if (current_element_sc == data->collection) { | |||||
| data->r_result_te = te; | |||||
| return TRAVERSE_BREAK; | |||||
| } | |||||
| return TRAVERSE_CONTINUE; | |||||
| } | |||||
| static TreeElement *outliner_tree_element_from_layer_collection_index( | |||||
| SpaceOops *soops, ViewLayer *view_layer, | |||||
| const int index) | |||||
| { | |||||
| LayerCollection *lc = BKE_layer_collection_from_index(view_layer, index); | |||||
| if (lc == NULL) { | |||||
| return NULL; | |||||
| } | |||||
| /* Find the tree element containing the LayerCollection's scene_collection. */ | |||||
| TreeElementFindData data = { | |||||
| .collection = lc->scene_collection, | |||||
| .r_result_te = NULL, | |||||
| }; | |||||
| outliner_tree_traverse(soops, &soops->tree, 0, 0, tree_element_find_by_scene_collection_cb, &data); | |||||
| return data.r_result_te; | |||||
| } | |||||
| static int collection_link_exec(bContext *C, wmOperator *op) | |||||
| { | |||||
| Scene *scene = CTX_data_scene(C); | |||||
| ViewLayer *view_layer = CTX_data_view_layer(C); | |||||
| SceneCollection *sc_master = BKE_collection_master(&scene->id); | |||||
| SceneCollection *sc; | |||||
| int scene_collection_index = RNA_enum_get(op->ptr, "scene_collection"); | |||||
| if (scene_collection_index == 0) { | |||||
| sc = sc_master; | |||||
| } | |||||
| else { | |||||
| int index = 1; | |||||
| sc = scene_collection_from_index(&sc_master->scene_collections, scene_collection_index, &index); | |||||
| BLI_assert(sc); | |||||
| } | |||||
| BKE_collection_link(view_layer, sc); | |||||
| DEG_relations_tag_update(CTX_data_main(C)); | |||||
| /* TODO(sergey): Use proper flag for tagging here. */ | |||||
| DEG_id_tag_update(&scene->id, 0); | |||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | } | ||||
| static int collection_link_invoke(bContext *C, wmOperator *op, const wmEvent *event) | if (ELEM(tselem->type, TSE_LAYER_COLLECTION, TSE_SCENE_COLLECTION_BASE)) { | ||||
| { | return true; | ||||
| Scene *scene = CTX_data_scene(C); | |||||
| SceneCollection *master_collection = BKE_collection_master(&scene->id); | |||||
| if (master_collection->scene_collections.first == NULL) { | |||||
| RNA_enum_set(op->ptr, "scene_collection", 0); | |||||
| return collection_link_exec(C, op); | |||||
| } | |||||
| else { | |||||
| return WM_enum_search_invoke(C, op, event); | |||||
| } | |||||
| } | |||||
| static void collection_scene_collection_itemf_recursive( | |||||
| EnumPropertyItem *tmp, EnumPropertyItem **item, int *totitem, int *value, SceneCollection *sc) | |||||
| { | |||||
| tmp->value = *value; | |||||
| tmp->icon = ICON_COLLAPSEMENU; | |||||
| tmp->identifier = sc->name; | |||||
| tmp->name = sc->name; | |||||
| RNA_enum_item_add(item, totitem, tmp); | |||||
| (*value)++; | |||||
| for (SceneCollection *ncs = sc->scene_collections.first; ncs; ncs = ncs->next) { | |||||
| collection_scene_collection_itemf_recursive(tmp, item, totitem, value, ncs); | |||||
| } | } | ||||
| else if (tselem->type == 0 && te->idcode == ID_GR) { | |||||
| return true; | |||||
| } | } | ||||
| static const EnumPropertyItem *collection_scene_collection_itemf( | return false; | ||||
| bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) | |||||
| { | |||||
| EnumPropertyItem tmp = {0, "", 0, "", ""}; | |||||
| EnumPropertyItem *item = NULL; | |||||
| int value = 0, totitem = 0; | |||||
| Scene *scene = CTX_data_scene(C); | |||||
| SceneCollection *sc = BKE_collection_master(&scene->id); | |||||
| collection_scene_collection_itemf_recursive(&tmp, &item, &totitem, &value, sc); | |||||
| RNA_enum_item_end(&item, &totitem); | |||||
| *r_free = true; | |||||
| return item; | |||||
| } | |||||
| void OUTLINER_OT_collection_link(wmOperatorType *ot) | |||||
| { | |||||
| PropertyRNA *prop; | |||||
| /* identifiers */ | |||||
| ot->name = "Link Collection"; | |||||
| ot->idname = "OUTLINER_OT_collection_link"; | |||||
| ot->description = "Link a new collection to the active layer"; | |||||
| /* api callbacks */ | |||||
| ot->exec = collection_link_exec; | |||||
| ot->invoke = collection_link_invoke; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| prop = RNA_def_enum(ot->srna, "scene_collection", DummyRNA_NULL_items, 0, "Scene Collection", ""); | |||||
| RNA_def_enum_funcs(prop, collection_scene_collection_itemf); | |||||
| RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE); | |||||
| ot->prop = prop; | |||||
| } | } | ||||
| /** | Collection *outliner_collection_from_tree_element(const TreeElement *te) | ||||
| * Returns true if selected element is a collection directly | |||||
| * linked to the active ViewLayer (not a nested collection) | |||||
| */ | |||||
| static int collection_unlink_poll(bContext *C) | |||||
| { | { | ||||
| if (collections_editor_poll(C) == 0) { | TreeStoreElem *tselem = TREESTORE(te); | ||||
| return 0; | |||||
| } | |||||
| LayerCollection *lc = outliner_collection_active(C); | |||||
| if (lc == NULL) { | |||||
| return 0; | |||||
| } | |||||
| ViewLayer *view_layer = CTX_data_view_layer(C); | if (!tselem) { | ||||
| return BLI_findindex(&view_layer->layer_collections, lc) != -1 ? 1 : 0; | return false; | ||||
| } | } | ||||
| static int collection_unlink_exec(bContext *C, wmOperator *op) | if (tselem->type == TSE_LAYER_COLLECTION) { | ||||
| { | LayerCollection *lc = te->directdata; | ||||
| LayerCollection *lc = outliner_collection_active(C); | return lc->collection; | ||||
| SpaceOops *soops = CTX_wm_space_outliner(C); | |||||
| if (lc == NULL) { | |||||
| BKE_report(op->reports, RPT_ERROR, "Active element is not a collection"); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | } | ||||
| else if (ELEM(tselem->type, TSE_SCENE_COLLECTION_BASE)) { | |||||
| ViewLayer *view_layer = CTX_data_view_layer(C); | Scene *scene = (Scene*)tselem->id; | ||||
| BKE_collection_unlink(view_layer, lc); | return BKE_collection_master(scene); | ||||
| if (soops) { | |||||
| outliner_cleanup_tree(soops); | |||||
| } | } | ||||
| else if (tselem->type == 0 && te->idcode == ID_GR) { | |||||
| DEG_relations_tag_update(CTX_data_main(C)); | return (Collection*)tselem->id; | ||||
| /* TODO(sergey): Use proper flag for tagging here. */ | |||||
| DEG_id_tag_update(&CTX_data_scene(C)->id, 0); | |||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | } | ||||
| void OUTLINER_OT_collection_unlink(wmOperatorType *ot) | return NULL; | ||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "Unlink Collection"; | |||||
| ot->idname = "OUTLINER_OT_collection_unlink"; | |||||
| ot->description = "Unlink collection from the active layer"; | |||||
| /* api callbacks */ | |||||
| ot->exec = collection_unlink_exec; | |||||
| ot->poll = collection_unlink_poll; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| } | } | ||||
| /**********************************************************************************/ | /* -------------------------------------------------------------------- */ | ||||
| /* Add new collection. */ | /* Poll functions. */ | ||||
| static int collection_new_exec(bContext *C, wmOperator *UNUSED(op)) | static int collections_editor_poll(bContext *C) | ||||
| { | { | ||||
| Main *bmain = CTX_data_main(C); | SpaceOops *so = CTX_wm_space_outliner(C); | ||||
| Scene *scene = CTX_data_scene(C); | return (so != NULL) && ELEM(so->outlinevis, SO_COLLECTIONS, SO_VIEW_LAYER, SO_SCENES); | ||||
| ViewLayer *view_layer = CTX_data_view_layer(C); | |||||
| SceneCollection *scene_collection_parent = BKE_collection_master(&scene->id); | |||||
| SceneCollection *scene_collection = BKE_collection_add(&scene->id, scene_collection_parent, COLLECTION_TYPE_NONE, NULL); | |||||
| BKE_collection_link(view_layer, scene_collection); | |||||
| DEG_relations_tag_update(bmain); | |||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | } | ||||
| void OUTLINER_OT_collection_new(wmOperatorType *ot) | |||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "New Collection"; | |||||
| ot->idname = "OUTLINER_OT_collection_new"; | |||||
| ot->description = "Add a new collection to the scene"; | |||||
| /* api callbacks */ | /********************************* New Collection ****************************/ | ||||
| ot->exec = collection_new_exec; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| } | |||||
| /**********************************************************************************/ | |||||
| /* Add new nested collection. */ | |||||
| struct CollectionNewData | struct CollectionNewData | ||||
| { | { | ||||
| bool error; | bool error; | ||||
| SceneCollection *scene_collection; | Collection *collection; | ||||
| }; | }; | ||||
| static TreeTraversalAction collection_find_selected_to_add(TreeElement *te, void *customdata) | static TreeTraversalAction collection_find_selected_to_add(TreeElement *te, void *customdata) | ||||
| { | { | ||||
| struct CollectionNewData *data = customdata; | struct CollectionNewData *data = customdata; | ||||
| SceneCollection *scene_collection = outliner_scene_collection_from_tree_element(te); | Collection *collection = outliner_collection_from_tree_element(te); | ||||
| if (!scene_collection) { | if (!collection) { | ||||
| return TRAVERSE_SKIP_CHILDS; | return TRAVERSE_SKIP_CHILDS; | ||||
| } | } | ||||
| if (data->scene_collection != NULL) { | if (data->collection != NULL) { | ||||
| data->error = true; | data->error = true; | ||||
| return TRAVERSE_BREAK; | return TRAVERSE_BREAK; | ||||
| } | } | ||||
| data->scene_collection = scene_collection; | data->collection = collection; | ||||
| return TRAVERSE_CONTINUE; | return TRAVERSE_CONTINUE; | ||||
| } | } | ||||
| static int collection_nested_new_exec(bContext *C, wmOperator *op) | static int collection_new_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| SpaceOops *soops = CTX_wm_space_outliner(C); | SpaceOops *soops = CTX_wm_space_outliner(C); | ||||
| Main *bmain = CTX_data_main(C); | Main *bmain = CTX_data_main(C); | ||||
| Scene *scene = CTX_data_scene(C); | Scene *scene = CTX_data_scene(C); | ||||
| struct CollectionNewData data = { | struct CollectionNewData data = { | ||||
| .error = false, | .error = false, | ||||
| .scene_collection = NULL, | .collection = NULL, | ||||
| }; | }; | ||||
| outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_selected_to_add, &data); | outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_selected_to_add, &data); | ||||
| if (data.error) { | if (data.error) { | ||||
| BKE_report(op->reports, RPT_ERROR, "More than one collection is selected"); | BKE_report(op->reports, RPT_ERROR, "More than one collection is selected"); | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| BKE_collection_add( | if (!data.collection) { | ||||
| &scene->id, | if (!(soops->outlinevis == SO_COLLECTIONS && | ||||
| data.scene_collection, | ELEM(soops->filter_collection, SO_FILTER_COLLECTION_UNLINKED, SO_FILTER_COLLECTION_ALL))) | ||||
| COLLECTION_TYPE_NONE, | |||||
| NULL); | |||||
| outliner_cleanup_tree(soops); | |||||
| DEG_relations_tag_update(bmain); | |||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void OUTLINER_OT_collection_nested_new(wmOperatorType *ot) | |||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "New Nested Collection"; | |||||
| ot->idname = "OUTLINER_OT_collection_nested_new"; | |||||
| ot->description = "Add a new collection inside selected collection"; | |||||
| /* api callbacks */ | |||||
| ot->exec = collection_nested_new_exec; | |||||
| ot->poll = collections_editor_poll; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| } | |||||
| /**********************************************************************************/ | |||||
| /* Delete selected collection. */ | |||||
| void OUTLINER_OT_collection_delete_selected(wmOperatorType *ot) | |||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "Delete Selected Collections"; | |||||
| ot->idname = "OUTLINER_OT_collection_delete_selected"; | |||||
| ot->description = "Delete all the selected collections"; | |||||
| /* api callbacks */ | |||||
| ot->exec = collection_delete_exec; | |||||
| ot->poll = collections_editor_poll; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| } | |||||
| /**********************************************************************************/ | |||||
| /* Add new selected objects. */ | |||||
| struct SceneCollectionSelectedData { | |||||
| ListBase scene_collections_array; | |||||
| }; | |||||
| static TreeTraversalAction collection_find_selected_scene_collections(TreeElement *te, void *customdata) | |||||
| { | |||||
| struct SceneCollectionSelectedData *data = customdata; | |||||
| SceneCollection *scene_collection = outliner_scene_collection_from_tree_element(te); | |||||
| if (!scene_collection) { | |||||
| return TRAVERSE_SKIP_CHILDS; | |||||
| } | |||||
| BLI_addtail(&data->scene_collections_array, BLI_genericNodeN(scene_collection)); | |||||
| return TRAVERSE_CONTINUE; | |||||
| } | |||||
| static int collection_objects_add_exec(bContext *C, wmOperator *op) | |||||
| { | |||||
| SpaceOops *soops = CTX_wm_space_outliner(C); | |||||
| Main *bmain = CTX_data_main(C); | |||||
| Scene *scene = CTX_data_scene(C); | |||||
| struct SceneCollectionSelectedData data = { | |||||
| .scene_collections_array = {NULL, NULL}, | |||||
| }; | |||||
| outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_selected_scene_collections, &data); | |||||
| if (BLI_listbase_is_empty(&data.scene_collections_array)) { | |||||
| BKE_report(op->reports, RPT_ERROR, "No collection is selected"); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | |||||
| CTX_DATA_BEGIN (C, struct Object *, ob, selected_objects) | |||||
| { | |||||
| LISTBASE_FOREACH (LinkData *, link, &data.scene_collections_array) { | |||||
| SceneCollection *scene_collection = link->data; | |||||
| BKE_collection_object_add( | |||||
| &scene->id, | |||||
| scene_collection, | |||||
| ob); | |||||
| } | |||||
| } | |||||
| CTX_DATA_END; | |||||
| BLI_freelistN(&data.scene_collections_array); | |||||
| outliner_cleanup_tree(soops); | |||||
| DEG_relations_tag_update(bmain); | |||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void OUTLINER_OT_collection_objects_add(wmOperatorType *ot) | |||||
| { | { | ||||
| /* identifiers */ | data.collection = BKE_collection_master(scene); | ||||
| ot->name = "Add Objects"; | |||||
| ot->idname = "OUTLINER_OT_collection_objects_add"; | |||||
| ot->description = "Add selected objects to collection"; | |||||
| /* api callbacks */ | |||||
| ot->exec = collection_objects_add_exec; | |||||
| ot->poll = collections_editor_poll; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| } | } | ||||
| /**********************************************************************************/ | |||||
| /* Remove selected objects. */ | |||||
| static int collection_objects_remove_exec(bContext *C, wmOperator *op) | |||||
| { | |||||
| SpaceOops *soops = CTX_wm_space_outliner(C); | |||||
| Main *bmain = CTX_data_main(C); | |||||
| Scene *scene = CTX_data_scene(C); | |||||
| struct SceneCollectionSelectedData data = { | |||||
| .scene_collections_array = {NULL, NULL}, | |||||
| }; | |||||
| outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_selected_scene_collections, &data); | |||||
| if (BLI_listbase_is_empty(&data.scene_collections_array)) { | |||||
| BKE_report(op->reports, RPT_ERROR, "No collection is selected"); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | } | ||||
| CTX_DATA_BEGIN (C, struct Object *, ob, selected_objects) | BKE_collection_add( | ||||
| { | |||||
| LISTBASE_FOREACH (LinkData *, link, &data.scene_collections_array) { | |||||
| SceneCollection *scene_collection = link->data; | |||||
| BKE_collection_object_remove( | |||||
| bmain, | bmain, | ||||
| &scene->id, | data.collection, | ||||
| scene_collection, | NULL); | ||||
| ob, | |||||
| true); | |||||
| } | |||||
| } | |||||
| CTX_DATA_END; | |||||
| BLI_freelistN(&data.scene_collections_array); | |||||
| outliner_cleanup_tree(soops); | outliner_cleanup_tree(soops); | ||||
| DEG_relations_tag_update(bmain); | DEG_relations_tag_update(bmain); | ||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| void OUTLINER_OT_collection_objects_remove(wmOperatorType *ot) | void OUTLINER_OT_collection_new(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Remove Objects"; | ot->name = "New Collection"; | ||||
| ot->idname = "OUTLINER_OT_collection_objects_remove"; | ot->idname = "OUTLINER_OT_collection_new"; | ||||
| ot->description = "Remove selected objects from collection"; | ot->description = "Add a new collection inside selected collection"; | ||||
| /* api callbacks */ | /* api callbacks */ | ||||
| ot->exec = collection_objects_remove_exec; | ot->exec = collection_new_exec; | ||||
| ot->poll = collections_editor_poll; | ot->poll = collections_editor_poll; | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ||||
| } | } | ||||
| static TreeElement *outliner_collection_parent_element_get(TreeElement *te) | /**************************** Delete Collection ******************************/ | ||||
| { | |||||
| TreeElement *te_parent = te; | |||||
| while ((te_parent = te_parent->parent)) { | |||||
| if (outliner_scene_collection_from_tree_element(te->parent)) { | |||||
| return te_parent; | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| static int object_collection_remove_exec(bContext *C, wmOperator *UNUSED(op)) | |||||
| { | |||||
| SpaceOops *soops = CTX_wm_space_outliner(C); | |||||
| Main *bmain = CTX_data_main(C); | |||||
| struct ObjectsSelectedData data = { | |||||
| .objects_selected_array = {NULL, NULL}, | |||||
| }; | |||||
| outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_selected_objects, &data); | |||||
| LISTBASE_FOREACH (LinkData *, link, &data.objects_selected_array) { | |||||
| TreeElement *te = (TreeElement *)link->data; | |||||
| Object *ob = (Object *)TREESTORE(te)->id; | |||||
| SceneCollection *scene_collection = NULL; | |||||
| TreeElement *te_parent = outliner_collection_parent_element_get(te); | |||||
| if (te_parent != NULL) { | |||||
| scene_collection = outliner_scene_collection_from_tree_element(te_parent); | |||||
| ID *owner_id = TREESTORE(te_parent)->id; | |||||
| BKE_collection_object_remove(bmain, owner_id, scene_collection, ob, true); | |||||
| DEG_id_tag_update(owner_id, DEG_TAG_BASE_FLAGS_UPDATE); | |||||
| } | |||||
| } | |||||
| BLI_freelistN(&data.objects_selected_array); | |||||
| outliner_cleanup_tree(soops); | |||||
| DEG_relations_tag_update(bmain); | |||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | |||||
| WM_main_add_notifier(NC_SCENE | ND_OB_ACTIVE, NULL); | |||||
| WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void OUTLINER_OT_object_remove_from_collection(wmOperatorType *ot) | |||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "Remove Object from Collection"; | |||||
| ot->idname = "OUTLINER_OT_object_remove_from_collection"; | |||||
| ot->description = "Remove selected objects from their respective collection"; | |||||
| /* api callbacks */ | |||||
| ot->exec = object_collection_remove_exec; | |||||
| ot->poll = outliner_objects_collection_poll; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| } | |||||
| static int object_add_to_new_collection_exec(bContext *C, wmOperator *op) | |||||
| { | |||||
| int operator_result = OPERATOR_CANCELLED; | |||||
| SpaceOops *soops = CTX_wm_space_outliner(C); | |||||
| Main *bmain = CTX_data_main(C); | |||||
| SceneCollection *scene_collection_parent, *scene_collection_new; | |||||
| TreeElement *te_active, *te_parent; | |||||
| struct ObjectsSelectedData data = {{NULL}}, active = {{NULL}}; | |||||
| outliner_tree_traverse(soops, &soops->tree, 0, TSE_HIGHLIGHTED, outliner_find_selected_objects, &active); | |||||
| if (BLI_listbase_is_empty(&active.objects_selected_array)) { | |||||
| BKE_report(op->reports, RPT_ERROR, "No object is selected"); | |||||
| goto cleanup; | |||||
| } | |||||
| outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_selected_objects, &data); | |||||
| if (BLI_listbase_is_empty(&data.objects_selected_array)) { | |||||
| BKE_report(op->reports, RPT_ERROR, "No objects are selected"); | |||||
| goto cleanup; | |||||
| } | |||||
| /* Heuristic to get the "active" / "last object" */ | |||||
| te_active = ((LinkData *)active.objects_selected_array.first)->data; | |||||
| te_parent = outliner_collection_parent_element_get(te_active); | |||||
| if (te_parent == NULL) { | |||||
| BKE_reportf(op->reports, RPT_ERROR, "Couldn't find collection of \"%s\" object", te_active->name); | |||||
| goto cleanup; | |||||
| } | |||||
| ID *owner_id = TREESTORE(te_parent)->id; | |||||
| scene_collection_parent = outliner_scene_collection_from_tree_element(te_parent); | |||||
| scene_collection_new = BKE_collection_add(owner_id, scene_collection_parent, scene_collection_parent->type, NULL); | |||||
| LISTBASE_FOREACH (LinkData *, link, &data.objects_selected_array) { | |||||
| TreeElement *te = (TreeElement *)link->data; | |||||
| Object *ob = (Object *)TREESTORE(te)->id; | |||||
| BKE_collection_object_add(owner_id, scene_collection_new, ob); | |||||
| } | |||||
| outliner_cleanup_tree(soops); | |||||
| DEG_relations_tag_update(bmain); | |||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | |||||
| operator_result = OPERATOR_FINISHED; | |||||
| cleanup: | |||||
| BLI_freelistN(&active.objects_selected_array); | |||||
| BLI_freelistN(&data.objects_selected_array); | |||||
| return operator_result; | |||||
| } | |||||
| void OUTLINER_OT_object_add_to_new_collection(wmOperatorType *ot) | |||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "Add Objects to New Collection"; | |||||
| ot->idname = "OUTLINER_OT_object_add_to_new_collection"; | |||||
| ot->description = "Add objects to a new collection"; | |||||
| /* api callbacks */ | |||||
| ot->exec = object_add_to_new_collection_exec; | |||||
| ot->poll = outliner_objects_collection_poll; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| } | |||||
| struct CollectionDeleteData { | struct CollectionEditData { | ||||
| Scene *scene; | Scene *scene; | ||||
| SpaceOops *soops; | SpaceOops *soops; | ||||
| GSet *collections_to_delete; | GSet *collections_to_edit; | ||||
| }; | }; | ||||
| static TreeTraversalAction collection_find_data_to_delete(TreeElement *te, void *customdata) | static TreeTraversalAction collection_find_data_to_edit(TreeElement *te, void *customdata) | ||||
| { | { | ||||
| struct CollectionDeleteData *data = customdata; | struct CollectionEditData *data = customdata; | ||||
| SceneCollection *scene_collection = outliner_scene_collection_from_tree_element(te); | Collection *collection = outliner_collection_from_tree_element(te); | ||||
| if (!scene_collection) { | if (!collection) { | ||||
| return TRAVERSE_SKIP_CHILDS; | return TRAVERSE_SKIP_CHILDS; | ||||
| } | } | ||||
| if (scene_collection == BKE_collection_master(&data->scene->id)) { | if (collection == BKE_collection_master(data->scene)) { | ||||
| /* skip - showing warning/error message might be missleading | /* skip - showing warning/error message might be missleading | ||||
| * when deleting multiple collections, so just do nothing */ | * when deleting multiple collections, so just do nothing */ | ||||
| } | } | ||||
| else { | else { | ||||
| BLI_gset_add(data->collections_to_delete, scene_collection); | /* Delete, duplicate and link don't edit children, those will come along | ||||
| return TRAVERSE_SKIP_CHILDS; /* Childs will be gone anyway, no need to recurse deeper. */ | * with the parents. */ | ||||
| } | BLI_gset_add(data->collections_to_edit, collection); | ||||
| return TRAVERSE_CONTINUE; | |||||
| } | |||||
| static TreeTraversalAction collection_delete_elements_from_collection(TreeElement *te, void *customdata) | |||||
| { | |||||
| struct CollectionDeleteData *data = customdata; | |||||
| SceneCollection *scene_collection = outliner_scene_collection_from_tree_element(te); | |||||
| if (!scene_collection) { | |||||
| return TRAVERSE_SKIP_CHILDS; | |||||
| } | |||||
| const bool will_be_deleted = BLI_gset_haskey(data->collections_to_delete, scene_collection); | |||||
| if (will_be_deleted) { | |||||
| outliner_free_tree_element(te, te->parent ? &te->parent->subtree : &data->soops->tree); | |||||
| /* Childs are freed now, so don't recurse into them. */ | |||||
| return TRAVERSE_SKIP_CHILDS; | return TRAVERSE_SKIP_CHILDS; | ||||
| } | } | ||||
| return TRAVERSE_CONTINUE; | return TRAVERSE_CONTINUE; | ||||
| } | } | ||||
| static int collection_delete_exec(bContext *C, wmOperator *UNUSED(op)) | static int collection_delete_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| Main *bmain = CTX_data_main(C); | |||||
| Scene *scene = CTX_data_scene(C); | Scene *scene = CTX_data_scene(C); | ||||
| SpaceOops *soops = CTX_wm_space_outliner(C); | SpaceOops *soops = CTX_wm_space_outliner(C); | ||||
| struct CollectionDeleteData data = {.scene = scene, .soops = soops}; | struct CollectionEditData data = {.scene = scene, .soops = soops}; | ||||
| bool hierarchy = RNA_boolean_get(op->ptr, "hierarchy"); | |||||
| data.collections_to_delete = BLI_gset_ptr_new(__func__); | data.collections_to_edit = BLI_gset_ptr_new(__func__); | ||||
| /* We first walk over and find the SceneCollections we actually want to delete (ignoring duplicates). */ | /* We first walk over and find the Collections we actually want to delete (ignoring duplicates). */ | ||||
| outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_data_to_delete, &data); | outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_data_to_edit, &data); | ||||
| /* Now, delete all tree elements representing a collection that will be deleted. We'll look for a | |||||
| * new element to select in a few lines, so we can't wait until the tree is recreated on redraw. */ | |||||
| outliner_tree_traverse(soops, &soops->tree, 0, 0, collection_delete_elements_from_collection, &data); | |||||
| /* Effectively delete the collections. */ | /* Effectively delete the collections. */ | ||||
| GSetIterator collections_to_delete_iter; | GSetIterator collections_to_edit_iter; | ||||
| GSET_ITER(collections_to_delete_iter, data.collections_to_delete) { | GSET_ITER(collections_to_edit_iter, data.collections_to_edit) { | ||||
| SceneCollection *sc = BLI_gsetIterator_getKey(&collections_to_delete_iter); | /* TODO: what if collection was child and got deleted in the meantime? */ | ||||
| BKE_collection_remove(&data.scene->id, sc); | Collection *collection = BLI_gsetIterator_getKey(&collections_to_edit_iter); | ||||
| BKE_collection_delete(bmain, collection, hierarchy); | |||||
| } | } | ||||
| BLI_gset_free(data.collections_to_delete, NULL); | BLI_gset_free(data.collections_to_edit, NULL); | ||||
| TreeElement *select_te = outliner_tree_element_from_layer_collection_index(soops, CTX_data_view_layer(C), 0); | |||||
| if (select_te) { | |||||
| outliner_item_select(soops, select_te, false, false); | |||||
| } | |||||
| DEG_relations_tag_update(CTX_data_main(C)); | DEG_relations_tag_update(bmain); | ||||
| /* TODO(sergey): Use proper flag for tagging here. */ | /* TODO(sergey): Use proper flag for tagging here. */ | ||||
| DEG_id_tag_update(&scene->id, 0); | DEG_id_tag_update(&scene->id, 0); | ||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| void OUTLINER_OT_collections_delete(wmOperatorType *ot) | void OUTLINER_OT_collection_delete(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Delete"; | ot->name = "Delete Collection"; | ||||
| ot->idname = "OUTLINER_OT_collections_delete"; | ot->idname = "OUTLINER_OT_collection_delete"; | ||||
| ot->description = "Delete selected overrides or collections"; | ot->description = "Delete selected collections"; | ||||
| /* api callbacks */ | /* api callbacks */ | ||||
| ot->exec = collection_delete_exec; | ot->exec = collection_delete_exec; | ||||
| ot->poll = collections_editor_poll; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| } | |||||
| static int collection_select_exec(bContext *C, wmOperator *op) | |||||
| { | |||||
| ViewLayer *view_layer = CTX_data_view_layer(C); | |||||
| const int collection_index = RNA_int_get(op->ptr, "collection_index"); | |||||
| view_layer->active_collection = collection_index; | |||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void OUTLINER_OT_collection_select(wmOperatorType *ot) | |||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "Select"; | |||||
| ot->idname = "OUTLINER_OT_collection_select"; | |||||
| ot->description = "Change active collection or override"; | |||||
| /* api callbacks */ | |||||
| ot->exec = collection_select_exec; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| RNA_def_int(ot->srna, "collection_index", 0, 0, INT_MAX, "Index", | |||||
| "Index of collection to select", 0, INT_MAX); | |||||
| } | |||||
| #define ACTION_DISABLE 0 | |||||
| #define ACTION_ENABLE 1 | |||||
| #define ACTION_TOGGLE 2 | |||||
| static int collection_toggle_exec(bContext *C, wmOperator *op) | |||||
| { | |||||
| Main *bmain = CTX_data_main(C); | |||||
| Scene *scene = CTX_data_scene(C); | |||||
| int action = RNA_enum_get(op->ptr, "action"); | |||||
| LayerCollection *layer_collection = CTX_data_layer_collection(C); | |||||
| if (layer_collection->flag & COLLECTION_DISABLED) { | |||||
| if (ELEM(action, ACTION_TOGGLE, ACTION_ENABLE)) { | |||||
| layer_collection->flag &= ~COLLECTION_DISABLED; | |||||
| } | |||||
| else { /* ACTION_DISABLE */ | |||||
| BKE_reportf(op->reports, RPT_ERROR, "Layer collection %s already disabled", | |||||
| layer_collection->scene_collection->name); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | |||||
| } | |||||
| else { | |||||
| if (ELEM(action, ACTION_TOGGLE, ACTION_DISABLE)) { | |||||
| layer_collection->flag |= COLLECTION_DISABLED; | |||||
| } | |||||
| else { /* ACTION_ENABLE */ | |||||
| BKE_reportf(op->reports, RPT_ERROR, "Layer collection %s already enabled", | |||||
| layer_collection->scene_collection->name); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | |||||
| } | |||||
| DEG_relations_tag_update(bmain); | |||||
| /* TODO(sergey): Use proper flag for tagging here. */ | |||||
| DEG_id_tag_update(&scene->id, 0); | |||||
| WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); | |||||
| WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void OUTLINER_OT_collection_toggle(wmOperatorType *ot) | |||||
| { | |||||
| PropertyRNA *prop; | |||||
| static EnumPropertyItem actions_items[] = { | |||||
| {ACTION_DISABLE, "DISABLE", 0, "Disable", "Disable selected markers"}, | |||||
| {ACTION_ENABLE, "ENABLE", 0, "Enable", "Enable selected markers"}, | |||||
| {ACTION_TOGGLE, "TOGGLE", 0, "Toggle", "Toggle disabled flag for selected markers"}, | |||||
| {0, NULL, 0, NULL, NULL} | |||||
| }; | |||||
| /* identifiers */ | |||||
| ot->name = "Toggle Collection"; | |||||
| ot->idname = "OUTLINER_OT_collection_toggle"; | |||||
| ot->description = "Deselect collection objects"; | |||||
| /* api callbacks */ | |||||
| ot->exec = collection_toggle_exec; | |||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ||||
| /* properties */ | /* properties */ | ||||
| prop = RNA_def_int(ot->srna, "collection_index", -1, -1, INT_MAX, "Collection Index", "Index of collection to toggle", 0, INT_MAX); | RNA_def_boolean(ot->srna, "hierarchy", false, "Hierarchy", "Delete child objects and collections"); | ||||
| RNA_def_property_flag(prop, PROP_SKIP_SAVE); | |||||
| prop = RNA_def_enum(ot->srna, "action", actions_items, ACTION_TOGGLE, "Action", "Selection action to execute"); | |||||
| RNA_def_property_flag(prop, PROP_SKIP_SAVE); | |||||
| } | } | ||||
| #undef ACTION_TOGGLE | /****************************** Select Objects *******************************/ | ||||
| #undef ACTION_ENABLE | |||||
| #undef ACTION_DISABLE | |||||
| struct CollectionObjectsSelectData { | struct CollectionObjectsSelectData { | ||||
| bool error; | bool error; | ||||
| LayerCollection *layer_collection; | LayerCollection *layer_collection; | ||||
| }; | }; | ||||
| static TreeTraversalAction outliner_find_first_selected_layer_collection(TreeElement *te, void *customdata) | static TreeTraversalAction outliner_find_first_selected_layer_collection(TreeElement *te, void *customdata) | ||||
| { | { | ||||
| struct CollectionObjectsSelectData *data = customdata; | struct CollectionObjectsSelectData *data = customdata; | ||||
| TreeStoreElem *tselem = TREESTORE(te); | TreeStoreElem *tselem = TREESTORE(te); | ||||
| switch (tselem->type) { | switch (tselem->type) { | ||||
| case TSE_LAYER_COLLECTION: | case TSE_LAYER_COLLECTION: | ||||
| data->layer_collection = te->directdata; | data->layer_collection = te->directdata; | ||||
| return TRAVERSE_BREAK; | return TRAVERSE_BREAK; | ||||
| case TSE_LAYER_COLLECTION_BASE: | case TSE_R_LAYER: | ||||
| case TSE_SCENE_COLLECTION_BASE: | |||||
| return TRAVERSE_CONTINUE; | return TRAVERSE_CONTINUE; | ||||
| default: | default: | ||||
| return TRAVERSE_SKIP_CHILDS; | return TRAVERSE_SKIP_CHILDS; | ||||
| } | } | ||||
| } | } | ||||
| static LayerCollection *outliner_active_layer_collection(bContext *C) | static LayerCollection *outliner_active_layer_collection(bContext *C) | ||||
| { | { | ||||
| SpaceOops *soops = CTX_wm_space_outliner(C); | SpaceOops *soops = CTX_wm_space_outliner(C); | ||||
| struct CollectionObjectsSelectData data = { | struct CollectionObjectsSelectData data = { | ||||
| .layer_collection = NULL, | .layer_collection = NULL, | ||||
| }; | }; | ||||
| outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_first_selected_layer_collection, &data); | outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_first_selected_layer_collection, &data); | ||||
| return data.layer_collection; | return data.layer_collection; | ||||
| } | } | ||||
| static int collection_objects_select_exec(bContext *C, wmOperator *UNUSED(op)) | static int collection_objects_select_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| ViewLayer *view_layer = CTX_data_view_layer(C); | |||||
| LayerCollection *layer_collection = outliner_active_layer_collection(C); | LayerCollection *layer_collection = outliner_active_layer_collection(C); | ||||
| bool deselect = STREQ(op->idname, "OUTLINER_OT_collection_objects_deselect"); | |||||
| if (layer_collection == NULL) { | if (layer_collection == NULL) { | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| BKE_layer_collection_objects_select(layer_collection); | BKE_layer_collection_objects_select(view_layer, layer_collection, deselect); | ||||
| WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, CTX_data_scene(C)); | WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, CTX_data_scene(C)); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| void OUTLINER_OT_collection_objects_select(wmOperatorType *ot) | void OUTLINER_OT_collection_objects_select(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Select Objects"; | ot->name = "Select Objects"; | ||||
| ot->idname = "OUTLINER_OT_collection_objects_select"; | ot->idname = "OUTLINER_OT_collection_objects_select"; | ||||
| ot->description = "Select all the collection objects"; | ot->description = "Select objects in collection"; | ||||
| /* api callbacks */ | /* api callbacks */ | ||||
| ot->exec = collection_objects_select_exec; | ot->exec = collection_objects_select_exec; | ||||
| ot->poll = collections_editor_poll; | ot->poll = collections_editor_poll; | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ||||
| } | } | ||||
| void OUTLINER_OT_collection_objects_deselect(wmOperatorType *ot) | |||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "Deselect Objects"; | |||||
| ot->idname = "OUTLINER_OT_collection_objects_deselect"; | |||||
| ot->description = "Deselect objects in collection"; | |||||
| /* api callbacks */ | |||||
| ot->exec = collection_objects_select_exec; | |||||
| ot->poll = collections_editor_poll; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| } | |||||
| /************************** Duplicate Collection *****************************/ | |||||
| struct CollectionDuplicateData { | struct CollectionDuplicateData { | ||||
| TreeElement *te; | TreeElement *te; | ||||
| }; | }; | ||||
| static TreeTraversalAction outliner_find_first_selected_collection(TreeElement *te, void *customdata) | static TreeTraversalAction outliner_find_first_selected_collection(TreeElement *te, void *customdata) | ||||
| { | { | ||||
| struct CollectionDuplicateData *data = customdata; | struct CollectionDuplicateData *data = customdata; | ||||
| TreeStoreElem *tselem = TREESTORE(te); | TreeStoreElem *tselem = TREESTORE(te); | ||||
| switch (tselem->type) { | switch (tselem->type) { | ||||
| case TSE_LAYER_COLLECTION: | case TSE_LAYER_COLLECTION: | ||||
| case TSE_SCENE_COLLECTION: | |||||
| data->te = te; | data->te = te; | ||||
| return TRAVERSE_BREAK; | return TRAVERSE_BREAK; | ||||
| case TSE_LAYER_COLLECTION_BASE: | case TSE_R_LAYER: | ||||
| case TSE_SCENE_COLLECTION_BASE: | |||||
| default: | default: | ||||
| return TRAVERSE_CONTINUE; | return TRAVERSE_CONTINUE; | ||||
| } | } | ||||
| } | } | ||||
| static TreeElement *outliner_active_collection(bContext *C) | static TreeElement *outliner_active_collection(bContext *C) | ||||
| { | { | ||||
| SpaceOops *soops = CTX_wm_space_outliner(C); | SpaceOops *soops = CTX_wm_space_outliner(C); | ||||
| struct CollectionDuplicateData data = { | struct CollectionDuplicateData data = { | ||||
| .te = NULL, | .te = NULL, | ||||
| }; | }; | ||||
| outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_first_selected_collection, &data); | outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_first_selected_collection, &data); | ||||
| return data.te; | return data.te; | ||||
| } | } | ||||
| static int collection_duplicate_exec(bContext *C, wmOperator *op) | static int collection_duplicate_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| Main *bmain = CTX_data_main(C); | |||||
| SpaceOops *soops = CTX_wm_space_outliner(C); | SpaceOops *soops = CTX_wm_space_outliner(C); | ||||
| TreeElement *te = outliner_active_collection(C); | TreeElement *te = outliner_active_collection(C); | ||||
| BLI_assert(te != NULL); | 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"); | Collection *collection = outliner_collection_from_tree_element(te); | ||||
| Collection *parent = (te->parent) ? outliner_collection_from_tree_element(te->parent) : NULL; | |||||
| if (collection->flag & COLLECTION_IS_MASTER) { | |||||
| BKE_report(op->reports, RPT_ERROR, "Can't duplicate the master collection"); | |||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| switch (soops->outlinevis) { | switch (soops->outlinevis) { | ||||
| case SO_SCENES: | case SO_SCENES: | ||||
| BKE_collection_duplicate(TREESTORE(te)->id, (SceneCollection *)te->directdata); | case SO_VIEW_LAYER: | ||||
| break; | |||||
| case SO_COLLECTIONS: | case SO_COLLECTIONS: | ||||
| case SO_GROUPS: | BKE_collection_copy(bmain, parent, collection); | ||||
| BKE_layer_collection_duplicate(TREESTORE(te)->id, (LayerCollection *)te->directdata); | |||||
| break; | break; | ||||
| } | } | ||||
| DEG_relations_tag_update(CTX_data_main(C)); | DEG_relations_tag_update(bmain); | ||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, CTX_data_scene(C)); | WM_main_add_notifier(NC_SCENE | ND_LAYER, CTX_data_scene(C)); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| void OUTLINER_OT_collection_duplicate(wmOperatorType *ot) | void OUTLINER_OT_collection_duplicate(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Duplicate Collection"; | ot->name = "Duplicate Collection"; | ||||
| ot->idname = "OUTLINER_OT_collection_duplicate"; | ot->idname = "OUTLINER_OT_collection_duplicate"; | ||||
| ot->description = "Duplicate collection"; | ot->description = "Duplicate selected collections"; | ||||
| /* api callbacks */ | /* api callbacks */ | ||||
| ot->exec = collection_duplicate_exec; | ot->exec = collection_duplicate_exec; | ||||
| ot->poll = collections_editor_poll; | ot->poll = collections_editor_poll; | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ||||
| } | } | ||||
| /**************************** Link Collection ******************************/ | |||||
| static int collection_link_exec(bContext *C, wmOperator *UNUSED(op)) | |||||
| { | |||||
| Main *bmain = CTX_data_main(C); | |||||
| Scene *scene = CTX_data_scene(C); | |||||
| Collection *active_collection = CTX_data_layer_collection(C)->collection; | |||||
| SpaceOops *soops = CTX_wm_space_outliner(C); | |||||
| struct CollectionEditData data = {.scene = scene, .soops = soops}; | |||||
| data.collections_to_edit = BLI_gset_ptr_new(__func__); | |||||
| /* We first walk over and find the Collections we actually want to link (ignoring duplicates). */ | |||||
| outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_data_to_edit, &data); | |||||
| /* Effectively link the collections. */ | |||||
| GSetIterator collections_to_edit_iter; | |||||
| GSET_ITER(collections_to_edit_iter, data.collections_to_edit) { | |||||
| Collection *collection = BLI_gsetIterator_getKey(&collections_to_edit_iter); | |||||
| BKE_collection_child_add(bmain, active_collection, collection); | |||||
| id_fake_user_clear(&collection->id); | |||||
| } | |||||
| BLI_gset_free(data.collections_to_edit, NULL); | |||||
| DEG_relations_tag_update(bmain); | |||||
| /* TODO(sergey): Use proper flag for tagging here. */ | |||||
| DEG_id_tag_update(&scene->id, 0); | |||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void OUTLINER_OT_collection_link(wmOperatorType *ot) | |||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "Link Collection"; | |||||
| ot->idname = "OUTLINER_OT_collection_link"; | |||||
| ot->description = "Link selected collections to active scene"; | |||||
| /* api callbacks */ | |||||
| ot->exec = collection_link_exec; | |||||
| ot->poll = collections_editor_poll; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| } | |||||
| /************************** Instance Collection ******************************/ | |||||
| static int collection_instance_exec(bContext *C, wmOperator *UNUSED(op)) | |||||
| { | |||||
| Main *bmain = CTX_data_main(C); | |||||
| Scene *scene = CTX_data_scene(C); | |||||
| ViewLayer *view_layer = CTX_data_view_layer(C); | |||||
| SpaceOops *soops = CTX_wm_space_outliner(C); | |||||
| struct CollectionEditData data = {.scene = scene, .soops = soops}; | |||||
| data.collections_to_edit = BLI_gset_ptr_new(__func__); | |||||
| /* We first walk over and find the Collections we actually want to instance (ignoring duplicates). */ | |||||
| outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_data_to_edit, &data); | |||||
| /* Find an active collection to add to, that doesn't give dependency cycles. */ | |||||
| LayerCollection *active_lc = BKE_layer_collection_get_active(view_layer); | |||||
| GSetIterator collections_to_edit_iter; | |||||
| GSET_ITER(collections_to_edit_iter, data.collections_to_edit) { | |||||
| Collection *collection = BLI_gsetIterator_getKey(&collections_to_edit_iter); | |||||
| while (BKE_collection_find_cycle(active_lc->collection, collection)) { | |||||
| active_lc = BKE_layer_collection_activate_parent(view_layer, active_lc); | |||||
| } | |||||
| } | |||||
| /* Effectively instance the collections. */ | |||||
| GSET_ITER(collections_to_edit_iter, data.collections_to_edit) { | |||||
| Collection *collection = BLI_gsetIterator_getKey(&collections_to_edit_iter); | |||||
| Object *ob = ED_object_add_type(C, OB_EMPTY, collection->id.name + 2, scene->cursor.location, NULL, false, scene->layact); | |||||
| ob->dup_group = collection; | |||||
| ob->transflag |= OB_DUPLIGROUP; | |||||
| id_lib_extern(&collection->id); | |||||
| } | |||||
| BLI_gset_free(data.collections_to_edit, NULL); | |||||
| DEG_relations_tag_update(bmain); | |||||
| /* TODO(sergey): Use proper flag for tagging here. */ | |||||
| DEG_id_tag_update(&scene->id, 0); | |||||
| WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void OUTLINER_OT_collection_instance(wmOperatorType *ot) | |||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "Instance Collection"; | |||||
| ot->idname = "OUTLINER_OT_collection_instance"; | |||||
| ot->description = "Instance selected collections to active scene"; | |||||
| /* api callbacks */ | |||||
| ot->exec = collection_instance_exec; | |||||
| ot->poll = collections_editor_poll; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| } | |||||