[Bf-blender-cvs] [f167226b793] blender2.8: Move to Collection - initial operator

Dalai Felinto noreply at git.blender.org
Wed Mar 28 22:26:49 CEST 2018


Commit: f167226b793e5dd39816f0b250771177d088dc54
Author: Dalai Felinto
Date:   Wed Mar 28 14:54:17 2018 -0300
Branches: blender2.8
https://developer.blender.org/rBf167226b793e5dd39816f0b250771177d088dc54

Move to Collection - initial operator

How to use: Select a few objects, and press "M" in the viewport.

If you hold ctrl the objects will be added to the selected collection.
Otherwise they are removed from all their original collections and moved
to the selected one instead.

Development Notes
=================
The ideal solution would be to implement an elegant generic multi-level
menu system similar to toolbox_generic() in 2.49.

Instead I used `uiItemMenuF` to acchieve the required nesting of the menus.

The downside is that `uiItemMenuF` requires the data its callback uses to be
always valid until the menu is discarded. But since there is no callback we
can call when the menu is discarded for operators that exited with
`OPERATOR_INTERFACE`.

That means we are using static allocated data, that is only freed next time
the operator is called. Which also means there will always be some
memory leakage.

Reviewers: campbellbarton

Differential Revision: https://developer.blender.org/D3117

===================================================================

M	source/blender/blenkernel/intern/collection.c
M	source/blender/editors/object/object_edit.c
M	source/blender/editors/object/object_intern.h
M	source/blender/editors/object/object_ops.c

===================================================================

diff --git a/source/blender/blenkernel/intern/collection.c b/source/blender/blenkernel/intern/collection.c
index fb27249402b..880d28970c0 100644
--- a/source/blender/blenkernel/intern/collection.c
+++ b/source/blender/blenkernel/intern/collection.c
@@ -457,20 +457,12 @@ bool BKE_collection_object_remove(Main *bmain, ID *owner_id, SceneCollection *sc
 	return true;
 }
 
-/**
- * Move object from a collection into another
- */
-void BKE_collection_object_move(ID *owner_id, SceneCollection *sc_dst, SceneCollection *sc_src, Object *ob)
-{
-	if (BKE_collection_object_add(owner_id, sc_dst, ob)) {
-		BKE_collection_object_remove(NULL, owner_id, sc_src, ob, false);
-	}
-}
-
 /**
  * Remove object from all collections of scene
+ * \param scene_collection_skip: Don't remove base from this collection.
  */
-bool BKE_collections_object_remove(Main *bmain, ID *owner_id, Object *ob, const bool free_us)
+static bool collections_object_remove_ex(Main *bmain, ID *owner_id, Object *ob, const bool free_us,
+                                         SceneCollection *scene_collection_skip)
 {
 	bool removed = false;
 	if (GS(owner_id->name) == ID_SCE) {
@@ -482,12 +474,44 @@ bool BKE_collections_object_remove(Main *bmain, ID *owner_id, Object *ob, const
 
 	FOREACH_SCENE_COLLECTION_BEGIN(owner_id, sc)
 	{
-		removed |= BKE_collection_object_remove(bmain, owner_id, sc, ob, free_us);
+		if (sc != scene_collection_skip) {
+			removed |= BKE_collection_object_remove(bmain, owner_id, sc, ob, free_us);
+		}
 	}
 	FOREACH_SCENE_COLLECTION_END;
 	return removed;
 }
 
+/**
+ * Remove object from all collections of scene
+ */
+bool BKE_collections_object_remove(Main *bmain, ID *owner_id, Object *ob, const bool free_us)
+{
+	return collections_object_remove_ex(bmain, owner_id, ob, free_us, NULL);
+}
+
+/**
+ * Move object from a collection into another
+ *
+ * If source collection is NULL move it from all the existing collections.
+ */
+void BKE_collection_object_move(ID *owner_id, SceneCollection *sc_dst, SceneCollection *sc_src, Object *ob)
+{
+	/* In both cases we first add the object, then remove it from the other collections.
+	 * Otherwise we lose the original base and whether it was active and selected. */
+	if (sc_src != NULL) {
+		if (BKE_collection_object_add(owner_id, sc_dst, ob)) {
+			BKE_collection_object_remove(NULL, owner_id, sc_src, ob, false);
+		}
+	}
+	else {
+		/* Adding will fail if object is already in collection.
+		 * However we still need to remove it from the other collections. */
+		BKE_collection_object_add(owner_id, sc_dst, ob);
+		collections_object_remove_ex(NULL, owner_id, ob, false, sc_dst);
+	}
+}
+
 static void layer_collection_sync(LayerCollection *lc_dst, LayerCollection *lc_src)
 {
 	lc_dst->flag = lc_src->flag;
diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c
index 3e629c8d7f6..e60ddb98329 100644
--- a/source/blender/editors/object/object_edit.c
+++ b/source/blender/editors/object/object_edit.c
@@ -64,6 +64,7 @@
 #include "IMB_imbuf_types.h"
 
 #include "BKE_anim.h"
+#include "BKE_collection.h"
 #include "BKE_constraint.h"
 #include "BKE_context.h"
 #include "BKE_curve.h"
@@ -109,11 +110,16 @@
 /* for menu/popup icons etc etc*/
 
 #include "UI_interface.h"
+#include "UI_resources.h"
 #include "WM_api.h"
 #include "WM_types.h"
 
 #include "object_intern.h"  // own include
 
+/* prototypes */
+typedef struct MoveToCollectionData MoveToCollectionData;
+static void move_to_collection_menus_items(struct uiLayout *layout, struct MoveToCollectionData *menu);
+
 /* ************* XXX **************** */
 static void error(const char *UNUSED(arg)) {}
 static void waitcursor(int UNUSED(val)) {}
@@ -2039,3 +2045,242 @@ bool ED_object_editmode_calc_active_center(Object *obedit, const bool select_onl
 
 	return false;
 }
+
+#define COLLECTION_INVALID_INDEX -1
+
+static SceneCollection *scene_collection_from_index_recursive(SceneCollection *scene_collection, const int index, int *index_current)
+{
+	if (index == (*index_current)) {
+		return scene_collection;
+	}
+
+	(*index_current)++;
+
+	for (SceneCollection *scene_collection_iter = scene_collection->scene_collections.first;
+	     scene_collection_iter != NULL;
+	     scene_collection_iter = scene_collection_iter->next)
+	{
+		SceneCollection *nested = scene_collection_from_index_recursive(scene_collection_iter, index, index_current);
+		if (nested != NULL) {
+			return nested;
+		}
+	}
+	return NULL;
+}
+
+static SceneCollection *scene_collection_from_index(Scene *scene, const int index)
+{
+	int index_current = 0;
+	SceneCollection *master_collection = BKE_collection_master(&scene->id);
+	return scene_collection_from_index_recursive(master_collection, index, &index_current);
+}
+
+static int move_to_collection_exec(bContext *C, wmOperator *op)
+{
+	Scene *scene = CTX_data_scene(C);
+	PropertyRNA *prop = RNA_struct_find_property(op->ptr, "collection_index");
+	const bool is_add = RNA_boolean_get(op->ptr, "is_add");
+	SceneCollection *scene_collection;
+
+	if (!RNA_property_is_set(op->ptr, prop)) {
+		BKE_report(op->reports, RPT_ERROR, "No collection selected");
+		return OPERATOR_CANCELLED;
+	}
+
+	int collection_index = RNA_property_int_get(op->ptr, prop);
+	scene_collection = scene_collection_from_index(CTX_data_scene(C), collection_index);
+	if (scene_collection == NULL) {
+		BKE_report(op->reports, RPT_ERROR, "Unexpected error, collection not found");
+		return OPERATOR_CANCELLED;
+	}
+
+	Object *single_object = NULL;
+	CTX_DATA_BEGIN (C, Object *, ob, selected_objects)
+	{
+		if (single_object != NULL) {
+			single_object = NULL;
+			break;
+		}
+		else {
+			single_object = ob;
+		}
+	}
+	CTX_DATA_END;
+
+	if ((single_object != NULL) &&
+	    is_add &&
+	    BLI_findptr(&scene_collection->objects, single_object, offsetof(LinkData, data)))
+	{
+		BKE_reportf(op->reports, RPT_ERROR, "%s already in %s", single_object->id.name + 2, scene_collection->name);
+		return OPERATOR_CANCELLED;
+	}
+
+	CTX_DATA_BEGIN (C, Object *, ob, selected_objects)
+	{
+		if (!is_add) {
+			BKE_collection_object_move(&scene->id, scene_collection, NULL, ob);
+		}
+		else {
+			BKE_collection_object_add(&scene->id, scene_collection, ob);
+		}
+	}
+	CTX_DATA_END;
+
+	BKE_reportf(op->reports,
+	            RPT_INFO,
+	            "%s %s to %s",
+	            (single_object != NULL) ? single_object->id.name + 2 : "Objects",
+	            is_add ? "added" : "moved",
+	            scene_collection->name);
+
+	DEG_relations_tag_update(CTX_data_main(C));
+	DEG_id_tag_update(&scene->id, 0);
+
+	WM_event_add_notifier(C, NC_SCENE | ND_LAYER, scene);
+	WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
+	WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
+
+	return OPERATOR_FINISHED;
+}
+
+typedef struct MoveToCollectionData {
+	struct MoveToCollectionData *next, *prev;
+	int index;
+	struct SceneCollection *collection;
+	struct ListBase submenus;
+} MoveToCollectionData;
+
+static int move_to_collection_menus_create(MoveToCollectionData *menu)
+{
+	int index = menu->index;
+	for (SceneCollection *scene_collection = menu->collection->scene_collections.first;
+	     scene_collection != NULL;
+	     scene_collection = scene_collection->next)
+	{
+		MoveToCollectionData *submenu = MEM_callocN(sizeof(MoveToCollectionData),
+		                                            "MoveToCollectionData submenu - expected memleak");
+		BLI_addtail(&menu->submenus, submenu);
+		submenu->collection = scene_collection;
+		submenu->index = ++index;
+		index = move_to_collection_menus_create(submenu);
+	}
+	return index;
+}
+
+static void move_to_collection_menus_free(MoveToCollectionData *menu)
+{
+	for (MoveToCollectionData *submenu = menu->submenus.first;
+	     submenu != NULL;
+	     submenu = submenu->next)
+	{
+		move_to_collection_menus_free(submenu);
+	}
+	BLI_freelistN(&menu->submenus);
+}
+
+static void move_to_collection_menu_create(bContext *UNUSED(C), uiLayout *layout, void *menu_v)
+{
+	MoveToCollectionData *menu = menu_v;
+
+	uiItemIntO(layout,
+			   menu->collection->name,
+			   ICON_NONE,
+			   "OBJECT_OT_move_to_collection",
+			   "collection_index",
+			   menu->index);
+	uiItemS(layout);
+
+	for (MoveToCollectionData *submenu = menu->submenus.first;
+		 submenu != NULL;
+		 submenu = submenu->next)
+	{
+		move_to_collection_menus_items(layout, submenu);
+	}
+}
+
+static void move_to_collection_menus_items(uiLayout *layout, MoveToCollectionData *menu)
+{
+	if (BLI_listbase_is_empty(&menu->submenus)) {
+		uiItemIntO(layout,
+		           menu->collection->name,
+		           ICON_NONE,
+		           "OBJECT_OT_move_to_collection",
+		           "collection_index",
+		           menu->index);
+	}
+	else {
+		uiItemMenuF(layout,
+		            menu->collection->name,
+		            ICON_NONE,
+		            move_to_collection_menu_create,
+		            menu);
+	}
+}
+
+static int move_to_collection_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+	PropertyRNA *prop = RNA_struct_find_property(op->ptr, "collection_index");
+	if (RNA_property_is_set(op->ptr, prop)) {
+		RNA_boolean_set(op->ptr, "is_add", event->ctrl);
+		return move_to_collection_exec(C, op);
+	}
+
+	SceneCollection *master_collection = BKE_collection_master(&CTX_data_scene(C)->id);
+
+	/* We need the data to be allocated so it's available during menu drawing.
+	 * Technically we could use wmOperator->customdata. However there is no free callback
+	 * called to an operator that exit with OPERATOR_INTERFACE to launch a menu.
+	 *
+	 * So we are left with a memory that will necessarily leak. It's a small leak though.*/
+	static MoveToCollectionData *master_collection_menu = NULL;
+
+	if (master_collection_menu == NULL) {
+		master_collection_menu = MEM_callocN(sizeof(MoveToCollectionData),
+		                                     "MoveToCollectionData menu - expected memleak");
+	}
+
+	/* Reset the menus data for the current master collection, and free previously allocated data. */
+	move_to_collection_menus_free(master_collection_menu);
+	master_collection_menu->collection = master_collection;
+	move_to_collection_menus_create(master_collection_menu);
+
+	uiPopupMenu *pup;
+	uiLayout 

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list