Option not to select with un-hide
[blender.git] / source / blender / editors / metaball / mball_edit.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  
23  * Contributor(s): none yet.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/editors/metaball/mball_edit.c
29  *  \ingroup edmeta
30  */
31
32
33 #include <math.h>
34 #include <string.h>
35
36 #include "MEM_guardedalloc.h"
37
38 #include "BLI_blenlib.h"
39 #include "BLI_math.h"
40 #include "BLI_rand.h"
41 #include "BLI_utildefines.h"
42
43 #include "DNA_defs.h"
44 #include "DNA_meta_types.h"
45 #include "DNA_object_types.h"
46 #include "DNA_scene_types.h"
47
48 #include "RNA_define.h"
49 #include "RNA_access.h"
50
51 #include "BKE_depsgraph.h"
52 #include "BKE_context.h"
53 #include "BKE_mball.h"
54
55 #include "ED_mball.h"
56 #include "ED_screen.h"
57 #include "ED_view3d.h"
58 #include "ED_util.h"
59
60 #include "WM_api.h"
61 #include "WM_types.h"
62
63 #include "mball_intern.h"
64
65 /* This function is used to free all MetaElems from MetaBall */
66 void ED_mball_editmball_free(Object *obedit)
67 {
68         MetaBall *mb = (MetaBall *)obedit->data;
69
70         mb->editelems = NULL;
71         mb->lastelem = NULL;
72 }
73
74 /* This function is called, when MetaBall Object is
75  * switched from object mode to edit mode */
76 void ED_mball_editmball_make(Object *obedit)
77 {
78         MetaBall *mb = (MetaBall *)obedit->data;
79         MetaElem *ml; /*, *newml;*/
80
81         ml = mb->elems.first;
82         
83         while (ml) {
84                 if (ml->flag & SELECT) mb->lastelem = ml;
85                 ml = ml->next;
86         }
87
88         mb->editelems = &mb->elems;
89 }
90
91 /* This function is called, when MetaBall Object switched from
92  * edit mode to object mode. List of MetaElements is copied
93  * from object->data->edit_elems to object->data->elems. */
94 void ED_mball_editmball_load(Object *UNUSED(obedit))
95 {
96 }
97
98 /* Add metaelem primitive to metaball object (which is in edit mode) */
99 MetaElem *ED_mball_add_primitive(bContext *UNUSED(C), Object *obedit, float mat[4][4], float dia, int type)
100 {
101         MetaBall *mball = (MetaBall *)obedit->data;
102         MetaElem *ml;
103
104         /* Deselect all existing metaelems */
105         ml = mball->editelems->first;
106         while (ml) {
107                 ml->flag &= ~SELECT;
108                 ml = ml->next;
109         }
110         
111         ml = BKE_mball_element_add(mball, type);
112         ml->rad *= dia;
113         mball->wiresize *= dia;
114         mball->rendersize *= dia;
115         copy_v3_v3(&ml->x, mat[3]);
116
117         ml->flag |= SELECT;
118         mball->lastelem = ml;
119         return ml;
120 }
121
122 /***************************** Select/Deselect operator *****************************/
123
124 /* Select or deselect all MetaElements */
125 static int mball_select_all_exec(bContext *C, wmOperator *op)
126 {
127         Object *obedit = CTX_data_edit_object(C);
128         MetaBall *mb = (MetaBall *)obedit->data;
129         MetaElem *ml;
130         int action = RNA_enum_get(op->ptr, "action");
131
132         if (BLI_listbase_is_empty(mb->editelems))
133                 return OPERATOR_CANCELLED;
134
135         if (action == SEL_TOGGLE) {
136                 action = SEL_SELECT;
137                 for (ml = mb->editelems->first; ml; ml = ml->next) {
138                         if (ml->flag & SELECT) {
139                                 action = SEL_DESELECT;
140                                 break;
141                         }
142                 }
143         }
144
145         switch (action) {
146                 case SEL_SELECT:
147                         BKE_mball_select_all(mb);
148                         break;
149                 case SEL_DESELECT:
150                         BKE_mball_deselect_all(mb);
151                         break;
152                 case SEL_INVERT:
153                         BKE_mball_select_swap(mb);
154                         break;
155         }
156
157         WM_event_add_notifier(C, NC_GEOM | ND_SELECT, mb);
158
159         return OPERATOR_FINISHED;
160 }
161
162 void MBALL_OT_select_all(wmOperatorType *ot)
163 {
164         /* identifiers */
165         ot->name = "(De)select All";
166         ot->description = "Change selection of all meta elements";
167         ot->idname = "MBALL_OT_select_all";
168
169         /* callback functions */
170         ot->exec = mball_select_all_exec;
171         ot->poll = ED_operator_editmball;
172
173         /* flags */
174         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
175
176         WM_operator_properties_select_all(ot);
177 }
178
179
180 /* -------------------------------------------------------------------- */
181 /* Select Similar */
182
183 enum {
184         SIMMBALL_TYPE = 1,
185         SIMMBALL_RADIUS,
186         SIMMBALL_STIFFNESS,
187         SIMMBALL_ROTATION
188 };
189
190 static const EnumPropertyItem prop_similar_types[] = {
191         {SIMMBALL_TYPE, "TYPE", 0, "Type", ""},
192         {SIMMBALL_RADIUS, "RADIUS", 0, "Radius", ""},
193         {SIMMBALL_STIFFNESS, "STIFFNESS", 0, "Stiffness", ""},
194         {SIMMBALL_ROTATION, "ROTATION", 0, "Rotation", ""},
195         {0, NULL, 0, NULL, NULL}
196 };
197
198 static bool mball_select_similar_type(MetaBall *mb)
199 {
200         MetaElem *ml;
201         bool changed = false;
202
203         for (ml = mb->editelems->first; ml; ml = ml->next) {
204                 if (ml->flag & SELECT) {
205                         MetaElem *ml_iter;
206
207                         for (ml_iter = mb->editelems->first; ml_iter; ml_iter = ml_iter->next) {
208                                 if ((ml_iter->flag & SELECT) == 0) {
209                                         if (ml->type == ml_iter->type) {
210                                                 ml_iter->flag |= SELECT;
211                                                 changed = true;
212                                         }
213                                 }
214                         }
215                 }
216         }
217
218         return changed;
219 }
220
221 static bool mball_select_similar_radius(MetaBall *mb, const float thresh)
222 {
223         MetaElem *ml;
224         bool changed = false;
225
226         for (ml = mb->editelems->first; ml; ml = ml->next) {
227                 if (ml->flag & SELECT) {
228                         MetaElem *ml_iter;
229
230                         for (ml_iter = mb->editelems->first; ml_iter; ml_iter = ml_iter->next) {
231                                 if ((ml_iter->flag & SELECT) == 0) {
232                                         if (fabsf(ml_iter->rad - ml->rad) <= (thresh * ml->rad)) {
233                                                 ml_iter->flag |= SELECT;
234                                                 changed = true;
235                                         }
236                                 }
237                         }
238                 }
239         }
240
241         return changed;
242 }
243
244 static bool mball_select_similar_stiffness(MetaBall *mb, const float thresh)
245 {
246         MetaElem *ml;
247         bool changed = false;
248
249         for (ml = mb->editelems->first; ml; ml = ml->next) {
250                 if (ml->flag & SELECT) {
251                         MetaElem *ml_iter;
252
253                         for (ml_iter = mb->editelems->first; ml_iter; ml_iter = ml_iter->next) {
254                                 if ((ml_iter->flag & SELECT) == 0) {
255                                         if (fabsf(ml_iter->s - ml->s) <= thresh) {
256                                                 ml_iter->flag |= SELECT;
257                                                 changed = true;
258                                         }
259                                 }
260                         }
261                 }
262         }
263
264         return changed;
265 }
266
267 static bool mball_select_similar_rotation(MetaBall *mb, const float thresh)
268 {
269         const float thresh_rad = thresh * (float)M_PI_2;
270         MetaElem *ml;
271         bool changed = false;
272
273         for (ml = mb->editelems->first; ml; ml = ml->next) {
274                 if (ml->flag & SELECT) {
275                         MetaElem *ml_iter;
276
277                         float ml_mat[3][3];
278
279                         unit_m3(ml_mat);
280                         mul_qt_v3(ml->quat, ml_mat[0]);
281                         mul_qt_v3(ml->quat, ml_mat[1]);
282                         mul_qt_v3(ml->quat, ml_mat[2]);
283                         normalize_m3(ml_mat);
284
285                         for (ml_iter = mb->editelems->first; ml_iter; ml_iter = ml_iter->next) {
286                                 if ((ml_iter->flag & SELECT) == 0) {
287                                         float ml_iter_mat[3][3];
288
289                                         unit_m3(ml_iter_mat);
290                                         mul_qt_v3(ml_iter->quat, ml_iter_mat[0]);
291                                         mul_qt_v3(ml_iter->quat, ml_iter_mat[1]);
292                                         mul_qt_v3(ml_iter->quat, ml_iter_mat[2]);
293                                         normalize_m3(ml_iter_mat);
294
295                                         if ((angle_normalized_v3v3(ml_mat[0], ml_iter_mat[0]) +
296                                              angle_normalized_v3v3(ml_mat[1], ml_iter_mat[1]) +
297                                              angle_normalized_v3v3(ml_mat[2], ml_iter_mat[2])) < thresh_rad)
298                                         {
299                                                 ml_iter->flag |= SELECT;
300                                                 changed = true;
301                                         }
302                                 }
303                         }
304                 }
305         }
306
307         return changed;
308 }
309
310 static int mball_select_similar_exec(bContext *C, wmOperator *op)
311 {
312         Object *obedit = CTX_data_edit_object(C);
313         MetaBall *mb = (MetaBall *)obedit->data;
314
315         int type = RNA_enum_get(op->ptr, "type");
316         float thresh = RNA_float_get(op->ptr, "threshold");
317         bool changed = false;
318
319         switch (type) {
320                 case SIMMBALL_TYPE:
321                         changed = mball_select_similar_type(mb);
322                         break;
323                 case SIMMBALL_RADIUS:
324                         changed = mball_select_similar_radius(mb, thresh);
325                         break;
326                 case SIMMBALL_STIFFNESS:
327                         changed = mball_select_similar_stiffness(mb, thresh);
328                         break;
329                 case SIMMBALL_ROTATION:
330                         changed = mball_select_similar_rotation(mb, thresh);
331                         break;
332                 default:
333                         BLI_assert(0);
334                         break;
335         }
336
337         if (changed) {
338                 WM_event_add_notifier(C, NC_GEOM | ND_SELECT, mb);
339         }
340
341         return OPERATOR_FINISHED;
342 }
343
344 void MBALL_OT_select_similar(wmOperatorType *ot)
345 {
346         /* identifiers */
347         ot->name = "Select Similar";
348         ot->idname = "MBALL_OT_select_similar";
349
350         /* callback functions */
351         ot->invoke = WM_menu_invoke;
352         ot->exec = mball_select_similar_exec;
353         ot->poll = ED_operator_editmball;
354         ot->description = "Select similar metaballs by property types";
355
356         /* flags */
357         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
358
359         /* properties */
360         ot->prop = RNA_def_enum(ot->srna, "type", prop_similar_types, 0, "Type", "");
361
362         RNA_def_float(ot->srna, "threshold", 0.1, 0.0, 1.0, "Threshold", "", 0.01, 1.0);
363 }
364
365
366 /***************************** Select random operator *****************************/
367
368 /* Random metaball selection */
369 static int select_random_metaelems_exec(bContext *C, wmOperator *op)
370 {
371         Object *obedit = CTX_data_edit_object(C);
372         MetaBall *mb = (MetaBall *)obedit->data;
373         MetaElem *ml;
374         const bool select = (RNA_enum_get(op->ptr, "action") == SEL_SELECT);
375         const float randfac = RNA_float_get(op->ptr, "percent") / 100.0f;
376         const int seed = WM_operator_properties_select_random_seed_increment_get(op);
377         
378         RNG *rng = BLI_rng_new_srandom(seed);
379
380         for (ml = mb->editelems->first; ml; ml = ml->next) {
381                 if (BLI_rng_get_float(rng) < randfac) {
382                         if (select)
383                                 ml->flag |= SELECT;
384                         else
385                                 ml->flag &= ~SELECT;
386                 }
387         }
388
389         BLI_rng_free(rng);
390
391         WM_event_add_notifier(C, NC_GEOM | ND_SELECT, mb);
392         
393         return OPERATOR_FINISHED;
394 }
395
396
397 void MBALL_OT_select_random_metaelems(struct wmOperatorType *ot)
398 {
399         /* identifiers */
400         ot->name = "Select Random";
401         ot->description = "Randomly select metaelements";
402         ot->idname = "MBALL_OT_select_random_metaelems";
403         
404         /* callback functions */
405         ot->exec = select_random_metaelems_exec;
406         ot->poll = ED_operator_editmball;
407         
408         /* flags */
409         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
410         
411         /* properties */
412         WM_operator_properties_select_random(ot);
413 }
414
415 /***************************** Duplicate operator *****************************/
416
417 /* Duplicate selected MetaElements */
418 static int duplicate_metaelems_exec(bContext *C, wmOperator *UNUSED(op))
419 {
420         Object *obedit = CTX_data_edit_object(C);
421         MetaBall *mb = (MetaBall *)obedit->data;
422         MetaElem *ml, *newml;
423         
424         ml = mb->editelems->last;
425         if (ml) {
426                 while (ml) {
427                         if (ml->flag & SELECT) {
428                                 newml = MEM_dupallocN(ml);
429                                 BLI_addtail(mb->editelems, newml);
430                                 mb->lastelem = newml;
431                                 ml->flag &= ~SELECT;
432                         }
433                         ml = ml->prev;
434                 }
435                 WM_event_add_notifier(C, NC_GEOM | ND_DATA, mb);
436                 DAG_id_tag_update(obedit->data, 0);
437         }
438
439         return OPERATOR_FINISHED;
440 }
441
442 void MBALL_OT_duplicate_metaelems(wmOperatorType *ot)
443 {
444         /* identifiers */
445         ot->name = "Duplicate Metaelements";
446         ot->description = "Duplicate selected metaelement(s)";
447         ot->idname = "MBALL_OT_duplicate_metaelems";
448
449         /* callback functions */
450         ot->exec = duplicate_metaelems_exec;
451         ot->poll = ED_operator_editmball;
452
453         /* flags */
454         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
455 }
456
457 /***************************** Delete operator *****************************/
458
459 /* Delete all selected MetaElems (not MetaBall) */
460 static int delete_metaelems_exec(bContext *C, wmOperator *UNUSED(op))
461 {
462         Object *obedit = CTX_data_edit_object(C);
463         MetaBall *mb = (MetaBall *)obedit->data;
464         MetaElem *ml, *next;
465         
466         ml = mb->editelems->first;
467         if (ml) {
468                 while (ml) {
469                         next = ml->next;
470                         if (ml->flag & SELECT) {
471                                 if (mb->lastelem == ml) mb->lastelem = NULL;
472                                 BLI_remlink(mb->editelems, ml);
473                                 MEM_freeN(ml);
474                         }
475                         ml = next;
476                 }
477                 WM_event_add_notifier(C, NC_GEOM | ND_DATA, mb);
478                 DAG_id_tag_update(obedit->data, 0);
479         }
480
481         return OPERATOR_FINISHED;
482 }
483
484 void MBALL_OT_delete_metaelems(wmOperatorType *ot)
485 {
486         /* identifiers */
487         ot->name = "Delete";
488         ot->description = "Delete selected metaelement(s)";
489         ot->idname = "MBALL_OT_delete_metaelems";
490
491         /* callback functions */
492         ot->exec = delete_metaelems_exec;
493         ot->poll = ED_operator_editmball;
494
495         /* flags */
496         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
497 }
498
499 /***************************** Hide operator *****************************/
500
501 /* Hide selected MetaElems */
502 static int hide_metaelems_exec(bContext *C, wmOperator *op)
503 {
504         Object *obedit = CTX_data_edit_object(C);
505         MetaBall *mb = (MetaBall *)obedit->data;
506         MetaElem *ml;
507         const bool invert = RNA_boolean_get(op->ptr, "unselected") ? SELECT : 0;
508
509         ml = mb->editelems->first;
510
511         if (ml) {
512                 while (ml) {
513                         if ((ml->flag & SELECT) != invert)
514                                 ml->flag |= MB_HIDE;
515                         ml = ml->next;
516                 }
517                 WM_event_add_notifier(C, NC_GEOM | ND_DATA, mb);
518                 DAG_id_tag_update(obedit->data, 0);
519         }
520
521         return OPERATOR_FINISHED;
522 }
523
524 void MBALL_OT_hide_metaelems(wmOperatorType *ot)
525 {
526         /* identifiers */
527         ot->name = "Hide";
528         ot->description = "Hide (un)selected metaelement(s)";
529         ot->idname = "MBALL_OT_hide_metaelems";
530
531         /* callback functions */
532         ot->exec = hide_metaelems_exec;
533         ot->poll = ED_operator_editmball;
534
535         /* flags */
536         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
537         
538         /* props */
539         RNA_def_boolean(ot->srna, "unselected", false, "Unselected", "Hide unselected rather than selected");
540 }
541
542 /***************************** Unhide operator *****************************/
543
544 /* Unhide all edited MetaElems */
545 static int reveal_metaelems_exec(bContext *C, wmOperator *op)
546 {
547         Object *obedit = CTX_data_edit_object(C);
548         MetaBall *mb = (MetaBall *)obedit->data;
549         const bool select = RNA_boolean_get(op->ptr, "select");
550         bool changed = false;
551
552         for (MetaElem *ml = mb->editelems->first; ml; ml = ml->next) {
553                 if (ml->flag & MB_HIDE) {
554                         SET_FLAG_FROM_TEST(ml->flag, select, SELECT);
555                         ml->flag &= ~MB_HIDE;
556                         changed = true;
557                 }
558         }
559         if (changed) {
560                 WM_event_add_notifier(C, NC_GEOM | ND_DATA, mb);
561                 DAG_id_tag_update(obedit->data, 0);
562         }
563         
564         return OPERATOR_FINISHED;
565 }
566
567 void MBALL_OT_reveal_metaelems(wmOperatorType *ot)
568 {
569         /* identifiers */
570         ot->name = "Reveal";
571         ot->description = "Reveal all hidden metaelements";
572         ot->idname = "MBALL_OT_reveal_metaelems";
573         
574         /* callback functions */
575         ot->exec = reveal_metaelems_exec;
576         ot->poll = ED_operator_editmball;
577         
578         /* flags */
579         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
580
581         /* props */
582         RNA_def_boolean(ot->srna, "select", true, "Select", "");
583 }
584
585 /* Select MetaElement with mouse click (user can select radius circle or
586  * stiffness circle) */
587 bool ED_mball_select_pick(bContext *C, const int mval[2], bool extend, bool deselect, bool toggle)
588 {
589         static MetaElem *startelem = NULL;
590         Object *obedit = CTX_data_edit_object(C);
591         ViewContext vc;
592         MetaBall *mb = (MetaBall *)obedit->data;
593         MetaElem *ml, *ml_act = NULL;
594         int a, hits;
595         unsigned int buffer[MAXPICKBUF];
596         rcti rect;
597
598         view3d_set_viewcontext(C, &vc);
599
600         BLI_rcti_init_pt_radius(&rect, mval, 12);
601
602         hits = view3d_opengl_select(&vc, buffer, MAXPICKBUF, &rect, VIEW3D_SELECT_PICK_NEAREST);
603
604         /* does startelem exist? */
605         ml = mb->editelems->first;
606         while (ml) {
607                 if (ml == startelem) break;
608                 ml = ml->next;
609         }
610
611         if (ml == NULL) startelem = mb->editelems->first;
612         
613         if (hits > 0) {
614                 ml = startelem;
615                 while (ml) {
616                         for (a = 0; a < hits; a++) {
617                                 /* index converted for gl stuff */
618                                 if (ml->selcol1 == buffer[4 * a + 3]) {
619                                         ml->flag |= MB_SCALE_RAD;
620                                         ml_act = ml;
621                                 }
622                                 if (ml->selcol2 == buffer[4 * a + 3]) {
623                                         ml->flag &= ~MB_SCALE_RAD;
624                                         ml_act = ml;
625                                 }
626                         }
627                         if (ml_act) break;
628                         ml = ml->next;
629                         if (ml == NULL) ml = mb->editelems->first;
630                         if (ml == startelem) break;
631                 }
632                 
633                 /* When some metaelem was found, then it is necessary to select or
634                  * deselect it. */
635                 if (ml_act) {
636                         if (extend) {
637                                 ml_act->flag |= SELECT;
638                         }
639                         else if (deselect) {
640                                 ml_act->flag &= ~SELECT;
641                         }
642                         else if (toggle) {
643                                 if (ml_act->flag & SELECT)
644                                         ml_act->flag &= ~SELECT;
645                                 else
646                                         ml_act->flag |= SELECT;
647                         }
648                         else {
649                                 /* Deselect all existing metaelems */
650                                 BKE_mball_deselect_all(mb);
651
652                                 /* Select only metaelem clicked on */
653                                 ml_act->flag |= SELECT;
654                         }
655                         
656                         mb->lastelem = ml_act;
657                         
658                         WM_event_add_notifier(C, NC_GEOM | ND_SELECT, mb);
659
660                         return true;
661                 }
662         }
663
664         return false;
665 }
666
667
668 /*  ************* undo for MetaBalls ************* */
669
670 typedef struct UndoMBall {
671         ListBase editelems;
672         int lastelem_index;
673 } UndoMBall;
674
675 /* free all MetaElems from ListBase */
676 static void freeMetaElemlist(ListBase *lb)
677 {
678         MetaElem *ml;
679
680         if (lb == NULL) return;
681
682         while ((ml = BLI_pophead(lb))) {
683                 MEM_freeN(ml);
684         }
685 }
686
687
688 static void undoMball_to_editMball(void *umb_v, void *mb_v, void *UNUSED(obdata))
689 {
690         MetaBall *mb = mb_v;
691         UndoMBall *umb = umb_v;
692
693         freeMetaElemlist(mb->editelems);
694         mb->lastelem = NULL;
695
696         /* copy 'undo' MetaElems to 'edit' MetaElems */
697         int index = 0;
698         for (MetaElem *ml_undo = umb->editelems.first; ml_undo; ml_undo = ml_undo->next, index += 1) {
699                 MetaElem *ml_edit = MEM_dupallocN(ml_undo);
700                 BLI_addtail(mb->editelems, ml_edit);
701                 if (index == umb->lastelem_index) {
702                         mb->lastelem = ml_edit;
703                 }
704         }
705         
706 }
707
708 static void *editMball_to_undoMball(void *mb_v, void *UNUSED(obdata))
709 {
710         MetaBall *mb = mb_v;
711         UndoMBall *umb;
712
713         /* allocate memory for undo ListBase */
714         umb = MEM_callocN(sizeof(UndoMBall), __func__);
715         umb->lastelem_index = -1;
716         
717         /* copy contents of current ListBase to the undo ListBase */
718         int index = 0;
719         for (MetaElem *ml_edit = mb->editelems->first; ml_edit; ml_edit = ml_edit->next, index += 1) {
720                 MetaElem *ml_undo = MEM_dupallocN(ml_edit);
721                 BLI_addtail(&umb->editelems, ml_undo);
722                 if (ml_edit == mb->lastelem) {
723                         umb->lastelem_index = index;
724                 }
725         }
726         
727         return umb;
728 }
729
730 /* free undo ListBase of MetaElems */
731 static void free_undoMball(void *umb_v)
732 {
733         UndoMBall *umb = umb_v;
734         
735         freeMetaElemlist(&umb->editelems);
736         MEM_freeN(umb);
737 }
738
739 static MetaBall *metaball_get_obdata(Object *ob)
740 {
741         if (ob && ob->type == OB_MBALL) {
742                 return ob->data;
743         }
744         return NULL;
745 }
746
747
748 static void *get_data(bContext *C)
749 {
750         Object *obedit = CTX_data_edit_object(C);
751         return metaball_get_obdata(obedit);
752 }
753
754 /* this is undo system for MetaBalls */
755 void undo_push_mball(bContext *C, const char *name)
756 {
757         undo_editmode_push(C, name, get_data, free_undoMball, undoMball_to_editMball, editMball_to_undoMball, NULL);
758 }