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