Fix T61272: Undo fails to track multi-edit mode enter/exit
[blender.git] / source / blender / editors / metaball / editmball_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
17 /** \file \ingroup edmeta
18  */
19
20 #include <math.h>
21 #include <string.h>
22
23 #include "MEM_guardedalloc.h"
24
25 #include "CLG_log.h"
26
27 #include "BLI_utildefines.h"
28 #include "BLI_listbase.h"
29 #include "BLI_array_utils.h"
30
31 #include "DNA_defs.h"
32 #include "DNA_meta_types.h"
33 #include "DNA_object_types.h"
34
35 #include "BKE_context.h"
36 #include "BKE_layer.h"
37 #include "BKE_undo_system.h"
38
39 #include "DEG_depsgraph.h"
40
41 #include "ED_object.h"
42 #include "ED_mball.h"
43 #include "ED_undo.h"
44 #include "ED_util.h"
45
46 #include "WM_types.h"
47 #include "WM_api.h"
48
49 /** We only need this locally. */
50 static CLG_LogRef LOG = {"ed.undo.mball"};
51
52 /* -------------------------------------------------------------------- */
53 /** \name Undo Conversion
54  * \{ */
55
56 typedef struct UndoMBall {
57         ListBase editelems;
58         int lastelem_index;
59         size_t undo_size;
60 } UndoMBall;
61
62 /* free all MetaElems from ListBase */
63 static void freeMetaElemlist(ListBase *lb)
64 {
65         MetaElem *ml;
66
67         if (lb == NULL) {
68                 return;
69         }
70
71         while ((ml = BLI_pophead(lb))) {
72                 MEM_freeN(ml);
73         }
74 }
75
76 static void undomball_to_editmball(UndoMBall *umb, MetaBall *mb)
77 {
78         freeMetaElemlist(mb->editelems);
79         mb->lastelem = NULL;
80
81         /* copy 'undo' MetaElems to 'edit' MetaElems */
82         int index = 0;
83         for (MetaElem *ml_undo = umb->editelems.first; ml_undo; ml_undo = ml_undo->next, index += 1) {
84                 MetaElem *ml_edit = MEM_dupallocN(ml_undo);
85                 BLI_addtail(mb->editelems, ml_edit);
86                 if (index == umb->lastelem_index) {
87                         mb->lastelem = ml_edit;
88                 }
89         }
90 }
91
92 static void *editmball_from_undomball(UndoMBall *umb, MetaBall *mb)
93 {
94         BLI_assert(BLI_array_is_zeroed(umb, 1));
95
96         /* allocate memory for undo ListBase */
97         umb->lastelem_index = -1;
98
99         /* copy contents of current ListBase to the undo ListBase */
100         int index = 0;
101         for (MetaElem *ml_edit = mb->editelems->first; ml_edit; ml_edit = ml_edit->next, index += 1) {
102                 MetaElem *ml_undo = MEM_dupallocN(ml_edit);
103                 BLI_addtail(&umb->editelems, ml_undo);
104                 if (ml_edit == mb->lastelem) {
105                         umb->lastelem_index = index;
106                 }
107                 umb->undo_size += sizeof(MetaElem);
108         }
109
110         return umb;
111 }
112
113 /* free undo ListBase of MetaElems */
114 static void undomball_free_data(UndoMBall *umb)
115 {
116         freeMetaElemlist(&umb->editelems);
117 }
118
119 static Object *editmball_object_from_context(bContext *C)
120 {
121         Object *obedit = CTX_data_edit_object(C);
122         if (obedit && obedit->type == OB_MBALL) {
123                 MetaBall *mb = obedit->data;
124                 if (mb->editelems != NULL) {
125                         return obedit;
126                 }
127         }
128         return NULL;
129 }
130
131 /** \} */
132
133 /* -------------------------------------------------------------------- */
134 /** \name Implements ED Undo System
135  *
136  * \note This is similar for all edit-mode types.
137  * \{ */
138
139 typedef struct MBallUndoStep_Elem {
140         UndoRefID_Object obedit_ref;
141         UndoMBall data;
142 } MBallUndoStep_Elem;
143
144 typedef struct MBallUndoStep {
145         UndoStep step;
146         MBallUndoStep_Elem *elems;
147         uint                elems_len;
148 } MBallUndoStep;
149
150 static bool mball_undosys_poll(bContext *C)
151 {
152         return editmball_object_from_context(C) != NULL;
153 }
154
155 static bool mball_undosys_step_encode(struct bContext *C, struct Main *UNUSED(bmain), UndoStep *us_p)
156 {
157         MBallUndoStep *us = (MBallUndoStep *)us_p;
158
159         /* Important not to use the 3D view when getting objects because all objects
160          * outside of this list will be moved out of edit-mode when reading back undo steps. */
161         ViewLayer *view_layer = CTX_data_view_layer(C);
162         uint objects_len = 0;
163         Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, NULL, &objects_len);
164
165         us->elems = MEM_callocN(sizeof(*us->elems) * objects_len, __func__);
166         us->elems_len = objects_len;
167
168         for (uint i = 0; i < objects_len; i++) {
169                 Object *ob = objects[i];
170                 MBallUndoStep_Elem *elem = &us->elems[i];
171
172                 elem->obedit_ref.ptr = ob;
173                 MetaBall *mb = ob->data;
174                 editmball_from_undomball(&elem->data, mb);
175                 us->step.data_size += elem->data.undo_size;
176         }
177         MEM_freeN(objects);
178         return true;
179 }
180
181 static void mball_undosys_step_decode(struct bContext *C, struct Main *UNUSED(bmain), UndoStep *us_p, int UNUSED(dir))
182 {
183         MBallUndoStep *us = (MBallUndoStep *)us_p;
184
185         /* Load all our objects  into edit-mode, clear everything else. */
186         ED_undo_object_editmode_restore_helper(C, &us->elems[0].obedit_ref.ptr, us->elems_len, sizeof(*us->elems));
187
188         BLI_assert(mball_undosys_poll(C));
189
190         for (uint i = 0; i < us->elems_len; i++) {
191                 MBallUndoStep_Elem *elem = &us->elems[i];
192                 Object *obedit = elem->obedit_ref.ptr;
193                 MetaBall *mb = obedit->data;
194                 if (mb->editelems == NULL) {
195                         /* Should never fail, may not crash but can give odd behavior. */
196                         CLOG_ERROR(&LOG, "name='%s', failed to enter edit-mode for object '%s', undo state invalid", us_p->name, obedit->id.name);
197                         continue;
198                 }
199                 undomball_to_editmball(&elem->data, mb);
200                 DEG_id_tag_update(&obedit->id, ID_RECALC_GEOMETRY);
201         }
202
203         /* The first element is always active */
204         ED_undo_object_set_active_or_warn(CTX_data_view_layer(C), us->elems[0].obedit_ref.ptr, us_p->name, &LOG);
205
206         WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL);
207 }
208
209 static void mball_undosys_step_free(UndoStep *us_p)
210 {
211         MBallUndoStep *us = (MBallUndoStep *)us_p;
212
213         for (uint i = 0; i < us->elems_len; i++) {
214                 MBallUndoStep_Elem *elem = &us->elems[i];
215                 undomball_free_data(&elem->data);
216         }
217         MEM_freeN(us->elems);
218 }
219
220 static void mball_undosys_foreach_ID_ref(
221         UndoStep *us_p, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data)
222 {
223         MBallUndoStep *us = (MBallUndoStep *)us_p;
224
225         for (uint i = 0; i < us->elems_len; i++) {
226                 MBallUndoStep_Elem *elem = &us->elems[i];
227                 foreach_ID_ref_fn(user_data, ((UndoRefID *)&elem->obedit_ref));
228         }
229 }
230
231 /* Export for ED_undo_sys. */
232 void ED_mball_undosys_type(UndoType *ut)
233 {
234         ut->name = "Edit MBall";
235         ut->poll = mball_undosys_poll;
236         ut->step_encode = mball_undosys_step_encode;
237         ut->step_decode = mball_undosys_step_decode;
238         ut->step_free = mball_undosys_step_free;
239
240         ut->step_foreach_ID_ref = mball_undosys_foreach_ID_ref;
241
242         ut->use_context = true;
243
244         ut->step_size = sizeof(MBallUndoStep);
245
246 }
247
248 /** \} */