diff options
Diffstat (limited to 'source/blender/editors/mesh/editmesh_tools.c')
-rw-r--r-- | source/blender/editors/mesh/editmesh_tools.c | 1360 |
1 files changed, 1345 insertions, 15 deletions
diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c index d7617a14ff3..76ba2e5c67e 100644 --- a/source/blender/editors/mesh/editmesh_tools.c +++ b/source/blender/editors/mesh/editmesh_tools.c @@ -41,11 +41,16 @@ #include "DNA_object_types.h" #include "DNA_scene_types.h" +#include "BLI_bitmap.h" +#include "BLI_heap.h" #include "BLI_listbase.h" +#include "BLI_linklist.h" +#include "BLI_linklist_stack.h" #include "BLI_noise.h" #include "BLI_math.h" #include "BLI_rand.h" #include "BLI_sort_utils.h" +#include "BLI_string.h" #include "BKE_layer.h" #include "BKE_material.h" @@ -54,6 +59,7 @@ #include "BKE_report.h" #include "BKE_texture.h" #include "BKE_main.h" +#include "BKE_mesh.h" #include "BKE_editmesh.h" #include "DEG_depsgraph.h" @@ -6001,10 +6007,10 @@ static int edbm_sort_elements_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -static bool edbm_sort_elements_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop) +static bool edbm_sort_elements_poll_property(const bContext *UNUSED(C), wmOperator *op, const PropertyRNA *prop) { const char *prop_id = RNA_property_identifier(prop); - const int action = RNA_enum_get(ptr, "type"); + const int action = RNA_enum_get(op->ptr, "type"); /* Only show seed for randomize action! */ if (STREQ(prop_id, "seed")) { @@ -6025,18 +6031,6 @@ static bool edbm_sort_elements_draw_check_prop(PointerRNA *ptr, PropertyRNA *pro return true; } -static void edbm_sort_elements_ui(bContext *C, wmOperator *op) -{ - uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; - - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - - /* Main auto-draw call. */ - uiDefAutoButsRNA(layout, &ptr, edbm_sort_elements_draw_check_prop, UI_BUT_LABEL_ALIGN_NONE, false); -} - void MESH_OT_sort_elements(wmOperatorType *ot) { static const EnumPropertyItem type_items[] = { @@ -6072,7 +6066,7 @@ void MESH_OT_sort_elements(wmOperatorType *ot) ot->invoke = WM_menu_invoke; ot->exec = edbm_sort_elements_exec; ot->poll = ED_operator_editmesh; - ot->ui = edbm_sort_elements_ui; + ot->poll_property = edbm_sort_elements_poll_property; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -6924,3 +6918,1339 @@ void MESH_OT_mark_freestyle_face(wmOperatorType *ot) /** \} */ #endif /* WITH_FREESTYLE */ + +/********************** Loop normals editing tools modal map. **********************/ + +/* NOTE: these defines are saved in keymap files, do not change values but just add new ones */ +/* NOTE: We could add more here, like e.g. a switch between local or global coordinates of target, + * use numinput to type in explicit vector values... */ +enum { + /* Generic commands. */ + EDBM_CLNOR_MODAL_CANCEL = 1, + EDBM_CLNOR_MODAL_CONFIRM = 2, + + /* Point To operator. */ + EDBM_CLNOR_MODAL_POINTTO_RESET = 101, + EDBM_CLNOR_MODAL_POINTTO_INVERT = 102, + EDBM_CLNOR_MODAL_POINTTO_SPHERIZE = 103, + EDBM_CLNOR_MODAL_POINTTO_ALIGN = 104, + + EDBM_CLNOR_MODAL_POINTTO_USE_MOUSE = 110, + EDBM_CLNOR_MODAL_POINTTO_USE_PIVOT = 111, + EDBM_CLNOR_MODAL_POINTTO_USE_OBJECT = 112, + EDBM_CLNOR_MODAL_POINTTO_SET_USE_3DCURSOR = 113, + EDBM_CLNOR_MODAL_POINTTO_SET_USE_SELECTED = 114, +}; + +/* called in transform_ops.c, on each regeneration of keymaps */ +wmKeyMap *point_normals_modal_keymap(wmKeyConfig *keyconf) +{ + static const EnumPropertyItem modal_items[] = { + {EDBM_CLNOR_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""}, + {EDBM_CLNOR_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""}, + + /* Point To operator. */ + {EDBM_CLNOR_MODAL_POINTTO_RESET, "RESET", 0, "Reset", "Reset normals to initial ones"}, + {EDBM_CLNOR_MODAL_POINTTO_INVERT, "INVERT", 0, "Invert", "Toggle inversion of affected normals"}, + {EDBM_CLNOR_MODAL_POINTTO_SPHERIZE, "SPHERIZE", 0, "Spherize", "Interpolate between new and original normals"}, + {EDBM_CLNOR_MODAL_POINTTO_ALIGN, "ALIGN", 0, "Align", "Make all affected normals parallel"}, + + {EDBM_CLNOR_MODAL_POINTTO_USE_MOUSE, "USE_MOUSE", 0, "Use Mouse", "Follow mouse cursor position"}, + {EDBM_CLNOR_MODAL_POINTTO_USE_PIVOT, "USE_PIVOT", 0, "Use Pivot", + "Use current rotation/scaling pivot point coordinates"}, + {EDBM_CLNOR_MODAL_POINTTO_USE_OBJECT, "USE_OBJECT", 0, "Use Object", "Use current edited object's location"}, + {EDBM_CLNOR_MODAL_POINTTO_SET_USE_3DCURSOR, "SET_USE_3DCURSOR", 0, "Set and Use 3D Cursor", + "Set new 3D cursor position and use it"}, + {EDBM_CLNOR_MODAL_POINTTO_SET_USE_SELECTED, "SET_USE_SELECTED", 0, "Select and Use Mesh Item", + "Select new active mesh element and use its location"}, + {0, NULL, 0, NULL, NULL} + }; + static const char *keymap_name = "Custom Normals Modal Map"; + + wmKeyMap *keymap = WM_modalkeymap_get(keyconf, keymap_name); + + /* We only need to add map once */ + if (keymap && keymap->modal_items) + return NULL; + + keymap = WM_modalkeymap_add(keyconf, keymap_name, modal_items); + + /* Generic items for modal map. */ + WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, EDBM_CLNOR_MODAL_CANCEL); + WM_modalkeymap_add_item(keymap, RIGHTMOUSE, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_CANCEL); + + WM_modalkeymap_add_item(keymap, RETKEY, KM_PRESS, KM_ANY, 0, EDBM_CLNOR_MODAL_CONFIRM); + WM_modalkeymap_add_item(keymap, PADENTER, KM_PRESS, KM_ANY, 0, EDBM_CLNOR_MODAL_CONFIRM); + WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_CONFIRM); + + /* Point To items for modal map */ + WM_modalkeymap_add_item(keymap, RKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_RESET); + WM_modalkeymap_add_item(keymap, IKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_INVERT); + WM_modalkeymap_add_item(keymap, SKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_SPHERIZE); + WM_modalkeymap_add_item(keymap, AKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_ALIGN); + + WM_modalkeymap_add_item(keymap, MKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_USE_MOUSE); + WM_modalkeymap_add_item(keymap, LKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_USE_PIVOT); + WM_modalkeymap_add_item(keymap, OKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_USE_OBJECT); + + WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_CLICK, KM_CTRL, 0, EDBM_CLNOR_MODAL_POINTTO_SET_USE_3DCURSOR); + WM_modalkeymap_add_item(keymap, RIGHTMOUSE, KM_CLICK, KM_CTRL, 0, EDBM_CLNOR_MODAL_POINTTO_SET_USE_SELECTED); + + WM_modalkeymap_assign(keymap, "MESH_OT_point_normals"); + + return keymap; +} + +#define CLNORS_VALID_VEC_LEN (1e-4f) + +/********************** 'Point to' Loop Normals **********************/ + +enum { + EDBM_CLNOR_POINTTO_MODE_COORDINATES = 1, + EDBM_CLNOR_POINTTO_MODE_MOUSE = 2, +}; + +static EnumPropertyItem clnors_pointto_mode_items[] = { + {EDBM_CLNOR_POINTTO_MODE_COORDINATES, "COORDINATES", 0, "Coordinates", + "Use static coordinates (defined by various means)"}, + {EDBM_CLNOR_POINTTO_MODE_MOUSE, "MOUSE", 0, "Mouse", "Follow mouse cursor"}, + {0, NULL, 0, NULL, NULL} +}; + +/* Initialize loop normal data */ +static int point_normals_init(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + BKE_editmesh_lnorspace_update(em); + BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm); + + op->customdata = lnors_ed_arr; + + return lnors_ed_arr->totloop; +} + +static void point_normals_free(bContext *C, wmOperator *op) +{ + BMLoopNorEditDataArray *lnors_ed_arr = op->customdata; + BM_loop_normal_editdata_array_free(lnors_ed_arr); + op->customdata = NULL; + ED_area_status_text(CTX_wm_area(C), NULL); +} + +static void point_normals_update_header(bContext *C, wmOperator *op) +{ + char header[UI_MAX_DRAW_STR]; + char buf[UI_MAX_DRAW_STR]; + + char *p = buf; + int available_len = sizeof(buf); + +#define WM_MODALKEY(_id) \ + WM_modalkeymap_operator_items_to_string_buf(op->type, (_id), true, UI_MAX_SHORTCUT_STR, &available_len, &p) + + BLI_snprintf(header, sizeof(header), IFACE_("%s: confirm, %s: cancel, " + "%s: point to mouse (%s), %s: point to Pivot, " + "%s: point to object origin, %s: reset normals, " + "%s: set & point to 3D cursor, %s: select & point to mesh item, " + "%s: invert normals (%s), %s: spherize (%s), %s: align (%s)"), + WM_MODALKEY(EDBM_CLNOR_MODAL_CONFIRM), WM_MODALKEY(EDBM_CLNOR_MODAL_CANCEL), + WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_USE_MOUSE), + WM_bool_as_string(RNA_enum_get(op->ptr, "mode") == EDBM_CLNOR_POINTTO_MODE_MOUSE), + WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_USE_PIVOT), WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_USE_OBJECT), + WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_RESET), WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_SET_USE_3DCURSOR), + WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_SET_USE_SELECTED), + WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_INVERT), WM_bool_as_string(RNA_boolean_get(op->ptr, "invert")), + WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_SPHERIZE), + WM_bool_as_string(RNA_boolean_get(op->ptr, "spherize")), + WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_ALIGN), WM_bool_as_string(RNA_boolean_get(op->ptr, "align"))); + +#undef WM_MODALKEY + + ED_area_status_text(CTX_wm_area(C), header); +} + +/* TODO move that to generic function in BMesh? */ +static void bmesh_selected_verts_center_calc(BMesh *bm, float *r_center) +{ + BMVert *v; + BMIter viter; + int i = 0; + + zero_v3(r_center); + BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + add_v3_v3(r_center, v->co); + i++; + } + } + mul_v3_fl(r_center, 1.0f / (float)i); +} + +static void point_normals_apply(bContext *C, wmOperator *op, float target[3], const bool do_reset) +{ + Object *obedit = CTX_data_edit_object(C); + BMesh *bm = BKE_editmesh_from_object(obedit)->bm; + BMLoopNorEditDataArray *lnors_ed_arr = op->customdata; + + const bool do_invert = RNA_boolean_get(op->ptr, "invert"); + const bool do_spherize = RNA_boolean_get(op->ptr, "spherize"); + const bool do_align = RNA_boolean_get(op->ptr, "align"); + float center[3]; + + if (do_align && !do_reset) { + bmesh_selected_verts_center_calc(bm, center); + } + + sub_v3_v3(target, obedit->loc); /* Move target to local coordinates. */ + + BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata; + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + if (do_reset) { + copy_v3_v3(lnor_ed->nloc, lnor_ed->niloc); + } + else if (do_spherize) { + /* Note that this is *not* real spherical interpolation. Probably good enough in this case though? */ + const float strength = RNA_float_get(op->ptr, "spherize_strength"); + float spherized_normal[3]; + + sub_v3_v3v3(spherized_normal, target, lnor_ed->loc); + normalize_v3(spherized_normal); /* otherwise, multiplication by strength is meaningless... */ + mul_v3_fl(spherized_normal, strength); + mul_v3_v3fl(lnor_ed->nloc, lnor_ed->niloc, 1.0f - strength); + add_v3_v3(lnor_ed->nloc, spherized_normal); + } + else if (do_align) { + sub_v3_v3v3(lnor_ed->nloc, target, center); + } + else { + sub_v3_v3v3(lnor_ed->nloc, target, lnor_ed->loc); + } + + if (do_invert && !do_reset) { + negate_v3(lnor_ed->nloc); + } + if (normalize_v3(lnor_ed->nloc) >= CLNORS_VALID_VEC_LEN) { + BKE_lnor_space_custom_normal_to_data( + bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->nloc, lnor_ed->clnors_data); + } + } +} + +static int edbm_point_normals_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + View3D *v3d = CTX_wm_view3d(C); + Scene *scene = CTX_data_scene(C); + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + float target[3]; + + int ret = OPERATOR_PASS_THROUGH; + int mode = RNA_enum_get(op->ptr, "mode"); + int new_mode = mode; + bool force_mousemove = false; + bool do_reset = false; + + PropertyRNA *prop_target = RNA_struct_find_property(op->ptr, "target_location"); + + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EDBM_CLNOR_MODAL_CONFIRM: + RNA_property_float_get_array(op->ptr, prop_target, target); + ret = OPERATOR_FINISHED; + break; + + case EDBM_CLNOR_MODAL_CANCEL: + do_reset = true; + ret = OPERATOR_CANCELLED; + break; + + case EDBM_CLNOR_MODAL_POINTTO_RESET: + do_reset = true; + ret = OPERATOR_RUNNING_MODAL; + break; + + case EDBM_CLNOR_MODAL_POINTTO_INVERT: + { + PropertyRNA *prop_invert = RNA_struct_find_property(op->ptr, "invert"); + RNA_property_boolean_set(op->ptr, prop_invert, !RNA_property_boolean_get(op->ptr, prop_invert)); + RNA_property_float_get_array(op->ptr, prop_target, target); + ret = OPERATOR_RUNNING_MODAL; + break; + } + + case EDBM_CLNOR_MODAL_POINTTO_SPHERIZE: + { + PropertyRNA *prop_spherize = RNA_struct_find_property(op->ptr, "spherize"); + RNA_property_boolean_set(op->ptr, prop_spherize, !RNA_property_boolean_get(op->ptr, prop_spherize)); + RNA_property_float_get_array(op->ptr, prop_target, target); + ret = OPERATOR_RUNNING_MODAL; + break; + } + + case EDBM_CLNOR_MODAL_POINTTO_ALIGN: + { + PropertyRNA *prop_align = RNA_struct_find_property(op->ptr, "align"); + RNA_property_boolean_set(op->ptr, prop_align, !RNA_property_boolean_get(op->ptr, prop_align)); + RNA_property_float_get_array(op->ptr, prop_target, target); + ret = OPERATOR_RUNNING_MODAL; + break; + } + + case EDBM_CLNOR_MODAL_POINTTO_USE_MOUSE: + new_mode = EDBM_CLNOR_POINTTO_MODE_MOUSE; + force_mousemove = true; /* We want to immediately update to mouse cursor position... */ + ret = OPERATOR_RUNNING_MODAL; + break; + + case EDBM_CLNOR_MODAL_POINTTO_USE_OBJECT: + new_mode = EDBM_CLNOR_POINTTO_MODE_COORDINATES; + copy_v3_v3(target, obedit->loc); + ret = OPERATOR_RUNNING_MODAL; + break; + + case EDBM_CLNOR_MODAL_POINTTO_SET_USE_3DCURSOR: + new_mode = EDBM_CLNOR_POINTTO_MODE_COORDINATES; + ED_view3d_cursor3d_update(C, event->mval, false, V3D_CURSOR_ORIENT_NONE); + copy_v3_v3(target, ED_view3d_cursor3d_get(scene, v3d)->location); + break; + + case EDBM_CLNOR_MODAL_POINTTO_SET_USE_SELECTED: + new_mode = EDBM_CLNOR_POINTTO_MODE_COORDINATES; + view3d_operator_needs_opengl(C); + if (EDBM_select_pick(C, event->mval, false, false, false)) { + ED_object_editmode_calc_active_center(obedit, false, target); /* Point to newly selected active. */ + add_v3_v3(target, obedit->loc); + ret = OPERATOR_RUNNING_MODAL; + } + break; + + case EDBM_CLNOR_MODAL_POINTTO_USE_PIVOT: + new_mode = EDBM_CLNOR_POINTTO_MODE_COORDINATES; + switch (scene->toolsettings->transform_pivot_point) { + case V3D_AROUND_CENTER_BOUNDS: /* calculateCenterBound */ + { + BMVert *v; + BMIter viter; + float min[3], max[3]; + int i = 0; + + BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + if (i) { + minmax_v3v3_v3(min, max, v->co); + } + else { + copy_v3_v3(min, v->co); + copy_v3_v3(max, v->co); + } + i++; + } + } + mid_v3_v3v3(target, min, max); + add_v3_v3(target, obedit->loc); + break; + } + + case V3D_AROUND_CENTER_MEAN: + { + bmesh_selected_verts_center_calc(bm, target); + add_v3_v3(target, obedit->loc); + break; + } + + case V3D_AROUND_CURSOR: + copy_v3_v3(target, ED_view3d_cursor3d_get(scene, v3d)->location); + break; + + case V3D_AROUND_ACTIVE: + if (!ED_object_editmode_calc_active_center(obedit, false, target)) { + zero_v3(target); + } + add_v3_v3(target, obedit->loc); + break; + + default: + BKE_report(op->reports, RPT_WARNING, "Does not support Individual Origin as pivot"); + copy_v3_v3(target, obedit->loc); + } + ret = OPERATOR_RUNNING_MODAL; + break; + default: + break; + } + } + + if (new_mode != mode) { + mode = new_mode; + RNA_enum_set(op->ptr, "mode", mode); + } + + /* Only handle mousemove event in case we are in mouse mode. */ + if (event->type == MOUSEMOVE || force_mousemove) { + if (mode == EDBM_CLNOR_POINTTO_MODE_MOUSE) { + ARegion *ar = CTX_wm_region(C); + float center[3]; + + bmesh_selected_verts_center_calc(bm, center); + + ED_view3d_win_to_3d_int(v3d, ar, center, event->mval, target); + + ret = OPERATOR_RUNNING_MODAL; + } + } + + if (ret != OPERATOR_PASS_THROUGH) { + if (!ELEM(ret, OPERATOR_CANCELLED, OPERATOR_FINISHED)) { + RNA_property_float_set_array(op->ptr, prop_target, target); + } + point_normals_apply(C, op, target, do_reset); + EDBM_update_generic(em, true, false); /* Recheck bools. */ + + point_normals_update_header(C, op); + } + + if (ELEM(ret, OPERATOR_CANCELLED, OPERATOR_FINISHED)) { + point_normals_free(C, op); + } + + return ret; +} + +static int edbm_point_normals_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!point_normals_init(C, op, event)) { + point_normals_free(C, op); + return OPERATOR_CANCELLED; + } + + WM_event_add_modal_handler(C, op); + + point_normals_update_header(C, op); + + op->flag |= OP_IS_MODAL_GRAB_CURSOR; + return OPERATOR_RUNNING_MODAL; +} + +static int edbm_point_normals_exec(bContext *C, wmOperator *op) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + + if (!point_normals_init(C, op, NULL)) { + point_normals_free(C, op); + return OPERATOR_CANCELLED; + } + + /* Note that 'mode' is ignored in exec case, we directly use vector stored in target_location, whatever that is. */ + + float target[3]; + RNA_float_get_array(op->ptr, "target_location", target); + + point_normals_apply(C, op, target, false); + + EDBM_update_generic(em, true, false); + point_normals_free(C, op); + + return OPERATOR_FINISHED; +} + +static bool point_normals_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop, void *UNUSED(user_data)) +{ + const char *prop_id = RNA_property_identifier(prop); + + /* Only show strength option if spherize is enabled. */ + if (STREQ(prop_id, "spherize_strength")) { + return (bool)RNA_boolean_get(ptr, "spherize"); + } + + /* Else, show it! */ + return true; +} + +static void edbm_point_normals_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + wmWindowManager *wm = CTX_wm_manager(C); + PointerRNA ptr; + + RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); + + /* Main auto-draw call */ + uiDefAutoButsRNA(layout, &ptr, point_normals_draw_check_prop, NULL, '\0', false); +} + +void MESH_OT_point_normals(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Point Normals to Target"; + ot->description = "Point selected custom normals to specified Target"; + ot->idname = "MESH_OT_point_normals"; + + /* api callbacks */ + ot->exec = edbm_point_normals_exec; + ot->invoke = edbm_point_normals_invoke; + ot->modal = edbm_point_normals_modal; + ot->poll = ED_operator_editmesh_auto_smooth; + ot->ui = edbm_point_normals_ui; + ot->cancel = point_normals_free; + + /* flags */ + ot->flag = OPTYPE_BLOCKING | OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "mode", clnors_pointto_mode_items, EDBM_CLNOR_POINTTO_MODE_COORDINATES, + "Mode", "How to define coordinates to point custom normals to"); + RNA_def_property_flag(ot->prop, PROP_HIDDEN); + + RNA_def_boolean(ot->srna, "invert", false, "Invert", "Invert affected normals"); + + RNA_def_boolean(ot->srna, "align", false, "Align", "Make all affected normals parallel"); + + RNA_def_float_vector(ot->srna, "target_location", 3, (float[3]){0.0f, 0.0f, 0.0f}, -FLT_MAX, FLT_MAX, + "Target", "Target location to which normals will point", -1000.0f, 1000.0f); + + RNA_def_boolean(ot->srna, "spherize", false, + "Spherize", "Interpolate between original and new normals"); + + RNA_def_float(ot->srna, "spherize_strength", 0.1, 0.0f, 1.0f, + "Spherize Strength", "Ratio of spherized normal to original normal", 0.0f, 1.0f); +} + +/********************** Split/Merge Loop Normals **********************/ + +static void normals_merge(BMesh *bm, BMLoopNorEditDataArray *lnors_ed_arr) +{ + BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata; + + BLI_SMALLSTACK_DECLARE(clnors, short *); + + BLI_assert(bm->lnor_spacearr->data_type == MLNOR_SPACEARR_BMLOOP_PTR); + + BM_normals_loops_edges_tag(bm, false); + + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + if (BM_elem_flag_test(lnor_ed->loop, BM_ELEM_TAG)) { + continue; + } + + MLoopNorSpace *lnor_space = bm->lnor_spacearr->lspacearr[lnor_ed->loop_index]; + + if ((lnor_space->flags & MLNOR_SPACE_IS_SINGLE) == 0) { + LinkNode *loops = lnor_space->loops; + float avg_normal[3] = {0.0f, 0.0f, 0.0f}; + short *clnors_data; + + for (; loops; loops = loops->next) { + BMLoop *l = loops->link; + const int loop_index = BM_elem_index_get(l); + + BMLoopNorEditData *lnor_ed_tmp = lnors_ed_arr->lidx_to_lnor_editdata[loop_index]; + BLI_assert(lnor_ed_tmp->loop_index == loop_index && lnor_ed_tmp->loop == l); + add_v3_v3(avg_normal, lnor_ed_tmp->nloc); + BLI_SMALLSTACK_PUSH(clnors, lnor_ed_tmp->clnors_data); + BM_elem_flag_enable(l, BM_ELEM_TAG); + } + if (normalize_v3(avg_normal) < CLNORS_VALID_VEC_LEN) { + /* If avg normal is nearly 0, set clnor to default value. */ + zero_v3(avg_normal); + } + while ((clnors_data = BLI_SMALLSTACK_POP(clnors))) { + BKE_lnor_space_custom_normal_to_data(lnor_space, avg_normal, clnors_data); + } + } + } +} + +static void normals_split(BMesh *bm) +{ + BMFace *f; + BMLoop *l, *l_curr, *l_first; + BMIter fiter; + + BLI_assert(bm->lnor_spacearr->data_type == MLNOR_SPACEARR_BMLOOP_PTR); + + BM_normals_loops_edges_tag(bm, true); + + const int cd_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + l_curr = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_curr->v, BM_ELEM_SELECT) && (!BM_elem_flag_test(l_curr->e, BM_ELEM_TAG) || + (!BM_elem_flag_test(l_curr, BM_ELEM_TAG) && BM_loop_check_cyclic_smooth_fan(l_curr)))) + { + if (!BM_elem_flag_test(l_curr->e, BM_ELEM_TAG) && !BM_elem_flag_test(l_curr->prev->e, BM_ELEM_TAG)) { + const int loop_index = BM_elem_index_get(l_curr); + short *clnors = BM_ELEM_CD_GET_VOID_P(l_curr, cd_clnors_offset); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[loop_index], f->no, clnors); + } + else { + BMVert *v_pivot = l_curr->v; + BMEdge *e_next; + const BMEdge *e_org = l_curr->e; + BMLoop *lfan_pivot, *lfan_pivot_next; + + lfan_pivot = l_curr; + e_next = lfan_pivot->e; + BLI_SMALLSTACK_DECLARE(loops, BMLoop *); + float avg_normal[3] = { 0.0f }; + + 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); + add_v3_v3(avg_normal, lfan_pivot->f->no); + + if (!BM_elem_flag_test(e_next, BM_ELEM_TAG) || (e_next == e_org)) { + break; + } + lfan_pivot = lfan_pivot_next; + } + if (normalize_v3(avg_normal) < CLNORS_VALID_VEC_LEN) { + /* If avg normal is nearly 0, set clnor to default value. */ + zero_v3(avg_normal); + } + 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); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[l_index], avg_normal, clnors); + } + } + } + } while ((l_curr = l_curr->next) != l_first); + } +} + +static int normals_split_merge(bContext *C, const bool do_merge) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMEdge *e; + BMIter eiter; + + BKE_editmesh_lnorspace_update(em); + + BMLoopNorEditDataArray *lnors_ed_arr = do_merge ? BM_loop_normal_editdata_array_init(bm) : NULL; + + mesh_set_smooth_faces(em, do_merge); + + BM_ITER_MESH(e, &eiter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + BM_elem_flag_set(e, BM_ELEM_SMOOTH, do_merge); + } + } + if (do_merge == 0) { + Mesh *me = obedit->data; + me->drawflag |= ME_DRAWSHARP; + } + + bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; + BKE_editmesh_lnorspace_update(em); + + if (do_merge) { + normals_merge(bm, lnors_ed_arr); + } + else { + normals_split(bm); + } + + if (lnors_ed_arr) { + BM_loop_normal_editdata_array_free(lnors_ed_arr); + } + + EDBM_update_generic(em, true, false); + + return OPERATOR_FINISHED; +} + +static int edbm_merge_normals_exec(bContext *C, wmOperator *UNUSED(op)) +{ + return normals_split_merge(C, true); +} + +void MESH_OT_merge_normals(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Merge Normals"; + ot->description = "Merge custom normals of selected vertices"; + ot->idname = "MESH_OT_merge_normals"; + + /* api callbacks */ + ot->exec = edbm_merge_normals_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static int edbm_split_normals_exec(bContext *C, wmOperator *UNUSED(op)) +{ + return normals_split_merge(C, false); +} + +void MESH_OT_split_normals(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Split Normals"; + ot->description = "Split custom normals of selected vertices"; + ot->idname = "MESH_OT_split_normals"; + + /* api callbacks */ + ot->exec = edbm_split_normals_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/********************** Average Loop Normals **********************/ + +enum { + EDBM_CLNOR_AVERAGE_LOOP = 1, + EDBM_CLNOR_AVERAGE_FACE_AREA = 2, + EDBM_CLNOR_AVERAGE_ANGLE = 3, +}; + +static EnumPropertyItem average_method_items[] = { + {EDBM_CLNOR_AVERAGE_LOOP, "CUSTOM_NORMAL", 0, "Custom Normal", "Take Average of vert Normals"}, + {EDBM_CLNOR_AVERAGE_FACE_AREA, "FACE_AREA", 0, "Face Area", "Set all vert normals by Face Area"}, + {EDBM_CLNOR_AVERAGE_ANGLE, "CORNER_ANGLE", 0, "Corner Angle", "Set all vert normals by Corner Angle"}, + {0, NULL, 0, NULL, NULL} +}; + +static int edbm_average_normals_exec(bContext *C, wmOperator *op) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMFace *f; + BMLoop *l, *l_curr, *l_first; + BMIter fiter; + + bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; + BKE_editmesh_lnorspace_update(em); + + const int average_type = RNA_enum_get(op->ptr, "average_type"); + const int cd_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); + const float absweight = (float) RNA_int_get(op->ptr, "weight"); + const float threshold = RNA_float_get(op->ptr, "threshold"); + + float weight = absweight / 50.0f; + if (absweight == 100.0f) { + weight = (float)SHRT_MAX; + } + else if (absweight == 1.0f) { + weight = 1 / (float)SHRT_MAX; + } + else if ((weight - 1) * 25 > 1) { + weight = (weight - 1) * 25; + } + + BM_normals_loops_edges_tag(bm, true); + + Heap *loop_weight = BLI_heap_new(); + + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + l_curr = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_curr->v, BM_ELEM_SELECT) && (!BM_elem_flag_test(l_curr->e, BM_ELEM_TAG) || + (!BM_elem_flag_test(l_curr, BM_ELEM_TAG) && BM_loop_check_cyclic_smooth_fan(l_curr)))) + { + if (!BM_elem_flag_test(l_curr->e, BM_ELEM_TAG) && !BM_elem_flag_test(l_curr->prev->e, BM_ELEM_TAG)) { + const int loop_index = BM_elem_index_get(l_curr); + short *clnors = BM_ELEM_CD_GET_VOID_P(l_curr, cd_clnors_offset); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[loop_index], f->no, clnors); + } + else { + BMVert *v_pivot = l_curr->v; + BMEdge *e_next; + const BMEdge *e_org = l_curr->e; + BMLoop *lfan_pivot, *lfan_pivot_next; + + lfan_pivot = l_curr; + e_next = lfan_pivot->e; + + 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; + } + + float val = 1.0f; + if (average_type == EDBM_CLNOR_AVERAGE_FACE_AREA) { + val = 1.0f / BM_face_calc_area(lfan_pivot->f); + } + else if (average_type == EDBM_CLNOR_AVERAGE_ANGLE) { + val = 1.0f / BM_loop_calc_face_angle(lfan_pivot); + } + + BLI_heap_insert(loop_weight, val, lfan_pivot); + + if (!BM_elem_flag_test(e_next, BM_ELEM_TAG) || (e_next == e_org)) { + break; + } + lfan_pivot = lfan_pivot_next; + } + + BLI_SMALLSTACK_DECLARE(loops, BMLoop *); + float wnor[3], avg_normal[3] = { 0.0f }, count = 0; + float val = BLI_heap_node_value(BLI_heap_top(loop_weight)); + + while (!BLI_heap_is_empty(loop_weight)) { + const float cur_val = BLI_heap_node_value(BLI_heap_top(loop_weight)); + if (!compare_ff(val, cur_val, threshold)) { + count++; + val = cur_val; + } + l = BLI_heap_pop_min(loop_weight); + BLI_SMALLSTACK_PUSH(loops, l); + + const float n_weight = pow(weight, count); + + if (average_type == EDBM_CLNOR_AVERAGE_LOOP) { + const int l_index = BM_elem_index_get(l); + short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset); + BKE_lnor_space_custom_data_to_normal(bm->lnor_spacearr->lspacearr[l_index], clnors, wnor); + } + else { + copy_v3_v3(wnor, l->f->no); + } + mul_v3_fl(wnor, (1.0f / cur_val) * (1.0f / n_weight)); + add_v3_v3(avg_normal, wnor); + } + + if (normalize_v3(avg_normal) < CLNORS_VALID_VEC_LEN) { + /* If avg normal is nearly 0, set clnor to default value. */ + zero_v3(avg_normal); + } + 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); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[l_index], avg_normal, clnors); + } + } + } + } while ((l_curr = l_curr->next) != l_first); + } + + BLI_heap_free(loop_weight, NULL); + EDBM_update_generic(em, true, false); + + return OPERATOR_FINISHED; +} + +static bool average_normals_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop, void *UNUSED(user_data)) +{ + const char *prop_id = RNA_property_identifier(prop); + const int average_type = RNA_enum_get(ptr, "average_type"); + + /* Only show weight/threshold options in loop average type. */ + if (STREQ(prop_id, "weight")) { + return (average_type == EDBM_CLNOR_AVERAGE_LOOP); + } + else if (STREQ(prop_id, "threshold")) { + return (average_type == EDBM_CLNOR_AVERAGE_LOOP); + } + + /* Else, show it! */ + return true; +} + +static void edbm_average_normals_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + wmWindowManager *wm = CTX_wm_manager(C); + PointerRNA ptr; + + RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); + + /* Main auto-draw call */ + uiDefAutoButsRNA(layout, &ptr, average_normals_draw_check_prop, NULL, '\0', false); +} + +void MESH_OT_average_normals(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Average Normals"; + ot->description = "Average custom normals of selected vertices"; + ot->idname = "MESH_OT_average_normals"; + + /* api callbacks */ + ot->exec = edbm_average_normals_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + ot->ui = edbm_average_normals_ui; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "average_type", average_method_items, EDBM_CLNOR_AVERAGE_LOOP, + "Type", "Averaging method"); + RNA_def_property_flag(ot->prop, PROP_HIDDEN); + + RNA_def_int(ot->srna, "weight", 50, 1, 100, "Weight", "Weight applied per face", 1, 100); + + RNA_def_float(ot->srna, "threshold", 0.01f, 0, 10, "Threshold", + "Threshold value for different weights to be considered equal", 0, 5); +} + +/********************** Custom Normal Interface Tools **********************/ + +enum { + EDBM_CLNOR_TOOLS_COPY = 1, + EDBM_CLNOR_TOOLS_PASTE = 2, + EDBM_CLNOR_TOOLS_MULTIPLY = 3, + EDBM_CLNOR_TOOLS_ADD = 4, + EDBM_CLNOR_TOOLS_RESET = 5, +}; + +static EnumPropertyItem normal_vector_tool_items[] = { + {EDBM_CLNOR_TOOLS_COPY, "COPY", 0, "Copy Normal", "Copy normal to buffer"}, + {EDBM_CLNOR_TOOLS_PASTE, "PASTE", 0, "Paste Normal", "Paste normal from buffer"}, + {EDBM_CLNOR_TOOLS_ADD, "ADD", 0, "Add Normal", "Add normal vector with selection"}, + {EDBM_CLNOR_TOOLS_MULTIPLY, "MULTIPLY", 0, "Multiply Normal", "Multiply normal vector with selection"}, + {EDBM_CLNOR_TOOLS_RESET, "RESET", 0, "Reset Normal", "Reset buffer and/or normal of selected element"}, + {0, NULL, 0, NULL, NULL} +}; + +static int edbm_normals_tools_exec(bContext *C, wmOperator *op) +{ + Object *obedit = CTX_data_edit_object(C); + Scene *scene = CTX_data_scene(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + const int mode = RNA_enum_get(op->ptr, "mode"); + const bool absolute = RNA_boolean_get(op->ptr, "absolute"); + + BKE_editmesh_lnorspace_update(em); + BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm); + BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata; + + float *normal_vector = scene->toolsettings->normal_vector; + + switch (mode) { + case EDBM_CLNOR_TOOLS_COPY: + if (bm->totfacesel != 1 && lnors_ed_arr->totloop != 1 && bm->totvertsel != 1) { + BKE_report(op->reports, RPT_ERROR, "Can only copy custom normal, vertex normal or face normal"); + BM_loop_normal_editdata_array_free(lnors_ed_arr); + return OPERATOR_CANCELLED; + } + bool join = true; + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + if (!compare_v3v3(lnors_ed_arr->lnor_editdata->nloc, lnor_ed->nloc, 1e-4f)) { + join = false; + } + } + if (lnors_ed_arr->totloop == 1) { + copy_v3_v3(scene->toolsettings->normal_vector, lnors_ed_arr->lnor_editdata->nloc); + } + else if (bm->totfacesel == 1) { + BMFace *f; + BMIter fiter; + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + copy_v3_v3(scene->toolsettings->normal_vector, f->no); + } + } + } + else if (join) { + copy_v3_v3(scene->toolsettings->normal_vector, lnors_ed_arr->lnor_editdata->nloc); + } + break; + + case EDBM_CLNOR_TOOLS_PASTE: + if (!absolute) { + if (normalize_v3(normal_vector) < CLNORS_VALID_VEC_LEN) { + /* If normal is nearly 0, do nothing. */ + break; + } + } + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + if (absolute) { + float abs_normal[3]; + copy_v3_v3(abs_normal, lnor_ed->loc); + negate_v3(abs_normal); + add_v3_v3(abs_normal, normal_vector); + + if (normalize_v3(abs_normal) < CLNORS_VALID_VEC_LEN) { + /* If abs normal is nearly 0, set clnor to initial value. */ + copy_v3_v3(abs_normal, lnor_ed->niloc); + } + BKE_lnor_space_custom_normal_to_data( + bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], abs_normal, lnor_ed->clnors_data); + } + else { + BKE_lnor_space_custom_normal_to_data( + bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], normal_vector, lnor_ed->clnors_data); + } + } + break; + + case EDBM_CLNOR_TOOLS_MULTIPLY: + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + mul_v3_v3(lnor_ed->nloc, normal_vector); + + if (normalize_v3(lnor_ed->nloc) < CLNORS_VALID_VEC_LEN) { + /* If abs normal is nearly 0, set clnor to initial value. */ + copy_v3_v3(lnor_ed->nloc, lnor_ed->niloc); + } + BKE_lnor_space_custom_normal_to_data( + bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->nloc, lnor_ed->clnors_data); + } + break; + + case EDBM_CLNOR_TOOLS_ADD: + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + add_v3_v3(lnor_ed->nloc, normal_vector); + + if (normalize_v3(lnor_ed->nloc) < CLNORS_VALID_VEC_LEN) { + /* If abs normal is nearly 0, set clnor to initial value. */ + copy_v3_v3(lnor_ed->nloc, lnor_ed->niloc); + } + BKE_lnor_space_custom_normal_to_data( + bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->nloc, lnor_ed->clnors_data); + } + break; + + case EDBM_CLNOR_TOOLS_RESET: + zero_v3(normal_vector); + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + BKE_lnor_space_custom_normal_to_data( + bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], normal_vector, lnor_ed->clnors_data); + } + break; + + default: + BLI_assert(0); + break; + } + + BM_loop_normal_editdata_array_free(lnors_ed_arr); + + EDBM_update_generic(em, true, false); + return OPERATOR_FINISHED; +} + +static bool normals_tools_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop, void *UNUSED(user_data)) +{ + const char *prop_id = RNA_property_identifier(prop); + const int mode = RNA_enum_get(ptr, "mode"); + + /* Only show absolute option in paste mode. */ + if (STREQ(prop_id, "absolute")) { + return (mode == EDBM_CLNOR_TOOLS_PASTE); + } + + /* Else, show it! */ + return true; +} + +static void edbm_normals_tools_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + wmWindowManager *wm = CTX_wm_manager(C); + PointerRNA ptr; + + RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); + + /* Main auto-draw call */ + uiDefAutoButsRNA(layout, &ptr, normals_tools_draw_check_prop, NULL, '\0', false); +} + +void MESH_OT_normals_tools(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Normals Vector Tools"; + ot->description = "Custom normals tools using Normal Vector of UI"; + ot->idname = "MESH_OT_normals_tools"; + + /* api callbacks */ + ot->exec = edbm_normals_tools_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + ot->ui = edbm_normals_tools_ui; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "mode", normal_vector_tool_items, EDBM_CLNOR_TOOLS_COPY, + "Mode", "Mode of tools taking input from Interface"); + RNA_def_property_flag(ot->prop, PROP_HIDDEN); + + RNA_def_boolean(ot->srna, "absolute", false, "Absolute Coordinates", "Copy Absolute coordinates or Normal vector"); +} + +static int edbm_set_normals_from_faces_exec(bContext *C, wmOperator *op) +{ + ViewLayer *view_layer = CTX_data_view_layer(C); + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMFace *f; + BMVert *v; + BMEdge *e; + BMLoop *l; + BMIter fiter, viter, eiter, liter; + + const bool keep_sharp = RNA_boolean_get(op->ptr, "keep_sharp"); + + BKE_editmesh_lnorspace_update(em); + + float(*vnors)[3] = MEM_callocN(sizeof(*vnors) * bm->totvert, __func__); + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + BM_ITER_ELEM(v, &viter, f, BM_VERTS_OF_FACE) { + const int v_index = BM_elem_index_get(v); + add_v3_v3(vnors[v_index], f->no); + } + } + } + for (int i = 0; i < bm->totvert; i++) { + if (!is_zero_v3(vnors[i]) && normalize_v3(vnors[i]) < CLNORS_VALID_VEC_LEN) { + zero_v3(vnors[i]); + } + } + + BLI_bitmap *loop_set = BLI_BITMAP_NEW(bm->totloop, __func__); + const int cd_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); + + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + BM_ITER_ELEM(e, &eiter, f, BM_EDGES_OF_FACE) { + if (!keep_sharp || (BM_elem_flag_test(e, BM_ELEM_SMOOTH) && BM_elem_flag_test(e, BM_ELEM_SELECT))) { + BM_ITER_ELEM(v, &viter, e, BM_VERTS_OF_EDGE) { + l = BM_face_vert_share_loop(f, v); + const int l_index = BM_elem_index_get(l); + const int v_index = BM_elem_index_get(l->v); + + if (!is_zero_v3(vnors[v_index])) { + short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset); + BKE_lnor_space_custom_normal_to_data( + bm->lnor_spacearr->lspacearr[l_index], vnors[v_index], clnors); + + if (bm->lnor_spacearr->lspacearr[l_index]->flags & MLNOR_SPACE_IS_SINGLE) { + BLI_BITMAP_ENABLE(loop_set, l_index); + } + else { + LinkNode *loops = bm->lnor_spacearr->lspacearr[l_index]->loops; + for (; loops; loops = loops->next) { + BLI_BITMAP_ENABLE(loop_set, BM_elem_index_get((BMLoop *)loops->link)); + } + } + } + } + } + } + } + + int v_index; + BM_ITER_MESH_INDEX(v, &viter, bm, BM_VERTS_OF_MESH, v_index) { + BM_ITER_ELEM(l, &liter, v, BM_LOOPS_OF_VERT) { + if (BLI_BITMAP_TEST(loop_set, BM_elem_index_get(l))) { + const int loop_index = BM_elem_index_get(l); + short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset); + BKE_lnor_space_custom_normal_to_data( + bm->lnor_spacearr->lspacearr[loop_index], vnors[v_index], clnors); + } + } + } + + MEM_freeN(loop_set); + MEM_freeN(vnors); + EDBM_update_generic(em, true, false); + } + + return OPERATOR_FINISHED; +} + +void MESH_OT_set_normals_from_faces(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Set Normals From Faces"; + ot->description = "Set the custom normals from the selected faces ones"; + ot->idname = "MESH_OT_set_normals_from_faces"; + + /* api callbacks */ + ot->exec = edbm_set_normals_from_faces_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, "keep_sharp", 0, "Keep Sharp Edges", "Do not set sharp edges to face"); +} + +static int edbm_smoothen_normals_exec(bContext *C, wmOperator *op) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMFace *f; + BMLoop *l; + BMIter fiter, liter; + + BKE_editmesh_lnorspace_update(em); + BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm); + + float(*smooth_normal)[3] = MEM_callocN(sizeof(*smooth_normal) * lnors_ed_arr->totloop, __func__); + + /* This is weird choice of operation, taking all loops of faces of current vertex... Could lead to some rather + * far away loops weighting as much as very close ones (topologically speaking), with complex polygons. + * Using topological distance here (rather than geometrical one) makes sense imho, but would rather go with + * a more consistent and flexible code, we could even add max topological distance to take into account, + * and a weighting curve... + * Would do that later though, think for now we can live with that choice. --mont29 */ + BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata; + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + l = lnor_ed->loop; + float loop_normal[3]; + + BM_ITER_ELEM(f, &fiter, l->v, BM_FACES_OF_VERT) { + BMLoop *l_other; + BM_ITER_ELEM(l_other, &liter, f, BM_LOOPS_OF_FACE) { + const int l_index_other = BM_elem_index_get(l_other); + short *clnors = BM_ELEM_CD_GET_VOID_P(l_other, lnors_ed_arr->cd_custom_normal_offset); + BKE_lnor_space_custom_data_to_normal(bm->lnor_spacearr->lspacearr[l_index_other], clnors, loop_normal); + add_v3_v3(smooth_normal[i], loop_normal); + } + } + } + + const float factor = RNA_float_get(op->ptr, "factor"); + + lnor_ed = lnors_ed_arr->lnor_editdata; + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + float current_normal[3]; + + if (normalize_v3(smooth_normal[i]) < CLNORS_VALID_VEC_LEN) { + /* Skip in case smoothen normal is invalid... */ + continue; + } + + BKE_lnor_space_custom_data_to_normal( + bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->clnors_data, current_normal); + + /* Note: again, this is not true spherical interpolation that normals would need... + * But it's probably good enough for now. */ + mul_v3_fl(current_normal, 1.0f - factor); + mul_v3_fl(smooth_normal[i], factor); + add_v3_v3(current_normal, smooth_normal[i]); + + if (normalize_v3(current_normal) < CLNORS_VALID_VEC_LEN) { + /* Skip in case smoothen normal is invalid... */ + continue; + } + + BKE_lnor_space_custom_normal_to_data( + bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], current_normal, lnor_ed->clnors_data); + } + + BM_loop_normal_editdata_array_free(lnors_ed_arr); + MEM_freeN(smooth_normal); + + EDBM_update_generic(em, true, false); + + return OPERATOR_FINISHED; +} + +void MESH_OT_smoothen_normals(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Smoothen Normals"; + ot->description = "Smoothen custom normals based on adjacent vertex normals"; + ot->idname = "MESH_OT_smoothen_normals"; + + /* api callbacks */ + ot->exec = edbm_smoothen_normals_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_float(ot->srna, "factor", 0.5f, 0.0f, 1.0f, "Factor", + "Specifies weight of smooth vs original normal", 0.0f, 1.0f); +} + +/********************** Weighted Normal Modifier Face Strength **********************/ + +static int edbm_mod_weighted_strength_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMFace *f; + BMIter fiter; + + BM_select_history_clear(bm); + + const char *layer_id = MOD_WEIGHTEDNORMALS_FACEWEIGHT_CDLAYER_ID; + int cd_prop_int_index = CustomData_get_named_layer_index(&bm->pdata, CD_PROP_INT, layer_id); + if (cd_prop_int_index == -1) { + BM_data_layer_add_named(bm, &bm->pdata, CD_PROP_INT, layer_id); + cd_prop_int_index = CustomData_get_named_layer_index(&bm->pdata, CD_PROP_INT, layer_id); + } + cd_prop_int_index -= CustomData_get_layer_index(&bm->pdata, CD_PROP_INT); + const int cd_prop_int_offset = CustomData_get_n_offset(&bm->pdata, CD_PROP_INT, cd_prop_int_index); + + const int face_strength = scene->toolsettings->face_strength; + const bool set = RNA_boolean_get(op->ptr, "set"); + BM_mesh_elem_index_ensure(bm, BM_FACE); + + if (set) { + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + int *strength = BM_ELEM_CD_GET_VOID_P(f, cd_prop_int_offset); + *strength = face_strength; + } + } + } + else { + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + int *strength = BM_ELEM_CD_GET_VOID_P(f, cd_prop_int_offset); + if (*strength == face_strength) { + BM_face_select_set(bm, f, true); + BM_select_history_store(bm, f); + } + else { + BM_face_select_set(bm, f, false); + } + } + } + + EDBM_update_generic(em, false, false); + return OPERATOR_FINISHED; +} + +void MESH_OT_mod_weighted_strength(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Face Strength"; + ot->description = "Set/Get strength of face (used in Weighted Normal modifier)"; + ot->idname = "MESH_OT_mod_weighted_strength"; + + /* api callbacks */ + ot->exec = edbm_mod_weighted_strength_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_boolean(ot->srna, "set", 0, "Set value", "Set Value of faces"); + RNA_def_property_flag(ot->prop, PROP_HIDDEN); +} |