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