3966826a5b2e42708db18a41487b3dcb7ef4303a
[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 "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 HEADER_LENGTH 180
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         BMEditMesh *em;
68         NumInput num_input;
69
70         /* modal only */
71         int mcenter[2];
72         BMBackup mesh_backup;
73         void *draw_handle_pixel;
74         short twtype;
75 } InsetData;
76
77
78 static void edbm_inset_update_header(wmOperator *op, bContext *C)
79 {
80         InsetData *opdata = op->customdata;
81
82         const char *str = IFACE_("Confirm: Enter/LClick, Cancel: (Esc/RClick), Thickness: %s, "
83                                  "Depth (Ctrl to tweak): %s (%s), Outset (O): (%s), Boundary (B): (%s), Individual (I): (%s)");
84
85         char msg[HEADER_LENGTH];
86         ScrArea *sa = CTX_wm_area(C);
87
88         if (sa) {
89                 char flts_str[NUM_STR_REP_LEN * 2];
90                 if (hasNumInput(&opdata->num_input))
91                         outputNumInput(&opdata->num_input, flts_str);
92                 else {
93                         BLI_snprintf(flts_str, NUM_STR_REP_LEN, "%f", RNA_float_get(op->ptr, "thickness"));
94                         BLI_snprintf(flts_str + NUM_STR_REP_LEN, NUM_STR_REP_LEN, "%f", RNA_float_get(op->ptr, "depth"));
95                 }
96                 BLI_snprintf(msg, HEADER_LENGTH, str,
97                              flts_str,
98                              flts_str + NUM_STR_REP_LEN,
99                              opdata->modify_depth ? IFACE_("On") : IFACE_("Off"),
100                              RNA_boolean_get(op->ptr, "use_outset") ? IFACE_("On") : IFACE_("Off"),
101                              RNA_boolean_get(op->ptr, "use_boundary") ? IFACE_("On") : IFACE_("Off"),
102                              RNA_boolean_get(op->ptr, "use_individual") ? IFACE_("On") : IFACE_("Off")
103                             );
104
105                 ED_area_headerprint(sa, msg);
106         }
107 }
108
109
110 static bool edbm_inset_init(bContext *C, wmOperator *op, const bool is_modal)
111 {
112         InsetData *opdata;
113         Object *obedit = CTX_data_edit_object(C);
114         BMEditMesh *em = BKE_editmesh_from_object(obedit);
115
116         if (em->bm->totvertsel == 0) {
117                 return false;
118         }
119
120         op->customdata = opdata = MEM_mallocN(sizeof(InsetData), "inset_operator_data");
121
122         opdata->old_thickness = 0.01;
123         opdata->old_depth = 0.0;
124         opdata->modify_depth = false;
125         opdata->shift = false;
126         opdata->shift_amount = 0.0f;
127         opdata->is_modal = is_modal;
128         opdata->em = em;
129
130         initNumInput(&opdata->num_input);
131         opdata->num_input.idx_max = 1; /* Two elements. */
132
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 void edbm_inset_exit(bContext *C, wmOperator *op)
148 {
149         InsetData *opdata;
150         ScrArea *sa = CTX_wm_area(C);
151
152         opdata = op->customdata;
153
154         if (opdata->is_modal) {
155                 View3D *v3d = CTX_wm_view3d(C);
156                 ARegion *ar = CTX_wm_region(C);
157                 EDBM_redo_state_free(&opdata->mesh_backup, NULL, false);
158                 ED_region_draw_cb_exit(ar->type, opdata->draw_handle_pixel);
159                 v3d->twtype = opdata->twtype;
160                 G.moving = 0;
161         }
162
163         if (sa) {
164                 ED_area_headerprint(sa, NULL);
165         }
166         MEM_freeN(op->customdata);
167 }
168
169 static int edbm_inset_cancel(bContext *C, wmOperator *op)
170 {
171         InsetData *opdata;
172
173         opdata = op->customdata;
174         if (opdata->is_modal) {
175                 EDBM_redo_state_free(&opdata->mesh_backup, opdata->em, true);
176                 EDBM_update_generic(opdata->em, false, true);
177         }
178
179         edbm_inset_exit(C, op);
180
181         /* need to force redisplay or we may still view the modified result */
182         ED_region_tag_redraw(CTX_wm_region(C));
183         return OPERATOR_CANCELLED;
184 }
185
186 static bool edbm_inset_calc(wmOperator *op)
187 {
188         InsetData *opdata;
189         BMEditMesh *em;
190         BMOperator bmop;
191
192         const bool use_boundary        = RNA_boolean_get(op->ptr, "use_boundary");
193         const bool use_even_offset     = RNA_boolean_get(op->ptr, "use_even_offset");
194         const bool use_relative_offset = RNA_boolean_get(op->ptr, "use_relative_offset");
195         const float thickness          = RNA_float_get(op->ptr,   "thickness");
196         const float depth              = RNA_float_get(op->ptr,   "depth");
197         const bool use_outset          = RNA_boolean_get(op->ptr, "use_outset");
198         const bool use_select_inset    = RNA_boolean_get(op->ptr, "use_select_inset"); /* not passed onto the BMO */
199         const bool use_individual      = RNA_boolean_get(op->ptr, "use_individual");
200         const bool use_interpolate     = RNA_boolean_get(op->ptr, "use_interpolate");
201
202         opdata = op->customdata;
203         em = opdata->em;
204
205         if (opdata->is_modal) {
206                 EDBM_redo_state_restore(opdata->mesh_backup, em, false);
207         }
208
209         if (use_individual) {
210                 EDBM_op_init(em, &bmop, op,
211                              "inset_individual faces=%hf use_even_offset=%b  use_relative_offset=%b"
212                              "use_interpolate=%b thickness=%f depth=%f",
213                              BM_ELEM_SELECT, use_even_offset, use_relative_offset, use_interpolate,
214                              thickness, depth);
215         }
216         else {
217                 EDBM_op_init(em, &bmop, op,
218                              "inset_region faces=%hf use_boundary=%b use_even_offset=%b use_relative_offset=%b"
219                              " use_interpolate=%b thickness=%f depth=%f use_outset=%b",
220                              BM_ELEM_SELECT, use_boundary, use_even_offset, use_relative_offset, use_interpolate,
221                              thickness, depth, use_outset);
222         }
223         BMO_op_exec(em->bm, &bmop);
224
225         if (use_select_inset) {
226                 /* deselect original faces/verts */
227                 EDBM_flag_disable_all(em, BM_ELEM_SELECT);
228                 BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true);
229         }
230         else {
231                 EDBM_flag_disable_all(em, BM_ELEM_SELECT);
232                 BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_in, "faces", BM_FACE, BM_ELEM_SELECT, true);
233         }
234
235         if (!EDBM_op_finish(em, &bmop, op, true)) {
236                 return false;
237         }
238         else {
239                 EDBM_update_generic(em, true, true);
240                 return true;
241         }
242 }
243
244 static int edbm_inset_exec(bContext *C, wmOperator *op)
245 {
246         if (!edbm_inset_init(C, op, false)) {
247                 return OPERATOR_CANCELLED;
248         }
249
250         if (!edbm_inset_calc(op)) {
251                 edbm_inset_exit(C, op);
252                 return OPERATOR_CANCELLED;
253         }
254
255         edbm_inset_exit(C, op);
256         return OPERATOR_FINISHED;
257 }
258
259 static int edbm_inset_invoke(bContext *C, wmOperator *op, const wmEvent *event)
260 {
261         RegionView3D *rv3d = CTX_wm_region_view3d(C);
262         InsetData *opdata;
263         float mlen[2];
264         float center_3d[3];
265
266         if (!edbm_inset_init(C, op, true)) {
267                 return OPERATOR_CANCELLED;
268         }
269
270         opdata = op->customdata;
271
272         /* initialize mouse values */
273         if (!calculateTransformCenter(C, V3D_CENTROID, center_3d, opdata->mcenter)) {
274                 /* in this case the tool will likely do nothing,
275                  * ideally this will never happen and should be checked for above */
276                 opdata->mcenter[0] = opdata->mcenter[1] = 0;
277         }
278         mlen[0] = opdata->mcenter[0] - event->mval[0];
279         mlen[1] = opdata->mcenter[1] - event->mval[1];
280         opdata->initial_length = len_v2(mlen);
281         opdata->pixel_size = rv3d ? ED_view3d_pixel_size(rv3d, center_3d) : 1.0f;
282
283         edbm_inset_calc(op);
284
285         edbm_inset_update_header(op, C);
286
287         WM_event_add_modal_handler(C, op);
288         return OPERATOR_RUNNING_MODAL;
289 }
290
291 static int edbm_inset_modal(bContext *C, wmOperator *op, const wmEvent *event)
292 {
293         InsetData *opdata = op->customdata;
294
295         if (event->val == KM_PRESS) {
296                 /* Try to handle numeric inputs... */
297
298                 if (handleNumInput(&opdata->num_input, event)) {
299                         float amounts[2] = {RNA_float_get(op->ptr, "thickness"),
300                                             RNA_float_get(op->ptr, "depth")};
301                         applyNumInput(&opdata->num_input, amounts);
302                         amounts[0] = max_ff(amounts[0], 0.0f);
303                         RNA_float_set(op->ptr, "thickness", amounts[0]);
304                         RNA_float_set(op->ptr, "depth", amounts[1]);
305
306                         if (edbm_inset_calc(op)) {
307                                 edbm_inset_update_header(op, C);
308                                 return OPERATOR_RUNNING_MODAL;
309                         }
310                         else {
311                                 edbm_inset_cancel(C, op);
312                                 return OPERATOR_CANCELLED;
313                         }
314                 }
315         }
316
317         switch (event->type) {
318                 case ESCKEY:
319                 case RIGHTMOUSE:
320                         edbm_inset_cancel(C, op);
321                         return OPERATOR_CANCELLED;
322
323                 case MOUSEMOVE:
324                         if (!hasNumInput(&opdata->num_input)) {
325                                 float mdiff[2];
326                                 float amount;
327
328                                 mdiff[0] = opdata->mcenter[0] - event->mval[0];
329                                 mdiff[1] = opdata->mcenter[1] - event->mval[1];
330
331                                 if (opdata->modify_depth)
332                                         amount = opdata->old_depth     + ((len_v2(mdiff) - opdata->initial_length) * opdata->pixel_size);
333                                 else
334                                         amount = opdata->old_thickness - ((len_v2(mdiff) - opdata->initial_length) * opdata->pixel_size);
335
336                                 /* Fake shift-transform... */
337                                 if (opdata->shift)
338                                         amount = (amount - opdata->shift_amount) * 0.1f + opdata->shift_amount;
339
340                                 if (opdata->modify_depth)
341                                         RNA_float_set(op->ptr, "depth", amount);
342                                 else {
343                                         amount = max_ff(amount, 0.0f);
344                                         RNA_float_set(op->ptr, "thickness", amount);
345                                 }
346
347                                 if (edbm_inset_calc(op))
348                                         edbm_inset_update_header(op, C);
349                                 else {
350                                         edbm_inset_cancel(C, op);
351                                         return OPERATOR_CANCELLED;
352                                 }
353                         }
354                         break;
355
356                 case LEFTMOUSE:
357                 case PADENTER:
358                 case RETKEY:
359                         edbm_inset_calc(op);
360                         edbm_inset_exit(C, op);
361                         return OPERATOR_FINISHED;
362
363                 case LEFTSHIFTKEY:
364                 case RIGHTSHIFTKEY:
365                         if (event->val == KM_PRESS) {
366                                 if (opdata->modify_depth)
367                                         opdata->shift_amount = RNA_float_get(op->ptr, "depth");
368                                 else
369                                         opdata->shift_amount = RNA_float_get(op->ptr, "thickness");
370                                 opdata->shift = true;
371                         }
372                         else {
373                                 opdata->shift_amount = 0.0f;
374                                 opdata->shift = false;
375                         }
376                         break;
377
378                 case LEFTCTRLKEY:
379                 case RIGHTCTRLKEY:
380                 {
381                         float mlen[2];
382
383                         mlen[0] = opdata->mcenter[0] - event->mval[0];
384                         mlen[1] = opdata->mcenter[1] - event->mval[1];
385
386                         if (event->val == KM_PRESS) {
387                                 opdata->old_thickness = RNA_float_get(op->ptr, "thickness");
388                                 if (opdata->shift)
389                                         opdata->shift_amount = opdata->old_thickness;
390                                 opdata->modify_depth = true;
391                         }
392                         else {
393                                 opdata->old_depth = RNA_float_get(op->ptr, "depth");
394                                 if (opdata->shift)
395                                         opdata->shift_amount = opdata->old_depth;
396                                 opdata->modify_depth = false;
397                         }
398                         opdata->initial_length = len_v2(mlen);
399
400                         edbm_inset_update_header(op, C);
401                         break;
402                 }
403
404                 case OKEY:
405                         if (event->val == KM_PRESS) {
406                                 const bool use_outset = RNA_boolean_get(op->ptr, "use_outset");
407                                 RNA_boolean_set(op->ptr, "use_outset", !use_outset);
408                                 if (edbm_inset_calc(op)) {
409                                         edbm_inset_update_header(op, C);
410                                 }
411                                 else {
412                                         edbm_inset_cancel(C, op);
413                                         return OPERATOR_CANCELLED;
414                                 }
415                         }
416                         break;
417                 case BKEY:
418                         if (event->val == KM_PRESS) {
419                                 const bool use_boundary = RNA_boolean_get(op->ptr, "use_boundary");
420                                 RNA_boolean_set(op->ptr, "use_boundary", !use_boundary);
421                                 if (edbm_inset_calc(op)) {
422                                         edbm_inset_update_header(op, C);
423                                 }
424                                 else {
425                                         edbm_inset_cancel(C, op);
426                                         return OPERATOR_CANCELLED;
427                                 }
428                         }
429                         break;
430                 case IKEY:
431                         if (event->val == KM_PRESS) {
432                                 const bool use_individual = RNA_boolean_get(op->ptr, "use_individual");
433                                 RNA_boolean_set(op->ptr, "use_individual", !use_individual);
434                                 if (edbm_inset_calc(op)) {
435                                         edbm_inset_update_header(op, C);
436                                 }
437                                 else {
438                                         edbm_inset_cancel(C, op);
439                                         return OPERATOR_CANCELLED;
440                                 }
441                         }
442                         break;
443
444         }
445
446         return OPERATOR_RUNNING_MODAL;
447 }
448
449
450 void MESH_OT_inset(wmOperatorType *ot)
451 {
452         PropertyRNA *prop;
453
454         /* identifiers */
455         ot->name = "Inset Faces";
456         ot->idname = "MESH_OT_inset";
457         ot->description = "Inset new faces into selected faces";
458
459         /* api callbacks */
460         ot->invoke = edbm_inset_invoke;
461         ot->modal = edbm_inset_modal;
462         ot->exec = edbm_inset_exec;
463         ot->cancel = edbm_inset_cancel;
464         ot->poll = ED_operator_editmesh;
465
466         /* flags */
467         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_POINTER | OPTYPE_BLOCKING;
468
469         /* properties */
470         RNA_def_boolean(ot->srna, "use_boundary",        true, "Boundary",  "Inset face boundaries");
471         RNA_def_boolean(ot->srna, "use_even_offset",     true, "Offset Even",      "Scale the offset to give more even thickness");
472         RNA_def_boolean(ot->srna, "use_relative_offset", false, "Offset Relative", "Scale the offset by surrounding geometry");
473
474         prop = RNA_def_float(ot->srna, "thickness", 0.01f, 0.0f, FLT_MAX, "Thickness", "", 0.0f, 10.0f);
475         /* use 1 rather then 10 for max else dragging the button moves too far */
476         RNA_def_property_ui_range(prop, 0.0, 1.0, 0.01, 4);
477         prop = RNA_def_float(ot->srna, "depth", 0.0f, -FLT_MAX, FLT_MAX, "Depth", "", -10.0f, 10.0f);
478         RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.01, 4);
479
480         RNA_def_boolean(ot->srna, "use_outset", false, "Outset", "Outset rather than inset");
481         RNA_def_boolean(ot->srna, "use_select_inset", true, "Select Outer", "Select the new inset faces");
482         RNA_def_boolean(ot->srna, "use_individual", false, "Individual", "Individual Face Inset");
483         RNA_def_boolean(ot->srna, "use_interpolate", true, "Interpolate", "Blend face data across the inset");
484 }