Fix navmesh creation w/ multiple objects
[blender-staging.git] / source / blender / editors / mesh / editmesh_inset.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  * Contributor(s): Campbell Barton
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/editors/mesh/editmesh_inset.c
24  *  \ingroup edmesh
25  */
26
27 #include "MEM_guardedalloc.h"
28
29 #include "DNA_object_types.h"
30
31 #include "BLI_string.h"
32 #include "BLI_math.h"
33
34 #include "BLT_translation.h"
35
36 #include "BKE_context.h"
37 #include "BKE_global.h"
38 #include "BKE_editmesh.h"
39 #include "BKE_unit.h"
40
41 #include "RNA_define.h"
42 #include "RNA_access.h"
43
44 #include "WM_api.h"
45 #include "WM_types.h"
46
47 #include "UI_interface.h"
48
49 #include "ED_mesh.h"
50 #include "ED_numinput.h"
51 #include "ED_screen.h"
52 #include "ED_space_api.h"
53 #include "ED_transform.h"
54 #include "ED_view3d.h"
55
56 #include "mesh_intern.h"  /* own include */
57
58
59 typedef struct {
60         float old_thickness;
61         float old_depth;
62         bool modify_depth;
63         float initial_length;
64         float pixel_size;  /* use when mouse input is interpreted as spatial distance */
65         bool is_modal;
66         bool shift;
67         float shift_amount;
68         BMEditMesh *em;
69         NumInput num_input;
70
71         /* modal only */
72         float mcenter[2];
73         BMBackup mesh_backup;
74         void *draw_handle_pixel;
75         short twtype;
76 } InsetData;
77
78
79 static void edbm_inset_update_header(wmOperator *op, bContext *C)
80 {
81         InsetData *opdata = op->customdata;
82
83         const char *str = IFACE_(
84                 "Confirm: Enter/LClick, Cancel: (Esc/RClick), Thickness: %s, "
85                 "Depth (Ctrl to tweak): %s (%s), Outset (O): (%s), Boundary (B): (%s), Individual (I): (%s)"
86         );
87
88         char msg[UI_MAX_DRAW_STR];
89         ScrArea *sa = CTX_wm_area(C);
90         Scene *sce = CTX_data_scene(C);
91
92         if (sa) {
93                 char flts_str[NUM_STR_REP_LEN * 2];
94                 if (hasNumInput(&opdata->num_input))
95                         outputNumInput(&opdata->num_input, flts_str, &sce->unit);
96                 else {
97                         BLI_snprintf(flts_str, NUM_STR_REP_LEN, "%f", RNA_float_get(op->ptr, "thickness"));
98                         BLI_snprintf(flts_str + NUM_STR_REP_LEN, NUM_STR_REP_LEN, "%f", RNA_float_get(op->ptr, "depth"));
99                 }
100                 BLI_snprintf(msg, sizeof(msg), str,
101                              flts_str,
102                              flts_str + NUM_STR_REP_LEN,
103                              WM_bool_as_string(opdata->modify_depth),
104                              WM_bool_as_string(RNA_boolean_get(op->ptr, "use_outset")),
105                              WM_bool_as_string(RNA_boolean_get(op->ptr, "use_boundary")),
106                              WM_bool_as_string(RNA_boolean_get(op->ptr, "use_individual"))
107                             );
108
109                 ED_area_headerprint(sa, msg);
110         }
111 }
112
113
114 static bool edbm_inset_init(bContext *C, wmOperator *op, const bool is_modal)
115 {
116         InsetData *opdata;
117         Scene *scene = CTX_data_scene(C);
118         Object *obedit = CTX_data_edit_object(C);
119         BMEditMesh *em = BKE_editmesh_from_object(obedit);
120
121         if (em->bm->totvertsel == 0) {
122                 return false;
123         }
124
125         op->customdata = opdata = MEM_mallocN(sizeof(InsetData), "inset_operator_data");
126
127         opdata->old_thickness = 0.01;
128         opdata->old_depth = 0.0;
129         opdata->modify_depth = false;
130         opdata->shift = false;
131         opdata->shift_amount = 0.0f;
132         opdata->is_modal = is_modal;
133         opdata->em = em;
134
135         initNumInput(&opdata->num_input);
136         opdata->num_input.idx_max = 1; /* Two elements. */
137         opdata->num_input.unit_sys = scene->unit.system;
138         opdata->num_input.unit_type[0] = B_UNIT_LENGTH;
139         opdata->num_input.unit_type[1] = B_UNIT_LENGTH;
140
141         if (is_modal) {
142                 View3D *v3d = CTX_wm_view3d(C);
143                 ARegion *ar = CTX_wm_region(C);
144
145                 opdata->mesh_backup = EDBM_redo_state_store(em);
146                 opdata->draw_handle_pixel = ED_region_draw_cb_activate(
147                         ar->type, ED_region_draw_mouse_line_cb, opdata->mcenter, REGION_DRAW_POST_PIXEL);
148                 G.moving = G_TRANSFORM_EDIT;
149                 if (v3d) {
150                         opdata->twtype = v3d->twtype;
151                         v3d->twtype = 0;
152                 }
153         }
154
155         return true;
156 }
157
158 static void edbm_inset_exit(bContext *C, wmOperator *op)
159 {
160         InsetData *opdata;
161         ScrArea *sa = CTX_wm_area(C);
162
163         opdata = op->customdata;
164
165         if (opdata->is_modal) {
166                 View3D *v3d = CTX_wm_view3d(C);
167                 ARegion *ar = CTX_wm_region(C);
168                 EDBM_redo_state_free(&opdata->mesh_backup, NULL, false);
169                 ED_region_draw_cb_exit(ar->type, opdata->draw_handle_pixel);
170                 if (v3d) {
171                         v3d->twtype = opdata->twtype;
172                 }
173                 G.moving = 0;
174         }
175
176         if (sa) {
177                 ED_area_headerprint(sa, NULL);
178         }
179         MEM_freeN(op->customdata);
180 }
181
182 static void edbm_inset_cancel(bContext *C, wmOperator *op)
183 {
184         InsetData *opdata;
185
186         opdata = op->customdata;
187         if (opdata->is_modal) {
188                 EDBM_redo_state_free(&opdata->mesh_backup, opdata->em, true);
189                 EDBM_update_generic(opdata->em, false, true);
190         }
191
192         edbm_inset_exit(C, op);
193
194         /* need to force redisplay or we may still view the modified result */
195         ED_region_tag_redraw(CTX_wm_region(C));
196 }
197
198 static bool edbm_inset_calc(wmOperator *op)
199 {
200         InsetData *opdata;
201         BMEditMesh *em;
202         BMOperator bmop;
203
204         const bool use_boundary        = RNA_boolean_get(op->ptr, "use_boundary");
205         const bool use_even_offset     = RNA_boolean_get(op->ptr, "use_even_offset");
206         const bool use_relative_offset = RNA_boolean_get(op->ptr, "use_relative_offset");
207         const bool use_edge_rail       = RNA_boolean_get(op->ptr, "use_edge_rail");
208         const float thickness          = RNA_float_get(op->ptr,   "thickness");
209         const float depth              = RNA_float_get(op->ptr,   "depth");
210         const bool use_outset          = RNA_boolean_get(op->ptr, "use_outset");
211         const bool use_select_inset    = RNA_boolean_get(op->ptr, "use_select_inset"); /* not passed onto the BMO */
212         const bool use_individual      = RNA_boolean_get(op->ptr, "use_individual");
213         const bool use_interpolate     = RNA_boolean_get(op->ptr, "use_interpolate");
214
215         opdata = op->customdata;
216         em = opdata->em;
217
218         if (opdata->is_modal) {
219                 EDBM_redo_state_restore(opdata->mesh_backup, em, false);
220         }
221
222         if (use_individual) {
223                 EDBM_op_init(em, &bmop, op,
224                              "inset_individual faces=%hf use_even_offset=%b  use_relative_offset=%b "
225                              "use_interpolate=%b thickness=%f depth=%f",
226                              BM_ELEM_SELECT, use_even_offset, use_relative_offset, use_interpolate,
227                              thickness, depth);
228         }
229         else {
230                 EDBM_op_init(em, &bmop, op,
231                              "inset_region faces=%hf use_boundary=%b use_even_offset=%b use_relative_offset=%b "
232                              "use_interpolate=%b thickness=%f depth=%f use_outset=%b use_edge_rail=%b",
233                              BM_ELEM_SELECT, use_boundary, use_even_offset, use_relative_offset, use_interpolate,
234                              thickness, depth, use_outset, use_edge_rail);
235
236                 if (use_outset) {
237                         BMO_slot_buffer_from_enabled_hflag(em->bm, &bmop, bmop.slots_in, "faces_exclude", BM_FACE, BM_ELEM_HIDDEN);
238                 }
239         }
240         BMO_op_exec(em->bm, &bmop);
241
242         if (use_select_inset) {
243                 /* deselect original faces/verts */
244                 EDBM_flag_disable_all(em, BM_ELEM_SELECT);
245                 BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true);
246         }
247         else {
248                 EDBM_flag_disable_all(em, BM_ELEM_SELECT);
249                 BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_in, "faces", BM_FACE, BM_ELEM_SELECT, true);
250         }
251
252         if (!EDBM_op_finish(em, &bmop, op, true)) {
253                 return false;
254         }
255         else {
256                 EDBM_update_generic(em, true, true);
257                 return true;
258         }
259 }
260
261 static int edbm_inset_exec(bContext *C, wmOperator *op)
262 {
263         if (!edbm_inset_init(C, op, false)) {
264                 return OPERATOR_CANCELLED;
265         }
266
267         if (!edbm_inset_calc(op)) {
268                 edbm_inset_exit(C, op);
269                 return OPERATOR_CANCELLED;
270         }
271
272         edbm_inset_exit(C, op);
273         return OPERATOR_FINISHED;
274 }
275
276 static int edbm_inset_invoke(bContext *C, wmOperator *op, const wmEvent *event)
277 {
278         RegionView3D *rv3d = CTX_wm_region_view3d(C);
279         InsetData *opdata;
280         float mlen[2];
281         float center_3d[3];
282
283         if (!edbm_inset_init(C, op, true)) {
284                 return OPERATOR_CANCELLED;
285         }
286
287         opdata = op->customdata;
288
289         /* initialize mouse values */
290         if (!calculateTransformCenter(C, V3D_AROUND_CENTER_MEAN, center_3d, opdata->mcenter)) {
291                 /* in this case the tool will likely do nothing,
292                  * ideally this will never happen and should be checked for above */
293                 opdata->mcenter[0] = opdata->mcenter[1] = 0;
294         }
295         mlen[0] = opdata->mcenter[0] - event->mval[0];
296         mlen[1] = opdata->mcenter[1] - event->mval[1];
297         opdata->initial_length = len_v2(mlen);
298         opdata->pixel_size = rv3d ? ED_view3d_pixel_size(rv3d, center_3d) : 1.0f;
299
300         edbm_inset_calc(op);
301
302         edbm_inset_update_header(op, C);
303
304         WM_event_add_modal_handler(C, op);
305         return OPERATOR_RUNNING_MODAL;
306 }
307
308 static int edbm_inset_modal(bContext *C, wmOperator *op, const wmEvent *event)
309 {
310         InsetData *opdata = op->customdata;
311         const bool has_numinput = hasNumInput(&opdata->num_input);
312
313         /* Modal numinput active, try to handle numeric inputs first... */
314         if (event->val == KM_PRESS && has_numinput && handleNumInput(C, &opdata->num_input, event)) {
315                 float amounts[2] = {RNA_float_get(op->ptr, "thickness"),
316                                     RNA_float_get(op->ptr, "depth")};
317                 applyNumInput(&opdata->num_input, amounts);
318                 amounts[0] = max_ff(amounts[0], 0.0f);
319                 RNA_float_set(op->ptr, "thickness", amounts[0]);
320                 RNA_float_set(op->ptr, "depth", amounts[1]);
321
322                 if (edbm_inset_calc(op)) {
323                         edbm_inset_update_header(op, C);
324                         return OPERATOR_RUNNING_MODAL;
325                 }
326                 else {
327                         edbm_inset_cancel(C, op);
328                         return OPERATOR_CANCELLED;
329                 }
330         }
331         else {
332                 bool handled = false;
333                 switch (event->type) {
334                         case ESCKEY:
335                         case RIGHTMOUSE:
336                                 edbm_inset_cancel(C, op);
337                                 return OPERATOR_CANCELLED;
338
339                         case MOUSEMOVE:
340                                 if (!has_numinput) {
341                                         float mdiff[2];
342                                         float amount;
343
344                                         mdiff[0] = opdata->mcenter[0] - event->mval[0];
345                                         mdiff[1] = opdata->mcenter[1] - event->mval[1];
346
347                                         if (opdata->modify_depth)
348                                                 amount = opdata->old_depth     + ((len_v2(mdiff) - opdata->initial_length) * opdata->pixel_size);
349                                         else
350                                                 amount = opdata->old_thickness - ((len_v2(mdiff) - opdata->initial_length) * opdata->pixel_size);
351
352                                         /* Fake shift-transform... */
353                                         if (opdata->shift)
354                                                 amount = (amount - opdata->shift_amount) * 0.1f + opdata->shift_amount;
355
356                                         if (opdata->modify_depth)
357                                                 RNA_float_set(op->ptr, "depth", amount);
358                                         else {
359                                                 amount = max_ff(amount, 0.0f);
360                                                 RNA_float_set(op->ptr, "thickness", amount);
361                                         }
362
363                                         if (edbm_inset_calc(op))
364                                                 edbm_inset_update_header(op, C);
365                                         else {
366                                                 edbm_inset_cancel(C, op);
367                                                 return OPERATOR_CANCELLED;
368                                         }
369                                         handled = true;
370                                 }
371                                 break;
372
373                         case LEFTMOUSE:
374                         case PADENTER:
375                         case RETKEY:
376                                 if (event->val == KM_PRESS) {
377                                         edbm_inset_calc(op);
378                                         edbm_inset_exit(C, op);
379                                         return OPERATOR_FINISHED;
380                                 }
381                                 break;
382                         case LEFTSHIFTKEY:
383                         case RIGHTSHIFTKEY:
384                                 if (event->val == KM_PRESS) {
385                                         if (opdata->modify_depth)
386                                                 opdata->shift_amount = RNA_float_get(op->ptr, "depth");
387                                         else
388                                                 opdata->shift_amount = RNA_float_get(op->ptr, "thickness");
389                                         opdata->shift = true;
390                                         handled = true;
391                                 }
392                                 else {
393                                         opdata->shift_amount = 0.0f;
394                                         opdata->shift = false;
395                                         handled = true;
396                                 }
397                                 break;
398
399                         case LEFTCTRLKEY:
400                         case RIGHTCTRLKEY:
401                         {
402                                 float mlen[2];
403
404                                 mlen[0] = opdata->mcenter[0] - event->mval[0];
405                                 mlen[1] = opdata->mcenter[1] - event->mval[1];
406
407                                 if (event->val == KM_PRESS) {
408                                         opdata->old_thickness = RNA_float_get(op->ptr, "thickness");
409                                         if (opdata->shift)
410                                                 opdata->shift_amount = opdata->old_thickness;
411                                         opdata->modify_depth = true;
412                                 }
413                                 else {
414                                         opdata->old_depth = RNA_float_get(op->ptr, "depth");
415                                         if (opdata->shift)
416                                                 opdata->shift_amount = opdata->old_depth;
417                                         opdata->modify_depth = false;
418                                 }
419                                 opdata->initial_length = len_v2(mlen);
420
421                                 edbm_inset_update_header(op, C);
422                                 handled = true;
423                                 break;
424                         }
425
426                         case OKEY:
427                                 if (event->val == KM_PRESS) {
428                                         const bool use_outset = RNA_boolean_get(op->ptr, "use_outset");
429                                         RNA_boolean_set(op->ptr, "use_outset", !use_outset);
430                                         if (edbm_inset_calc(op)) {
431                                                 edbm_inset_update_header(op, C);
432                                         }
433                                         else {
434                                                 edbm_inset_cancel(C, op);
435                                                 return OPERATOR_CANCELLED;
436                                         }
437                                         handled = true;
438                                 }
439                                 break;
440                         case BKEY:
441                                 if (event->val == KM_PRESS) {
442                                         const bool use_boundary = RNA_boolean_get(op->ptr, "use_boundary");
443                                         RNA_boolean_set(op->ptr, "use_boundary", !use_boundary);
444                                         if (edbm_inset_calc(op)) {
445                                                 edbm_inset_update_header(op, C);
446                                         }
447                                         else {
448                                                 edbm_inset_cancel(C, op);
449                                                 return OPERATOR_CANCELLED;
450                                         }
451                                         handled = true;
452                                 }
453                                 break;
454                         case IKEY:
455                                 if (event->val == KM_PRESS) {
456                                         const bool use_individual = RNA_boolean_get(op->ptr, "use_individual");
457                                         RNA_boolean_set(op->ptr, "use_individual", !use_individual);
458                                         if (edbm_inset_calc(op)) {
459                                                 edbm_inset_update_header(op, C);
460                                         }
461                                         else {
462                                                 edbm_inset_cancel(C, op);
463                                                 return OPERATOR_CANCELLED;
464                                         }
465                                         handled = true;
466                                 }
467                                 break;
468                 }
469
470                 /* Modal numinput inactive, try to handle numeric inputs last... */
471                 if (!handled && event->val == KM_PRESS && handleNumInput(C, &opdata->num_input, event)) {
472                         float amounts[2] = {RNA_float_get(op->ptr, "thickness"),
473                                             RNA_float_get(op->ptr, "depth")};
474                         applyNumInput(&opdata->num_input, amounts);
475                         amounts[0] = max_ff(amounts[0], 0.0f);
476                         RNA_float_set(op->ptr, "thickness", amounts[0]);
477                         RNA_float_set(op->ptr, "depth", amounts[1]);
478
479                         if (edbm_inset_calc(op)) {
480                                 edbm_inset_update_header(op, C);
481                                 return OPERATOR_RUNNING_MODAL;
482                         }
483                         else {
484                                 edbm_inset_cancel(C, op);
485                                 return OPERATOR_CANCELLED;
486                         }
487                 }
488         }
489
490         return OPERATOR_RUNNING_MODAL;
491 }
492
493
494 void MESH_OT_inset(wmOperatorType *ot)
495 {
496         PropertyRNA *prop;
497
498         /* identifiers */
499         ot->name = "Inset Faces";
500         ot->idname = "MESH_OT_inset";
501         ot->description = "Inset new faces into selected faces";
502
503         /* api callbacks */
504         ot->invoke = edbm_inset_invoke;
505         ot->modal = edbm_inset_modal;
506         ot->exec = edbm_inset_exec;
507         ot->cancel = edbm_inset_cancel;
508         ot->poll = ED_operator_editmesh;
509
510         /* flags */
511         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_CURSOR | OPTYPE_BLOCKING;
512
513         /* properties */
514         RNA_def_boolean(
515                 ot->srna, "use_boundary",
516                 true, "Boundary",  "Inset face boundaries");
517         RNA_def_boolean(
518                 ot->srna, "use_even_offset",
519                 true, "Offset Even",      "Scale the offset to give more even thickness");
520         RNA_def_boolean(
521                 ot->srna, "use_relative_offset",
522                 false, "Offset Relative", "Scale the offset by surrounding geometry");
523         RNA_def_boolean(
524                 ot->srna, "use_edge_rail",
525                 false, "Edge Rail", "Inset the region along existing edges");
526
527         prop = RNA_def_float_distance(ot->srna, "thickness", 0.01f, 0.0f, 1e12f, "Thickness", "", 0.0f, 10.0f);
528         /* use 1 rather then 10 for max else dragging the button moves too far */
529         RNA_def_property_ui_range(prop, 0.0, 1.0, 0.01, 4);
530         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
531
532         prop = RNA_def_float_distance(ot->srna, "depth", 0.0f, -1e12f, 1e12f, "Depth", "", -10.0f, 10.0f);
533         RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.01, 4);
534         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
535
536         RNA_def_boolean(ot->srna, "use_outset", false, "Outset", "Outset rather than inset");
537         RNA_def_boolean(ot->srna, "use_select_inset", false, "Select Outer", "Select the new inset faces");
538         RNA_def_boolean(ot->srna, "use_individual", false, "Individual", "Individual Face Inset");
539         RNA_def_boolean(ot->srna, "use_interpolate", true, "Interpolate", "Blend face data across the inset");
540 }