diff options
author | Dalai Felinto <dfelinto@gmail.com> | 2017-12-01 17:23:05 +0300 |
---|---|---|
committer | Dalai Felinto <dfelinto@gmail.com> | 2017-12-01 19:15:54 +0300 |
commit | aeaf87bbeb011e9a571eefa12d81fa6fb2b8bd5b (patch) | |
tree | 895f28e2650a2436ebbf8f699d838d71c83d9d5b | |
parent | be9e469ead227aee8d4c29b98a125cf599c5c8bb (diff) |
Groups and collection: create group from collection
You could still create groups as before, with Ctl + G. This will create a group
with a single visible collection.
However you can also create a group from an existing collection. Just go to
the menu you get in the outliner when clicking in a collection and pick
"Create Group".
Remember to instance the group afterwards, or link it into a new scene or file.
The group and the collection are not kept in sync afterwards. You need to manually
edit the group for further changes.
-rw-r--r-- | source/blender/blenkernel/BKE_collection.h | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/collection.c | 80 | ||||
-rw-r--r-- | source/blender/editors/space_outliner/outliner_tools.c | 13 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_layer.c | 32 | ||||
-rw-r--r-- | tests/python/view_layer/CMakeLists.txt | 4 | ||||
-rw-r--r-- | tests/python/view_layer/test_group_a.py | 46 | ||||
-rw-r--r-- | tests/python/view_layer/test_group_b.py | 72 | ||||
-rw-r--r-- | tests/python/view_layer/test_group_c.py | 56 | ||||
-rw-r--r-- | tests/python/view_layer/test_group_d.py | 76 |
9 files changed, 381 insertions, 0 deletions
diff --git a/source/blender/blenkernel/BKE_collection.h b/source/blender/blenkernel/BKE_collection.h index c305a05ddd0..48b4ff881ae 100644 --- a/source/blender/blenkernel/BKE_collection.h +++ b/source/blender/blenkernel/BKE_collection.h @@ -58,6 +58,8 @@ bool BKE_collection_object_remove(struct Main *bmain, struct ID *owner_id, struc bool BKE_collections_object_remove(struct Main *bmain, struct ID *owner_id, struct Object *object, const bool free_us); void BKE_collection_object_move(struct ID *owner_id, struct SceneCollection *sc_dst, struct SceneCollection *sc_src, struct Object *ob); +struct Group *BKE_collection_group_create(struct Main *bmain, struct Scene *scene, struct LayerCollection *lc); + void BKE_collection_reinsert_after(const struct Scene *scene, struct SceneCollection *sc_reinsert, struct SceneCollection *sc_after); void BKE_collection_reinsert_into(struct SceneCollection *sc_reinsert, struct SceneCollection *sc_into); diff --git a/source/blender/blenkernel/intern/collection.c b/source/blender/blenkernel/intern/collection.c index aa5b9e7eb25..8d69563f5ff 100644 --- a/source/blender/blenkernel/intern/collection.c +++ b/source/blender/blenkernel/intern/collection.c @@ -34,6 +34,7 @@ #include "BLI_string_utils.h" #include "BKE_collection.h" +#include "BKE_group.h" #include "BKE_idprop.h" #include "BKE_layer.h" #include "BKE_library.h" @@ -48,6 +49,9 @@ #include "MEM_guardedalloc.h" +/* Prototypes. */ +static bool is_collection_in_tree(const struct SceneCollection *sc_reference, struct SceneCollection *sc_parent); + static SceneCollection *collection_master_from_id(const ID *owner_id) { switch (GS(owner_id->name)) { @@ -407,6 +411,82 @@ bool BKE_collections_object_remove(Main *bmain, ID *owner_id, Object *ob, const return removed; } +static void layer_collection_sync(LayerCollection *lc_dst, LayerCollection *lc_src) +{ + lc_dst->flag = lc_src->flag; + + /* Pending: sync overrides. */ + IDP_MergeGroup(lc_dst->properties, lc_src->properties, true); + + /* Continue recursively. */ + LayerCollection *lc_dst_nested, *lc_src_nested; + lc_src_nested = lc_src->layer_collections.first; + for (lc_dst_nested = lc_dst->layer_collections.first; + lc_dst_nested && lc_src_nested; + lc_dst_nested = lc_dst_nested->next, lc_src_nested = lc_src_nested->next) + { + layer_collection_sync(lc_dst_nested, lc_src_nested); + } +} + +/** + * Leave only the master collection in, remove everything else. + * @param group + */ +static void collection_group_cleanup(Group *group) +{ + /* Unlink all the LayerCollections. */ + while (group->view_layer->layer_collections.last != NULL) { + BKE_collection_unlink(group->view_layer, group->view_layer->layer_collections.last); + } + + /* Remove all the SceneCollections but the master. */ + collection_free(group->collection, false); +} + +/** + * Create a group from a collection + * + * Any ViewLayer that may have this the related SceneCollection linked is converted + * to a Group Collection. + */ +Group *BKE_collection_group_create(Main *bmain, Scene *scene, LayerCollection *lc_src) +{ + SceneCollection *sc_dst, *sc_src = lc_src->scene_collection; + LayerCollection *lc_dst; + + /* The master collection can't be converted. */ + if (sc_src == BKE_collection_master(&scene->id)) { + return NULL; + } + + /* If a sub-collection of sc_dst is directly linked into a ViewLayer we can't convert. */ + for (ViewLayer *view_layer = scene->view_layers.first; view_layer; view_layer = view_layer->next) { + for (LayerCollection *lc_child = view_layer->layer_collections.first; lc_child; lc_child = lc_child->next) { + if (is_collection_in_tree(lc_child->scene_collection, sc_src)) { + return NULL; + } + } + } + + /* Create new group with the same data as the original collection. */ + Group *group = BKE_group_add(bmain, sc_src->name); + collection_group_cleanup(group); + + sc_dst = BKE_collection_add(&group->id, NULL, COLLECTION_TYPE_GROUP_INTERNAL, sc_src->name); + BKE_collection_copy_data(sc_dst, sc_src, 0); + FOREACH_SCENE_COLLECTION(&group->id, sc_group) + { + sc_group->type = COLLECTION_TYPE_GROUP_INTERNAL; + } + FOREACH_SCENE_COLLECTION_END + + lc_dst = BKE_collection_link(group->view_layer, sc_dst); + layer_collection_sync(lc_dst, lc_src); + + return group; +} + /* ---------------------------------------------------------------------- */ /* Outliner drag and drop */ diff --git a/source/blender/editors/space_outliner/outliner_tools.c b/source/blender/editors/space_outliner/outliner_tools.c index 203942b3c9b..99fd539293f 100644 --- a/source/blender/editors/space_outliner/outliner_tools.c +++ b/source/blender/editors/space_outliner/outliner_tools.c @@ -664,6 +664,7 @@ typedef enum eOutliner_PropCollectionOps { OL_COLLECTION_OP_COLLECTION_NEW, OL_COLLECTION_OP_COLLECTION_DEL, OL_COLLECTION_OP_COLLECTION_UNLINK, + OL_COLLECTION_OP_GROUP_CREATE, } eOutliner_PropCollectionOps; static void pchan_cb(int event, TreeElement *te, TreeStoreElem *UNUSED(tselem), void *UNUSED(arg)) @@ -878,6 +879,17 @@ static void collection_cb(int event, TreeElement *te, TreeStoreElem *UNUSED(tsel TODO_LAYER_OPERATORS; /* this shouldn't be in the menu in those cases */ } } + else if (event == OL_COLLECTION_OP_GROUP_CREATE) { + Main *bmain = CTX_data_main(C); + BKE_collection_group_create(bmain, scene, lc); + 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_LAYER, scene); + } + else { + BLI_assert(!"Collection operation not fully implemented!"); + } } static void outliner_do_data_operation(SpaceOops *soops, int type, int event, ListBase *lb, @@ -1808,6 +1820,7 @@ static EnumPropertyItem prop_collection_op_none_types[] = { {OL_COLLECTION_OP_COLLECTION_NEW, "COLLECTION_NEW", ICON_NEW, "New Collection", "Add a new nested 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"}, {0, NULL, 0, NULL, NULL} }; diff --git a/source/blender/makesrna/intern/rna_layer.c b/source/blender/makesrna/intern/rna_layer.c index df6c2188799..557b9a11997 100644 --- a/source/blender/makesrna/intern/rna_layer.c +++ b/source/blender/makesrna/intern/rna_layer.c @@ -738,6 +738,32 @@ static void rna_LayerCollection_enable_set( WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene); } +static Group *rna_LayerCollection_create_group( + ID *id, LayerCollection *layer_collection, Main *bmain, bContext *C, ReportList *reports) +{ + Group *group; + Scene *scene = (Scene *)id; + SceneCollection *scene_collection = layer_collection->scene_collection; + + /* The master collection can't be converted. */ + if (scene_collection == BKE_collection_master(&scene->id)) { + BKE_report(reports, RPT_ERROR, "The master collection can't be converted to group"); + return NULL; + } + + group = BKE_collection_group_create(bmain, scene, layer_collection); + if (group == NULL) { + BKE_reportf(reports, RPT_ERROR, "Failed to convert collection %s", scene_collection->name); + return NULL; + } + + 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_LAYER, scene); + return group; +} + static int rna_LayerCollections_active_collection_index_get(PointerRNA *ptr) { ViewLayer *view_layer = (ViewLayer *)ptr->data; @@ -2019,6 +2045,12 @@ static void rna_def_layer_collection(BlenderRNA *brna) parm = RNA_def_boolean(func, "value", 1, "Enable", ""); RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_CONTEXT | FUNC_USE_REPORTS); + func = RNA_def_function(srna, "create_group", "rna_LayerCollection_create_group"); + RNA_def_function_ui_description(func, "Enable or disable a 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", "Group", "", "Newly created Group"); + RNA_def_function_return(func, parm); + /* Flags */ prop = RNA_def_property(srna, "is_enabled", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", COLLECTION_DISABLED); diff --git a/tests/python/view_layer/CMakeLists.txt b/tests/python/view_layer/CMakeLists.txt index e5b271dcb1e..22cfcd344f3 100644 --- a/tests/python/view_layer/CMakeLists.txt +++ b/tests/python/view_layer/CMakeLists.txt @@ -87,6 +87,10 @@ VIEW_LAYER_TEST(evaluation_selectability_c) VIEW_LAYER_TEST(evaluation_selectability_d) VIEW_LAYER_TEST(evaluation_selectability_e) VIEW_LAYER_TEST(evaluation_selectability_f) +VIEW_LAYER_TEST(group_a) +VIEW_LAYER_TEST(group_b) +VIEW_LAYER_TEST(group_c) +VIEW_LAYER_TEST(group_d) VIEW_LAYER_TEST(object_add_cylinder) VIEW_LAYER_TEST(object_add_empty) VIEW_LAYER_TEST(object_add_torus) diff --git a/tests/python/view_layer/test_group_a.py b/tests/python/view_layer/test_group_a.py new file mode 100644 index 00000000000..6e1b83efbe5 --- /dev/null +++ b/tests/python/view_layer/test_group_a.py @@ -0,0 +1,46 @@ +# ############################################################ +# Importing - Same For All Render Layer Tests +# ############################################################ + +import unittest +import os +import sys + +from view_layer_common import * + + +# ############################################################ +# Testing +# ############################################################ + +class UnitTesting(ViewLayerTesting): + def test_group_create_basic(self): + """ + See if the creation of new groups is not crashing anything. + """ + import bpy + scene = bpy.context.scene + layer_collection = bpy.context.layer_collection + + # Cleanup Viewport view layer + # technically this shouldn't be needed but + # for now we need it because depsgraph build all the view layers + # at once. + + while len(scene.view_layers) > 1: + scene.view_layers.remove(scene.view_layers[1]) + + # create group + group = layer_collection.create_group() + + # update depsgraph + scene.update() + + +# ############################################################ +# Main - Same For All Render Layer Tests +# ############################################################ + +if __name__ == '__main__': + UnitTesting._extra_arguments = setup_extra_arguments(__file__) + unittest.main() diff --git a/tests/python/view_layer/test_group_b.py b/tests/python/view_layer/test_group_b.py new file mode 100644 index 00000000000..6a4478aeba8 --- /dev/null +++ b/tests/python/view_layer/test_group_b.py @@ -0,0 +1,72 @@ +# ############################################################ +# Importing - Same For All Render Layer Tests +# ############################################################ + +import unittest +import os +import sys + +from view_layer_common import * + + +# ############################################################ +# Testing +# ############################################################ + +class UnitTesting(ViewLayerTesting): + def test_group_create_basic(self): + """ + See if the creation of new groups is preserving visibility flags + from the original collections. + """ + import bpy + scene = bpy.context.scene + + # clean slate + self.cleanup_tree() + + master_collection = scene.master_collection + grandma = master_collection.collections.new('бабушка') + mom = grandma.collections.new('матушка') + + child = bpy.data.objects.new("Child", None) + mom.objects.link(child) + + grandma_layer_collection = scene.view_layers[0].collections.link(grandma) + mom_layer_collection = grandma_layer_collection.collections[0] + + grandma_layer_collection.hide = False + grandma_layer_collection.hide = False + mom_layer_collection.hide = True + mom_layer_collection.hide_select = False + + # update depsgraph + scene.update() + + # create group + group = grandma_layer_collection.create_group() + + # update depsgraph + scene.update() + + # compare + self.assertEqual(len(group.view_layer.collections), 1) + grandma_group_layer = group.view_layer.collections[0] + + self.assertEqual(grandma_group_layer.hide, False) + self.assertEqual(grandma_group_layer.hide_select, False) + + self.assertEqual(len(grandma_group_layer.collections), 1) + mom_group_layer = grandma_group_layer.collections[0] + + self.assertEqual(mom_group_layer.hide, True) + self.assertEqual(mom_group_layer.hide_select, False) + + +# ############################################################ +# Main - Same For All Render Layer Tests +# ############################################################ + +if __name__ == '__main__': + UnitTesting._extra_arguments = setup_extra_arguments(__file__) + unittest.main() diff --git a/tests/python/view_layer/test_group_c.py b/tests/python/view_layer/test_group_c.py new file mode 100644 index 00000000000..69feab1a56e --- /dev/null +++ b/tests/python/view_layer/test_group_c.py @@ -0,0 +1,56 @@ +# ############################################################ +# Importing - Same For All Render Layer Tests +# ############################################################ + +import unittest +import os +import sys + +from view_layer_common import * + + +# ############################################################ +# Testing +# ############################################################ + +class UnitTesting(ViewLayerTesting): + def test_group_create_basic(self): + """ + More advanced creation of group from a collection not directly linked + to the scene layer. + """ + import bpy + scene = bpy.context.scene + + # clean slate + self.cleanup_tree() + + children = [bpy.data.objects.new("Child", None) for i in range(3)] + master_collection = scene.master_collection + + grandma_scene_collection = master_collection.collections.new('Grand-Mother') + mom_scene_collection = grandma_scene_collection.collections.new('Mother') + + grandma_scene_collection.objects.link(children[0]) + mom_scene_collection.objects.link(children[1]) + + grandma_layer_collection = scene.view_layers[0].collections.link(grandma_scene_collection) + mom_layer_collection = grandma_layer_collection.collections[mom_scene_collection.name] + + # update depsgraph + scene.update() + + # create group + group = mom_layer_collection.create_group() + + # update depsgraph + scene.update() + + +# ############################################################ +# Main - Same For All Render Layer Tests +# ############################################################ + +if __name__ == '__main__': + UnitTesting._extra_arguments = setup_extra_arguments(__file__) + unittest.main() diff --git a/tests/python/view_layer/test_group_d.py b/tests/python/view_layer/test_group_d.py new file mode 100644 index 00000000000..6f54ca5340a --- /dev/null +++ b/tests/python/view_layer/test_group_d.py @@ -0,0 +1,76 @@ +# ############################################################ +# Importing - Same For All Render Layer Tests +# ############################################################ + +import unittest +import os +import sys + +from view_layer_common import * + + +# ############################################################ +# Testing +# ############################################################ + +class UnitTesting(ViewLayerTesting): + def test_group_write_load(self): + """ + See if saving/loading is working for groups + """ + import bpy + scene = bpy.context.scene + layer_collection = bpy.context.layer_collection + + while len(scene.view_layers) > 1: + scene.view_layers.remove(scene.view_layers[1]) + + # create group + group = layer_collection.create_group() + + self.assertEqual(1, len(bpy.data.groups)) + self.assertEqual(1, bpy.data.groups[0].users) + self.assertEqual(3, len(bpy.data.groups[0].objects)) + + import os + import tempfile + with tempfile.TemporaryDirectory() as dirpath: + filepath = os.path.join(dirpath, 'layers.blend') + + for i in range(3): + # save and re-open file + bpy.ops.wm.save_mainfile('EXEC_DEFAULT', filepath=filepath) + bpy.ops.wm.open_mainfile('EXEC_DEFAULT', filepath=filepath) + + self.assertEqual(1, len(bpy.data.groups)) + self.assertEqual(1, bpy.data.groups[0].users) + self.assertEqual(3, len(bpy.data.groups[0].objects)) + + + # empty the group of objects + group = bpy.data.groups[0] + while group.objects: + group.view_layer.collections[0].collection.objects.unlink(group.objects[0]) + + # save and re-open file + bpy.ops.wm.save_mainfile('EXEC_DEFAULT', filepath=filepath) + bpy.ops.wm.open_mainfile('EXEC_DEFAULT', filepath=filepath) + + self.assertEqual(1, len(bpy.data.groups)) + self.assertEqual(0, bpy.data.groups[0].users) + self.assertEqual(0, len(bpy.data.groups[0].objects)) + + # save and re-open file + bpy.ops.wm.save_mainfile('EXEC_DEFAULT', filepath=filepath) + bpy.ops.wm.open_mainfile('EXEC_DEFAULT', filepath=filepath) + + self.assertEqual(0, len(bpy.data.groups)) + + +# ############################################################ +# Main - Same For All Render Layer Tests +# ############################################################ + +if __name__ == '__main__': + UnitTesting._extra_arguments = setup_extra_arguments(__file__) + unittest.main() |