UI: show confirmation for metaball delete
[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 #include <math.h>
33 #include <string.h>
34
35 #include "MEM_guardedalloc.h"
36
37 #include "BLI_blenlib.h"
38 #include "BLI_math.h"
39 #include "BLI_rand.h"
40 #include "BLI_utildefines.h"
41
42 #include "DNA_defs.h"
43 #include "DNA_meta_types.h"
44 #include "DNA_object_types.h"
45 #include "DNA_scene_types.h"
46
47 #include "RNA_define.h"
48 #include "RNA_access.h"
49
50 #include "BKE_context.h"
51 #include "BKE_mball.h"
52 #include "BKE_layer.h"
53
54 #include "DEG_depsgraph.h"
55
56 #include "ED_mball.h"
57 #include "ED_screen.h"
58 #include "ED_select_utils.h"
59 #include "ED_view3d.h"
60
61 #include "WM_api.h"
62 #include "WM_types.h"
63
64 #include "mball_intern.h"
65
66 /* This function is used to free all MetaElems from MetaBall */
67 void ED_mball_editmball_free(Object *obedit)
68 {
69         MetaBall *mb = (MetaBall *)obedit->data;
70
71         mb->editelems = NULL;
72         mb->lastelem = NULL;
73 }
74
75 /* This function is called, when MetaBall Object is
76  * switched from object mode to edit mode */
77 void ED_mball_editmball_make(Object *obedit)
78 {
79         MetaBall *mb = (MetaBall *)obedit->data;
80         MetaElem *ml; /*, *newml;*/
81
82         ml = mb->elems.first;
83
84         while (ml) {
85                 if (ml->flag & SELECT) mb->lastelem = ml;
86                 ml = ml->next;
87         }
88
89         mb->editelems = &mb->elems;
90 }
91
92 /* This function is called, when MetaBall Object switched from
93  * edit mode to object mode. List of MetaElements is copied
94  * from object->data->edit_elems to object->data->elems. */
95 void ED_mball_editmball_load(Object *UNUSED(obedit))
96 {
97 }
98
99 /* Add metaelem primitive to metaball object (which is in edit mode) */
100 MetaElem *ED_mball_add_primitive(bContext *UNUSED(C), Object *obedit, float mat[4][4], float dia, int type)
101 {
102         MetaBall *mball = (MetaBall *)obedit->data;
103         MetaElem *ml;
104
105         /* Deselect all existing metaelems */
106         ml = mball->editelems->first;
107         while (ml) {
108                 ml->flag &= ~SELECT;
109                 ml = ml->next;
110         }
111
112         ml = BKE_mball_element_add(mball, type);
113         ml->rad *= dia;
114         mball->wiresize *= dia;
115         mball->rendersize *= dia;
116         copy_v3_v3(&ml->x, mat[3]);
117
118         ml->flag |= SELECT;
119         mball->lastelem = ml;
120         return ml;
121 }
122
123 /***************************** Select/Deselect operator *****************************/
124
125 /* Select or deselect all MetaElements */
126 static int mball_select_all_exec(bContext *C, wmOperator *op)
127 {
128         Object *obedit = CTX_data_edit_object(C);
129         MetaBall *mb = (MetaBall *)obedit->data;
130         MetaElem *ml;
131         int action = RNA_enum_get(op->ptr, "action");
132
133         if (BLI_listbase_is_empty(mb->editelems))
134                 return OPERATOR_CANCELLED;
135
136         if (action == SEL_TOGGLE) {
137                 action = SEL_SELECT;
138                 for (ml = mb->editelems->first; ml; ml = ml->next) {
139                         if (ml->flag & SELECT) {
140                                 action = SEL_DESELECT;
141                                 break;
142                         }
143                 }
144         }
145
146         switch (action) {
147                 case SEL_SELECT:
148                         BKE_mball_select_all(mb);
149                         break;
150                 case SEL_DESELECT:
151                         BKE_mball_deselect_all(mb);
152                         break;
153                 case SEL_INVERT:
154                         BKE_mball_select_swap(mb);
155                         break;
156         }
157
158         DEG_id_tag_update(&mb->id, DEG_TAG_SELECT_UPDATE);
159         WM_event_add_notifier(C, NC_GEOM | ND_SELECT, mb);
160
161         return OPERATOR_FINISHED;
162 }
163
164 void MBALL_OT_select_all(wmOperatorType *ot)
165 {
166         /* identifiers */
167         ot->name = "(De)select All";
168         ot->description = "Change selection of all meta elements";
169         ot->idname = "MBALL_OT_select_all";
170
171         /* callback functions */
172         ot->exec = mball_select_all_exec;
173         ot->poll = ED_operator_editmball;
174
175         /* flags */
176         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
177
178         WM_operator_properties_select_all(ot);
179 }
180
181
182 /* -------------------------------------------------------------------- */
183 /* Select Similar */
184
185 enum {
186         SIMMBALL_TYPE = 1,
187         SIMMBALL_RADIUS,
188         SIMMBALL_STIFFNESS,
189         SIMMBALL_ROTATION
190 };
191
192 static const EnumPropertyItem prop_similar_types[] = {
193         {SIMMBALL_TYPE, "TYPE", 0, "Type", ""},
194         {SIMMBALL_RADIUS, "RADIUS", 0, "Radius", ""},
195         {SIMMBALL_STIFFNESS, "STIFFNESS", 0, "Stiffness", ""},
196         {SIMMBALL_ROTATION, "ROTATION", 0, "Rotation", ""},
197         {0, NULL, 0, NULL, NULL}
198 };
199
200 static bool mball_select_similar_type(MetaBall *mb)
201 {
202         MetaElem *ml;
203         bool changed = false;
204
205         for (ml = mb->editelems->first; ml; ml = ml->next) {
206                 if (ml->flag & SELECT) {
207                         MetaElem *ml_iter;
208
209                         for (ml_iter = mb->editelems->first; ml_iter; ml_iter = ml_iter->next) {
210                                 if ((ml_iter->flag & SELECT) == 0) {
211                                         if (ml->type == ml_iter->type) {
212                                                 ml_iter->flag |= SELECT;
213                                                 changed = true;
214                                         }
215                                 }
216                         }
217                 }
218         }
219
220         return changed;
221 }
222
223 static bool mball_select_similar_radius(MetaBall *mb, const float thresh)
224 {
225         MetaElem *ml;
226         bool changed = false;
227
228         for (ml = mb->editelems->first; ml; ml = ml->next) {
229                 if (ml->flag & SELECT) {
230                         MetaElem *ml_iter;
231
232                         for (ml_iter = mb->editelems->first; ml_iter; ml_iter = ml_iter->next) {
233                                 if ((ml_iter->flag & SELECT) == 0) {
234                                         if (fabsf(ml_iter->rad - ml->rad) <= (thresh * ml->rad)) {
235                                                 ml_iter->flag |= SELECT;
236                                                 changed = true;
237                                         }
238                                 }
239                         }
240                 }
241         }
242
243         return changed;
244 }
245
246 static bool mball_select_similar_stiffness(MetaBall *mb, const float thresh)
247 {
248         MetaElem *ml;
249         bool changed = false;
250
251         for (ml = mb->editelems->first; ml; ml = ml->next) {
252                 if (ml->flag & SELECT) {
253                         MetaElem *ml_iter;
254
255                         for (ml_iter = mb->editelems->first; ml_iter; ml_iter = ml_iter->next) {
256                                 if ((ml_iter->flag & SELECT) == 0) {
257                                         if (fabsf(ml_iter->s - ml->s) <= thresh) {
258                                                 ml_iter->flag |= SELECT;
259                                                 changed = true;
260                                         }
261                                 }
262                         }
263                 }
264         }
265
266         return changed;
267 }
268
269 static bool mball_select_similar_rotation(MetaBall *mb, const float thresh)
270 {
271         const float thresh_rad = thresh * (float)M_PI_2;
272         MetaElem *ml;
273         bool changed = false;
274
275         for (ml = mb->editelems->first; ml; ml = ml->next) {
276                 if (ml->flag & SELECT) {
277                         MetaElem *ml_iter;
278
279                         float ml_mat[3][3];
280
281                         unit_m3(ml_mat);
282                         mul_qt_v3(ml->quat, ml_mat[0]);
283                         mul_qt_v3(ml->quat, ml_mat[1]);
284                         mul_qt_v3(ml->quat, ml_mat[2]);
285                         normalize_m3(ml_mat);
286
287                         for (ml_iter = mb->editelems->first; ml_iter; ml_iter = ml_iter->next) {
288                                 if ((ml_iter->flag & SELECT) == 0) {
289                                         float ml_iter_mat[3][3];
290
291                                         unit_m3(ml_iter_mat);
292                                         mul_qt_v3(ml_iter->quat, ml_iter_mat[0]);
293                                         mul_qt_v3(ml_iter->quat, ml_iter_mat[1]);
294                                         mul_qt_v3(ml_iter->quat, ml_iter_mat[2]);
295                                         normalize_m3(ml_iter_mat);
296
297                                         if ((angle_normalized_v3v3(ml_mat[0], ml_iter_mat[0]) +
298                                              angle_normalized_v3v3(ml_mat[1], ml_iter_mat[1]) +
299                                              angle_normalized_v3v3(ml_mat[2], ml_iter_mat[2])) < thresh_rad)
300                                         {
301                                                 ml_iter->flag |= SELECT;
302                                                 changed = true;
303                                         }
304                                 }
305                         }
306                 }
307         }
308
309         return changed;
310 }
311
312 static int mball_select_similar_exec(bContext *C, wmOperator *op)
313 {
314         Object *obedit = CTX_data_edit_object(C);
315         MetaBall *mb = (MetaBall *)obedit->data;
316
317         int type = RNA_enum_get(op->ptr, "type");
318         float thresh = RNA_float_get(op->ptr, "threshold");
319         bool changed = false;
320
321         switch (type) {
322                 case SIMMBALL_TYPE:
323                         changed = mball_select_similar_type(mb);
324                         break;
325                 case SIMMBALL_RADIUS:
326                         changed = mball_select_similar_radius(mb, thresh);
327                         break;
328                 case SIMMBALL_STIFFNESS:
329                         changed = mball_select_similar_stiffness(mb, thresh);
330                         break;
331                 case SIMMBALL_ROTATION:
332                         changed = mball_select_similar_rotation(mb, thresh);
333                         break;
334                 default:
335                         BLI_assert(0);
336                         break;
337         }
338
339         if (changed) {
340                 DEG_id_tag_update(&mb->id, DEG_TAG_SELECT_UPDATE);
341                 WM_event_add_notifier(C, NC_GEOM | ND_SELECT, mb);
342         }
343
344         return OPERATOR_FINISHED;
345 }
346
347 void MBALL_OT_select_similar(wmOperatorType *ot)
348 {
349         /* identifiers */
350         ot->name = "Select Similar";
351         ot->idname = "MBALL_OT_select_similar";
352
353         /* callback functions */
354         ot->invoke = WM_menu_invoke;
355         ot->exec = mball_select_similar_exec;
356         ot->poll = ED_operator_editmball;
357         ot->description = "Select similar metaballs by property types";
358
359         /* flags */
360         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
361
362         /* properties */
363         ot->prop = RNA_def_enum(ot->srna, "type", prop_similar_types, 0, "Type", "");
364
365         RNA_def_float(ot->srna, "threshold", 0.1, 0.0, 1.0, "Threshold", "", 0.01, 1.0);
366 }
367
368
369 /***************************** Select random operator *****************************/
370
371 /* Random metaball selection */
372 static int select_random_metaelems_exec(bContext *C, wmOperator *op)
373 {
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         ViewLayer *view_layer = CTX_data_view_layer(C);
379         uint objects_len = 0;
380         Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
381         for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
382                 Object *obedit = objects[ob_index];
383                 MetaBall *mb = (MetaBall *)obedit->data;
384                 if (!BKE_mball_is_any_unselected(mb)) {
385                         continue;
386                 }
387                 int seed_iter = seed;
388
389                 /* This gives a consistent result regardless of object order. */
390                 if (ob_index) {
391                         seed_iter += BLI_ghashutil_strhash_p(obedit->id.name);
392                 }
393
394                 RNG *rng = BLI_rng_new_srandom(seed_iter);
395
396                 for (MetaElem *ml = mb->editelems->first; ml; ml = ml->next) {
397                         if (BLI_rng_get_float(rng) < randfac) {
398                                 if (select) {
399                                         ml->flag |= SELECT;
400                                 }
401                                 else {
402                                         ml->flag &= ~SELECT;
403                                 }
404                         }
405                 }
406
407                 BLI_rng_free(rng);
408
409                 DEG_id_tag_update(&mb->id, DEG_TAG_SELECT_UPDATE);
410                 WM_event_add_notifier(C, NC_GEOM | ND_SELECT, mb);
411         }
412         MEM_freeN(objects);
413         return OPERATOR_FINISHED;
414 }
415
416 void MBALL_OT_select_random_metaelems(struct wmOperatorType *ot)
417 {
418         /* identifiers */
419         ot->name = "Select Random";
420         ot->description = "Randomly select metaelements";
421         ot->idname = "MBALL_OT_select_random_metaelems";
422
423         /* callback functions */
424         ot->exec = select_random_metaelems_exec;
425         ot->poll = ED_operator_editmball;
426
427         /* flags */
428         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
429
430         /* properties */
431         WM_operator_properties_select_random(ot);
432 }
433
434 /***************************** Duplicate operator *****************************/
435
436 /* Duplicate selected MetaElements */
437 static int duplicate_metaelems_exec(bContext *C, wmOperator *UNUSED(op))
438 {
439         Object *obedit = CTX_data_edit_object(C);
440         MetaBall *mb = (MetaBall *)obedit->data;
441         MetaElem *ml, *newml;
442
443         ml = mb->editelems->last;
444         if (ml) {
445                 while (ml) {
446                         if (ml->flag & SELECT) {
447                                 newml = MEM_dupallocN(ml);
448                                 BLI_addtail(mb->editelems, newml);
449                                 mb->lastelem = newml;
450                                 ml->flag &= ~SELECT;
451                         }
452                         ml = ml->prev;
453                 }
454                 WM_event_add_notifier(C, NC_GEOM | ND_DATA, mb);
455                 DEG_id_tag_update(obedit->data, 0);
456         }
457
458         return OPERATOR_FINISHED;
459 }
460
461 void MBALL_OT_duplicate_metaelems(wmOperatorType *ot)
462 {
463         /* identifiers */
464         ot->name = "Duplicate Metaelements";
465         ot->description = "Duplicate selected metaelement(s)";
466         ot->idname = "MBALL_OT_duplicate_metaelems";
467
468         /* callback functions */
469         ot->exec = duplicate_metaelems_exec;
470         ot->poll = ED_operator_editmball;
471
472         /* flags */
473         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
474 }
475
476 /***************************** Delete operator *****************************/
477
478 /* Delete all selected MetaElems (not MetaBall) */
479 static int delete_metaelems_exec(bContext *C, wmOperator *UNUSED(op))
480 {
481         Object *obedit = CTX_data_edit_object(C);
482         MetaBall *mb = (MetaBall *)obedit->data;
483         MetaElem *ml, *next;
484
485         ml = mb->editelems->first;
486         if (ml) {
487                 while (ml) {
488                         next = ml->next;
489                         if (ml->flag & SELECT) {
490                                 if (mb->lastelem == ml) mb->lastelem = NULL;
491                                 BLI_remlink(mb->editelems, ml);
492                                 MEM_freeN(ml);
493                         }
494                         ml = next;
495                 }
496                 WM_event_add_notifier(C, NC_GEOM | ND_DATA, mb);
497                 DEG_id_tag_update(obedit->data, 0);
498         }
499
500         return OPERATOR_FINISHED;
501 }
502
503 void MBALL_OT_delete_metaelems(wmOperatorType *ot)
504 {
505         /* identifiers */
506         ot->name = "Delete";
507         ot->description = "Delete selected metaelement(s)";
508         ot->idname = "MBALL_OT_delete_metaelems";
509
510         /* callback functions */
511         ot->invoke = WM_operator_confirm;
512         ot->exec = delete_metaelems_exec;
513         ot->poll = ED_operator_editmball;
514
515         /* flags */
516         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
517 }
518
519 /***************************** Hide operator *****************************/
520
521 /* Hide selected MetaElems */
522 static int hide_metaelems_exec(bContext *C, wmOperator *op)
523 {
524         Object *obedit = CTX_data_edit_object(C);
525         MetaBall *mb = (MetaBall *)obedit->data;
526         MetaElem *ml;
527         const bool invert = RNA_boolean_get(op->ptr, "unselected") ? SELECT : 0;
528
529         ml = mb->editelems->first;
530
531         if (ml) {
532                 while (ml) {
533                         if ((ml->flag & SELECT) != invert)
534                                 ml->flag |= MB_HIDE;
535                         ml = ml->next;
536                 }
537                 WM_event_add_notifier(C, NC_GEOM | ND_DATA, mb);
538                 DEG_id_tag_update(obedit->data, 0);
539         }
540
541         return OPERATOR_FINISHED;
542 }
543
544 void MBALL_OT_hide_metaelems(wmOperatorType *ot)
545 {
546         /* identifiers */
547         ot->name = "Hide";
548         ot->description = "Hide (un)selected metaelement(s)";
549         ot->idname = "MBALL_OT_hide_metaelems";
550
551         /* callback functions */
552         ot->exec = hide_metaelems_exec;
553         ot->poll = ED_operator_editmball;
554
555         /* flags */
556         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
557
558         /* props */
559         RNA_def_boolean(ot->srna, "unselected", false, "Unselected", "Hide unselected rather than selected");
560 }
561
562 /***************************** Unhide operator *****************************/
563
564 /* Unhide all edited MetaElems */
565 static int reveal_metaelems_exec(bContext *C, wmOperator *op)
566 {
567         Object *obedit = CTX_data_edit_object(C);
568         MetaBall *mb = (MetaBall *)obedit->data;
569         const bool select = RNA_boolean_get(op->ptr, "select");
570         bool changed = false;
571
572         for (MetaElem *ml = mb->editelems->first; ml; ml = ml->next) {
573                 if (ml->flag & MB_HIDE) {
574                         SET_FLAG_FROM_TEST(ml->flag, select, SELECT);
575                         ml->flag &= ~MB_HIDE;
576                         changed = true;
577                 }
578         }
579         if (changed) {
580                 WM_event_add_notifier(C, NC_GEOM | ND_DATA, mb);
581                 DEG_id_tag_update(obedit->data, 0);
582         }
583
584         return OPERATOR_FINISHED;
585 }
586
587 void MBALL_OT_reveal_metaelems(wmOperatorType *ot)
588 {
589         /* identifiers */
590         ot->name = "Reveal";
591         ot->description = "Reveal all hidden metaelements";
592         ot->idname = "MBALL_OT_reveal_metaelems";
593
594         /* callback functions */
595         ot->exec = reveal_metaelems_exec;
596         ot->poll = ED_operator_editmball;
597
598         /* flags */
599         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
600
601         /* props */
602         RNA_def_boolean(ot->srna, "select", true, "Select", "");
603 }
604
605 /* Select MetaElement with mouse click (user can select radius circle or
606  * stiffness circle) */
607 bool ED_mball_select_pick(bContext *C, const int mval[2], bool extend, bool deselect, bool toggle)
608 {
609         static MetaElem *startelem = NULL;
610         Object *obedit = CTX_data_edit_object(C);
611         ViewContext vc;
612         MetaBall *mb = (MetaBall *)obedit->data;
613         MetaElem *ml, *ml_act = NULL;
614         int a, hits;
615         unsigned int buffer[MAXPICKBUF];
616         rcti rect;
617
618         ED_view3d_viewcontext_init(C, &vc);
619
620         BLI_rcti_init_pt_radius(&rect, mval, 12);
621
622         hits = view3d_opengl_select(
623                 &vc, buffer, MAXPICKBUF, &rect,
624                 VIEW3D_SELECT_PICK_NEAREST, VIEW3D_SELECT_FILTER_NOP);
625
626         /* does startelem exist? */
627         ml = mb->editelems->first;
628         while (ml) {
629                 if (ml == startelem) break;
630                 ml = ml->next;
631         }
632
633         if (ml == NULL) startelem = mb->editelems->first;
634
635         if (hits > 0) {
636                 ml = startelem;
637                 while (ml) {
638                         for (a = 0; a < hits; a++) {
639                                 /* index converted for gl stuff */
640                                 if (ml->selcol1 == buffer[4 * a + 3]) {
641                                         ml->flag |= MB_SCALE_RAD;
642                                         ml_act = ml;
643                                 }
644                                 if (ml->selcol2 == buffer[4 * a + 3]) {
645                                         ml->flag &= ~MB_SCALE_RAD;
646                                         ml_act = ml;
647                                 }
648                         }
649                         if (ml_act) break;
650                         ml = ml->next;
651                         if (ml == NULL) ml = mb->editelems->first;
652                         if (ml == startelem) break;
653                 }
654
655                 /* When some metaelem was found, then it is necessary to select or
656                  * deselect it. */
657                 if (ml_act) {
658                         if (extend) {
659                                 ml_act->flag |= SELECT;
660                         }
661                         else if (deselect) {
662                                 ml_act->flag &= ~SELECT;
663                         }
664                         else if (toggle) {
665                                 if (ml_act->flag & SELECT)
666                                         ml_act->flag &= ~SELECT;
667                                 else
668                                         ml_act->flag |= SELECT;
669                         }
670                         else {
671                                 /* Deselect all existing metaelems */
672                                 BKE_mball_deselect_all(mb);
673
674                                 /* Select only metaelem clicked on */
675                                 ml_act->flag |= SELECT;
676                         }
677
678                         mb->lastelem = ml_act;
679
680                         DEG_id_tag_update(&mb->id, DEG_TAG_SELECT_UPDATE);
681                         WM_event_add_notifier(C, NC_GEOM | ND_SELECT, mb);
682
683                         return true;
684                 }
685         }
686
687         return false;
688 }