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