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