6033e7ee471ca74b03abf01681cb55c612aee1b6
[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
40 #include "RNA_define.h"
41 #include "RNA_access.h"
42
43 #include "WM_api.h"
44 #include "WM_types.h"
45
46 #include "ED_mesh.h"
47 #include "ED_numinput.h"
48 #include "ED_screen.h"
49 #include "ED_space_api.h"
50 #include "ED_transform.h"
51 #include "ED_view3d.h"
52
53 #include "mesh_intern.h"  /* own include */
54
55
56 #define MVAL_PIXEL_MARGIN  5.0f
57
58 typedef struct {
59         BMEditMesh *em;
60         float initial_length;
61         float pixel_size;  /* use when mouse input is interpreted as spatial distance */
62         bool is_modal;
63         NumInput num_input;
64         float shift_factor; /* The current factor when shift is pressed. Negative when shift not active. */
65
66         /* modal only */
67         int mcenter[2];
68         BMBackup mesh_backup;
69         void *draw_handle_pixel;
70         short twtype;
71 } BevelData;
72
73 #define HEADER_LENGTH 180
74
75 static void edbm_bevel_update_header(wmOperator *op, bContext *C)
76 {
77         const char *str = IFACE_("Confirm: Enter/LClick, Cancel: (Esc/RMB), Offset: %s, Segments: %d");
78
79         char msg[HEADER_LENGTH];
80         ScrArea *sa = CTX_wm_area(C);
81
82         if (sa) {
83                 char offset_str[NUM_STR_REP_LEN];
84                 BLI_snprintf(offset_str, NUM_STR_REP_LEN, "%f", RNA_float_get(op->ptr, "offset"));
85                 BLI_snprintf(msg, HEADER_LENGTH, str,
86                              offset_str,
87                              RNA_int_get(op->ptr, "segments")
88                             );
89
90                 ED_area_headerprint(sa, msg);
91         }
92 }
93
94 static bool edbm_bevel_init(bContext *C, wmOperator *op, const bool is_modal)
95 {
96         Object *obedit = CTX_data_edit_object(C);
97         BMEditMesh *em = BKE_editmesh_from_object(obedit);
98         BevelData *opdata;
99
100         if (em->bm->totvertsel == 0) {
101                 return false;
102         }
103
104         op->customdata = opdata = MEM_mallocN(sizeof(BevelData), "beveldata_mesh_operator");
105
106         opdata->em = em;
107         opdata->is_modal = is_modal;
108         opdata->shift_factor = -1.0f;
109
110         initNumInput(&opdata->num_input);
111         opdata->num_input.flag = NUM_NO_NEGATIVE;
112
113         /* avoid the cost of allocating a bm copy */
114         if (is_modal) {
115                 View3D *v3d = CTX_wm_view3d(C);
116                 ARegion *ar = CTX_wm_region(C);
117
118                 opdata->mesh_backup = EDBM_redo_state_store(em);
119                 opdata->draw_handle_pixel = ED_region_draw_cb_activate(ar->type, ED_region_draw_mouse_line_cb, opdata->mcenter, REGION_DRAW_POST_PIXEL);
120                 G.moving = G_TRANSFORM_EDIT;
121                 opdata->twtype = v3d->twtype;
122                 v3d->twtype = 0;
123         }
124
125         return true;
126 }
127
128 static bool edbm_bevel_calc(wmOperator *op)
129 {
130         BevelData *opdata = op->customdata;
131         BMEditMesh *em = opdata->em;
132         BMOperator bmop;
133         const float offset = RNA_float_get(op->ptr, "offset");
134         const int segments = RNA_int_get(op->ptr, "segments");
135         const bool vertex_only = RNA_boolean_get(op->ptr, "vertex_only");
136
137         /* revert to original mesh */
138         if (opdata->is_modal) {
139                 EDBM_redo_state_restore(opdata->mesh_backup, em, false);
140         }
141
142         EDBM_op_init(em, &bmop, op,
143                      "bevel geom=%hev offset=%f segments=%i vertex_only=%b",
144                      BM_ELEM_SELECT, offset, segments, vertex_only);
145
146         BMO_op_exec(em->bm, &bmop);
147
148         if (offset != 0.0f) {
149                 /* not essential, but we may have some loose geometry that
150                  * won't get bevel'd and better not leave it selected */
151                 EDBM_flag_disable_all(em, BM_ELEM_SELECT);
152                 BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true);
153         }
154
155         /* no need to de-select existing geometry */
156         if (!EDBM_op_finish(em, &bmop, op, true))
157                 return false;
158
159         EDBM_mesh_normals_update(opdata->em);
160
161         EDBM_update_generic(opdata->em, true, true);
162
163         return true;
164 }
165
166 static void edbm_bevel_exit(bContext *C, wmOperator *op)
167 {
168         BevelData *opdata = op->customdata;
169
170         ScrArea *sa = CTX_wm_area(C);
171
172         if (sa) {
173                 ED_area_headerprint(sa, NULL);
174         }
175
176         if (opdata->is_modal) {
177                 View3D *v3d = CTX_wm_view3d(C);
178                 ARegion *ar = CTX_wm_region(C);
179                 EDBM_redo_state_free(&opdata->mesh_backup, NULL, false);
180                 ED_region_draw_cb_exit(ar->type, opdata->draw_handle_pixel);
181                 v3d->twtype = opdata->twtype;
182                 G.moving = 0;
183         }
184         MEM_freeN(opdata);
185         op->customdata = NULL;
186 }
187
188 static int edbm_bevel_cancel(bContext *C, wmOperator *op)
189 {
190         BevelData *opdata = op->customdata;
191         if (opdata->is_modal) {
192                 EDBM_redo_state_free(&opdata->mesh_backup, opdata->em, true);
193                 EDBM_update_generic(opdata->em, false, true);
194         }
195
196         edbm_bevel_exit(C, op);
197
198         /* need to force redisplay or we may still view the modified result */
199         ED_region_tag_redraw(CTX_wm_region(C));
200         return OPERATOR_CANCELLED;
201 }
202
203 /* bevel! yay!!*/
204 static int edbm_bevel_exec(bContext *C, wmOperator *op)
205 {
206         if (!edbm_bevel_init(C, op, false)) {
207                 return OPERATOR_CANCELLED;
208         }
209
210         if (!edbm_bevel_calc(op)) {
211                 edbm_bevel_cancel(C, op);
212                 return OPERATOR_CANCELLED;
213         }
214
215         edbm_bevel_exit(C, op);
216
217         return OPERATOR_FINISHED;
218 }
219
220 static int edbm_bevel_invoke(bContext *C, wmOperator *op, const wmEvent *event)
221 {
222         /* TODO make modal keymap (see fly mode) */
223         RegionView3D *rv3d = CTX_wm_region_view3d(C);
224         BevelData *opdata;
225         float mlen[2];
226         float center_3d[3];
227
228         if (!edbm_bevel_init(C, op, true)) {
229                 return OPERATOR_CANCELLED;
230         }
231
232         opdata = op->customdata;
233
234         /* initialize mouse values */
235         if (!calculateTransformCenter(C, V3D_CENTROID, center_3d, opdata->mcenter)) {
236                 /* in this case the tool will likely do nothing,
237                  * ideally this will never happen and should be checked for above */
238                 opdata->mcenter[0] = opdata->mcenter[1] = 0;
239         }
240         mlen[0] = opdata->mcenter[0] - event->mval[0];
241         mlen[1] = opdata->mcenter[1] - event->mval[1];
242         opdata->initial_length = len_v2(mlen);
243         opdata->pixel_size = rv3d ? ED_view3d_pixel_size(rv3d, center_3d) : 1.0f;
244
245         edbm_bevel_update_header(op, C);
246
247         if (!edbm_bevel_calc(op)) {
248                 edbm_bevel_cancel(C, op);
249                 return OPERATOR_CANCELLED;
250         }
251
252         WM_event_add_modal_handler(C, op);
253
254         return OPERATOR_RUNNING_MODAL;
255 }
256
257 static float edbm_bevel_mval_factor(wmOperator *op, const wmEvent *event)
258 {
259         BevelData *opdata = op->customdata;
260         int use_dist = true;
261         float mdiff[2];
262         float factor;
263
264         mdiff[0] = opdata->mcenter[0] - event->mval[0];
265         mdiff[1] = opdata->mcenter[1] - event->mval[1];
266
267         if (use_dist) {
268                 factor = ((len_v2(mdiff) - MVAL_PIXEL_MARGIN) - opdata->initial_length) * opdata->pixel_size;
269         }
270         else {
271                 factor = (len_v2(mdiff) - MVAL_PIXEL_MARGIN) / opdata->initial_length;
272                 factor = factor - 1.0f;  /* a different kind of buffer where nothing happens */
273         }
274
275         /* Fake shift-transform... */
276         if (event->shift) {
277                 if (opdata->shift_factor < 0.0f) {
278                         opdata->shift_factor = RNA_float_get(op->ptr, "offset");
279                 }
280                 factor = (factor - opdata->shift_factor) * 0.1f + opdata->shift_factor;
281         }
282         else if (opdata->shift_factor >= 0.0f)
283                 opdata->shift_factor = -1.0f;
284
285         /* clamp differently based on distance/factor */
286         if (use_dist) {
287                 if (factor < 0.0f) factor = 0.0f;
288         }
289         else {
290                 CLAMP(factor, 0.0f, 1.0f);
291         }
292
293         return factor;
294 }
295
296 static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
297 {
298         BevelData *opdata = op->customdata;
299         int segments = RNA_int_get(op->ptr, "segments");
300
301         if (event->val == KM_PRESS) {
302                 /* Try to handle numeric inputs... */
303
304                 if (handleNumInput(&opdata->num_input, event)) {
305                         float value = RNA_float_get(op->ptr, "offset");
306                         applyNumInput(&opdata->num_input, &value);
307                         RNA_float_set(op->ptr, "offset", value);
308                         edbm_bevel_calc(op);
309                         edbm_bevel_update_header(op, C);
310                         return OPERATOR_RUNNING_MODAL;
311                 }
312         }
313
314         switch (event->type) {
315                 case ESCKEY:
316                 case RIGHTMOUSE:
317                         edbm_bevel_cancel(C, op);
318                         return OPERATOR_CANCELLED;
319
320                 case MOUSEMOVE:
321                         if (!hasNumInput(&opdata->num_input)) {
322                                 const float factor = edbm_bevel_mval_factor(op, event);
323                                 RNA_float_set(op->ptr, "offset", factor);
324
325                                 edbm_bevel_calc(op);
326                                 edbm_bevel_update_header(op, C);
327                         }
328                         break;
329
330                 case LEFTMOUSE:
331                 case PADENTER:
332                 case RETKEY:
333                         edbm_bevel_calc(op);
334                         edbm_bevel_exit(C, op);
335                         return OPERATOR_FINISHED;
336
337                 case WHEELUPMOUSE:  /* change number of segments */
338                 case PADPLUSKEY:
339                         if (event->val == KM_RELEASE)
340                                 break;
341
342                         segments++;
343                         RNA_int_set(op->ptr, "segments", segments);
344                         edbm_bevel_calc(op);
345                         edbm_bevel_update_header(op, C);
346                         break;
347
348                 case WHEELDOWNMOUSE:  /* change number of segments */
349                 case PADMINUS:
350                         if (event->val == KM_RELEASE)
351                                 break;
352
353                         segments = max_ii(segments - 1, 1);
354                         RNA_int_set(op->ptr, "segments", segments);
355                         edbm_bevel_calc(op);
356                         edbm_bevel_update_header(op, C);
357                         break;
358         }
359
360         return OPERATOR_RUNNING_MODAL;
361 }
362
363 void MESH_OT_bevel(wmOperatorType *ot)
364 {
365         /* identifiers */
366         ot->name = "Bevel";
367         ot->description = "Edge Bevel";
368         ot->idname = "MESH_OT_bevel";
369
370         /* api callbacks */
371         ot->exec = edbm_bevel_exec;
372         ot->invoke = edbm_bevel_invoke;
373         ot->modal = edbm_bevel_modal;
374         ot->cancel = edbm_bevel_cancel;
375         ot->poll = ED_operator_editmesh;
376
377         /* flags */
378         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_POINTER | OPTYPE_BLOCKING;
379
380         RNA_def_float(ot->srna, "offset", 0.0f, -FLT_MAX, FLT_MAX, "Offset", "", 0.0f, 1.0f);
381         RNA_def_int(ot->srna, "segments", 1, 1, 50, "Segments", "Segments for curved edge", 1, 8);
382         RNA_def_boolean(ot->srna, "vertex_only", false, "Vertex only", "Bevel only vertices");
383 }