/* * ***** BEGIN GPL LICENSE BLOCK ***** * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Contributor(s): Joseph Eagar, Howard Trickey, Campbell Barton * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/editors/mesh/editmesh_bevel.c * \ingroup edmesh */ #include "MEM_guardedalloc.h" #include "DNA_object_types.h" #include "BLI_string.h" #include "BLI_math.h" #include "BLI_linklist_stack.h" #include "BLT_translation.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_editmesh.h" #include "BKE_unit.h" #include "BKE_layer.h" #include "BKE_mesh.h" #include "RNA_define.h" #include "RNA_access.h" #include "WM_api.h" #include "WM_types.h" #include "UI_interface.h" #include "ED_mesh.h" #include "ED_numinput.h" #include "ED_screen.h" #include "ED_space_api.h" #include "ED_transform.h" #include "ED_view3d.h" #include "mesh_intern.h" /* own include */ #define MVAL_PIXEL_MARGIN 5.0f #define PROFILE_HARD_MIN 0.0f #define SEGMENTS_HARD_MAX 1000 /* which value is mouse movement and numeric input controlling? */ #define OFFSET_VALUE 0 #define OFFSET_VALUE_PERCENT 1 #define PROFILE_VALUE 2 #define SEGMENTS_VALUE 3 #define NUM_VALUE_KINDS 4 static const char *value_rna_name[NUM_VALUE_KINDS] = {"offset", "offset", "profile", "segments"}; static const float value_clamp_min[NUM_VALUE_KINDS] = {0.0f, 0.0f, PROFILE_HARD_MIN, 1.0f}; static const float value_clamp_max[NUM_VALUE_KINDS] = {1e6, 100.0f, 1.0f, SEGMENTS_HARD_MAX}; static const float value_start[NUM_VALUE_KINDS] = {0.0f, 0.0f, 0.5f, 1.0f}; static const float value_scale_per_inch[NUM_VALUE_KINDS] = { 0.0f, 100.0f, 1.0f, 4.0f}; typedef struct { BMEditMesh *em; BMBackup mesh_backup; } BevelObjectStore; typedef struct { float initial_length[NUM_VALUE_KINDS]; float scale[NUM_VALUE_KINDS]; NumInput num_input[NUM_VALUE_KINDS]; float shift_value[NUM_VALUE_KINDS]; /* The current value when shift is pressed. Negative when shift not active. */ bool is_modal; BevelObjectStore *ob_store; uint ob_store_len; /* modal only */ float mcenter[2]; void *draw_handle_pixel; short gizmo_flag; short value_mode; /* Which value does mouse movement and numeric input affect? */ float segments; /* Segments as float so smooth mouse pan works in small increments */ } BevelData; static void edbm_bevel_update_header(bContext *C, wmOperator *op) { const char *str = IFACE_("Confirm: (Enter/LMB), Cancel: (Esc/RMB), Mode: %s (M), Clamp Overlap: %s (C), " "Vertex Only: %s (V), Profile Control: %s (P), Offset: %s, Segments: %d, Profile: %.3f"); char msg[UI_MAX_DRAW_STR]; ScrArea *sa = CTX_wm_area(C); Scene *sce = CTX_data_scene(C); if (sa) { BevelData *opdata = op->customdata; char offset_str[NUM_STR_REP_LEN]; const char *type_str; PropertyRNA *prop = RNA_struct_find_property(op->ptr, "offset_type"); if (hasNumInput(&opdata->num_input[OFFSET_VALUE])) { outputNumInput(&opdata->num_input[OFFSET_VALUE], offset_str, &sce->unit); } else { BLI_snprintf(offset_str, NUM_STR_REP_LEN, "%f", RNA_float_get(op->ptr, "offset")); } RNA_property_enum_name_gettexted(C, op->ptr, prop, RNA_property_enum_get(op->ptr, prop), &type_str); BLI_snprintf(msg, sizeof(msg), str, type_str, WM_bool_as_string(RNA_boolean_get(op->ptr, "clamp_overlap")), WM_bool_as_string(RNA_boolean_get(op->ptr, "vertex_only")), WM_bool_as_string(opdata->value_mode == PROFILE_VALUE), offset_str, RNA_int_get(op->ptr, "segments"), RNA_float_get(op->ptr, "profile")); ED_area_status_text(sa, msg); } } static void bevel_harden_normals(BMEditMesh *em, BMOperator *bmop, float face_strength) { BKE_editmesh_lnorspace_update(em); BM_normals_loops_edges_tag(em->bm, true); const int cd_clnors_offset = CustomData_get_offset(&em->bm->ldata, CD_CUSTOMLOOPNORMAL); BMesh *bm = em->bm; BMFace *f; BMLoop *l, *l_cur, *l_first; BMIter fiter; BMOpSlot *nslot = BMO_slot_get(bmop->slots_out, "normals.out"); /* Per vertex normals depending on hn_mode */ /* Similar functionality to bm_mesh_loops_calc_normals... Edges that can be smoothed are tagged */ BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { l_cur = l_first = BM_FACE_FIRST_LOOP(f); do { if ((BM_elem_flag_test(l_cur->v, BM_ELEM_SELECT)) && ((!BM_elem_flag_test(l_cur->e, BM_ELEM_TAG)) || (!BM_elem_flag_test(l_cur, BM_ELEM_TAG) && BM_loop_check_cyclic_smooth_fan(l_cur)))) { /* Both adjacent loops are sharp, set clnor to face normal */ if (!BM_elem_flag_test(l_cur->e, BM_ELEM_TAG) && !BM_elem_flag_test(l_cur->prev->e, BM_ELEM_TAG)) { const int loop_index = BM_elem_index_get(l_cur); short *clnors = BM_ELEM_CD_GET_VOID_P(l_cur, cd_clnors_offset); BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[loop_index], f->no, clnors); } else { /* Find next corresponding sharp edge in this smooth fan */ BMVert *v_pivot = l_cur->v; float *calc_n = BLI_ghash_lookup(nslot->data.ghash, v_pivot); BMEdge *e_next; const BMEdge *e_org = l_cur->e; BMLoop *lfan_pivot, *lfan_pivot_next; lfan_pivot = l_cur; e_next = lfan_pivot->e; BLI_SMALLSTACK_DECLARE(loops, BMLoop *); float cn_wght[3] = { 0.0f, 0.0f, 0.0f }, cn_unwght[3] = { 0.0f, 0.0f, 0.0f }; /* Fan through current vert and accumulate normals and loops */ while (true) { lfan_pivot_next = BM_vert_step_fan_loop(lfan_pivot, &e_next); if (lfan_pivot_next) { BLI_assert(lfan_pivot_next->v == v_pivot); } else { e_next = (lfan_pivot->e == e_next) ? lfan_pivot->prev->e : lfan_pivot->e; } BLI_SMALLSTACK_PUSH(loops, lfan_pivot); float cur[3]; mul_v3_v3fl(cur, lfan_pivot->f->no, BM_face_calc_area(lfan_pivot->f)); add_v3_v3(cn_wght, cur); if (BM_elem_flag_test(lfan_pivot->f, BM_ELEM_SELECT)) add_v3_v3(cn_unwght, cur); if (!BM_elem_flag_test(e_next, BM_ELEM_TAG) || (e_next == e_org)) { break; } lfan_pivot = lfan_pivot_next; } normalize_v3(cn_wght); normalize_v3(cn_unwght); if (calc_n) { mul_v3_fl(cn_wght, face_strength); mul_v3_fl(calc_n, 1.0f - face_strength); add_v3_v3(calc_n, cn_wght); normalize_v3(calc_n); } while ((l = BLI_SMALLSTACK_POP(loops))) { const int l_index = BM_elem_index_get(l); short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset); if (calc_n) { BKE_lnor_space_custom_normal_to_data( bm->lnor_spacearr->lspacearr[l_index], calc_n, clnors); } else { BKE_lnor_space_custom_normal_to_data( bm->lnor_spacearr->lspacearr[l_index], cn_unwght, clnors); } } BLI_ghash_remove(nslot->data.ghash, v_pivot, NULL, MEM_freeN); } } } while ((l_cur = l_cur->next) != l_first); } } static bool edbm_bevel_init(bContext *C, wmOperator *op, const bool is_modal) { Scene *scene = CTX_data_scene(C); BevelData *opdata; ViewLayer *view_layer = CTX_data_view_layer(C); float pixels_per_inch; int i; if (is_modal) { RNA_float_set(op->ptr, "offset", 0.0f); } op->customdata = opdata = MEM_mallocN(sizeof(BevelData), "beveldata_mesh_operator"); uint objects_used_len = 0; { uint ob_store_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &ob_store_len); opdata->ob_store = MEM_malloc_arrayN(ob_store_len, sizeof(*opdata->ob_store), __func__); for (uint ob_index = 0; ob_index < ob_store_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); if (em->bm->totvertsel > 0) { opdata->ob_store[objects_used_len].em = em; objects_used_len++; } } MEM_freeN(objects); opdata->ob_store_len = objects_used_len; } opdata->is_modal = is_modal; opdata->value_mode = OFFSET_VALUE; opdata->segments = (float) RNA_int_get(op->ptr, "segments"); pixels_per_inch = U.dpi * U.pixelsize; for (i = 0; i < NUM_VALUE_KINDS; i++) { opdata->shift_value[i] = -1.0f; opdata->initial_length[i] = -1.0f; /* note: scale for OFFSET_VALUE will get overwritten in edbm_bevel_invoke */ opdata->scale[i] = value_scale_per_inch[i] / pixels_per_inch; initNumInput(&opdata->num_input[i]); opdata->num_input[i].idx_max = 0; opdata->num_input[i].val_flag[0] |= NUM_NO_NEGATIVE; if (i == SEGMENTS_VALUE) { opdata->num_input[i].val_flag[0] |= NUM_NO_FRACTION | NUM_NO_ZERO; } if (i == OFFSET_VALUE) { opdata->num_input[i].unit_sys = scene->unit.system; } opdata->num_input[i].unit_type[0] = B_UNIT_NONE; /* Not sure this is a factor or a unit? */ } /* avoid the cost of allocating a bm copy */ if (is_modal) { View3D *v3d = CTX_wm_view3d(C); ARegion *ar = CTX_wm_region(C); for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) { opdata->ob_store[ob_index].mesh_backup = EDBM_redo_state_store(opdata->ob_store[ob_index].em); } opdata->draw_handle_pixel = ED_region_draw_cb_activate(ar->type, ED_region_draw_mouse_line_cb, opdata->mcenter, REGION_DRAW_POST_PIXEL); G.moving = G_TRANSFORM_EDIT; if (v3d) { opdata->gizmo_flag = v3d->gizmo_flag; v3d->gizmo_flag = V3D_GIZMO_HIDE; } } return true; } static bool edbm_bevel_calc(wmOperator *op) { BevelData *opdata = op->customdata; BMEditMesh *em; BMOperator bmop; bool changed = false; const float offset = RNA_float_get(op->ptr, "offset"); const int offset_type = RNA_enum_get(op->ptr, "offset_type"); const int segments = RNA_int_get(op->ptr, "segments"); const float profile = RNA_float_get(op->ptr, "profile"); const bool vertex_only = RNA_boolean_get(op->ptr, "vertex_only"); const bool clamp_overlap = RNA_boolean_get(op->ptr, "clamp_overlap"); int material = RNA_int_get(op->ptr, "material"); const bool loop_slide = RNA_boolean_get(op->ptr, "loop_slide"); const bool mark_seam = RNA_boolean_get(op->ptr, "mark_seam"); const bool mark_sharp = RNA_boolean_get(op->ptr, "mark_sharp"); const float hn_strength = RNA_float_get(op->ptr, "strength"); const int hnmode = RNA_enum_get(op->ptr, "hnmode"); for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) { em = opdata->ob_store[ob_index].em; /* revert to original mesh */ if (opdata->is_modal) { EDBM_redo_state_restore(opdata->ob_store[ob_index].mesh_backup, em, false); } if (em->ob) { material = CLAMPIS(material, -1, em->ob->totcol - 1); } EDBM_op_init( em, &bmop, op, "bevel geom=%hev offset=%f segments=%i vertex_only=%b offset_type=%i profile=%f clamp_overlap=%b " "material=%i loop_slide=%b mark_seam=%b mark_sharp=%b strength=%f hnmode=%i", BM_ELEM_SELECT, offset, segments, vertex_only, offset_type, profile, clamp_overlap, material, loop_slide, mark_seam, mark_sharp, hn_strength, hnmode); BMO_op_exec(em->bm, &bmop); if (offset != 0.0f) { /* not essential, but we may have some loose geometry that * won't get bevel'd and better not leave it selected */ EDBM_flag_disable_all(em, BM_ELEM_SELECT); BMO_slot_buffer_hflag_enable(em->bm, bmop.slots_out, "faces.out", BM_FACE, BM_ELEM_SELECT, true); } if (hnmode != BEVEL_HN_NONE) { bevel_harden_normals(em, &bmop, hn_strength); } /* no need to de-select existing geometry */ if (!EDBM_op_finish(em, &bmop, op, true)) { continue; } EDBM_mesh_normals_update(em); EDBM_update_generic(em, true, true); changed = true; } return changed; } static void edbm_bevel_exit(bContext *C, wmOperator *op) { BevelData *opdata = op->customdata; ScrArea *sa = CTX_wm_area(C); if (sa) { ED_area_status_text(sa, NULL); } if (opdata->is_modal) { View3D *v3d = CTX_wm_view3d(C); ARegion *ar = CTX_wm_region(C); for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) { EDBM_redo_state_free(&opdata->ob_store[ob_index].mesh_backup, NULL, false); } ED_region_draw_cb_exit(ar->type, opdata->draw_handle_pixel); if (v3d) { v3d->gizmo_flag = opdata->gizmo_flag; } G.moving = 0; } MEM_SAFE_FREE(opdata->ob_store); MEM_SAFE_FREE(op->customdata); op->customdata = NULL; } static void edbm_bevel_cancel(bContext *C, wmOperator *op) { BevelData *opdata = op->customdata; if (opdata->is_modal) { for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) { EDBM_redo_state_free(&opdata->ob_store[ob_index].mesh_backup, opdata->ob_store[ob_index].em, true); EDBM_update_generic(opdata->ob_store[ob_index].em, false, true); } } edbm_bevel_exit(C, op); /* need to force redisplay or we may still view the modified result */ ED_region_tag_redraw(CTX_wm_region(C)); } /* bevel! yay!!*/ static int edbm_bevel_exec(bContext *C, wmOperator *op) { if (!edbm_bevel_init(C, op, false)) { return OPERATOR_CANCELLED; } if (!edbm_bevel_calc(op)) { edbm_bevel_cancel(C, op); return OPERATOR_CANCELLED; } edbm_bevel_exit(C, op); return OPERATOR_FINISHED; } static void edbm_bevel_calc_initial_length(wmOperator *op, const wmEvent *event, bool mode_changed) { BevelData *opdata; float mlen[2], len, value, sc, st; int vmode; opdata = op->customdata; mlen[0] = opdata->mcenter[0] - event->mval[0]; mlen[1] = opdata->mcenter[1] - event->mval[1]; len = len_v2(mlen); vmode = opdata->value_mode; if (mode_changed || opdata->initial_length[vmode] == -1.0f) { /* If current value is not default start value, adjust len so that * the scaling and offset in edbm_bevel_mouse_set_value will * start at current value */ value = (vmode == SEGMENTS_VALUE) ? opdata->segments : RNA_float_get(op->ptr, value_rna_name[vmode]); sc = opdata->scale[vmode]; st = value_start[vmode]; if (value != value_start[vmode]) { len = (st + sc * (len - MVAL_PIXEL_MARGIN) - value) / sc; } } opdata->initial_length[opdata->value_mode] = len; } static int edbm_bevel_invoke(bContext *C, wmOperator *op, const wmEvent *event) { /* TODO make modal keymap (see fly mode) */ RegionView3D *rv3d = CTX_wm_region_view3d(C); BevelData *opdata; float center_3d[3]; if (!edbm_bevel_init(C, op, true)) { return OPERATOR_CANCELLED; } opdata = op->customdata; /* initialize mouse values */ if (!calculateTransformCenter(C, V3D_AROUND_CENTER_MEAN, center_3d, opdata->mcenter)) { /* in this case the tool will likely do nothing, * ideally this will never happen and should be checked for above */ opdata->mcenter[0] = opdata->mcenter[1] = 0; } /* for OFFSET_VALUE only, the scale is the size of a pixel under the mouse in 3d space */ opdata->scale[OFFSET_VALUE] = rv3d ? ED_view3d_pixel_size(rv3d, center_3d) : 1.0f; edbm_bevel_calc_initial_length(op, event, false); edbm_bevel_update_header(C, op); if (!edbm_bevel_calc(op)) { edbm_bevel_cancel(C, op); return OPERATOR_CANCELLED; } WM_event_add_modal_handler(C, op); return OPERATOR_RUNNING_MODAL; } static void edbm_bevel_mouse_set_value(wmOperator *op, const wmEvent *event) { BevelData *opdata = op->customdata; int vmode = opdata->value_mode; float mdiff[2]; float value; mdiff[0] = opdata->mcenter[0] - event->mval[0]; mdiff[1] = opdata->mcenter[1] - event->mval[1]; value = ((len_v2(mdiff) - MVAL_PIXEL_MARGIN) - opdata->initial_length[vmode]); /* Scale according to value mode */ value = value_start[vmode] + value * opdata->scale[vmode]; /* Fake shift-transform... */ if (event->shift) { if (opdata->shift_value[vmode] < 0.0f) { opdata->shift_value[vmode] = (vmode == SEGMENTS_VALUE) ? opdata->segments : RNA_float_get(op->ptr, value_rna_name[vmode]); } value = (value - opdata->shift_value[vmode]) * 0.1f + opdata->shift_value[vmode]; } else if (opdata->shift_value[vmode] >= 0.0f) { opdata->shift_value[vmode] = -1.0f; } /* clamp accordingto value mode, and store value back */ CLAMP(value, value_clamp_min[vmode], value_clamp_max[vmode]); if (vmode == SEGMENTS_VALUE) { opdata->segments = value; RNA_int_set(op->ptr, "segments", (int)(value + 0.5f)); } else { RNA_float_set(op->ptr, value_rna_name[vmode], value); } } static void edbm_bevel_numinput_set_value(wmOperator *op) { BevelData *opdata = op->customdata; float value; int vmode; vmode = opdata->value_mode; value = (vmode == SEGMENTS_VALUE) ? opdata->segments : RNA_float_get(op->ptr, value_rna_name[vmode]); applyNumInput(&opdata->num_input[vmode], &value); CLAMP(value, value_clamp_min[vmode], value_clamp_max[vmode]); if (vmode == SEGMENTS_VALUE) { opdata->segments = value; RNA_int_set(op->ptr, "segments", (int)value); } else { RNA_float_set(op->ptr, value_rna_name[vmode], value); } } static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) { BevelData *opdata = op->customdata; const bool has_numinput = hasNumInput(&opdata->num_input[opdata->value_mode]); /* Modal numinput active, try to handle numeric inputs first... */ if (event->val == KM_PRESS && has_numinput && handleNumInput(C, &opdata->num_input[opdata->value_mode], event)) { edbm_bevel_numinput_set_value(op); edbm_bevel_calc(op); edbm_bevel_update_header(C, op); return OPERATOR_RUNNING_MODAL; } else { bool handled = false; switch (event->type) { case ESCKEY: case RIGHTMOUSE: edbm_bevel_cancel(C, op); return OPERATOR_CANCELLED; case MOUSEMOVE: if (!has_numinput) { edbm_bevel_mouse_set_value(op, event); edbm_bevel_calc(op); edbm_bevel_update_header(C, op); handled = true; } break; case LEFTMOUSE: case PADENTER: case RETKEY: if ((event->val == KM_PRESS) || ((event->val == KM_RELEASE) && RNA_boolean_get(op->ptr, "release_confirm"))) { edbm_bevel_calc(op); edbm_bevel_exit(C, op); return OPERATOR_FINISHED; } break; case MOUSEPAN: { float delta = 0.02f * (event->y - event->prevy); if (opdata->segments >= 1 && opdata->segments + delta < 1) opdata->segments = 1; else opdata->segments += delta; RNA_int_set(op->ptr, "segments", (int)opdata->segments); edbm_bevel_calc(op); edbm_bevel_update_header(C, op); handled = true; break; } /* Note this will prevent padplus and padminus to ever activate modal numinput. * This is not really an issue though, as we only expect positive values here... * Else we could force them to only modify segments number when shift is pressed, or so. */ case WHEELUPMOUSE: /* change number of segments */ case PADPLUSKEY: if (event->val == KM_RELEASE) break; opdata->segments = opdata->segments + 1; RNA_int_set(op->ptr, "segments", (int)opdata->segments); edbm_bevel_calc(op); edbm_bevel_update_header(C, op); handled = true; break; case WHEELDOWNMOUSE: /* change number of segments */ case PADMINUS: if (event->val == KM_RELEASE) break; opdata->segments = max_ff(opdata->segments - 1, 1); RNA_int_set(op->ptr, "segments", (int)opdata->segments); edbm_bevel_calc(op); edbm_bevel_update_header(C, op); handled = true; break; case MKEY: if (event->val == KM_RELEASE) break; { PropertyRNA *prop = RNA_struct_find_property(op->ptr, "offset_type"); int type = RNA_property_enum_get(op->ptr, prop); type++; if (type > BEVEL_AMT_PERCENT) { type = BEVEL_AMT_OFFSET; } if (opdata->value_mode == OFFSET_VALUE && type == BEVEL_AMT_PERCENT) opdata->value_mode = OFFSET_VALUE_PERCENT; else if (opdata->value_mode == OFFSET_VALUE_PERCENT && type != BEVEL_AMT_PERCENT) opdata->value_mode = OFFSET_VALUE; RNA_property_enum_set(op->ptr, prop, type); if (opdata->initial_length[opdata->value_mode] == -1.0f) edbm_bevel_calc_initial_length(op, event, true); } /* Update offset accordingly to new offset_type. */ if (!has_numinput && (opdata->value_mode == OFFSET_VALUE || opdata->value_mode == OFFSET_VALUE_PERCENT)) { edbm_bevel_mouse_set_value(op, event); } edbm_bevel_calc(op); edbm_bevel_update_header(C, op); handled = true; break; case CKEY: if (event->val == KM_RELEASE) break; { PropertyRNA *prop = RNA_struct_find_property(op->ptr, "clamp_overlap"); RNA_property_boolean_set(op->ptr, prop, !RNA_property_boolean_get(op->ptr, prop)); } edbm_bevel_calc(op); edbm_bevel_update_header(C, op); handled = true; break; case PKEY: if (event->val == KM_RELEASE) break; if (opdata->value_mode == PROFILE_VALUE) { opdata->value_mode = OFFSET_VALUE; } else { opdata->value_mode = PROFILE_VALUE; } edbm_bevel_calc_initial_length(op, event, true); break; case SKEY: if (event->val == KM_RELEASE) break; if (opdata->value_mode == SEGMENTS_VALUE) { opdata->value_mode = OFFSET_VALUE; } else { opdata->value_mode = SEGMENTS_VALUE; } edbm_bevel_calc_initial_length(op, event, true); break; case VKEY: if (event->val == KM_RELEASE) break; { PropertyRNA *prop = RNA_struct_find_property(op->ptr, "vertex_only"); RNA_property_boolean_set(op->ptr, prop, !RNA_property_boolean_get(op->ptr, prop)); } edbm_bevel_calc(op); edbm_bevel_update_header(C, op); handled = true; break; case UKEY: if (event->val == KM_RELEASE) break; else { bool mark_seam = RNA_boolean_get(op->ptr, "mark_seam"); RNA_boolean_set(op->ptr, "mark_seam", !mark_seam); edbm_bevel_calc(op); handled = true; break; } case KKEY: if (event->val == KM_RELEASE) break; else { bool mark_sharp = RNA_boolean_get(op->ptr, "mark_sharp"); RNA_boolean_set(op->ptr, "mark_sharp", !mark_sharp); edbm_bevel_calc(op); handled = true; break; } } /* Modal numinput inactive, try to handle numeric inputs last... */ if (!handled && event->val == KM_PRESS && handleNumInput(C, &opdata->num_input[opdata->value_mode], event)) { edbm_bevel_numinput_set_value(op); edbm_bevel_calc(op); edbm_bevel_update_header(C, op); return OPERATOR_RUNNING_MODAL; } } return OPERATOR_RUNNING_MODAL; } static void mesh_ot_bevel_offset_range_func(PointerRNA *ptr, PropertyRNA *UNUSED(prop), float *min, float *max, float *softmin, float *softmax) { const int offset_type = RNA_enum_get(ptr, "offset_type"); *min = -FLT_MAX; *max = FLT_MAX; *softmin = 0.0f; *softmax = (offset_type == BEVEL_AMT_PERCENT) ? 100.0f : 1.0f; } void MESH_OT_bevel(wmOperatorType *ot) { PropertyRNA *prop; static const EnumPropertyItem offset_type_items[] = { {BEVEL_AMT_OFFSET, "OFFSET", 0, "Offset", "Amount is offset of new edges from original"}, {BEVEL_AMT_WIDTH, "WIDTH", 0, "Width", "Amount is width of new face"}, {BEVEL_AMT_DEPTH, "DEPTH", 0, "Depth", "Amount is perpendicular distance from original edge to bevel face"}, {BEVEL_AMT_PERCENT, "PERCENT", 0, "Percent", "Amount is percent of adjacent edge length"}, {0, NULL, 0, NULL, NULL}, }; static EnumPropertyItem harden_normals_items[] = { { BEVEL_HN_NONE, "HN_NONE", 0, "Off", "Do not use Harden Normals" }, { BEVEL_HN_FACE, "HN_FACE", 0, "Face Area", "Use faces as weight" }, { BEVEL_HN_ADJ, "HN_ADJ", 0, "Vertex average", "Use adjacent vertices as weight" }, { 0, NULL, 0, NULL, NULL }, }; /* identifiers */ ot->name = "Bevel"; ot->description = "Edge Bevel"; ot->idname = "MESH_OT_bevel"; /* api callbacks */ ot->exec = edbm_bevel_exec; ot->invoke = edbm_bevel_invoke; ot->modal = edbm_bevel_modal; ot->cancel = edbm_bevel_cancel; ot->poll = ED_operator_editmesh; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_CURSOR | OPTYPE_BLOCKING; RNA_def_enum(ot->srna, "offset_type", offset_type_items, 0, "Amount Type", "What distance Amount measures"); prop = RNA_def_float(ot->srna, "offset", 0.0f, -1e6f, 1e6f, "Amount", "", 0.0f, 100.0f); RNA_def_property_float_array_funcs_runtime(prop, NULL, NULL, mesh_ot_bevel_offset_range_func); RNA_def_int(ot->srna, "segments", 1, 1, SEGMENTS_HARD_MAX, "Segments", "Segments for curved edge", 1, 8); RNA_def_float(ot->srna, "profile", 0.5f, PROFILE_HARD_MIN, 1.0f, "Profile", "Controls profile shape (0.5 = round)", PROFILE_HARD_MIN, 1.0f); RNA_def_boolean(ot->srna, "vertex_only", false, "Vertex Only", "Bevel only vertices"); RNA_def_boolean(ot->srna, "clamp_overlap", false, "Clamp Overlap", "Do not allow beveled edges/vertices to overlap each other"); RNA_def_boolean(ot->srna, "loop_slide", true, "Loop Slide", "Prefer slide along edge to even widths"); RNA_def_boolean(ot->srna, "mark_seam", false, "Mark Seams", "Mark Seams along beveled edges"); RNA_def_boolean(ot->srna, "mark_sharp", false, "Mark Sharp", "Mark beveled edges as sharp"); RNA_def_int(ot->srna, "material", -1, -1, INT_MAX, "Material", "Material for bevel faces (-1 means use adjacent faces)", -1, 100); RNA_def_float(ot->srna, "strength", 0.5f, 0.0f, 1.0f, "Normal Strength", "Strength of calculated normal", 0.0f, 1.0f); RNA_def_enum(ot->srna, "hnmode", harden_normals_items, BEVEL_HN_NONE, "Normal Mode", "Weighting mode for Harden Normals"); prop = RNA_def_boolean(ot->srna, "release_confirm", 0, "Confirm on Release", ""); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); }