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