Merge branch 'blender2.7'
[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 gizmo_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, CTX_wm_view3d(C), &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->gizmo_flag = v3d->gizmo_flag;
177                         v3d->gizmo_flag = V3D_GIZMO_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->gizmo_flag = opdata->gizmo_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         /* not passed onto the BMO */
245         const bool use_select_inset    = RNA_boolean_get(op->ptr, "use_select_inset");
246         const bool use_individual      = RNA_boolean_get(op->ptr, "use_individual");
247         const bool use_interpolate     = RNA_boolean_get(op->ptr, "use_interpolate");
248
249         opdata = op->customdata;
250
251         for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) {
252                 em = opdata->ob_store[ob_index].em;
253
254                 if (opdata->is_modal) {
255                         EDBM_redo_state_restore(opdata->ob_store[ob_index].mesh_backup, em, false);
256                 }
257
258                 if (use_individual) {
259                         EDBM_op_init(
260                                 em, &bmop, op,
261                                 "inset_individual faces=%hf use_even_offset=%b  use_relative_offset=%b "
262                                 "use_interpolate=%b thickness=%f depth=%f",
263                                 BM_ELEM_SELECT, use_even_offset, use_relative_offset, use_interpolate,
264                                 thickness, depth);
265                 }
266                 else {
267                         EDBM_op_init(
268                                 em, &bmop, op,
269                                 "inset_region faces=%hf use_boundary=%b use_even_offset=%b use_relative_offset=%b "
270                                 "use_interpolate=%b thickness=%f depth=%f use_outset=%b use_edge_rail=%b",
271                                 BM_ELEM_SELECT, use_boundary, use_even_offset, use_relative_offset, use_interpolate,
272                                 thickness, depth, use_outset, use_edge_rail);
273
274                         if (use_outset) {
275                                 BMO_slot_buffer_from_enabled_hflag(em->bm, &bmop, bmop.slots_in, "faces_exclude", BM_FACE, BM_ELEM_HIDDEN);
276                         }
277                 }
278                 BMO_op_exec(em->bm, &bmop);
279
280                 if (use_select_inset) {
281                         /* deselect original faces/verts */
282                         EDBM_flag_disable_all(em, BM_ELEM_SELECT);
283                         BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true);
284                 }
285                 else {
286                         EDBM_flag_disable_all(em, BM_ELEM_SELECT);
287                         BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_in, "faces", BM_FACE, BM_ELEM_SELECT, true);
288                 }
289
290                 if (!EDBM_op_finish(em, &bmop, op, true)) {
291                         continue;
292                 }
293                 else {
294                         EDBM_update_generic(em, true, true);
295                         changed = true;
296                 }
297         }
298         return changed;
299 }
300
301 static int edbm_inset_exec(bContext *C, wmOperator *op)
302 {
303         if (!edbm_inset_init(C, op, false)) {
304                 return OPERATOR_CANCELLED;
305         }
306
307         if (!edbm_inset_calc(op)) {
308                 edbm_inset_exit(C, op);
309                 return OPERATOR_CANCELLED;
310         }
311
312         edbm_inset_exit(C, op);
313         return OPERATOR_FINISHED;
314 }
315
316 static int edbm_inset_invoke(bContext *C, wmOperator *op, const wmEvent *event)
317 {
318         RegionView3D *rv3d = CTX_wm_region_view3d(C);
319         InsetData *opdata;
320         float mlen[2];
321         float center_3d[3];
322
323         if (!edbm_inset_init(C, op, true)) {
324                 return OPERATOR_CANCELLED;
325         }
326
327         opdata = op->customdata;
328
329         /* initialize mouse values */
330         if (!calculateTransformCenter(C, V3D_AROUND_CENTER_MEDIAN, center_3d, opdata->mcenter)) {
331                 /* in this case the tool will likely do nothing,
332                  * ideally this will never happen and should be checked for above */
333                 opdata->mcenter[0] = opdata->mcenter[1] = 0;
334         }
335         mlen[0] = opdata->mcenter[0] - event->mval[0];
336         mlen[1] = opdata->mcenter[1] - event->mval[1];
337         opdata->initial_length = len_v2(mlen);
338         opdata->pixel_size = rv3d ? ED_view3d_pixel_size(rv3d, center_3d) : 1.0f;
339
340         edbm_inset_calc(op);
341
342         edbm_inset_update_header(op, C);
343
344         WM_event_add_modal_handler(C, op);
345         return OPERATOR_RUNNING_MODAL;
346 }
347
348 static int edbm_inset_modal(bContext *C, wmOperator *op, const wmEvent *event)
349 {
350         InsetData *opdata = op->customdata;
351         const bool has_numinput = hasNumInput(&opdata->num_input);
352
353         /* Modal numinput active, try to handle numeric inputs first... */
354         if (event->val == KM_PRESS && has_numinput && handleNumInput(C, &opdata->num_input, event)) {
355                 float amounts[2] = {RNA_float_get(op->ptr, "thickness"),
356                                     RNA_float_get(op->ptr, "depth")};
357                 applyNumInput(&opdata->num_input, amounts);
358                 amounts[0] = max_ff(amounts[0], 0.0f);
359                 RNA_float_set(op->ptr, "thickness", amounts[0]);
360                 RNA_float_set(op->ptr, "depth", amounts[1]);
361
362                 if (edbm_inset_calc(op)) {
363                         edbm_inset_update_header(op, C);
364                         return OPERATOR_RUNNING_MODAL;
365                 }
366                 else {
367                         edbm_inset_cancel(C, op);
368                         return OPERATOR_CANCELLED;
369                 }
370         }
371         else {
372                 bool handled = false;
373                 switch (event->type) {
374                         case ESCKEY:
375                         case RIGHTMOUSE:
376                                 edbm_inset_cancel(C, op);
377                                 return OPERATOR_CANCELLED;
378
379                         case MOUSEMOVE:
380                                 if (!has_numinput) {
381                                         float mdiff[2];
382                                         float amount;
383
384                                         mdiff[0] = opdata->mcenter[0] - event->mval[0];
385                                         mdiff[1] = opdata->mcenter[1] - event->mval[1];
386
387                                         if (opdata->modify_depth)
388                                                 amount = opdata->old_depth     + ((len_v2(mdiff) - opdata->initial_length) * opdata->pixel_size);
389                                         else
390                                                 amount = opdata->old_thickness - ((len_v2(mdiff) - opdata->initial_length) * opdata->pixel_size);
391
392                                         /* Fake shift-transform... */
393                                         if (opdata->shift)
394                                                 amount = (amount - opdata->shift_amount) * 0.1f + opdata->shift_amount;
395
396                                         if (opdata->modify_depth)
397                                                 RNA_float_set(op->ptr, "depth", amount);
398                                         else {
399                                                 amount = max_ff(amount, 0.0f);
400                                                 RNA_float_set(op->ptr, "thickness", amount);
401                                         }
402
403                                         if (edbm_inset_calc(op))
404                                                 edbm_inset_update_header(op, C);
405                                         else {
406                                                 edbm_inset_cancel(C, op);
407                                                 return OPERATOR_CANCELLED;
408                                         }
409                                         handled = true;
410                                 }
411                                 break;
412
413                         case LEFTMOUSE:
414                         case PADENTER:
415                         case RETKEY:
416                                 if ((event->val == KM_PRESS) ||
417                                     ((event->val == KM_RELEASE) && RNA_boolean_get(op->ptr, "release_confirm")))
418                                 {
419                                         edbm_inset_calc(op);
420                                         edbm_inset_exit(C, op);
421                                         return OPERATOR_FINISHED;
422                                 }
423                                 break;
424                         case LEFTSHIFTKEY:
425                         case RIGHTSHIFTKEY:
426                                 if (event->val == KM_PRESS) {
427                                         if (opdata->modify_depth)
428                                                 opdata->shift_amount = RNA_float_get(op->ptr, "depth");
429                                         else
430                                                 opdata->shift_amount = RNA_float_get(op->ptr, "thickness");
431                                         opdata->shift = true;
432                                         handled = true;
433                                 }
434                                 else {
435                                         opdata->shift_amount = 0.0f;
436                                         opdata->shift = false;
437                                         handled = true;
438                                 }
439                                 break;
440
441                         case LEFTCTRLKEY:
442                         case RIGHTCTRLKEY:
443                         {
444                                 float mlen[2];
445
446                                 mlen[0] = opdata->mcenter[0] - event->mval[0];
447                                 mlen[1] = opdata->mcenter[1] - event->mval[1];
448
449                                 if (event->val == KM_PRESS) {
450                                         opdata->old_thickness = RNA_float_get(op->ptr, "thickness");
451                                         if (opdata->shift)
452                                                 opdata->shift_amount = opdata->old_thickness;
453                                         opdata->modify_depth = true;
454                                 }
455                                 else {
456                                         opdata->old_depth = RNA_float_get(op->ptr, "depth");
457                                         if (opdata->shift)
458                                                 opdata->shift_amount = opdata->old_depth;
459                                         opdata->modify_depth = false;
460                                 }
461                                 opdata->initial_length = len_v2(mlen);
462
463                                 edbm_inset_update_header(op, C);
464                                 handled = true;
465                                 break;
466                         }
467
468                         case OKEY:
469                                 if (event->val == KM_PRESS) {
470                                         const bool use_outset = RNA_boolean_get(op->ptr, "use_outset");
471                                         RNA_boolean_set(op->ptr, "use_outset", !use_outset);
472                                         if (edbm_inset_calc(op)) {
473                                                 edbm_inset_update_header(op, C);
474                                         }
475                                         else {
476                                                 edbm_inset_cancel(C, op);
477                                                 return OPERATOR_CANCELLED;
478                                         }
479                                         handled = true;
480                                 }
481                                 break;
482                         case BKEY:
483                                 if (event->val == KM_PRESS) {
484                                         const bool use_boundary = RNA_boolean_get(op->ptr, "use_boundary");
485                                         RNA_boolean_set(op->ptr, "use_boundary", !use_boundary);
486                                         if (edbm_inset_calc(op)) {
487                                                 edbm_inset_update_header(op, C);
488                                         }
489                                         else {
490                                                 edbm_inset_cancel(C, op);
491                                                 return OPERATOR_CANCELLED;
492                                         }
493                                         handled = true;
494                                 }
495                                 break;
496                         case IKEY:
497                                 if (event->val == KM_PRESS) {
498                                         const bool use_individual = RNA_boolean_get(op->ptr, "use_individual");
499                                         RNA_boolean_set(op->ptr, "use_individual", !use_individual);
500                                         if (edbm_inset_calc(op)) {
501                                                 edbm_inset_update_header(op, C);
502                                         }
503                                         else {
504                                                 edbm_inset_cancel(C, op);
505                                                 return OPERATOR_CANCELLED;
506                                         }
507                                         handled = true;
508                                 }
509                                 break;
510                 }
511
512                 /* Modal numinput inactive, try to handle numeric inputs last... */
513                 if (!handled && event->val == KM_PRESS && handleNumInput(C, &opdata->num_input, event)) {
514                         float amounts[2] = {RNA_float_get(op->ptr, "thickness"),
515                                             RNA_float_get(op->ptr, "depth")};
516                         applyNumInput(&opdata->num_input, amounts);
517                         amounts[0] = max_ff(amounts[0], 0.0f);
518                         RNA_float_set(op->ptr, "thickness", amounts[0]);
519                         RNA_float_set(op->ptr, "depth", amounts[1]);
520
521                         if (edbm_inset_calc(op)) {
522                                 edbm_inset_update_header(op, C);
523                                 return OPERATOR_RUNNING_MODAL;
524                         }
525                         else {
526                                 edbm_inset_cancel(C, op);
527                                 return OPERATOR_CANCELLED;
528                         }
529                 }
530         }
531
532         return OPERATOR_RUNNING_MODAL;
533 }
534
535
536 void MESH_OT_inset(wmOperatorType *ot)
537 {
538         PropertyRNA *prop;
539
540         /* identifiers */
541         ot->name = "Inset Faces";
542         ot->idname = "MESH_OT_inset";
543         ot->description = "Inset new faces into selected faces";
544
545         /* api callbacks */
546         ot->invoke = edbm_inset_invoke;
547         ot->modal = edbm_inset_modal;
548         ot->exec = edbm_inset_exec;
549         ot->cancel = edbm_inset_cancel;
550         ot->poll = ED_operator_editmesh;
551
552         /* flags */
553         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_CURSOR | OPTYPE_BLOCKING;
554
555         /* properties */
556         RNA_def_boolean(
557                 ot->srna, "use_boundary",
558                 true, "Boundary",  "Inset face boundaries");
559         RNA_def_boolean(
560                 ot->srna, "use_even_offset",
561                 true, "Offset Even",      "Scale the offset to give more even thickness");
562         RNA_def_boolean(
563                 ot->srna, "use_relative_offset",
564                 false, "Offset Relative", "Scale the offset by surrounding geometry");
565         RNA_def_boolean(
566                 ot->srna, "use_edge_rail",
567                 false, "Edge Rail", "Inset the region along existing edges");
568
569         prop = RNA_def_float_distance(ot->srna, "thickness", 0.01f, 0.0f, 1e12f, "Thickness", "", 0.0f, 10.0f);
570         /* use 1 rather then 10 for max else dragging the button moves too far */
571         RNA_def_property_ui_range(prop, 0.0, 1.0, 0.01, 4);
572
573         prop = RNA_def_float_distance(ot->srna, "depth", 0.0f, -1e12f, 1e12f, "Depth", "", -10.0f, 10.0f);
574         RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.01, 4);
575
576         RNA_def_boolean(ot->srna, "use_outset", false, "Outset", "Outset rather than inset");
577         RNA_def_boolean(ot->srna, "use_select_inset", false, "Select Outer", "Select the new inset faces");
578         RNA_def_boolean(ot->srna, "use_individual", false, "Individual", "Individual Face Inset");
579         RNA_def_boolean(ot->srna, "use_interpolate", true, "Interpolate", "Blend face data across the inset");
580
581         prop = RNA_def_boolean(ot->srna, "release_confirm", 0, "Confirm on Release", "");
582         RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
583 }