Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/object/object_edit.c
| Show First 20 Lines • Show All 1,496 Lines • ▼ Show 20 Lines | bool ED_object_editmode_calc_active_center(Object *obedit, const bool select_only, float r_center[3]) | ||||
| return false; | return false; | ||||
| } | } | ||||
| #define COLLECTION_INVALID_INDEX -1 | #define COLLECTION_INVALID_INDEX -1 | ||||
| static int move_to_collection_exec(bContext *C, wmOperator *op) | static int move_to_collection_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| Main *bmain = CTX_data_main(C); | |||||
| Scene *scene = CTX_data_scene(C); | Scene *scene = CTX_data_scene(C); | ||||
| PropertyRNA *prop = RNA_struct_find_property(op->ptr, "collection_index"); | PropertyRNA *prop = RNA_struct_find_property(op->ptr, "collection_index"); | ||||
| const bool is_add = RNA_boolean_get(op->ptr, "is_add"); | const bool is_link = STREQ(op->idname, "OBJECT_OT_link_to_collection"); | ||||
| const bool is_new = RNA_boolean_get(op->ptr, "is_new"); | const bool is_new = RNA_boolean_get(op->ptr, "is_new"); | ||||
| SceneCollection *scene_collection; | Collection *collection; | ||||
| if (!RNA_property_is_set(op->ptr, prop)) { | if (!RNA_property_is_set(op->ptr, prop)) { | ||||
| BKE_report(op->reports, RPT_ERROR, "No collection selected"); | BKE_report(op->reports, RPT_ERROR, "No collection selected"); | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| int collection_index = RNA_property_int_get(op->ptr, prop); | int collection_index = RNA_property_int_get(op->ptr, prop); | ||||
| scene_collection = BKE_collection_from_index(CTX_data_scene(C), collection_index); | collection = BKE_collection_from_index(CTX_data_scene(C), collection_index); | ||||
| if (scene_collection == NULL) { | if (collection == NULL) { | ||||
| BKE_report(op->reports, RPT_ERROR, "Unexpected error, collection not found"); | BKE_report(op->reports, RPT_ERROR, "Unexpected error, collection not found"); | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| Object *single_object = NULL; | Object *single_object = NULL; | ||||
| CTX_DATA_BEGIN (C, Object *, ob, selected_objects) | CTX_DATA_BEGIN (C, Object *, ob, selected_objects) | ||||
| { | { | ||||
| if (single_object != NULL) { | if (single_object != NULL) { | ||||
| single_object = NULL; | single_object = NULL; | ||||
| break; | break; | ||||
| } | } | ||||
| else { | else { | ||||
| single_object = ob; | single_object = ob; | ||||
| } | } | ||||
| } | } | ||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| if (is_new) { | if (is_new) { | ||||
| char new_collection_name[MAX_NAME]; | char new_collection_name[MAX_NAME]; | ||||
| RNA_string_get(op->ptr, "new_collection_name", new_collection_name); | RNA_string_get(op->ptr, "new_collection_name", new_collection_name); | ||||
| scene_collection = BKE_collection_add(&scene->id, scene_collection, COLLECTION_TYPE_NONE, new_collection_name); | collection = BKE_collection_add(bmain, collection, new_collection_name); | ||||
| } | } | ||||
| if ((single_object != NULL) && | if ((single_object != NULL) && | ||||
| is_add && | is_link && | ||||
| BLI_findptr(&scene_collection->objects, single_object, offsetof(LinkData, data))) | BLI_findptr(&collection->gobject, single_object, offsetof(CollectionObject, ob))) | ||||
| { | { | ||||
| BKE_reportf(op->reports, RPT_ERROR, "%s already in %s", single_object->id.name + 2, scene_collection->name); | BKE_reportf(op->reports, RPT_ERROR, "%s already in %s", single_object->id.name + 2, collection->id.name + 2); | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| CTX_DATA_BEGIN (C, Object *, ob, selected_objects) | CTX_DATA_BEGIN (C, Object *, ob, selected_objects) | ||||
| { | { | ||||
| if (!is_add) { | if (!is_link) { | ||||
| BKE_collection_object_move(&scene->id, scene_collection, NULL, ob); | BKE_collection_object_move(bmain, scene, collection, NULL, ob); | ||||
| } | } | ||||
| else { | else { | ||||
| BKE_collection_object_add(&scene->id, scene_collection, ob); | BKE_collection_object_add(bmain, collection, ob); | ||||
| } | } | ||||
| } | } | ||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| BKE_reportf(op->reports, | BKE_reportf(op->reports, | ||||
| RPT_INFO, | RPT_INFO, | ||||
| "%s %s to %s", | "%s %s to %s", | ||||
| (single_object != NULL) ? single_object->id.name + 2 : "Objects", | (single_object != NULL) ? single_object->id.name + 2 : "Objects", | ||||
| is_add ? "added" : "moved", | is_link ? "linked" : "moved", | ||||
| scene_collection->name); | collection->id.name + 2); | ||||
| DEG_relations_tag_update(CTX_data_main(C)); | DEG_relations_tag_update(CTX_data_main(C)); | ||||
| DEG_id_tag_update(&scene->id, 0); | 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_LAYER, scene); | ||||
| WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene); | WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene); | ||||
| WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene); | WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| typedef struct MoveToCollectionData { | struct MoveToCollectionData { | ||||
| struct MoveToCollectionData *next, *prev; | struct MoveToCollectionData *next, *prev; | ||||
| int index; | int index; | ||||
| struct SceneCollection *collection; | struct Collection *collection; | ||||
| struct ListBase submenus; | struct ListBase submenus; | ||||
| PointerRNA ptr; | PointerRNA ptr; | ||||
| struct wmOperatorType *ot; | struct wmOperatorType *ot; | ||||
| } MoveToCollectionData; | }; | ||||
| static int move_to_collection_menus_create(wmOperator *op, MoveToCollectionData *menu) | static int move_to_collection_menus_create(wmOperator *op, MoveToCollectionData *menu) | ||||
| { | { | ||||
| int index = menu->index; | int index = menu->index; | ||||
| for (SceneCollection *scene_collection = menu->collection->scene_collections.first; | for (CollectionChild *child = menu->collection->children.first; | ||||
| scene_collection != NULL; | child != NULL; | ||||
| scene_collection = scene_collection->next) | child = child->next) | ||||
| { | { | ||||
| Collection *collection = child->collection; | |||||
| MoveToCollectionData *submenu = MEM_callocN(sizeof(MoveToCollectionData), | MoveToCollectionData *submenu = MEM_callocN(sizeof(MoveToCollectionData), | ||||
| "MoveToCollectionData submenu - expected memleak"); | "MoveToCollectionData submenu - expected memleak"); | ||||
| BLI_addtail(&menu->submenus, submenu); | BLI_addtail(&menu->submenus, submenu); | ||||
| submenu->collection = scene_collection; | submenu->collection = collection; | ||||
| submenu->index = ++index; | submenu->index = ++index; | ||||
| index = move_to_collection_menus_create(op, submenu); | index = move_to_collection_menus_create(op, submenu); | ||||
| submenu->ot = op->type; | submenu->ot = op->type; | ||||
| } | } | ||||
| return index; | return index; | ||||
| } | } | ||||
| static void move_to_collection_menus_free_recursive(MoveToCollectionData *menu) | static void move_to_collection_menus_free_recursive(MoveToCollectionData *menu) | ||||
| Show All 18 Lines | static void move_to_collection_menus_free(MoveToCollectionData **menu) | ||||
| *menu = NULL; | *menu = NULL; | ||||
| } | } | ||||
| static void move_to_collection_menu_create(bContext *UNUSED(C), uiLayout *layout, void *menu_v) | static void move_to_collection_menu_create(bContext *UNUSED(C), uiLayout *layout, void *menu_v) | ||||
| { | { | ||||
| MoveToCollectionData *menu = menu_v; | MoveToCollectionData *menu = menu_v; | ||||
| uiItemIntO(layout, | uiItemIntO(layout, | ||||
| menu->collection->name, | menu->collection->id.name + 2, | ||||
| ICON_NONE, | ICON_NONE, | ||||
| "OBJECT_OT_move_to_collection", | menu->ot->idname, | ||||
| "collection_index", | "collection_index", | ||||
| menu->index); | menu->index); | ||||
| uiItemS(layout); | uiItemS(layout); | ||||
| for (MoveToCollectionData *submenu = menu->submenus.first; | for (MoveToCollectionData *submenu = menu->submenus.first; | ||||
| submenu != NULL; | submenu != NULL; | ||||
| submenu = submenu->next) | submenu = submenu->next) | ||||
| { | { | ||||
| move_to_collection_menus_items(layout, submenu); | move_to_collection_menus_items(layout, submenu); | ||||
| } | } | ||||
| uiItemS(layout); | uiItemS(layout); | ||||
| WM_operator_properties_create_ptr(&menu->ptr, menu->ot); | WM_operator_properties_create_ptr(&menu->ptr, menu->ot); | ||||
| RNA_int_set(&menu->ptr, "collection_index", menu->index); | RNA_int_set(&menu->ptr, "collection_index", menu->index); | ||||
| RNA_boolean_set(&menu->ptr, "is_new", true); | RNA_boolean_set(&menu->ptr, "is_new", true); | ||||
| uiItemFullO_ptr(layout, | uiItemFullO_ptr(layout, | ||||
| menu->ot, | menu->ot, | ||||
| "New Collection", | "New Collection", | ||||
| ICON_ZOOMIN, | ICON_ZOOMIN, | ||||
| menu->ptr.data, | menu->ptr.data, | ||||
| /* We use invoke here so we can read ctrl from event. */ | |||||
| WM_OP_INVOKE_DEFAULT, | WM_OP_INVOKE_DEFAULT, | ||||
| 0, | 0, | ||||
| NULL); | NULL); | ||||
| } | } | ||||
| static void move_to_collection_menus_items(uiLayout *layout, MoveToCollectionData *menu) | static void move_to_collection_menus_items(uiLayout *layout, MoveToCollectionData *menu) | ||||
| { | { | ||||
| if (BLI_listbase_is_empty(&menu->submenus)) { | if (BLI_listbase_is_empty(&menu->submenus)) { | ||||
| uiItemIntO(layout, | uiItemIntO(layout, | ||||
| menu->collection->name, | menu->collection->id.name + 2, | ||||
| ICON_NONE, | ICON_NONE, | ||||
| "OBJECT_OT_move_to_collection", | menu->ot->idname, | ||||
| "collection_index", | "collection_index", | ||||
| menu->index); | menu->index); | ||||
| } | } | ||||
| else { | else { | ||||
| uiItemMenuF(layout, | uiItemMenuF(layout, | ||||
| menu->collection->name, | menu->collection->id.name + 2, | ||||
| ICON_NONE, | ICON_NONE, | ||||
| move_to_collection_menu_create, | move_to_collection_menu_create, | ||||
| menu); | menu); | ||||
| } | } | ||||
| } | } | ||||
| /* This is allocated statically because we need this available for the menus creation callback. */ | /* This is allocated statically because we need this available for the menus creation callback. */ | ||||
| static MoveToCollectionData *master_collection_menu = NULL; | static MoveToCollectionData *master_collection_menu = NULL; | ||||
| static int move_to_collection_invoke(bContext *C, wmOperator *op, const wmEvent *event) | static int move_to_collection_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) | ||||
| { | { | ||||
| Scene *scene = CTX_data_scene(C); | |||||
| /* Reset the menus data for the current master collection, and free previously allocated data. */ | /* Reset the menus data for the current master collection, and free previously allocated data. */ | ||||
| move_to_collection_menus_free(&master_collection_menu); | move_to_collection_menus_free(&master_collection_menu); | ||||
| PropertyRNA *prop; | PropertyRNA *prop; | ||||
| prop = RNA_struct_find_property(op->ptr, "collection_index"); | prop = RNA_struct_find_property(op->ptr, "collection_index"); | ||||
| if (RNA_property_is_set(op->ptr, prop)) { | if (RNA_property_is_set(op->ptr, prop)) { | ||||
| int collection_index = RNA_property_int_get(op->ptr, prop); | int collection_index = RNA_property_int_get(op->ptr, prop); | ||||
| RNA_boolean_set(op->ptr, "is_add", event->ctrl); | |||||
| if (RNA_boolean_get(op->ptr, "is_new")) { | if (RNA_boolean_get(op->ptr, "is_new")) { | ||||
| prop = RNA_struct_find_property(op->ptr, "new_collection_name"); | prop = RNA_struct_find_property(op->ptr, "new_collection_name"); | ||||
| if (!RNA_property_is_set(op->ptr, prop)) { | if (!RNA_property_is_set(op->ptr, prop)) { | ||||
| char name[MAX_NAME]; | char name[MAX_NAME]; | ||||
| SceneCollection *scene_collection; | Collection *collection; | ||||
| scene_collection = BKE_collection_from_index(CTX_data_scene(C), collection_index); | collection = BKE_collection_from_index(scene, collection_index); | ||||
| BKE_collection_new_name_get(&CTX_data_scene(C)->id, scene_collection, name); | BKE_collection_new_name_get(collection, name); | ||||
| RNA_property_string_set(op->ptr, prop, name); | RNA_property_string_set(op->ptr, prop, name); | ||||
| return WM_operator_props_dialog_popup(C, op, 10 * UI_UNIT_X, 5 * UI_UNIT_Y); | return WM_operator_props_dialog_popup(C, op, 10 * UI_UNIT_X, 5 * UI_UNIT_Y); | ||||
| } | } | ||||
| } | } | ||||
| return move_to_collection_exec(C, op); | return move_to_collection_exec(C, op); | ||||
| } | } | ||||
| SceneCollection *master_collection = BKE_collection_master(&CTX_data_scene(C)->id); | Collection *master_collection = BKE_collection_master(scene); | ||||
| /* We need the data to be allocated so it's available during menu drawing. | /* 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 | * 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. | * 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.*/ | * So we are left with a memory that will necessarily leak. It's a small leak though.*/ | ||||
| if (master_collection_menu == NULL) { | if (master_collection_menu == NULL) { | ||||
| master_collection_menu = MEM_callocN(sizeof(MoveToCollectionData), | master_collection_menu = MEM_callocN(sizeof(MoveToCollectionData), | ||||
| "MoveToCollectionData menu - expected eventual memleak"); | "MoveToCollectionData menu - expected eventual memleak"); | ||||
| } | } | ||||
| master_collection_menu->collection = master_collection; | master_collection_menu->collection = master_collection; | ||||
| master_collection_menu->ot = op->type; | master_collection_menu->ot = op->type; | ||||
| move_to_collection_menus_create(op, master_collection_menu); | move_to_collection_menus_create(op, master_collection_menu); | ||||
| uiPopupMenu *pup; | uiPopupMenu *pup; | ||||
| uiLayout *layout; | uiLayout *layout; | ||||
| /* Build the menus. */ | /* Build the menus. */ | ||||
| pup = UI_popup_menu_begin(C, IFACE_("Move to Collection"), ICON_NONE); | const char *title = CTX_IFACE_(op->type->translation_context, op->type->name); | ||||
| pup = UI_popup_menu_begin(C, title, ICON_NONE); | |||||
| layout = UI_popup_menu_layout(pup); | layout = UI_popup_menu_layout(pup); | ||||
| /* We use invoke here so we can read ctrl from event. */ | |||||
| uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT); | uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT); | ||||
| move_to_collection_menu_create(C, layout, master_collection_menu); | move_to_collection_menu_create(C, layout, master_collection_menu); | ||||
| UI_popup_menu_end(C, pup); | UI_popup_menu_end(C, pup); | ||||
| return OPERATOR_INTERFACE; | return OPERATOR_INTERFACE; | ||||
| } | } | ||||
| void OBJECT_OT_move_to_collection(wmOperatorType *ot) | void OBJECT_OT_move_to_collection(wmOperatorType *ot) | ||||
| { | { | ||||
| PropertyRNA *prop; | PropertyRNA *prop; | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Move to Collection"; | ot->name = "Move to Collection"; | ||||
| ot->description = "Move to a collection only (Ctrl to add)"; | ot->description = "Move objects to a scene collection"; | ||||
| ot->idname = "OBJECT_OT_move_to_collection"; | ot->idname = "OBJECT_OT_move_to_collection"; | ||||
| /* api callbacks */ | /* api callbacks */ | ||||
| ot->exec = move_to_collection_exec; | ot->exec = move_to_collection_exec; | ||||
| ot->invoke = move_to_collection_invoke; | ot->invoke = move_to_collection_invoke; | ||||
| ot->poll = ED_operator_object_active_editable; | ot->poll = ED_operator_object_active_editable; | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ||||
| prop = RNA_def_int(ot->srna, "collection_index", COLLECTION_INVALID_INDEX, COLLECTION_INVALID_INDEX, INT_MAX, | prop = RNA_def_int(ot->srna, "collection_index", COLLECTION_INVALID_INDEX, COLLECTION_INVALID_INDEX, INT_MAX, | ||||
| "Collection Index", "Index of the collection to move to", 0, INT_MAX); | "Collection Index", "Index of the collection to move to", 0, INT_MAX); | ||||
| RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); | RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); | ||||
| prop = RNA_def_boolean(ot->srna, "is_add", false, "Add", "Keep object in original collections as well"); | prop = RNA_def_boolean(ot->srna, "is_new", false, "New", "Move objects to a new collection"); | ||||
| RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); | |||||
| prop = RNA_def_string(ot->srna, "new_collection_name", NULL, MAX_NAME, "Name", | |||||
| "Name of the newly added collection"); | |||||
| RNA_def_property_flag(prop, PROP_SKIP_SAVE); | |||||
| } | |||||
| void OBJECT_OT_link_to_collection(wmOperatorType *ot) | |||||
| { | |||||
| PropertyRNA *prop; | |||||
| /* identifiers */ | |||||
| ot->name = "Link to Collection"; | |||||
| ot->description = "Link objects to a collection"; | |||||
| ot->idname = "OBJECT_OT_link_to_collection"; | |||||
| /* api callbacks */ | |||||
| ot->exec = move_to_collection_exec; | |||||
| ot->invoke = move_to_collection_invoke; | |||||
| ot->poll = ED_operator_object_active_editable; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| prop = RNA_def_int(ot->srna, "collection_index", COLLECTION_INVALID_INDEX, COLLECTION_INVALID_INDEX, INT_MAX, | |||||
| "Collection Index", "Index of the collection to move to", 0, INT_MAX); | |||||
| RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); | RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); | ||||
| prop = RNA_def_boolean(ot->srna, "is_new", false, "New", "Move objects to a new collection"); | prop = RNA_def_boolean(ot->srna, "is_new", false, "New", "Move objects to a new collection"); | ||||
| RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); | RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); | ||||
| prop = RNA_def_string(ot->srna, "new_collection_name", NULL, MAX_NAME, "Name", | prop = RNA_def_string(ot->srna, "new_collection_name", NULL, MAX_NAME, "Name", | ||||
| "Name of the newly added collection"); | "Name of the newly added collection"); | ||||
| RNA_def_property_flag(prop, PROP_SKIP_SAVE); | RNA_def_property_flag(prop, PROP_SKIP_SAVE); | ||||
| } | } | ||||
| #undef COLLECTION_INVALID_INDEX | #undef COLLECTION_INVALID_INDEX | ||||