846 lines
24 KiB
C
846 lines
24 KiB
C
/*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Contributor(s): Blender Foundation, Dalai Felinto
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*/
|
|
|
|
/** \file blender/editors/space_outliner/outliner_collections.c
|
|
* \ingroup spoutliner
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_listbase.h"
|
|
|
|
#include "DNA_collection_types.h"
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_collection.h"
|
|
#include "BKE_layer.h"
|
|
#include "BKE_library.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_report.h"
|
|
|
|
#include "DEG_depsgraph.h"
|
|
#include "DEG_depsgraph_build.h"
|
|
|
|
#include "ED_object.h"
|
|
#include "ED_outliner.h"
|
|
#include "ED_screen.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
#include "RNA_enum_types.h"
|
|
|
|
#include "UI_resources.h"
|
|
|
|
#include "outliner_intern.h" /* own include */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
bool outliner_is_collection_tree_element(const TreeElement *te)
|
|
{
|
|
TreeStoreElem *tselem = TREESTORE(te);
|
|
|
|
if (!tselem) {
|
|
return false;
|
|
}
|
|
|
|
if (ELEM(tselem->type, TSE_LAYER_COLLECTION, TSE_SCENE_COLLECTION_BASE, TSE_VIEW_COLLECTION_BASE)) {
|
|
return true;
|
|
}
|
|
else if (tselem->type == 0 && te->idcode == ID_GR) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Collection *outliner_collection_from_tree_element(const TreeElement *te)
|
|
{
|
|
TreeStoreElem *tselem = TREESTORE(te);
|
|
|
|
if (!tselem) {
|
|
return NULL;
|
|
}
|
|
|
|
if (tselem->type == TSE_LAYER_COLLECTION) {
|
|
LayerCollection *lc = te->directdata;
|
|
return lc->collection;
|
|
}
|
|
else if (ELEM(tselem->type, TSE_SCENE_COLLECTION_BASE, TSE_VIEW_COLLECTION_BASE)) {
|
|
Scene *scene = (Scene *)tselem->id;
|
|
return BKE_collection_master(scene);
|
|
}
|
|
else if (tselem->type == 0 && te->idcode == ID_GR) {
|
|
return (Collection *)tselem->id;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
TreeTraversalAction outliner_find_selected_collections(TreeElement *te, void *customdata)
|
|
{
|
|
struct IDsSelectedData *data = customdata;
|
|
TreeStoreElem *tselem = TREESTORE(te);
|
|
|
|
if (outliner_is_collection_tree_element(te)) {
|
|
BLI_addtail(&data->selected_array, BLI_genericNodeN(te));
|
|
return TRAVERSE_CONTINUE;
|
|
}
|
|
|
|
if (tselem->type || (tselem->id && GS(tselem->id->name) != ID_GR)) {
|
|
return TRAVERSE_SKIP_CHILDS;
|
|
}
|
|
|
|
return TRAVERSE_CONTINUE;
|
|
}
|
|
|
|
TreeTraversalAction outliner_find_selected_objects(TreeElement *te, void *customdata)
|
|
{
|
|
struct IDsSelectedData *data = customdata;
|
|
TreeStoreElem *tselem = TREESTORE(te);
|
|
|
|
if (outliner_is_collection_tree_element(te)) {
|
|
return TRAVERSE_CONTINUE;
|
|
}
|
|
|
|
if (tselem->type || (tselem->id == NULL) || (GS(tselem->id->name) != ID_OB)) {
|
|
return TRAVERSE_SKIP_CHILDS;
|
|
}
|
|
|
|
BLI_addtail(&data->selected_array, BLI_genericNodeN(te));
|
|
|
|
return TRAVERSE_CONTINUE;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Poll functions. */
|
|
|
|
bool ED_outliner_collections_editor_poll(bContext *C)
|
|
{
|
|
SpaceOops *so = CTX_wm_space_outliner(C);
|
|
return (so != NULL) && ELEM(so->outlinevis, SO_VIEW_LAYER, SO_SCENES, SO_LIBRARIES);
|
|
}
|
|
|
|
|
|
/********************************* New Collection ****************************/
|
|
|
|
struct CollectionNewData
|
|
{
|
|
bool error;
|
|
Collection *collection;
|
|
};
|
|
|
|
static TreeTraversalAction collection_find_selected_to_add(TreeElement *te, void *customdata)
|
|
{
|
|
struct CollectionNewData *data = customdata;
|
|
Collection *collection = outliner_collection_from_tree_element(te);
|
|
|
|
if (!collection) {
|
|
return TRAVERSE_SKIP_CHILDS;
|
|
}
|
|
|
|
if (data->collection != NULL) {
|
|
data->error = true;
|
|
return TRAVERSE_BREAK;
|
|
}
|
|
|
|
data->collection = collection;
|
|
return TRAVERSE_CONTINUE;
|
|
}
|
|
|
|
static int collection_new_exec(bContext *C, wmOperator *op)
|
|
{
|
|
SpaceOops *soops = CTX_wm_space_outliner(C);
|
|
ARegion *ar = CTX_wm_region(C);
|
|
Main *bmain = CTX_data_main(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
|
|
struct CollectionNewData data = {
|
|
.error = false,
|
|
.collection = NULL,
|
|
};
|
|
|
|
if (RNA_boolean_get(op->ptr, "nested")) {
|
|
outliner_build_tree(bmain, scene, view_layer, soops, ar);
|
|
|
|
outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, collection_find_selected_to_add, &data);
|
|
|
|
if (data.error) {
|
|
BKE_report(op->reports, RPT_ERROR, "More than one collection is selected");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
|
|
if (data.collection == NULL) {
|
|
data.collection = BKE_collection_master(scene);
|
|
}
|
|
|
|
BKE_collection_add(
|
|
bmain,
|
|
data.collection,
|
|
NULL);
|
|
|
|
DEG_id_tag_update(&data.collection->id, ID_RECALC_COPY_ON_WRITE);
|
|
DEG_relations_tag_update(bmain);
|
|
|
|
outliner_cleanup_tree(soops);
|
|
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 inside selected collection";
|
|
|
|
/* api callbacks */
|
|
ot->exec = collection_new_exec;
|
|
ot->poll = ED_outliner_collections_editor_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* properties */
|
|
PropertyRNA *prop = RNA_def_boolean(ot->srna, "nested", true, "Nested", "Add as child of selected collection");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
}
|
|
|
|
/**************************** Delete Collection ******************************/
|
|
|
|
struct CollectionEditData {
|
|
Scene *scene;
|
|
SpaceOops *soops;
|
|
GSet *collections_to_edit;
|
|
};
|
|
|
|
static TreeTraversalAction collection_find_data_to_edit(TreeElement *te, void *customdata)
|
|
{
|
|
struct CollectionEditData *data = customdata;
|
|
Collection *collection = outliner_collection_from_tree_element(te);
|
|
|
|
if (!collection) {
|
|
return TRAVERSE_SKIP_CHILDS;
|
|
}
|
|
|
|
if (collection == BKE_collection_master(data->scene)) {
|
|
/* skip - showing warning/error message might be misleading
|
|
* when deleting multiple collections, so just do nothing */
|
|
}
|
|
else {
|
|
/* Delete, duplicate and link don't edit children, those will come along
|
|
* with the parents. */
|
|
BLI_gset_add(data->collections_to_edit, collection);
|
|
return TRAVERSE_SKIP_CHILDS;
|
|
}
|
|
|
|
return TRAVERSE_CONTINUE;
|
|
}
|
|
|
|
static int collection_delete_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
SpaceOops *soops = CTX_wm_space_outliner(C);
|
|
struct CollectionEditData data = {.scene = scene, .soops = soops,};
|
|
bool hierarchy = RNA_boolean_get(op->ptr, "hierarchy");
|
|
|
|
data.collections_to_edit = BLI_gset_ptr_new(__func__);
|
|
|
|
/* 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_edit, &data);
|
|
|
|
/* Effectively delete 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);
|
|
|
|
/* Test in case collection got deleted as part of another one. */
|
|
if (BLI_findindex(&bmain->collection, collection) != -1) {
|
|
BKE_collection_delete(bmain, collection, hierarchy);
|
|
}
|
|
}
|
|
|
|
BLI_gset_free(data.collections_to_edit, NULL);
|
|
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
|
|
DEG_relations_tag_update(bmain);
|
|
|
|
WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void OUTLINER_OT_collection_delete(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Delete Collection";
|
|
ot->idname = "OUTLINER_OT_collection_delete";
|
|
ot->description = "Delete selected collections";
|
|
|
|
/* api callbacks */
|
|
ot->exec = collection_delete_exec;
|
|
ot->poll = ED_outliner_collections_editor_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* properties */
|
|
PropertyRNA *prop = RNA_def_boolean(ot->srna, "hierarchy", false, "Hierarchy", "Delete child objects and collections");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
}
|
|
|
|
/****************************** Select Objects *******************************/
|
|
|
|
struct CollectionObjectsSelectData {
|
|
bool error;
|
|
LayerCollection *layer_collection;
|
|
};
|
|
|
|
static TreeTraversalAction outliner_find_first_selected_layer_collection(TreeElement *te, void *customdata)
|
|
{
|
|
struct CollectionObjectsSelectData *data = customdata;
|
|
TreeStoreElem *tselem = TREESTORE(te);
|
|
|
|
switch (tselem->type) {
|
|
case TSE_LAYER_COLLECTION:
|
|
data->layer_collection = te->directdata;
|
|
return TRAVERSE_BREAK;
|
|
case TSE_R_LAYER:
|
|
case TSE_SCENE_COLLECTION_BASE:
|
|
case TSE_VIEW_COLLECTION_BASE:
|
|
return TRAVERSE_CONTINUE;
|
|
default:
|
|
return TRAVERSE_SKIP_CHILDS;
|
|
}
|
|
}
|
|
|
|
static LayerCollection *outliner_active_layer_collection(bContext *C)
|
|
{
|
|
SpaceOops *soops = CTX_wm_space_outliner(C);
|
|
|
|
struct CollectionObjectsSelectData data = {
|
|
.layer_collection = NULL,
|
|
};
|
|
|
|
outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_first_selected_layer_collection, &data);
|
|
return data.layer_collection;
|
|
}
|
|
|
|
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);
|
|
bool deselect = STREQ(op->idname, "OUTLINER_OT_collection_objects_deselect");
|
|
|
|
if (layer_collection == NULL) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
BKE_layer_collection_objects_select(view_layer, layer_collection, deselect);
|
|
|
|
Scene *scene = CTX_data_scene(C);
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
|
|
WM_main_add_notifier(NC_SCENE | ND_OB_SELECT, scene);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void OUTLINER_OT_collection_objects_select(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Select Objects";
|
|
ot->idname = "OUTLINER_OT_collection_objects_select";
|
|
ot->description = "Select objects in collection";
|
|
|
|
/* api callbacks */
|
|
ot->exec = collection_objects_select_exec;
|
|
ot->poll = ED_outliner_collections_editor_poll;
|
|
|
|
/* flags */
|
|
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 = ED_outliner_collections_editor_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/************************** Duplicate Collection *****************************/
|
|
|
|
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:
|
|
data->te = te;
|
|
return TRAVERSE_BREAK;
|
|
case TSE_R_LAYER:
|
|
case TSE_SCENE_COLLECTION_BASE:
|
|
case TSE_VIEW_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)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
SpaceOops *soops = CTX_wm_space_outliner(C);
|
|
TreeElement *te = outliner_active_collection(C);
|
|
|
|
/* Can happen when calling from a key binding. */
|
|
if (te == NULL) {
|
|
BKE_report(op->reports, RPT_ERROR, "No active collection");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
switch (soops->outlinevis) {
|
|
case SO_SCENES:
|
|
case SO_VIEW_LAYER:
|
|
case SO_LIBRARIES:
|
|
BKE_collection_copy(bmain, parent, collection);
|
|
break;
|
|
}
|
|
|
|
DEG_relations_tag_update(bmain);
|
|
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 selected collections";
|
|
|
|
/* api callbacks */
|
|
ot->exec = collection_duplicate_exec;
|
|
ot->poll = ED_outliner_collections_editor_poll;
|
|
|
|
/* flags */
|
|
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_id_tag_update(&active_collection->id, ID_RECALC_COPY_ON_WRITE);
|
|
DEG_relations_tag_update(bmain);
|
|
|
|
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 = ED_outliner_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, 0);
|
|
ob->dup_group = collection;
|
|
ob->transflag |= OB_DUPLICOLLECTION;
|
|
id_lib_extern(&collection->id);
|
|
}
|
|
|
|
BLI_gset_free(data.collections_to_edit, NULL);
|
|
|
|
DEG_relations_tag_update(bmain);
|
|
|
|
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 = ED_outliner_collections_editor_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/************************** Exclude Collection ******************************/
|
|
|
|
static TreeTraversalAction layer_collection_find_data_to_edit(TreeElement *te, void *customdata)
|
|
{
|
|
struct CollectionEditData *data = customdata;
|
|
TreeStoreElem *tselem = TREESTORE(te);
|
|
|
|
if (!(tselem && tselem->type == TSE_LAYER_COLLECTION)) {
|
|
return TRAVERSE_CONTINUE;
|
|
}
|
|
|
|
LayerCollection *lc = te->directdata;
|
|
|
|
if (lc->collection->flag & COLLECTION_IS_MASTER) {
|
|
/* skip - showing warning/error message might be misleading
|
|
* when deleting multiple collections, so just do nothing */
|
|
}
|
|
else {
|
|
/* Delete, duplicate and link don't edit children, those will come along
|
|
* with the parents. */
|
|
BLI_gset_add(data->collections_to_edit, lc);
|
|
}
|
|
|
|
return TRAVERSE_CONTINUE;
|
|
}
|
|
|
|
static bool collections_view_layer_poll(bContext *C, bool clear, int flag)
|
|
{
|
|
/* Poll function so the right click menu show current state of selected collections. */
|
|
SpaceOops *soops = CTX_wm_space_outliner(C);
|
|
if (!(soops && soops->outlinevis == SO_VIEW_LAYER)) {
|
|
return false;
|
|
}
|
|
|
|
Scene *scene = CTX_data_scene(C);
|
|
struct CollectionEditData data = {.scene = scene, .soops = soops,};
|
|
data.collections_to_edit = BLI_gset_ptr_new(__func__);
|
|
bool result = false;
|
|
|
|
outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, layer_collection_find_data_to_edit, &data);
|
|
|
|
GSetIterator collections_to_edit_iter;
|
|
GSET_ITER(collections_to_edit_iter, data.collections_to_edit) {
|
|
LayerCollection *lc = BLI_gsetIterator_getKey(&collections_to_edit_iter);
|
|
|
|
if (clear && (lc->flag & flag)) {
|
|
result = true;
|
|
}
|
|
else if (!clear && !(lc->flag & flag)) {
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
BLI_gset_free(data.collections_to_edit, NULL);
|
|
return result;
|
|
}
|
|
|
|
static bool collections_exclude_set_poll(bContext *C)
|
|
{
|
|
return collections_view_layer_poll(C, false, LAYER_COLLECTION_EXCLUDE);
|
|
}
|
|
|
|
static bool collections_exclude_clear_poll(bContext *C)
|
|
{
|
|
return collections_view_layer_poll(C, true, LAYER_COLLECTION_EXCLUDE);
|
|
}
|
|
|
|
static bool collections_holdout_set_poll(bContext *C)
|
|
{
|
|
return collections_view_layer_poll(C, false, LAYER_COLLECTION_HOLDOUT);
|
|
}
|
|
|
|
static bool collections_holdout_clear_poll(bContext *C)
|
|
{
|
|
return collections_view_layer_poll(C, true, LAYER_COLLECTION_HOLDOUT);
|
|
}
|
|
|
|
static bool collections_indirect_only_set_poll(bContext *C)
|
|
{
|
|
return collections_view_layer_poll(C, false, LAYER_COLLECTION_INDIRECT_ONLY);
|
|
}
|
|
|
|
static bool collections_indirect_only_clear_poll(bContext *C)
|
|
{
|
|
return collections_view_layer_poll(C, true, LAYER_COLLECTION_INDIRECT_ONLY);
|
|
}
|
|
|
|
static void layer_collection_flag_recursive_set(LayerCollection *lc, int flag)
|
|
{
|
|
for (LayerCollection *nlc = lc->layer_collections.first; nlc; nlc = nlc->next) {
|
|
if (lc->flag & flag) {
|
|
nlc->flag |= flag;
|
|
}
|
|
else {
|
|
nlc->flag &= ~flag;
|
|
}
|
|
|
|
layer_collection_flag_recursive_set(nlc, flag);
|
|
}
|
|
}
|
|
|
|
static int collection_view_layer_exec(bContext *C, wmOperator *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,};
|
|
bool clear = strstr(op->idname, "clear") != NULL;
|
|
int flag = strstr(op->idname, "holdout") ? LAYER_COLLECTION_HOLDOUT :
|
|
strstr(op->idname, "indirect_only") ? LAYER_COLLECTION_INDIRECT_ONLY :
|
|
LAYER_COLLECTION_EXCLUDE;
|
|
|
|
data.collections_to_edit = BLI_gset_ptr_new(__func__);
|
|
|
|
outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, layer_collection_find_data_to_edit, &data);
|
|
|
|
GSetIterator collections_to_edit_iter;
|
|
GSET_ITER(collections_to_edit_iter, data.collections_to_edit) {
|
|
LayerCollection *lc = BLI_gsetIterator_getKey(&collections_to_edit_iter);
|
|
|
|
if (!(lc->collection->flag & COLLECTION_IS_MASTER)) {
|
|
if (clear) {
|
|
lc->flag &= ~flag;
|
|
}
|
|
else {
|
|
lc->flag |= flag;
|
|
}
|
|
|
|
layer_collection_flag_recursive_set(lc, flag);
|
|
}
|
|
}
|
|
|
|
BLI_gset_free(data.collections_to_edit, NULL);
|
|
|
|
BKE_layer_collection_sync(scene, view_layer);
|
|
DEG_relations_tag_update(bmain);
|
|
|
|
WM_main_add_notifier(NC_SCENE | ND_LAYER, NULL);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void OUTLINER_OT_collection_exclude_set(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Set Exclude";
|
|
ot->idname = "OUTLINER_OT_collection_exclude_set";
|
|
ot->description = "Exclude collection from the active view layer";
|
|
|
|
/* api callbacks */
|
|
ot->exec = collection_view_layer_exec;
|
|
ot->poll = collections_exclude_set_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
void OUTLINER_OT_collection_exclude_clear(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Clear Exclude";
|
|
ot->idname = "OUTLINER_OT_collection_exclude_clear";
|
|
ot->description = "Include collection in the active view layer";
|
|
|
|
/* api callbacks */
|
|
ot->exec = collection_view_layer_exec;
|
|
ot->poll = collections_exclude_clear_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
void OUTLINER_OT_collection_holdout_set(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Set Holdout";
|
|
ot->idname = "OUTLINER_OT_collection_holdout_set";
|
|
ot->description = "Mask collection in the active view layer";
|
|
|
|
/* api callbacks */
|
|
ot->exec = collection_view_layer_exec;
|
|
ot->poll = collections_holdout_set_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
void OUTLINER_OT_collection_holdout_clear(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Clear Holdout";
|
|
ot->idname = "OUTLINER_OT_collection_holdout_clear";
|
|
ot->description = "Clear masking of collection in the active view layer";
|
|
|
|
/* api callbacks */
|
|
ot->exec = collection_view_layer_exec;
|
|
ot->poll = collections_holdout_clear_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
void OUTLINER_OT_collection_indirect_only_set(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Set Indirect Only";
|
|
ot->idname = "OUTLINER_OT_collection_indirect_only_set";
|
|
ot->description = "Set collection to only contribute indirectly (through shadows and reflections) in the view layer";
|
|
|
|
/* api callbacks */
|
|
ot->exec = collection_view_layer_exec;
|
|
ot->poll = collections_indirect_only_set_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
void OUTLINER_OT_collection_indirect_only_clear(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Clear Indirect Only";
|
|
ot->idname = "OUTLINER_OT_collection_indirect_only_clear";
|
|
ot->description = "Clear collection contributing only indirectly in the view layer";
|
|
|
|
/* api callbacks */
|
|
ot->exec = collection_view_layer_exec;
|
|
ot->poll = collections_indirect_only_clear_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/**
|
|
* Populates the \param objects: ListBase with all the outliner selected objects
|
|
* We store it as (Object *)LinkData->data
|
|
* \param objects: expected to be empty
|
|
*/
|
|
void ED_outliner_selected_objects_get(const bContext *C, ListBase *objects)
|
|
{
|
|
SpaceOops *soops = CTX_wm_space_outliner(C);
|
|
struct IDsSelectedData data = {{NULL}};
|
|
outliner_tree_traverse(soops, &soops->tree, 0, TSE_SELECTED, outliner_find_selected_objects, &data);
|
|
LISTBASE_FOREACH (LinkData *, link, &data.selected_array) {
|
|
TreeElement *ten_selected = (TreeElement *)link->data;
|
|
Object *ob = (Object *)TREESTORE(ten_selected)->id;
|
|
BLI_addtail(objects, BLI_genericNodeN(ob));
|
|
}
|
|
BLI_freelistN(&data.selected_array);
|
|
}
|