individual inset was missing relative option.
[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 = true;
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 = false;
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                 BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT | BM_EDGE, BM_ELEM_SELECT, false);
232                 BMO_slot_buffer_hflag_disable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, false);
233                 /* re-select faces so the verts and edges get selected too */
234                 BM_mesh_elem_hflag_enable_test(em->bm, BM_FACE, BM_ELEM_SELECT, true, BM_ELEM_SELECT);
235         }
236
237         if (!EDBM_op_finish(em, &bmop, op, true)) {
238                 return false;
239         }
240         else {
241                 EDBM_update_generic(em, true, true);
242                 return true;
243         }
244 }
245
246 static int edbm_inset_exec(bContext *C, wmOperator *op)
247 {
248         if (!edbm_inset_init(C, op, false)) {
249                 return OPERATOR_CANCELLED;
250         }
251
252         if (!edbm_inset_calc(op)) {
253                 edbm_inset_exit(C, op);
254                 return OPERATOR_CANCELLED;
255         }
256
257         edbm_inset_exit(C, op);
258         return OPERATOR_FINISHED;
259 }
260
261 static int edbm_inset_invoke(bContext *C, wmOperator *op, const wmEvent *event)
262 {
263         RegionView3D *rv3d = CTX_wm_region_view3d(C);
264         InsetData *opdata;
265         float mlen[2];
266         float center_3d[3];
267
268         if (!edbm_inset_init(C, op, true)) {
269                 return OPERATOR_CANCELLED;
270         }
271
272         opdata = op->customdata;
273
274         /* initialize mouse values */
275         if (!calculateTransformCenter(C, V3D_CENTROID, center_3d, opdata->mcenter)) {
276                 /* in this case the tool will likely do nothing,
277                  * ideally this will never happen and should be checked for above */
278                 opdata->mcenter[0] = opdata->mcenter[1] = 0;
279         }
280         mlen[0] = opdata->mcenter[0] - event->mval[0];
281         mlen[1] = opdata->mcenter[1] - event->mval[1];
282         opdata->initial_length = len_v2(mlen);
283         opdata->pixel_size = rv3d ? ED_view3d_pixel_size(rv3d, center_3d) : 1.0f;
284
285         edbm_inset_calc(op);
286
287         edbm_inset_update_header(op, C);
288
289         WM_event_add_modal_handler(C, op);
290         return OPERATOR_RUNNING_MODAL;
291 }
292
293 static int edbm_inset_modal(bContext *C, wmOperator *op, const wmEvent *event)
294 {
295         InsetData *opdata = op->customdata;
296
297         if (event->val == KM_PRESS) {
298                 /* Try to handle numeric inputs... */
299
300                 if (handleNumInput(&opdata->num_input, event)) {
301                         float amounts[2] = {RNA_float_get(op->ptr, "thickness"),
302                                             RNA_float_get(op->ptr, "depth")};
303                         applyNumInput(&opdata->num_input, amounts);
304                         amounts[0] = max_ff(amounts[0], 0.0f);
305                         RNA_float_set(op->ptr, "thickness", amounts[0]);
306                         RNA_float_set(op->ptr, "depth", amounts[1]);
307
308                         if (edbm_inset_calc(op)) {
309                                 edbm_inset_update_header(op, C);
310                                 return OPERATOR_RUNNING_MODAL;
311                         }
312                         else {
313                                 edbm_inset_cancel(C, op);
314                                 return OPERATOR_CANCELLED;
315                         }
316                 }
317         }
318
319         switch (event->type) {
320                 case ESCKEY:
321                 case RIGHTMOUSE:
322                         edbm_inset_cancel(C, op);
323                         return OPERATOR_CANCELLED;
324
325                 case MOUSEMOVE:
326                         if (!hasNumInput(&opdata->num_input)) {
327                                 float mdiff[2];
328                                 float amount;
329
330                                 mdiff[0] = opdata->mcenter[0] - event->mval[0];
331                                 mdiff[1] = opdata->mcenter[1] - event->mval[1];
332
333                                 if (opdata->modify_depth)
334                                         amount = opdata->old_depth     + ((len_v2(mdiff) - opdata->initial_length) * opdata->pixel_size);
335                                 else
336                                         amount = opdata->old_thickness - ((len_v2(mdiff) - opdata->initial_length) * opdata->pixel_size);
337
338                                 /* Fake shift-transform... */
339                                 if (opdata->shift)
340                                         amount = (amount - opdata->shift_amount) * 0.1f + opdata->shift_amount;
341
342                                 if (opdata->modify_depth)
343                                         RNA_float_set(op->ptr, "depth", amount);
344                                 else {
345                                         amount = max_ff(amount, 0.0f);
346                                         RNA_float_set(op->ptr, "thickness", amount);
347                                 }
348
349                                 if (edbm_inset_calc(op))
350                                         edbm_inset_update_header(op, C);
351                                 else {
352                                         edbm_inset_cancel(C, op);
353                                         return OPERATOR_CANCELLED;
354                                 }
355                         }
356                         break;
357
358                 case LEFTMOUSE:
359                 case PADENTER:
360                 case RETKEY:
361                         edbm_inset_calc(op);
362                         edbm_inset_exit(C, op);
363                         return OPERATOR_FINISHED;
364
365                 case LEFTSHIFTKEY:
366                 case RIGHTSHIFTKEY:
367                         if (event->val == KM_PRESS) {
368                                 if (opdata->modify_depth)
369                                         opdata->shift_amount = RNA_float_get(op->ptr, "depth");
370                                 else
371                                         opdata->shift_amount = RNA_float_get(op->ptr, "thickness");
372                                 opdata->shift = true;
373                         }
374                         else {
375                                 opdata->shift_amount = 0.0f;
376                                 opdata->shift = false;
377                         }
378                         break;
379
380                 case LEFTCTRLKEY:
381                 case RIGHTCTRLKEY:
382                 {
383                         float mlen[2];
384
385                         mlen[0] = opdata->mcenter[0] - event->mval[0];
386                         mlen[1] = opdata->mcenter[1] - event->mval[1];
387
388                         if (event->val == KM_PRESS) {
389                                 opdata->old_thickness = RNA_float_get(op->ptr, "thickness");
390                                 if (opdata->shift)
391                                         opdata->shift_amount = opdata->old_thickness;
392                                 opdata->modify_depth = true;
393                         }
394                         else {
395                                 opdata->old_depth = RNA_float_get(op->ptr, "depth");
396                                 if (opdata->shift)
397                                         opdata->shift_amount = opdata->old_depth;
398                                 opdata->modify_depth = false;
399                         }
400                         opdata->initial_length = len_v2(mlen);
401
402                         edbm_inset_update_header(op, C);
403                         break;
404                 }
405
406                 case OKEY:
407                         if (event->val == KM_PRESS) {
408                                 const bool use_outset = RNA_boolean_get(op->ptr, "use_outset");
409                                 RNA_boolean_set(op->ptr, "use_outset", !use_outset);
410                                 if (edbm_inset_calc(op)) {
411                                         edbm_inset_update_header(op, C);
412                                 }
413                                 else {
414                                         edbm_inset_cancel(C, op);
415                                         return OPERATOR_CANCELLED;
416                                 }
417                         }
418                         break;
419                 case BKEY:
420                         if (event->val == KM_PRESS) {
421                                 const bool use_boundary = RNA_boolean_get(op->ptr, "use_boundary");
422                                 RNA_boolean_set(op->ptr, "use_boundary", !use_boundary);
423                                 if (edbm_inset_calc(op)) {
424                                         edbm_inset_update_header(op, C);
425                                 }
426                                 else {
427                                         edbm_inset_cancel(C, op);
428                                         return OPERATOR_CANCELLED;
429                                 }
430                         }
431                         break;
432                 case IKEY:
433                         if (event->val == KM_PRESS) {
434                                 const bool use_individual = RNA_boolean_get(op->ptr, "use_individual");
435                                 RNA_boolean_set(op->ptr, "use_individual", !use_individual);
436                                 if (edbm_inset_calc(op)) {
437                                         edbm_inset_update_header(op, C);
438                                 }
439                                 else {
440                                         edbm_inset_cancel(C, op);
441                                         return OPERATOR_CANCELLED;
442                                 }
443                         }
444                         break;
445
446         }
447
448         return OPERATOR_RUNNING_MODAL;
449 }
450
451
452 void MESH_OT_inset(wmOperatorType *ot)
453 {
454         PropertyRNA *prop;
455
456         /* identifiers */
457         ot->name = "Inset Faces";
458         ot->idname = "MESH_OT_inset";
459         ot->description = "Inset new faces into selected faces";
460
461         /* api callbacks */
462         ot->invoke = edbm_inset_invoke;
463         ot->modal = edbm_inset_modal;
464         ot->exec = edbm_inset_exec;
465         ot->cancel = edbm_inset_cancel;
466         ot->poll = ED_operator_editmesh;
467
468         /* flags */
469         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_POINTER | OPTYPE_BLOCKING;
470
471         /* properties */
472         RNA_def_boolean(ot->srna, "use_boundary",        true, "Boundary",  "Inset face boundaries");
473         RNA_def_boolean(ot->srna, "use_even_offset",     true, "Offset Even",      "Scale the offset to give more even thickness");
474         RNA_def_boolean(ot->srna, "use_relative_offset", false, "Offset Relative", "Scale the offset by surrounding geometry");
475
476         prop = RNA_def_float(ot->srna, "thickness", 0.01f, 0.0f, FLT_MAX, "Thickness", "", 0.0f, 10.0f);
477         /* use 1 rather then 10 for max else dragging the button moves too far */
478         RNA_def_property_ui_range(prop, 0.0, 1.0, 0.01, 4);
479         prop = RNA_def_float(ot->srna, "depth", 0.0f, -FLT_MAX, FLT_MAX, "Depth", "", -10.0f, 10.0f);
480         RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.01, 4);
481
482         RNA_def_boolean(ot->srna, "use_outset", false, "Outset", "Outset rather than inset");
483         RNA_def_boolean(ot->srna, "use_select_inset", true, "Select Outer", "Select the new inset faces");
484         RNA_def_boolean(ot->srna, "use_individual", false, "Individual", "Individual Face Inset");
485         RNA_def_boolean(ot->srna, "use_interpolate", true, "Interpolate", "Blend face data across the inset");
486 }