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