doxygen: add newline after \file
[blender.git] / source / blender / editors / undo / ed_undo.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2004 Blender Foundation
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup edundo
22  */
23
24 #include <string.h>
25
26 #include "MEM_guardedalloc.h"
27
28 #include "CLG_log.h"
29
30 #include "DNA_scene_types.h"
31 #include "DNA_object_types.h"
32
33 #include "BLI_utildefines.h"
34 #include "BLI_callbacks.h"
35 #include "BLI_listbase.h"
36
37 #include "BLT_translation.h"
38
39 #include "BKE_blender_undo.h"
40 #include "BKE_context.h"
41 #include "BKE_global.h"
42 #include "BKE_main.h"
43 #include "BKE_report.h"
44 #include "BKE_scene.h"
45 #include "BKE_screen.h"
46 #include "BKE_layer.h"
47 #include "BKE_undo_system.h"
48 #include "BKE_workspace.h"
49 #include "BKE_paint.h"
50
51 #include "BLO_blend_validate.h"
52
53 #include "ED_gpencil.h"
54 #include "ED_render.h"
55 #include "ED_object.h"
56 #include "ED_screen.h"
57 #include "ED_undo.h"
58
59 #include "WM_api.h"
60 #include "WM_types.h"
61 #include "WM_toolsystem.h"
62
63 #include "RNA_access.h"
64 #include "RNA_define.h"
65
66 #include "UI_interface.h"
67 #include "UI_resources.h"
68
69 /** We only need this locally. */
70 static CLG_LogRef LOG = {"ed.undo"};
71
72 /* -------------------------------------------------------------------- */
73 /** \name Generic Undo System Access
74  *
75  * Non-operator undo editor functions.
76  * \{ */
77
78 void ED_undo_push(bContext *C, const char *str)
79 {
80         CLOG_INFO(&LOG, 1, "name='%s'", str);
81
82         const int steps = U.undosteps;
83
84         if (steps <= 0) {
85                 return;
86         }
87
88         wmWindowManager *wm = CTX_wm_manager(C);
89
90         /* Only apply limit if this is the last undo step. */
91         if (wm->undo_stack->step_active && (wm->undo_stack->step_active->next == NULL)) {
92                 BKE_undosys_stack_limit_steps_and_memory(wm->undo_stack, steps - 1, 0);
93         }
94
95         BKE_undosys_step_push(wm->undo_stack, C, str);
96
97         if (U.undomemory != 0) {
98                 const size_t memory_limit = (size_t)U.undomemory * 1024 * 1024;
99                 BKE_undosys_stack_limit_steps_and_memory(wm->undo_stack, 0, memory_limit);
100         }
101
102         WM_file_tag_modified();
103 }
104
105 /**
106  * \note Also check #undo_history_exec in bottom if you change notifiers.
107  */
108 static int ed_undo_step(bContext *C, int step, const char *undoname, ReportList *reports)
109 {
110         /* Mutually exclusives, ensure correct input. */
111         BLI_assert((undoname && !step) || (!undoname && step));
112         CLOG_INFO(&LOG, 1, "name='%s', step=%d", undoname, step);
113         wmWindowManager *wm = CTX_wm_manager(C);
114         Scene *scene = CTX_data_scene(C);
115         ScrArea *sa = CTX_wm_area(C);
116
117         /* undo during jobs are running can easily lead to freeing data using by jobs,
118          * or they can just lead to freezing job in some other cases */
119         WM_jobs_kill_all(wm);
120
121         if (G.debug & G_DEBUG_IO) {
122                 Main *bmain = CTX_data_main(C);
123                 if (bmain->lock != NULL) {
124                         BKE_report(reports, RPT_INFO, "Checking sanity of current .blend file *BEFORE* undo step");
125                         BLO_main_validate_libraries(bmain, reports);
126                 }
127         }
128
129         /* TODO(campbell): undo_system: use undo system */
130         /* grease pencil can be can be used in plenty of spaces, so check it first */
131         if (ED_gpencil_session_active()) {
132                 return ED_undo_gpencil_step(C, step, undoname);
133         }
134         if (sa && (sa->spacetype == SPACE_VIEW3D)) {
135                 Object *obact = CTX_data_active_object(C);
136                 if (obact && (obact->type == OB_GPENCIL)) {
137                         ED_gpencil_toggle_brush_cursor(C, false, NULL);
138                 }
139         }
140
141         UndoStep *step_data_from_name = NULL;
142         int step_for_callback = step;
143         if (undoname != NULL) {
144                 step_data_from_name = BKE_undosys_step_find_by_name(wm->undo_stack, undoname);
145                 if (step_data_from_name == NULL) {
146                         return OPERATOR_CANCELLED;
147                 }
148
149                 /* TODO(campbell), could use simple optimization. */
150                 /* Pointers match on redo. */
151                 step_for_callback = (
152                         BLI_findindex(&wm->undo_stack->steps, step_data_from_name) <
153                         BLI_findindex(&wm->undo_stack->steps, wm->undo_stack->step_active)) ? 1 : -1;
154         }
155
156         /* App-Handlers (pre). */
157         {
158                 /* Note: ignore grease pencil for now. */
159                 Main *bmain = CTX_data_main(C);
160                 wm->op_undo_depth++;
161                 BLI_callback_exec(bmain, &scene->id, (step_for_callback > 0) ? BLI_CB_EVT_UNDO_PRE : BLI_CB_EVT_REDO_PRE);
162                 wm->op_undo_depth--;
163         }
164
165
166         /* Undo System */
167         {
168                 if (undoname) {
169                         BKE_undosys_step_undo_with_data(wm->undo_stack, C, step_data_from_name);
170                 }
171                 else {
172                         if (step == 1) {
173                                 BKE_undosys_step_undo(wm->undo_stack, C);
174                         }
175                         else {
176                                 BKE_undosys_step_redo(wm->undo_stack, C);
177                         }
178                 }
179
180                 /* Set special modes for grease pencil */
181                 if (sa && (sa->spacetype == SPACE_VIEW3D)) {
182                         Object *obact = CTX_data_active_object(C);
183                         if (obact && (obact->type == OB_GPENCIL)) {
184                                 /* set cursor */
185                                 if (ELEM(obact->mode, OB_MODE_PAINT_GPENCIL, OB_MODE_SCULPT_GPENCIL, OB_MODE_WEIGHT_GPENCIL)) {
186                                         ED_gpencil_toggle_brush_cursor(C, true, NULL);
187                                 }
188                                 else {
189                                         ED_gpencil_toggle_brush_cursor(C, false, NULL);
190                                 }
191                                 /* set workspace mode */
192                                 Base *basact = CTX_data_active_base(C);
193                                 ED_object_base_activate(C, basact);
194                         }
195                 }
196         }
197
198         /* App-Handlers (post). */
199         {
200                 Main *bmain = CTX_data_main(C);
201                 scene = CTX_data_scene(C);
202                 wm->op_undo_depth++;
203                 BLI_callback_exec(bmain, &scene->id, step_for_callback > 0 ? BLI_CB_EVT_UNDO_POST : BLI_CB_EVT_REDO_POST);
204                 wm->op_undo_depth--;
205         }
206
207         if (G.debug & G_DEBUG_IO) {
208                 Main *bmain = CTX_data_main(C);
209                 if (bmain->lock != NULL) {
210                         BKE_report(reports, RPT_INFO, "Checking sanity of current .blend file *AFTER* undo step");
211                         BLO_main_validate_libraries(bmain, reports);
212                 }
213         }
214
215         WM_event_add_notifier(C, NC_WINDOW, NULL);
216         WM_event_add_notifier(C, NC_WM | ND_UNDO, NULL);
217
218         WM_toolsystem_refresh_active(C);
219
220         Main *bmain = CTX_data_main(C);
221         WM_toolsystem_refresh_screen_all(bmain);
222
223         return OPERATOR_FINISHED;
224 }
225
226 void ED_undo_grouped_push(bContext *C, const char *str)
227 {
228         /* do nothing if previous undo task is the same as this one (or from the same undo group) */
229         wmWindowManager *wm = CTX_wm_manager(C);
230         const UndoStep *us = wm->undo_stack->step_active;
231         if (us && STREQ(str, us->name)) {
232                 BKE_undosys_stack_clear_active(wm->undo_stack);
233         }
234
235         /* push as usual */
236         ED_undo_push(C, str);
237 }
238
239 void ED_undo_pop(bContext *C)
240 {
241         ed_undo_step(C, 1, NULL, NULL);
242 }
243 void ED_undo_redo(bContext *C)
244 {
245         ed_undo_step(C, -1, NULL, NULL);
246 }
247
248 void ED_undo_push_op(bContext *C, wmOperator *op)
249 {
250         /* in future, get undo string info? */
251         ED_undo_push(C, op->type->name);
252 }
253
254 void ED_undo_grouped_push_op(bContext *C, wmOperator *op)
255 {
256         if (op->type->undo_group[0] != '\0') {
257                 ED_undo_grouped_push(C, op->type->undo_group);
258         }
259         else {
260                 ED_undo_grouped_push(C, op->type->name);
261         }
262 }
263
264 void ED_undo_pop_op(bContext *C, wmOperator *op)
265 {
266         /* search back a couple of undo's, in case something else added pushes */
267         ed_undo_step(C, 0, op->type->name, op->reports);
268 }
269
270 /* name optionally, function used to check for operator redo panel */
271 bool ED_undo_is_valid(const bContext *C, const char *undoname)
272 {
273         wmWindowManager *wm = CTX_wm_manager(C);
274         return BKE_undosys_stack_has_undo(wm->undo_stack, undoname);
275 }
276
277 bool ED_undo_is_memfile_compatible(const bContext *C)
278 {
279         /* Some modes don't co-exist with memfile undo, disable their use: T60593
280          * (this matches 2.7x behavior). */
281         ViewLayer *view_layer = CTX_data_view_layer(C);
282         if (view_layer != NULL) {
283                 Object *obact = OBACT(view_layer);
284                 if (obact != NULL) {
285                         if (obact->mode & (OB_MODE_SCULPT | OB_MODE_EDIT)) {
286                                 return false;
287                         }
288                 }
289         }
290         return true;
291 }
292
293
294 /**
295  * Ideally we wont access the stack directly,
296  * this is needed for modes which handle undo themselves (bypassing #ED_undo_push).
297  *
298  * Using global isn't great, this just avoids doing inline,
299  * causing 'BKE_global.h' & 'BKE_main.h' includes.
300  */
301 UndoStack *ED_undo_stack_get(void)
302 {
303         wmWindowManager *wm = G_MAIN->wm.first;
304         return wm->undo_stack;
305 }
306
307 /** \} */
308
309 /* -------------------------------------------------------------------- */
310 /** \name Undo, Undo Push & Redo Operators
311  * \{ */
312
313 static int ed_undo_exec(bContext *C, wmOperator *op)
314 {
315         /* "last operator" should disappear, later we can tie this with undo stack nicer */
316         WM_operator_stack_clear(CTX_wm_manager(C));
317         int ret = ed_undo_step(C, 1, NULL, op->reports);
318         if (ret & OPERATOR_FINISHED) {
319                 /* Keep button under the cursor active. */
320                 WM_event_add_mousemove(C);
321         }
322         return ret;
323 }
324
325 static int ed_undo_push_exec(bContext *C, wmOperator *op)
326 {
327         if (G.background) {
328                 /* Exception for background mode, see: T60934.
329                  * Note: since the undo stack isn't initialized on startup, background mode behavior
330                  * won't match regular usage, this is just for scripts to do explicit undo pushes. */
331                 wmWindowManager *wm = CTX_wm_manager(C);
332                 if (wm->undo_stack == NULL) {
333                         wm->undo_stack = BKE_undosys_stack_create();
334                 }
335         }
336         char str[BKE_UNDO_STR_MAX];
337         RNA_string_get(op->ptr, "message", str);
338         ED_undo_push(C, str);
339         return OPERATOR_FINISHED;
340 }
341
342 static int ed_redo_exec(bContext *C, wmOperator *op)
343 {
344         int ret = ed_undo_step(C, -1, NULL, op->reports);
345         if (ret & OPERATOR_FINISHED) {
346                 /* Keep button under the cursor active. */
347                 WM_event_add_mousemove(C);
348         }
349         return ret;
350 }
351
352 static int ed_undo_redo_exec(bContext *C, wmOperator *UNUSED(op))
353 {
354         wmOperator *last_op = WM_operator_last_redo(C);
355         int ret = ED_undo_operator_repeat(C, last_op);
356         ret = ret ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
357         if (ret & OPERATOR_FINISHED) {
358                 /* Keep button under the cursor active. */
359                 WM_event_add_mousemove(C);
360         }
361         return ret;
362 }
363
364 /* Disable in background mode, we could support if it's useful, T60934. */
365
366 static bool ed_undo_is_init_poll(bContext *C)
367 {
368         wmWindowManager *wm = CTX_wm_manager(C);
369         if (wm->undo_stack == NULL) {
370                 CTX_wm_operator_poll_msg_set(C, "Undo disabled at startup");
371                 return false;
372         }
373         return true;
374 }
375
376 static bool ed_undo_is_init_and_screenactive_poll(bContext *C)
377 {
378         if (ed_undo_is_init_poll(C) == false) {
379                 return false;
380         }
381         return ED_operator_screenactive(C);
382 }
383
384 static bool ed_undo_redo_poll(bContext *C)
385 {
386         wmOperator *last_op = WM_operator_last_redo(C);
387         return (last_op && ed_undo_is_init_and_screenactive_poll(C) &&
388                 WM_operator_check_ui_enabled(C, last_op->type->name));
389 }
390
391 void ED_OT_undo(wmOperatorType *ot)
392 {
393         /* identifiers */
394         ot->name = "Undo";
395         ot->description = "Undo previous action";
396         ot->idname = "ED_OT_undo";
397
398         /* api callbacks */
399         ot->exec = ed_undo_exec;
400         ot->poll = ed_undo_is_init_and_screenactive_poll;
401 }
402
403 void ED_OT_undo_push(wmOperatorType *ot)
404 {
405         /* identifiers */
406         ot->name = "Undo Push";
407         ot->description = "Add an undo state (internal use only)";
408         ot->idname = "ED_OT_undo_push";
409
410         /* api callbacks */
411         ot->exec = ed_undo_push_exec;
412         /* Unlike others undo operators this initializes undo stack. */
413         ot->poll = ED_operator_screenactive;
414
415         ot->flag = OPTYPE_INTERNAL;
416
417         RNA_def_string(ot->srna, "message", "Add an undo step *function may be moved*", BKE_UNDO_STR_MAX, "Undo Message", "");
418 }
419
420 void ED_OT_redo(wmOperatorType *ot)
421 {
422         /* identifiers */
423         ot->name = "Redo";
424         ot->description = "Redo previous action";
425         ot->idname = "ED_OT_redo";
426
427         /* api callbacks */
428         ot->exec = ed_redo_exec;
429         ot->poll = ed_undo_is_init_and_screenactive_poll;
430 }
431
432 void ED_OT_undo_redo(wmOperatorType *ot)
433 {
434         /* identifiers */
435         ot->name = "Undo and Redo";
436         ot->description = "Undo and redo previous action";
437         ot->idname = "ED_OT_undo_redo";
438
439         /* api callbacks */
440         ot->exec = ed_undo_redo_exec;
441         ot->poll = ed_undo_redo_poll;
442 }
443
444 /** \} */
445
446 /* -------------------------------------------------------------------- */
447 /** \name Operator Repeat
448  * \{ */
449
450 /* ui callbacks should call this rather than calling WM_operator_repeat() themselves */
451 int ED_undo_operator_repeat(bContext *C, wmOperator *op)
452 {
453         int ret = 0;
454
455         if (op) {
456                 CLOG_INFO(&LOG, 1, "idname='%s'", op->type->idname);
457                 wmWindowManager *wm = CTX_wm_manager(C);
458                 struct Scene *scene = CTX_data_scene(C);
459
460                 /* keep in sync with logic in view3d_panel_operator_redo() */
461                 ARegion *ar_orig = CTX_wm_region(C);
462                 ARegion *ar_win = BKE_area_find_region_active_win(CTX_wm_area(C));
463
464                 if (ar_win) {
465                         CTX_wm_region_set(C, ar_win);
466                 }
467
468                 if ((WM_operator_repeat_check(C, op)) &&
469                     (WM_operator_poll(C, op->type)) &&
470                      /* note, undo/redo cant run if there are jobs active,
471                       * check for screen jobs only so jobs like material/texture/world preview
472                       * (which copy their data), wont stop redo, see [#29579]],
473                       *
474                       * note, - WM_operator_check_ui_enabled() jobs test _must_ stay in sync with this */
475                     (WM_jobs_test(wm, scene, WM_JOB_TYPE_ANY) == 0))
476                 {
477                         int retval;
478
479                         if (G.debug & G_DEBUG)
480                                 printf("redo_cb: operator redo %s\n", op->type->name);
481
482                         WM_operator_free_all_after(wm, op);
483
484                         ED_undo_pop_op(C, op);
485
486                         if (op->type->check) {
487                                 if (op->type->check(C, op)) {
488                                         /* check for popup and re-layout buttons */
489                                         ARegion *ar_menu = CTX_wm_menu(C);
490                                         if (ar_menu) {
491                                                 ED_region_tag_refresh_ui(ar_menu);
492                                         }
493                                 }
494                         }
495
496                         if (op->type->flag & OPTYPE_USE_EVAL_DATA) {
497                                 /* We need to force refresh of depsgraph after undo step,
498                                  * redoing the operator *may* rely on some valid evaluated data. */
499                                 Main *bmain = CTX_data_main(C);
500                                 scene = CTX_data_scene(C);
501                                 ViewLayer *view_layer = CTX_data_view_layer(C);
502                                 BKE_scene_view_layer_graph_evaluated_ensure(bmain, scene, view_layer);
503                         }
504
505                         retval = WM_operator_repeat(C, op);
506                         if ((retval & OPERATOR_FINISHED) == 0) {
507                                 if (G.debug & G_DEBUG)
508                                         printf("redo_cb: operator redo failed: %s, return %d\n", op->type->name, retval);
509                                 ED_undo_redo(C);
510                         }
511                         else {
512                                 ret = 1;
513                         }
514                 }
515                 else {
516                         if (G.debug & G_DEBUG) {
517                                 printf("redo_cb: WM_operator_repeat_check returned false %s\n", op->type->name);
518                         }
519                 }
520
521                 /* set region back */
522                 CTX_wm_region_set(C, ar_orig);
523         }
524         else {
525                 CLOG_WARN(&LOG, "called with NULL 'op'");
526         }
527
528         return ret;
529 }
530
531
532 void ED_undo_operator_repeat_cb(bContext *C, void *arg_op, void *UNUSED(arg_unused))
533 {
534         ED_undo_operator_repeat(C, (wmOperator *)arg_op);
535 }
536
537 void ED_undo_operator_repeat_cb_evt(bContext *C, void *arg_op, int UNUSED(arg_event))
538 {
539         ED_undo_operator_repeat(C, (wmOperator *)arg_op);
540 }
541
542 /** \} */
543
544 /* -------------------------------------------------------------------- */
545 /** \name Undo History Operator
546  * \{ */
547
548 /* create enum based on undo items */
549 static const EnumPropertyItem *rna_undo_itemf(bContext *C, int *totitem)
550 {
551         EnumPropertyItem item_tmp = {0}, *item = NULL;
552         int i = 0;
553
554         wmWindowManager *wm = CTX_wm_manager(C);
555         if (wm->undo_stack == NULL) {
556                 return NULL;
557         }
558
559         for (UndoStep *us = wm->undo_stack->steps.first; us; us = us->next, i++) {
560                 if (us->skip == false) {
561                         item_tmp.identifier = us->name;
562                         item_tmp.name = IFACE_(us->name);
563                         if (us == wm->undo_stack->step_active) {
564                                 item_tmp.icon = ICON_HIDE_OFF;
565                         }
566                         else {
567                                 item_tmp.icon = ICON_NONE;
568                         }
569                         item_tmp.value = i;
570                         RNA_enum_item_add(&item, totitem, &item_tmp);
571                 }
572         }
573         RNA_enum_item_end(&item, totitem);
574
575         return item;
576 }
577
578
579 static int undo_history_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
580 {
581         int totitem = 0;
582
583         {
584                 const EnumPropertyItem *item = rna_undo_itemf(C, &totitem);
585
586                 if (totitem > 0) {
587                         uiPopupMenu *pup = UI_popup_menu_begin(C, RNA_struct_ui_name(op->type->srna), ICON_NONE);
588                         uiLayout *layout = UI_popup_menu_layout(pup);
589                         uiLayout *split = uiLayoutSplit(layout, 0.0f, false);
590                         uiLayout *column = NULL;
591                         const int col_size = 20 + totitem / 12;
592                         int i, c;
593                         bool add_col = true;
594
595                         for (c = 0, i = totitem; i--;) {
596                                 if (add_col && !(c % col_size)) {
597                                         column = uiLayoutColumn(split, false);
598                                         add_col = false;
599                                 }
600                                 if (item[i].identifier) {
601                                         uiItemIntO(column, item[i].name, item[i].icon, op->type->idname, "item", item[i].value);
602                                         ++c;
603                                         add_col = true;
604                                 }
605                         }
606
607                         MEM_freeN((void *)item);
608
609                         UI_popup_menu_end(C, pup);
610                 }
611
612         }
613         return OPERATOR_CANCELLED;
614 }
615
616 /* note: also check ed_undo_step() in top if you change notifiers */
617 static int undo_history_exec(bContext *C, wmOperator *op)
618 {
619         PropertyRNA *prop = RNA_struct_find_property(op->ptr, "item");
620         if (RNA_property_is_set(op->ptr, prop)) {
621                 int item = RNA_property_int_get(op->ptr, prop);
622                 wmWindowManager *wm = CTX_wm_manager(C);
623                 BKE_undosys_step_undo_from_index(wm->undo_stack, C, item);
624                 WM_event_add_notifier(C, NC_WINDOW, NULL);
625                 return OPERATOR_FINISHED;
626         }
627         return OPERATOR_CANCELLED;
628 }
629
630 void ED_OT_undo_history(wmOperatorType *ot)
631 {
632         /* identifiers */
633         ot->name = "Undo History";
634         ot->description = "Redo specific action in history";
635         ot->idname = "ED_OT_undo_history";
636
637         /* api callbacks */
638         ot->invoke = undo_history_invoke;
639         ot->exec = undo_history_exec;
640         ot->poll = ed_undo_is_init_and_screenactive_poll;
641
642         RNA_def_int(ot->srna, "item", 0, 0, INT_MAX, "Item", "", 0, INT_MAX);
643
644 }
645
646 /** \} */
647
648 /* -------------------------------------------------------------------- */
649 /** \name Undo Helper Functions
650  * \{ */
651
652 void ED_undo_object_set_active_or_warn(ViewLayer *view_layer, Object *ob, const char *info, CLG_LogRef *log)
653 {
654         Object *ob_prev = OBACT(view_layer);
655         if (ob_prev != ob) {
656                 Base *base = BKE_view_layer_base_find(view_layer, ob);
657                 if (base != NULL) {
658                         view_layer->basact = base;
659                 }
660                 else {
661                         /* Should never fail, may not crash but can give odd behavior. */
662                         CLOG_WARN(log, "'%s' failed to restore active object: '%s'", info, ob->id.name + 2);
663                 }
664         }
665 }
666
667 void ED_undo_object_editmode_restore_helper(
668         struct bContext *C, Object **object_array, uint object_array_len, uint object_array_stride)
669 {
670         Main *bmain = CTX_data_main(C);
671         ViewLayer *view_layer = CTX_data_view_layer(C);
672         uint bases_len = 0;
673         /* Don't request unique data because we wan't to de-select objects when exiting edit-mode
674          * for that to be done on all objects we can't skip ones that share data. */
675         Base **bases = BKE_view_layer_array_from_bases_in_edit_mode(
676                 view_layer, NULL, &bases_len);
677         for (uint i = 0; i < bases_len; i++) {
678                 ((ID *)bases[i]->object->data)->tag |= LIB_TAG_DOIT;
679         }
680         Scene *scene = CTX_data_scene(C);
681         Object **ob_p = object_array;
682         for (uint i = 0; i < object_array_len; i++, ob_p = POINTER_OFFSET(ob_p, object_array_stride)) {
683                 Object *obedit = *ob_p;
684                 ED_object_editmode_enter_ex(bmain, scene, obedit, EM_NO_CONTEXT);
685                 ((ID *)obedit->data)->tag &= ~LIB_TAG_DOIT;
686         }
687         for (uint i = 0; i < bases_len; i++) {
688                 ID *id = bases[i]->object->data;
689                 if (id->tag & LIB_TAG_DOIT) {
690                         ED_object_editmode_exit_ex(bmain, scene, bases[i]->object, EM_FREEDATA);
691                         /* Ideally we would know the selection state it was before entering edit-mode,
692                          * for now follow the convention of having them unselected when exiting the mode. */
693                         ED_object_base_select(bases[i], BA_DESELECT);
694
695                 }
696         }
697         MEM_freeN(bases);
698 }
699
700 /** \} */