/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup edmesh * * Tools to implement polygon building tool, * an experimental tool for quickly constructing/manipulating faces. */ #include "MEM_guardedalloc.h" #include "DNA_object_types.h" #include "BLI_math.h" #include "BKE_context.h" #include "BKE_editmesh.h" #include "BKE_layer.h" #include "BKE_mesh.h" #include "BKE_report.h" #include "WM_types.h" #include "ED_mesh.h" #include "ED_object.h" #include "ED_scene.h" #include "ED_screen.h" #include "ED_transform.h" #include "ED_view3d.h" #include "bmesh.h" #include "mesh_intern.h" /* own include */ #include "RNA_access.h" #include "RNA_define.h" #include "WM_api.h" #include "DEG_depsgraph.h" /* -------------------------------------------------------------------- */ /** \name Local Utilities * \{ */ static void edbm_selectmode_ensure(Scene *scene, BMEditMesh *em, short selectmode) { if ((scene->toolsettings->selectmode & selectmode) == 0) { scene->toolsettings->selectmode |= selectmode; em->selectmode = scene->toolsettings->selectmode; EDBM_selectmode_set(em); } } /* Could make public, for now just keep here. */ static void edbm_flag_disable_all_multi(const Scene *scene, ViewLayer *view_layer, View3D *v3d, const char hflag) { uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( scene, view_layer, v3d, &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob_iter = objects[ob_index]; BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); BMesh *bm_iter = em_iter->bm; if (bm_iter->totvertsel) { EDBM_flag_disable_all(em_iter, hflag); DEG_id_tag_update(ob_iter->data, ID_RECALC_SELECT); } } MEM_freeN(objects); } /** When accessed as a tool, get the active edge from the pre-selection gizmo. */ static bool edbm_preselect_or_active(bContext *C, const View3D *v3d, Base **r_base, BMElem **r_ele) { ARegion *region = CTX_wm_region(C); const bool show_gizmo = !(v3d->gizmo_flag & (V3D_GIZMO_HIDE | V3D_GIZMO_HIDE_TOOL)); wmGizmoMap *gzmap = show_gizmo ? region->gizmo_map : NULL; wmGizmoGroup *gzgroup = gzmap ? WM_gizmomap_group_find(gzmap, "VIEW3D_GGT_mesh_preselect_elem") : NULL; if (gzgroup != NULL) { wmGizmo *gz = gzgroup->gizmos.first; ED_view3d_gizmo_mesh_preselect_get_active(C, gz, r_base, r_ele); } else { const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_active_base_get(view_layer); Object *obedit = base->object; BMEditMesh *em = BKE_editmesh_from_object(obedit); BMesh *bm = em->bm; *r_base = base; *r_ele = BM_mesh_active_elem_get(bm); } return (*r_ele != NULL); } static bool edbm_preselect_or_active_init_viewcontext(bContext *C, ViewContext *vc, Base **r_base, BMElem **r_ele) { em_setup_viewcontext(C, vc); bool ok = edbm_preselect_or_active(C, vc->v3d, r_base, r_ele); if (ok) { ED_view3d_viewcontext_init_object(vc, (*r_base)->object); } return ok; } static int edbm_polybuild_transform_at_cursor_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(event)) { ViewContext vc; Base *basact = NULL; BMElem *ele_act = NULL; edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act); BMEditMesh *em = vc.em; BMesh *bm = em->bm; invert_m4_m4(vc.obedit->world_to_object, vc.obedit->object_to_world); ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d); if (!ele_act) { return OPERATOR_CANCELLED; } edbm_selectmode_ensure(vc.scene, vc.em, SCE_SELECT_VERTEX); edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); if (ele_act->head.htype == BM_VERT) { BM_vert_select_set(bm, (BMVert *)ele_act, true); } if (ele_act->head.htype == BM_EDGE) { BM_edge_select_set(bm, (BMEdge *)ele_act, true); } if (ele_act->head.htype == BM_FACE) { BM_face_select_set(bm, (BMFace *)ele_act, true); } EDBM_update(vc.obedit->data, &(const struct EDBMUpdate_Params){ .calc_looptri = true, .calc_normals = true, .is_destructive = true, }); if (basact != NULL) { BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } } BM_select_history_store(bm, ele_act); WM_event_add_mousemove(vc.win); return OPERATOR_FINISHED; } void MESH_OT_polybuild_transform_at_cursor(wmOperatorType *ot) { /* identifiers */ ot->name = "Poly Build Transform at Cursor"; ot->idname = "MESH_OT_polybuild_transform_at_cursor"; /* api callbacks */ ot->invoke = edbm_polybuild_transform_at_cursor_invoke; ot->poll = EDBM_view3d_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* to give to transform */ Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR_DUMMY); } static int edbm_polybuild_delete_at_cursor_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { bool changed = false; ViewContext vc; Base *basact = NULL; BMElem *ele_act = NULL; edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act); BMEditMesh *em = vc.em; BMesh *bm = em->bm; invert_m4_m4(vc.obedit->world_to_object, vc.obedit->object_to_world); ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d); if (!ele_act) { return OPERATOR_CANCELLED; } edbm_selectmode_ensure(vc.scene, vc.em, SCE_SELECT_VERTEX); if (ele_act->head.htype == BM_FACE) { BMFace *f_act = (BMFace *)ele_act; EDBM_flag_disable_all(em, BM_ELEM_TAG); BM_elem_flag_enable(f_act, BM_ELEM_TAG); if (!EDBM_op_callf(em, op, "delete geom=%hf context=%i", BM_ELEM_TAG, DEL_FACES)) { return OPERATOR_CANCELLED; } changed = true; } if (ele_act->head.htype == BM_VERT) { BMVert *v_act = (BMVert *)ele_act; if (BM_vert_is_edge_pair(v_act) && !BM_vert_is_wire(v_act)) { BM_edge_collapse(bm, v_act->e, v_act, true, true); changed = true; } else { EDBM_flag_disable_all(em, BM_ELEM_TAG); BM_elem_flag_enable(v_act, BM_ELEM_TAG); if (!EDBM_op_callf(em, op, "dissolve_verts verts=%hv use_face_split=%b use_boundary_tear=%b", BM_ELEM_TAG, false, false)) { return OPERATOR_CANCELLED; } changed = true; } } if (changed) { EDBM_update(vc.obedit->data, &(const struct EDBMUpdate_Params){ .calc_looptri = true, .calc_normals = true, .is_destructive = true, }); if (basact != NULL) { BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } } WM_event_add_mousemove(vc.win); return OPERATOR_FINISHED; } return OPERATOR_CANCELLED; } void MESH_OT_polybuild_delete_at_cursor(wmOperatorType *ot) { /* identifiers */ ot->name = "Poly Build Delete at Cursor"; ot->idname = "MESH_OT_polybuild_delete_at_cursor"; /* api callbacks */ ot->invoke = edbm_polybuild_delete_at_cursor_invoke; ot->poll = EDBM_view3d_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* to give to transform */ Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR_DUMMY); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Face at Cursor * \{ */ static int edbm_polybuild_face_at_cursor_invoke(bContext *C, wmOperator *op, const wmEvent *event) { float center[3]; bool changed = false; ViewContext vc; Base *basact = NULL; BMElem *ele_act = NULL; edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act); BMEditMesh *em = vc.em; BMesh *bm = em->bm; invert_m4_m4(vc.obedit->world_to_object, vc.obedit->object_to_world); ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d); edbm_selectmode_ensure(vc.scene, vc.em, SCE_SELECT_VERTEX); if (ele_act == NULL || ele_act->head.htype == BM_FACE) { /* Just add vert */ copy_v3_v3(center, vc.scene->cursor.location); mul_v3_m4v3(center, vc.obedit->object_to_world, center); ED_view3d_win_to_3d_int(vc.v3d, vc.region, center, event->mval, center); mul_m4_v3(vc.obedit->world_to_object, center); BMVert *v_new = BM_vert_create(bm, center, NULL, BM_CREATE_NOP); edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); BM_vert_select_set(bm, v_new, true); BM_select_history_store(bm, v_new); changed = true; } else if (ele_act->head.htype == BM_EDGE) { BMEdge *e_act = (BMEdge *)ele_act; BMFace *f_reference = e_act->l ? e_act->l->f : NULL; mid_v3_v3v3(center, e_act->v1->co, e_act->v2->co); mul_m4_v3(vc.obedit->object_to_world, center); ED_view3d_win_to_3d_int(vc.v3d, vc.region, center, event->mval, center); mul_m4_v3(vc.obedit->world_to_object, center); if (f_reference->len == 3 && RNA_boolean_get(op->ptr, "create_quads")) { const float fac = line_point_factor_v3(center, e_act->v1->co, e_act->v2->co); BMVert *v_new = BM_edge_split(bm, e_act, e_act->v1, NULL, CLAMPIS(fac, 0.0f, 1.0f)); copy_v3_v3(v_new->co, center); edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); BM_vert_select_set(bm, v_new, true); BM_select_history_store(bm, v_new); } else { BMVert *v_tri[3]; v_tri[0] = e_act->v1; v_tri[1] = e_act->v2; v_tri[2] = BM_vert_create(bm, center, NULL, BM_CREATE_NOP); if (e_act->l && e_act->l->v == v_tri[0]) { SWAP(BMVert *, v_tri[0], v_tri[1]); } BM_face_create_verts(bm, v_tri, 3, f_reference, BM_CREATE_NOP, true); edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); BM_vert_select_set(bm, v_tri[2], true); BM_select_history_store(bm, v_tri[2]); } changed = true; } else if (ele_act->head.htype == BM_VERT) { BMVert *v_act = (BMVert *)ele_act; BMEdge *e_pair[2] = {NULL}; if (v_act->e != NULL) { for (uint allow_wire = 0; allow_wire < 2 && (e_pair[1] == NULL); allow_wire++) { int i = 0; BMEdge *e_iter = v_act->e; do { if ((BM_elem_flag_test(e_iter, BM_ELEM_HIDDEN) == false) && (allow_wire ? BM_edge_is_wire(e_iter) : BM_edge_is_boundary(e_iter))) { if (i == 2) { e_pair[0] = e_pair[1] = NULL; break; } e_pair[i++] = e_iter; } } while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v_act)) != v_act->e); } } if (e_pair[1] != NULL) { /* Quad from edge pair. */ if (BM_edge_calc_length_squared(e_pair[0]) < BM_edge_calc_length_squared(e_pair[1])) { SWAP(BMEdge *, e_pair[0], e_pair[1]); } BMFace *f_reference = e_pair[0]->l ? e_pair[0]->l->f : NULL; mul_v3_m4v3(center, vc.obedit->object_to_world, v_act->co); ED_view3d_win_to_3d_int(vc.v3d, vc.region, center, event->mval, center); mul_m4_v3(vc.obedit->world_to_object, center); BMVert *v_quad[4]; v_quad[0] = v_act; v_quad[1] = BM_edge_other_vert(e_pair[0], v_act); v_quad[2] = BM_vert_create(bm, center, NULL, BM_CREATE_NOP); v_quad[3] = BM_edge_other_vert(e_pair[1], v_act); if (e_pair[0]->l && e_pair[0]->l->v == v_quad[0]) { SWAP(BMVert *, v_quad[1], v_quad[3]); } // BMFace *f_new = BM_face_create_verts(bm, v_quad, 4, f_reference, BM_CREATE_NOP, true); edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); BM_vert_select_set(bm, v_quad[2], true); BM_select_history_store(bm, v_quad[2]); changed = true; } else { /* Just add edge */ mul_m4_v3(vc.obedit->object_to_world, center); ED_view3d_win_to_3d_int(vc.v3d, vc.region, v_act->co, event->mval, center); mul_m4_v3(vc.obedit->world_to_object, center); BMVert *v_new = BM_vert_create(bm, center, NULL, BM_CREATE_NOP); BM_edge_create(bm, v_act, v_new, NULL, BM_CREATE_NOP); BM_vert_select_set(bm, v_new, true); BM_select_history_store(bm, v_new); changed = true; } } if (changed) { EDBM_update(vc.obedit->data, &(const struct EDBMUpdate_Params){ .calc_looptri = true, .calc_normals = true, .is_destructive = true, }); if (basact != NULL) { BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } } WM_event_add_mousemove(vc.win); return OPERATOR_FINISHED; } return OPERATOR_CANCELLED; } void MESH_OT_polybuild_face_at_cursor(wmOperatorType *ot) { /* identifiers */ ot->name = "Poly Build Face at Cursor"; ot->idname = "MESH_OT_polybuild_face_at_cursor"; /* api callbacks */ ot->invoke = edbm_polybuild_face_at_cursor_invoke; ot->poll = EDBM_view3d_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; RNA_def_boolean(ot->srna, "create_quads", true, "Create Quads", "Automatically split edges in triangles to maintain quad topology"); /* to give to transform */ Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR_DUMMY); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Split at Cursor * \{ */ static int edbm_polybuild_split_at_cursor_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) { float center[3]; bool changed = false; ViewContext vc; Base *basact = NULL; BMElem *ele_act = NULL; edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act); BMEditMesh *em = vc.em; BMesh *bm = em->bm; invert_m4_m4(vc.obedit->world_to_object, vc.obedit->object_to_world); ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d); edbm_selectmode_ensure(vc.scene, vc.em, SCE_SELECT_VERTEX); if (ele_act == NULL || ele_act->head.hflag == BM_FACE) { return OPERATOR_PASS_THROUGH; } if (ele_act->head.htype == BM_EDGE) { BMEdge *e_act = (BMEdge *)ele_act; mid_v3_v3v3(center, e_act->v1->co, e_act->v2->co); mul_m4_v3(vc.obedit->object_to_world, center); ED_view3d_win_to_3d_int(vc.v3d, vc.region, center, event->mval, center); mul_m4_v3(vc.obedit->world_to_object, center); const float fac = line_point_factor_v3(center, e_act->v1->co, e_act->v2->co); BMVert *v_new = BM_edge_split(bm, e_act, e_act->v1, NULL, CLAMPIS(fac, 0.0f, 1.0f)); copy_v3_v3(v_new->co, center); edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); BM_vert_select_set(bm, v_new, true); BM_select_history_store(bm, v_new); changed = true; } else if (ele_act->head.htype == BM_VERT) { /* Just do nothing, allow dragging. */ return OPERATOR_FINISHED; } if (changed) { EDBM_update(vc.obedit->data, &(const struct EDBMUpdate_Params){ .calc_looptri = true, .calc_normals = true, .is_destructive = true, }); WM_event_add_mousemove(vc.win); BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } return OPERATOR_FINISHED; } return OPERATOR_CANCELLED; } void MESH_OT_polybuild_split_at_cursor(wmOperatorType *ot) { /* identifiers */ ot->name = "Poly Build Split at Cursor"; ot->idname = "MESH_OT_polybuild_split_at_cursor"; /* api callbacks */ ot->invoke = edbm_polybuild_split_at_cursor_invoke; ot->poll = EDBM_view3d_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* to give to transform */ Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR_DUMMY); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Dissolve at Cursor * \{ */ static int edbm_polybuild_dissolve_at_cursor_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { bool changed = false; ViewContext vc; Base *basact = NULL; BMElem *ele_act = NULL; edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act); BMEditMesh *em = vc.em; BMesh *bm = em->bm; if (ele_act == NULL) { /* pass */ } else if (ele_act->head.htype == BM_EDGE) { BMEdge *e_act = (BMEdge *)ele_act; BMLoop *l_a, *l_b; if (BM_edge_loop_pair(e_act, &l_a, &l_b)) { BMFace *f_new = BM_faces_join_pair(bm, l_a, l_b, true); if (f_new) { changed = true; } } } else if (ele_act->head.htype == BM_VERT) { BMVert *v_act = (BMVert *)ele_act; if (BM_vert_is_edge_pair(v_act)) { BM_edge_collapse(bm, v_act->e, v_act, true, true); } else { /* too involved to do inline */ /* Avoid using selection so failure won't leave modified state. */ EDBM_flag_disable_all(em, BM_ELEM_TAG); BM_elem_flag_enable(v_act, BM_ELEM_TAG); if (!EDBM_op_callf(em, op, "dissolve_verts verts=%hv use_face_split=%b use_boundary_tear=%b", BM_ELEM_TAG, false, false)) { return OPERATOR_CANCELLED; } } changed = true; } if (changed) { edbm_flag_disable_all_multi(vc.scene, vc.view_layer, vc.v3d, BM_ELEM_SELECT); EDBM_update(vc.obedit->data, &(const struct EDBMUpdate_Params){ .calc_looptri = true, .calc_normals = true, .is_destructive = true, }); BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { ED_object_base_activate(C, basact); } WM_event_add_mousemove(vc.win); return OPERATOR_FINISHED; } return OPERATOR_CANCELLED; } void MESH_OT_polybuild_dissolve_at_cursor(wmOperatorType *ot) { /* identifiers */ ot->name = "Poly Build Dissolve at Cursor"; ot->idname = "MESH_OT_polybuild_dissolve_at_cursor"; /* api callbacks */ ot->invoke = edbm_polybuild_dissolve_at_cursor_invoke; ot->poll = EDBM_view3d_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */