From cc7cfd661736f67d3f97108c38998b159d87ec9b Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 17 Feb 2014 11:32:35 +1100 Subject: [PATCH] Mesh Tool: removes degenerate edges, faces and face ears. --- release/scripts/startup/bl_ui/space_view3d.py | 4 +- source/blender/bmesh/intern/bmesh_opdefines.c | 22 ++- .../bmesh/intern/bmesh_operators_private.h | 3 +- source/blender/bmesh/operators/bmo_dissolve.c | 141 ++++++++++++++++++ source/blender/editors/mesh/editmesh_tools.c | 55 ++++++- source/blender/editors/mesh/mesh_intern.h | 1 + source/blender/editors/mesh/mesh_ops.c | 1 + 7 files changed, 221 insertions(+), 6 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 1ebe9aa0f8a..c79ad56fc9b 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -2318,8 +2318,10 @@ class VIEW3D_MT_edit_mesh_clean(Menu): layout.separator() - layout.operator("mesh.fill_holes") + layout.operator("mesh.dissolve_degenerate") + layout.operator("mesh.dissolve_limited") layout.operator("mesh.vert_connect_nonplanar") + layout.operator("mesh.fill_holes") class VIEW3D_MT_edit_mesh_delete(Menu): diff --git a/source/blender/bmesh/intern/bmesh_opdefines.c b/source/blender/bmesh/intern/bmesh_opdefines.c index 0c75597282e..0c8347f980e 100644 --- a/source/blender/bmesh/intern/bmesh_opdefines.c +++ b/source/blender/bmesh/intern/bmesh_opdefines.c @@ -1021,6 +1021,25 @@ static BMOpDefine bmo_dissolve_limit_def = { BMO_OPTYPE_FLAG_UNTAN_MULTIRES | BMO_OPTYPE_FLAG_NORMALS_CALC | BMO_OPTYPE_FLAG_SELECT_FLUSH, }; +/* + * Degenerate Dissolve. + * + * Dissolve edges with no length, faces with no area. + */ +static BMOpDefine bmo_dissolve_degenerate_def = { + "dissolve_degenerate", + /* slots_in */ + {{"dist", BMO_OP_SLOT_FLT}, /* minimum distance to consider degenerate */ + {"edges", BMO_OP_SLOT_ELEMENT_BUF, {BM_EDGE}}, + {{'\0'}}, + }, + /* slots_out */ + {{"verts.out", BMO_OP_SLOT_ELEMENT_BUF, {BM_VERT}}, /* output vertices */ + {{'\0'}}}, + bmo_dissolve_degenerate_exec, + BMO_OPTYPE_FLAG_UNTAN_MULTIRES | BMO_OPTYPE_FLAG_NORMALS_CALC | BMO_OPTYPE_FLAG_SELECT_FLUSH, +}; + /* * Triangulate. */ @@ -1828,8 +1847,9 @@ const BMOpDefine *bmo_opdefines[] = { &bmo_delete_def, &bmo_dissolve_edges_def, &bmo_dissolve_faces_def, - &bmo_dissolve_limit_def, &bmo_dissolve_verts_def, + &bmo_dissolve_limit_def, + &bmo_dissolve_degenerate_def, &bmo_duplicate_def, &bmo_holes_fill_def, &bmo_face_attribute_fill_def, diff --git a/source/blender/bmesh/intern/bmesh_operators_private.h b/source/blender/bmesh/intern/bmesh_operators_private.h index 517a2c4fa01..9c1b7085835 100644 --- a/source/blender/bmesh/intern/bmesh_operators_private.h +++ b/source/blender/bmesh/intern/bmesh_operators_private.h @@ -56,8 +56,9 @@ void bmo_create_vert_exec(BMesh *bm, BMOperator *op); void bmo_delete_exec(BMesh *bm, BMOperator *op); void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op); void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op); -void bmo_dissolve_limit_exec(BMesh *bm, BMOperator *op); void bmo_dissolve_verts_exec(BMesh *bm, BMOperator *op); +void bmo_dissolve_limit_exec(BMesh *bm, BMOperator *op); +void bmo_dissolve_degenerate_exec(BMesh *bm, BMOperator *op); void bmo_duplicate_exec(BMesh *bm, BMOperator *op); void bmo_edgeloop_fill_exec(BMesh *bm, BMOperator *op); void bmo_face_attribute_fill_exec(BMesh *bm, BMOperator *op); diff --git a/source/blender/bmesh/operators/bmo_dissolve.c b/source/blender/bmesh/operators/bmo_dissolve.c index 60b96e35057..334242f3100 100644 --- a/source/blender/bmesh/operators/bmo_dissolve.c +++ b/source/blender/bmesh/operators/bmo_dissolve.c @@ -474,3 +474,144 @@ void bmo_dissolve_limit_exec(BMesh *bm, BMOperator *op) BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "region.out", BM_FACE, FACE_NEW); } + + +#define EDGE_MARK 1 +#define EDGE_COLLAPSE 2 + +static void bm_mesh_edge_collapse_flagged(BMesh *bm, const int flag, const short oflag) +{ + BMO_op_callf(bm, flag, "collapse edges=%fe", oflag); +} + +void bmo_dissolve_degenerate_exec(BMesh *bm, BMOperator *op) +{ + const float dist = BMO_slot_float_get(op->slots_in, "dist"); + const float dist_sq = dist * dist; + + bool found; + BMIter eiter; + BMEdge *e; + + + BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK); + + /* collapse zero length edges, this accounts for zero area faces too */ + found = false; + BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) { + if (BMO_elem_flag_test(bm, e, EDGE_MARK)) { + if (BM_edge_calc_length_squared(e) < dist_sq) { + BMO_elem_flag_enable(bm, e, EDGE_COLLAPSE); + found = true; + } + } + + /* clear all loop tags (checked later) */ + if (e->l) { + BMLoop *l_iter, *l_first; + l_iter = l_first = e->l; + do { + BM_elem_flag_disable(l_iter, BM_ELEM_TAG); + } while ((l_iter = l_iter->radial_next) != l_first); + } + } + + if (found) { + bm_mesh_edge_collapse_flagged(bm, op->flag, EDGE_COLLAPSE); + } + + + /* clip degenerate ears from the face */ + found = false; + BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) { + if (e->l && BMO_elem_flag_test(bm, e, EDGE_MARK)) { + BMLoop *l_iter, *l_first; + l_iter = l_first = e->l; + do { + if ( + /* check the loop hasn't already been tested (and flag not to test again) */ + !BM_elem_flag_test(l_iter, BM_ELEM_TAG) && + (BM_elem_flag_enable(l_iter, BM_ELEM_TAG), + + /* check we're marked to tested (radial edge already tested) */ + BMO_elem_flag_test(bm, l_iter->prev->e, EDGE_MARK) && + + /* check edges are not already going to be collapsed */ + !BMO_elem_flag_test(bm, l_iter->e, EDGE_COLLAPSE) && + !BMO_elem_flag_test(bm, l_iter->prev->e, EDGE_COLLAPSE))) + { + /* test if the faces loop (ear) is degenerate */ + float dir_prev[3], len_prev; + float dir_next[3], len_next; + + + sub_v3_v3v3(dir_prev, l_iter->prev->v->co, l_iter->v->co); + sub_v3_v3v3(dir_next, l_iter->next->v->co, l_iter->v->co); + + len_prev = normalize_v3(dir_prev); + len_next = normalize_v3(dir_next); + + if ((len_v3v3(dir_prev, dir_next) * min_ff(len_prev, len_next)) <= dist) { + bool reset = false; + + if (fabsf(len_prev - len_next) <= dist) { + /* both edges the same length */ + if (l_iter->f->len == 3) { + /* ideally this would have been discovered with short edge test above */ + BMO_elem_flag_enable(bm, l_iter->next->e, EDGE_COLLAPSE); + found = true; + } + else { + /* add a joining edge and tag for removal */ + BMLoop *l_split; + if (BM_face_split(bm, l_iter->f, l_iter->prev, l_iter->next, &l_split, NULL, true)) { + BMO_elem_flag_enable(bm, l_split->e, EDGE_COLLAPSE); + found = true; + reset = true; + } + } + } + else if (len_prev < len_next) { + /* split 'l_iter->e', then join the vert with next */ + BMVert *v_new; + BMEdge *e_new; + BMLoop *l_split; + v_new = BM_edge_split(bm, l_iter->e, l_iter->v, &e_new, len_prev / len_next); + BLI_assert(v_new == l_iter->next->v); + (void)v_new; + if (BM_face_split(bm, l_iter->f, l_iter->prev, l_iter->next, &l_split, NULL, true)) { + BMO_elem_flag_enable(bm, l_split->e, EDGE_COLLAPSE); + found = true; + } + reset = true; + } + else if (len_next < len_prev) { + /* split 'l_iter->prev->e', then join the vert with next */ + BMVert *v_new; + BMEdge *e_new; + BMLoop *l_split; + v_new = BM_edge_split(bm, l_iter->prev->e, l_iter->v, &e_new, len_next / len_prev); + BLI_assert(v_new == l_iter->prev->v); + (void)v_new; + if (BM_face_split(bm, l_iter->f, l_iter->prev, l_iter->next, &l_split, NULL, true)) { + BMO_elem_flag_enable(bm, l_split->e, EDGE_COLLAPSE); + found = true; + } + reset = true; + } + + if (reset) { + /* we can't easily track where we are on the radial edge, reset! */ + l_first = l_iter; + } + } + } + } while ((l_iter = l_iter->radial_next) != l_first); + } + } + + if (found) { + bm_mesh_edge_collapse_flagged(bm, op->flag, EDGE_COLLAPSE); + } + +} diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c index dbb15525dfe..f8b50d4e5c7 100644 --- a/source/blender/editors/mesh/editmesh_tools.c +++ b/source/blender/editors/mesh/editmesh_tools.c @@ -313,6 +313,12 @@ void EMBM_project_snap_verts(bContext *C, ARegion *ar, BMEditMesh *em) } } +static void edbm_report_delete_info(ReportList *reports, BMesh *bm, const int totelem[3]) +{ + BKE_reportf(reports, RPT_INFO, + "Removed: %d vertices, %d edges, %d faces", + totelem[0] - bm->totvert, totelem[1] - bm->totedge, totelem[2] - bm->totface); +} /* Note, these values must match delete_mesh() event values */ static EnumPropertyItem prop_mesh_delete_types[] = { @@ -453,9 +459,7 @@ static int edbm_delete_loose_exec(bContext *C, wmOperator *op) EDBM_update_generic(em, true, true); - BKE_reportf(op->reports, RPT_INFO, - "Removed: %d vertices, %d edges, %d faces", - totelem[0] - bm->totvert, totelem[1] - bm->totedge, totelem[2] - bm->totface); + edbm_report_delete_info(op->reports, bm, totelem); return OPERATOR_FINISHED; } @@ -3676,6 +3680,51 @@ void MESH_OT_dissolve_limited(wmOperatorType *ot) "Delimit dissolve operation"); } +static int edbm_dissolve_degenerate_exec(bContext *C, wmOperator *op) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + const float thresh = RNA_float_get(op->ptr, "threshold"); + BMesh *bm = em->bm; + const int totelem[3] = {bm->totvert, bm->totedge, bm->totface}; + + if (!EDBM_op_callf( + em, op, + "dissolve_degenerate edges=%he dist=%f", + BM_ELEM_SELECT, thresh)) + { + return OPERATOR_CANCELLED; + } + + /* tricky to maintain correct selection here, so just flush up from verts */ + EDBM_select_flush(em); + + EDBM_update_generic(em, true, true); + + edbm_report_delete_info(op->reports, bm, totelem); + + return OPERATOR_FINISHED; +} + +void MESH_OT_dissolve_degenerate(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Degenerate Dissolve"; + ot->idname = "MESH_OT_dissolve_degenerate"; + ot->description = "Dissolve zero area faces and zero length edges"; + + /* api callbacks */ + ot->exec = edbm_dissolve_degenerate_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_float(ot->srna, "threshold", 0.0001f, 0.000001f, 50.0f, "Merge Distance", + "Minimum distance between elements to merge", 0.00001, 10.0); +} + + /* internally uses dissolve */ static int edbm_delete_edgeloop_exec(bContext *C, wmOperator *op) { diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h index e2111a9fd49..8aea932e93a 100644 --- a/source/blender/editors/mesh/mesh_intern.h +++ b/source/blender/editors/mesh/mesh_intern.h @@ -210,6 +210,7 @@ void MESH_OT_dissolve_edges(struct wmOperatorType *ot); void MESH_OT_dissolve_faces(struct wmOperatorType *ot); void MESH_OT_dissolve_mode(struct wmOperatorType *ot); void MESH_OT_dissolve_limited(struct wmOperatorType *ot); +void MESH_OT_dissolve_degenerate(struct wmOperatorType *ot); void MESH_OT_delete_edgeloop(struct wmOperatorType *ot); void MESH_OT_edge_face_add(struct wmOperatorType *ot); void MESH_OT_duplicate(struct wmOperatorType *ot); diff --git a/source/blender/editors/mesh/mesh_ops.c b/source/blender/editors/mesh/mesh_ops.c index 26a6f41b615..ccfb0818420 100644 --- a/source/blender/editors/mesh/mesh_ops.c +++ b/source/blender/editors/mesh/mesh_ops.c @@ -112,6 +112,7 @@ void ED_operatortypes_mesh(void) WM_operatortype_append(MESH_OT_dissolve_faces); WM_operatortype_append(MESH_OT_dissolve_mode); WM_operatortype_append(MESH_OT_dissolve_limited); + WM_operatortype_append(MESH_OT_dissolve_degenerate); WM_operatortype_append(MESH_OT_delete_edgeloop); WM_operatortype_append(MESH_OT_faces_shade_smooth); WM_operatortype_append(MESH_OT_faces_shade_flat);