Mesh Tool: removes degenerate edges, faces and face ears.
This commit is contained in:
@@ -2318,8 +2318,10 @@ class VIEW3D_MT_edit_mesh_clean(Menu):
|
|||||||
|
|
||||||
layout.separator()
|
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.vert_connect_nonplanar")
|
||||||
|
layout.operator("mesh.fill_holes")
|
||||||
|
|
||||||
|
|
||||||
class VIEW3D_MT_edit_mesh_delete(Menu):
|
class VIEW3D_MT_edit_mesh_delete(Menu):
|
||||||
|
@@ -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,
|
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.
|
* Triangulate.
|
||||||
*/
|
*/
|
||||||
@@ -1828,8 +1847,9 @@ const BMOpDefine *bmo_opdefines[] = {
|
|||||||
&bmo_delete_def,
|
&bmo_delete_def,
|
||||||
&bmo_dissolve_edges_def,
|
&bmo_dissolve_edges_def,
|
||||||
&bmo_dissolve_faces_def,
|
&bmo_dissolve_faces_def,
|
||||||
&bmo_dissolve_limit_def,
|
|
||||||
&bmo_dissolve_verts_def,
|
&bmo_dissolve_verts_def,
|
||||||
|
&bmo_dissolve_limit_def,
|
||||||
|
&bmo_dissolve_degenerate_def,
|
||||||
&bmo_duplicate_def,
|
&bmo_duplicate_def,
|
||||||
&bmo_holes_fill_def,
|
&bmo_holes_fill_def,
|
||||||
&bmo_face_attribute_fill_def,
|
&bmo_face_attribute_fill_def,
|
||||||
|
@@ -56,8 +56,9 @@ void bmo_create_vert_exec(BMesh *bm, BMOperator *op);
|
|||||||
void bmo_delete_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_edges_exec(BMesh *bm, BMOperator *op);
|
||||||
void bmo_dissolve_faces_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_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_duplicate_exec(BMesh *bm, BMOperator *op);
|
||||||
void bmo_edgeloop_fill_exec(BMesh *bm, BMOperator *op);
|
void bmo_edgeloop_fill_exec(BMesh *bm, BMOperator *op);
|
||||||
void bmo_face_attribute_fill_exec(BMesh *bm, BMOperator *op);
|
void bmo_face_attribute_fill_exec(BMesh *bm, BMOperator *op);
|
||||||
|
@@ -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);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@@ -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 */
|
/* Note, these values must match delete_mesh() event values */
|
||||||
static EnumPropertyItem prop_mesh_delete_types[] = {
|
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);
|
EDBM_update_generic(em, true, true);
|
||||||
|
|
||||||
BKE_reportf(op->reports, RPT_INFO,
|
edbm_report_delete_info(op->reports, bm, totelem);
|
||||||
"Removed: %d vertices, %d edges, %d faces",
|
|
||||||
totelem[0] - bm->totvert, totelem[1] - bm->totedge, totelem[2] - bm->totface);
|
|
||||||
|
|
||||||
return OPERATOR_FINISHED;
|
return OPERATOR_FINISHED;
|
||||||
}
|
}
|
||||||
@@ -3676,6 +3680,51 @@ void MESH_OT_dissolve_limited(wmOperatorType *ot)
|
|||||||
"Delimit dissolve operation");
|
"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 */
|
/* internally uses dissolve */
|
||||||
static int edbm_delete_edgeloop_exec(bContext *C, wmOperator *op)
|
static int edbm_delete_edgeloop_exec(bContext *C, wmOperator *op)
|
||||||
{
|
{
|
||||||
|
@@ -210,6 +210,7 @@ void MESH_OT_dissolve_edges(struct wmOperatorType *ot);
|
|||||||
void MESH_OT_dissolve_faces(struct wmOperatorType *ot);
|
void MESH_OT_dissolve_faces(struct wmOperatorType *ot);
|
||||||
void MESH_OT_dissolve_mode(struct wmOperatorType *ot);
|
void MESH_OT_dissolve_mode(struct wmOperatorType *ot);
|
||||||
void MESH_OT_dissolve_limited(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_delete_edgeloop(struct wmOperatorType *ot);
|
||||||
void MESH_OT_edge_face_add(struct wmOperatorType *ot);
|
void MESH_OT_edge_face_add(struct wmOperatorType *ot);
|
||||||
void MESH_OT_duplicate(struct wmOperatorType *ot);
|
void MESH_OT_duplicate(struct wmOperatorType *ot);
|
||||||
|
@@ -112,6 +112,7 @@ void ED_operatortypes_mesh(void)
|
|||||||
WM_operatortype_append(MESH_OT_dissolve_faces);
|
WM_operatortype_append(MESH_OT_dissolve_faces);
|
||||||
WM_operatortype_append(MESH_OT_dissolve_mode);
|
WM_operatortype_append(MESH_OT_dissolve_mode);
|
||||||
WM_operatortype_append(MESH_OT_dissolve_limited);
|
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_delete_edgeloop);
|
||||||
WM_operatortype_append(MESH_OT_faces_shade_smooth);
|
WM_operatortype_append(MESH_OT_faces_shade_smooth);
|
||||||
WM_operatortype_append(MESH_OT_faces_shade_flat);
|
WM_operatortype_append(MESH_OT_faces_shade_flat);
|
||||||
|
Reference in New Issue
Block a user