Add 'loop slide' option to bevel. See T45260
[blender.git] / source / blender / editors / mesh / editmesh_bevel.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): Joseph Eagar, Howard Trickey, Campbell Barton
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/editors/mesh/editmesh_bevel.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 "BLF_translation.h"
35
36 #include "BKE_context.h"
37 #include "BKE_global.h"
38 #include "BKE_editmesh.h"
39 #include "BKE_unit.h"
40
41 #include "RNA_define.h"
42 #include "RNA_access.h"
43
44 #include "WM_api.h"
45 #include "WM_types.h"
46
47 #include "ED_mesh.h"
48 #include "ED_numinput.h"
49 #include "ED_screen.h"
50 #include "ED_space_api.h"
51 #include "ED_transform.h"
52 #include "ED_view3d.h"
53
54 #include "mesh_intern.h"  /* own include */
55
56
57 #define MVAL_PIXEL_MARGIN  5.0f
58
59 typedef struct {
60         BMEditMesh *em;
61         float initial_length;
62         float pixel_size;  /* use when mouse input is interpreted as spatial distance */
63         bool is_modal;
64         NumInput num_input;
65         float shift_factor; /* The current factor when shift is pressed. Negative when shift not active. */
66
67         /* modal only */
68         float mcenter[2];
69         BMBackup mesh_backup;
70         void *draw_handle_pixel;
71         short twtype;
72 } BevelData;
73
74 #define HEADER_LENGTH 180
75
76 static void edbm_bevel_update_header(bContext *C, wmOperator *op)
77 {
78         const char *str = IFACE_("Confirm: (Enter/LMB), Cancel: (Esc/RMB), Mode: %s (M), Clamp Overlap: %s (C), "
79                                  "Vertex Only: %s (V), Offset: %s, Segments: %d");
80
81         char msg[HEADER_LENGTH];
82         ScrArea *sa = CTX_wm_area(C);
83         Scene *sce = CTX_data_scene(C);
84
85         if (sa) {
86                 BevelData *opdata = op->customdata;
87                 char offset_str[NUM_STR_REP_LEN];
88                 const char *type_str;
89                 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "offset_type");
90
91                 if (hasNumInput(&opdata->num_input)) {
92                         outputNumInput(&opdata->num_input, offset_str, &sce->unit);
93                 }
94                 else {
95                         BLI_snprintf(offset_str, NUM_STR_REP_LEN, "%f", RNA_float_get(op->ptr, "offset"));
96                 }
97
98                 RNA_property_enum_name_gettexted(C, op->ptr, prop, RNA_property_enum_get(op->ptr, prop), &type_str);
99
100                 BLI_snprintf(msg, HEADER_LENGTH, str, type_str,
101                              WM_bool_as_string(RNA_boolean_get(op->ptr, "clamp_overlap")),
102                              WM_bool_as_string(RNA_boolean_get(op->ptr, "vertex_only")),
103                              offset_str, RNA_int_get(op->ptr, "segments"));
104
105                 ED_area_headerprint(sa, msg);
106         }
107 }
108
109 static bool edbm_bevel_init(bContext *C, wmOperator *op, const bool is_modal)
110 {
111         Object *obedit = CTX_data_edit_object(C);
112         Scene *scene = CTX_data_scene(C);
113         BMEditMesh *em = BKE_editmesh_from_object(obedit);
114         BevelData *opdata;
115
116         if (em->bm->totvertsel == 0) {
117                 return false;
118         }
119
120         op->customdata = opdata = MEM_mallocN(sizeof(BevelData), "beveldata_mesh_operator");
121
122         opdata->em = em;
123         opdata->is_modal = is_modal;
124         opdata->shift_factor = -1.0f;
125
126         initNumInput(&opdata->num_input);
127         opdata->num_input.idx_max = 0;
128         opdata->num_input.val_flag[0] |= NUM_NO_NEGATIVE;
129         opdata->num_input.unit_sys = scene->unit.system;
130         opdata->num_input.unit_type[0] = B_UNIT_NONE;  /* Not sure this is a factor or a unit? */
131
132         /* avoid the cost of allocating a bm copy */
133         if (is_modal) {
134                 View3D *v3d = CTX_wm_view3d(C);
135                 ARegion *ar = CTX_wm_region(C);
136
137                 opdata->mesh_backup = EDBM_redo_state_store(em);
138                 opdata->draw_handle_pixel = ED_region_draw_cb_activate(ar->type, ED_region_draw_mouse_line_cb, opdata->mcenter, REGION_DRAW_POST_PIXEL);
139                 G.moving = G_TRANSFORM_EDIT;
140                 opdata->twtype = v3d->twtype;
141                 v3d->twtype = 0;
142         }
143
144         return true;
145 }
146
147 static bool edbm_bevel_calc(wmOperator *op)
148 {
149         BevelData *opdata = op->customdata;
150         BMEditMesh *em = opdata->em;
151         BMOperator bmop;
152         const float offset = RNA_float_get(op->ptr, "offset");
153         const int offset_type = RNA_enum_get(op->ptr, "offset_type");
154         const int segments = RNA_int_get(op->ptr, "segments");
155         const float profile = RNA_float_get(op->ptr, "profile");
156         const bool vertex_only = RNA_boolean_get(op->ptr, "vertex_only");
157         const bool clamp_overlap = RNA_boolean_get(op->ptr, "clamp_overlap");
158         int material = RNA_int_get(op->ptr, "material");
159         const bool loop_slide = RNA_boolean_get(op->ptr, "loop_slide");
160
161         /* revert to original mesh */
162         if (opdata->is_modal) {
163                 EDBM_redo_state_restore(opdata->mesh_backup, em, false);
164         }
165
166         if (em->ob)
167                 material = CLAMPIS(material, -1, em->ob->totcol - 1);
168
169         EDBM_op_init(em, &bmop, op,
170                      "bevel geom=%hev offset=%f segments=%i vertex_only=%b offset_type=%i profile=%f clamp_overlap=%b "
171                      "material=%i loop_slide=%b",
172                      BM_ELEM_SELECT, offset, segments, vertex_only, offset_type, profile, clamp_overlap, material, loop_slide);
173
174         BMO_op_exec(em->bm, &bmop);
175
176         if (offset != 0.0f) {
177                 /* not essential, but we may have some loose geometry that
178                  * won't get bevel'd and better not leave it selected */
179                 EDBM_flag_disable_all(em, BM_ELEM_SELECT);
180                 BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true);
181         }
182
183         /* no need to de-select existing geometry */
184         if (!EDBM_op_finish(em, &bmop, op, true))
185                 return false;
186
187         EDBM_mesh_normals_update(opdata->em);
188
189         EDBM_update_generic(opdata->em, true, true);
190
191         return true;
192 }
193
194 static void edbm_bevel_exit(bContext *C, wmOperator *op)
195 {
196         BevelData *opdata = op->customdata;
197
198         ScrArea *sa = CTX_wm_area(C);
199
200         if (sa) {
201                 ED_area_headerprint(sa, NULL);
202         }
203
204         if (opdata->is_modal) {
205                 View3D *v3d = CTX_wm_view3d(C);
206                 ARegion *ar = CTX_wm_region(C);
207                 EDBM_redo_state_free(&opdata->mesh_backup, NULL, false);
208                 ED_region_draw_cb_exit(ar->type, opdata->draw_handle_pixel);
209                 v3d->twtype = opdata->twtype;
210                 G.moving = 0;
211         }
212         MEM_freeN(opdata);
213         op->customdata = NULL;
214 }
215
216 static void edbm_bevel_cancel(bContext *C, wmOperator *op)
217 {
218         BevelData *opdata = op->customdata;
219         if (opdata->is_modal) {
220                 EDBM_redo_state_free(&opdata->mesh_backup, opdata->em, true);
221                 EDBM_update_generic(opdata->em, false, true);
222         }
223
224         edbm_bevel_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 /* bevel! yay!!*/
231 static int edbm_bevel_exec(bContext *C, wmOperator *op)
232 {
233         if (!edbm_bevel_init(C, op, false)) {
234                 return OPERATOR_CANCELLED;
235         }
236
237         if (!edbm_bevel_calc(op)) {
238                 edbm_bevel_cancel(C, op);
239                 return OPERATOR_CANCELLED;
240         }
241
242         edbm_bevel_exit(C, op);
243
244         return OPERATOR_FINISHED;
245 }
246
247 static int edbm_bevel_invoke(bContext *C, wmOperator *op, const wmEvent *event)
248 {
249         /* TODO make modal keymap (see fly mode) */
250         RegionView3D *rv3d = CTX_wm_region_view3d(C);
251         BevelData *opdata;
252         float mlen[2];
253         float center_3d[3];
254
255         if (!edbm_bevel_init(C, op, true)) {
256                 return OPERATOR_CANCELLED;
257         }
258
259         opdata = op->customdata;
260
261         /* initialize mouse values */
262         if (!calculateTransformCenter(C, V3D_CENTROID, center_3d, opdata->mcenter)) {
263                 /* in this case the tool will likely do nothing,
264                  * ideally this will never happen and should be checked for above */
265                 opdata->mcenter[0] = opdata->mcenter[1] = 0;
266         }
267         mlen[0] = opdata->mcenter[0] - event->mval[0];
268         mlen[1] = opdata->mcenter[1] - event->mval[1];
269         opdata->initial_length = len_v2(mlen);
270         opdata->pixel_size = rv3d ? ED_view3d_pixel_size(rv3d, center_3d) : 1.0f;
271
272         edbm_bevel_update_header(C, op);
273
274         if (!edbm_bevel_calc(op)) {
275                 edbm_bevel_cancel(C, op);
276                 return OPERATOR_CANCELLED;
277         }
278
279         WM_event_add_modal_handler(C, op);
280
281         return OPERATOR_RUNNING_MODAL;
282 }
283
284 static float edbm_bevel_mval_factor(wmOperator *op, const wmEvent *event)
285 {
286         BevelData *opdata = op->customdata;
287         bool use_dist;
288         bool is_percent;
289         float mdiff[2];
290         float factor;
291
292         mdiff[0] = opdata->mcenter[0] - event->mval[0];
293         mdiff[1] = opdata->mcenter[1] - event->mval[1];
294         is_percent = (RNA_enum_get(op->ptr, "offset_type") == BEVEL_AMT_PERCENT);
295         use_dist = !is_percent;
296
297         factor = ((len_v2(mdiff) - MVAL_PIXEL_MARGIN) - opdata->initial_length) * opdata->pixel_size;
298
299         /* Fake shift-transform... */
300         if (event->shift) {
301                 if (opdata->shift_factor < 0.0f) {
302                         opdata->shift_factor = RNA_float_get(op->ptr, "offset");
303                         if (is_percent) {
304                                 opdata->shift_factor /= 100.0f;
305                         }
306                 }
307                 factor = (factor - opdata->shift_factor) * 0.1f + opdata->shift_factor;
308         }
309         else if (opdata->shift_factor >= 0.0f) {
310                 opdata->shift_factor = -1.0f;
311         }
312
313         /* clamp differently based on distance/factor */
314         if (use_dist) {
315                 if (factor < 0.0f) factor = 0.0f;
316         }
317         else {
318                 CLAMP(factor, 0.0f, 1.0f);
319                 if (is_percent) {
320                         factor *= 100.0f;
321                 }
322         }
323
324         return factor;
325 }
326
327 static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
328 {
329         BevelData *opdata = op->customdata;
330         int segments = RNA_int_get(op->ptr, "segments");
331         const bool has_numinput = hasNumInput(&opdata->num_input);
332
333         /* Modal numinput active, try to handle numeric inputs first... */
334         if (event->val == KM_PRESS && has_numinput && handleNumInput(C, &opdata->num_input, event)) {
335                 float value = RNA_float_get(op->ptr, "offset");
336                 applyNumInput(&opdata->num_input, &value);
337                 RNA_float_set(op->ptr, "offset", value);
338                 edbm_bevel_calc(op);
339                 edbm_bevel_update_header(C, op);
340                 return OPERATOR_RUNNING_MODAL;
341         }
342         else {
343                 bool handled = false;
344                 switch (event->type) {
345                         case ESCKEY:
346                         case RIGHTMOUSE:
347                                 edbm_bevel_cancel(C, op);
348                                 return OPERATOR_CANCELLED;
349
350                         case MOUSEMOVE:
351                                 if (!has_numinput) {
352                                         const float factor = edbm_bevel_mval_factor(op, event);
353                                         RNA_float_set(op->ptr, "offset", factor);
354
355                                         edbm_bevel_calc(op);
356                                         edbm_bevel_update_header(C, op);
357                                         handled = true;
358                                 }
359                                 break;
360
361                         case LEFTMOUSE:
362                         case PADENTER:
363                         case RETKEY:
364                                 edbm_bevel_calc(op);
365                                 edbm_bevel_exit(C, op);
366                                 return OPERATOR_FINISHED;
367
368                         /* Note this will prevent padplus and padminus to ever activate modal numinput.
369                          * This is not really an issue though, as we only expect positive values here...
370                          * Else we could force them to only modify segments number when shift is pressed, or so.
371                          */
372
373                         case WHEELUPMOUSE:  /* change number of segments */
374                         case PADPLUSKEY:
375                                 if (event->val == KM_RELEASE)
376                                         break;
377
378                                 segments++;
379                                 RNA_int_set(op->ptr, "segments", segments);
380                                 edbm_bevel_calc(op);
381                                 edbm_bevel_update_header(C, op);
382                                 handled = true;
383                                 break;
384
385                         case WHEELDOWNMOUSE:  /* change number of segments */
386                         case PADMINUS:
387                                 if (event->val == KM_RELEASE)
388                                         break;
389
390                                 segments = max_ii(segments - 1, 1);
391                                 RNA_int_set(op->ptr, "segments", segments);
392                                 edbm_bevel_calc(op);
393                                 edbm_bevel_update_header(C, op);
394                                 handled = true;
395                                 break;
396
397                         case MKEY:
398                                 if (event->val == KM_RELEASE)
399                                         break;
400
401                                 {
402                                         PropertyRNA *prop = RNA_struct_find_property(op->ptr, "offset_type");
403                                         int type = RNA_property_enum_get(op->ptr, prop);
404                                         type++;
405                                         if (type > BEVEL_AMT_PERCENT) {
406                                                 type = BEVEL_AMT_OFFSET;
407                                         }
408                                         RNA_property_enum_set(op->ptr, prop, type);
409                                 }
410                                 /* Update factor accordingly to new offset_type. */
411                                 if (!has_numinput) {
412                                         RNA_float_set(op->ptr, "offset", edbm_bevel_mval_factor(op, event));
413                                 }
414                                 edbm_bevel_calc(op);
415                                 edbm_bevel_update_header(C, op);
416                                 handled = true;
417                                 break;
418                         case CKEY:
419                                 if (event->val == KM_RELEASE)
420                                         break;
421
422                                 {
423                                         PropertyRNA *prop = RNA_struct_find_property(op->ptr, "clamp_overlap");
424                                         RNA_property_enum_set(op->ptr, prop, !RNA_property_boolean_get(op->ptr, prop));
425                                 }
426                                 edbm_bevel_calc(op);
427                                 edbm_bevel_update_header(C, op);
428                                 handled = true;
429                                 break;
430                         case VKEY:
431                                 if (event->val == KM_RELEASE)
432                                         break;
433                                 
434                                 {
435                                         PropertyRNA *prop = RNA_struct_find_property(op->ptr, "vertex_only");
436                                         RNA_property_enum_set(op->ptr, prop, !RNA_property_boolean_get(op->ptr, prop));
437                                 }
438                                 edbm_bevel_calc(op);
439                                 edbm_bevel_update_header(C, op);
440                                 handled = true;
441                                 break;
442                                 
443                 }
444
445                 /* Modal numinput inactive, try to handle numeric inputs last... */
446                 if (!handled && event->val == KM_PRESS && handleNumInput(C, &opdata->num_input, event)) {
447                         float value = RNA_float_get(op->ptr, "offset");
448                         applyNumInput(&opdata->num_input, &value);
449                         RNA_float_set(op->ptr, "offset", value);
450                         edbm_bevel_calc(op);
451                         edbm_bevel_update_header(C, op);
452                         return OPERATOR_RUNNING_MODAL;
453                 }
454         }
455
456         return OPERATOR_RUNNING_MODAL;
457 }
458
459 static void mesh_ot_bevel_offset_range_func(PointerRNA *ptr, PropertyRNA *UNUSED(prop),
460                                             float *min, float *max, float *softmin, float *softmax)
461 {
462         const int offset_type = RNA_enum_get(ptr, "offset_type");
463
464         *min = -FLT_MAX;
465         *max = FLT_MAX;
466         *softmin = 0.0f;
467         *softmax = (offset_type == BEVEL_AMT_PERCENT) ? 100.0f : 1.0f;
468 }
469
470 void MESH_OT_bevel(wmOperatorType *ot)
471 {
472         PropertyRNA *prop;
473
474         static EnumPropertyItem offset_type_items[] = {
475                 {BEVEL_AMT_OFFSET, "OFFSET", 0, "Offset", "Amount is offset of new edges from original"},
476                 {BEVEL_AMT_WIDTH, "WIDTH", 0, "Width", "Amount is width of new face"},
477                 {BEVEL_AMT_DEPTH, "DEPTH", 0, "Depth", "Amount is perpendicular distance from original edge to bevel face"},
478                 {BEVEL_AMT_PERCENT, "PERCENT", 0, "Percent", "Amount is percent of adjacent edge length"},
479                 {0, NULL, 0, NULL, NULL},
480         };
481
482         /* identifiers */
483         ot->name = "Bevel";
484         ot->description = "Edge Bevel";
485         ot->idname = "MESH_OT_bevel";
486
487         /* api callbacks */
488         ot->exec = edbm_bevel_exec;
489         ot->invoke = edbm_bevel_invoke;
490         ot->modal = edbm_bevel_modal;
491         ot->cancel = edbm_bevel_cancel;
492         ot->poll = ED_operator_editmesh;
493
494         /* flags */
495         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_CURSOR | OPTYPE_BLOCKING;
496
497         RNA_def_enum(ot->srna, "offset_type", offset_type_items, 0, "Amount Type", "What distance Amount measures");
498         prop = RNA_def_float(ot->srna, "offset", 0.0f, -1e6f, 1e6f, "Amount", "", 0.0f, 1.0f);
499         RNA_def_property_float_array_funcs_runtime(prop, NULL, NULL, mesh_ot_bevel_offset_range_func);
500         RNA_def_int(ot->srna, "segments", 1, 1, 50, "Segments", "Segments for curved edge", 1, 8);
501         RNA_def_float(ot->srna, "profile", 0.5f, 0.15f, 1.0f, "Profile", "Controls profile shape (0.5 = round)", 0.15f, 1.0f);
502         RNA_def_boolean(ot->srna, "vertex_only", false, "Vertex Only", "Bevel only vertices");
503         RNA_def_boolean(ot->srna, "clamp_overlap", false, "Clamp Overlap",
504                         "Do not allow beveled edges/vertices to overlap each other");
505         RNA_def_boolean(ot->srna, "loop_slide", false, "Loop Slide", "Prefer slide along edge to even widths");
506         RNA_def_int(ot->srna, "material", -1, -1, INT_MAX, "Material", "Material for bevel faces (-1 means use adjacent faces)", -1, 100);
507 }