diff --git a/source/blender/bmesh/intern/bmesh_opdefines.cc b/source/blender/bmesh/intern/bmesh_opdefines.cc index 0d1670653bc..a21008aa5b3 100644 --- a/source/blender/bmesh/intern/bmesh_opdefines.cc +++ b/source/blender/bmesh/intern/bmesh_opdefines.cc @@ -623,6 +623,9 @@ static BMOpDefine bmo_weld_verts_def = { { /* Maps welded vertices to verts they should weld to. */ {"targetmap", BMO_OP_SLOT_MAPPING, {eBMOpSlotSubType_Elem(BMO_OP_SLOT_SUBTYPE_MAP_ELEM)}}, + /* Merged vertices to their centroid position, + * otherwise the position of the target vertex is used. */ + {"use_centroid", BMO_OP_SLOT_BOOL}, {{'\0'}}, }, /*slot_types_out*/ diff --git a/source/blender/bmesh/operators/bmo_removedoubles.cc b/source/blender/bmesh/operators/bmo_removedoubles.cc index 10aa558fc52..51e59d84e6a 100644 --- a/source/blender/bmesh/operators/bmo_removedoubles.cc +++ b/source/blender/bmesh/operators/bmo_removedoubles.cc @@ -187,6 +187,7 @@ void bmo_weld_verts_exec(BMesh *bm, BMOperator *op) BMLoop *l; BMFace *f; BMOpSlot *slot_targetmap = BMO_slot_get(op->slots_in, "targetmap"); + const bool use_centroid = BMO_slot_bool_get(op->slots_in, "use_centroid"); /* Maintain selection history. */ const bool has_selected = !BLI_listbase_is_empty(&bm->selected); @@ -197,6 +198,51 @@ void bmo_weld_verts_exec(BMesh *bm, BMOperator *op) targetmap_all = BLI_ghash_ptr_new(__func__); } + if (use_centroid) { + GHash *clusters = BLI_ghash_ptr_new(__func__); + + /* Group vertices by their survivor. */ + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + BMVert *v_dst = static_cast(BMO_slot_map_elem_get(slot_targetmap, v)); + if (v_dst && v_dst != v) { + void **cluster_p; + if (!BLI_ghash_ensure_p(clusters, v_dst, &cluster_p)) { + *cluster_p = MEM_new>(__func__); + } + blender::Vector *cluster = static_cast *>(*cluster_p); + cluster->append(v); + } + } + + /* Compute centroid for each survivor. */ + GHashIterator gh_iter; + GHASH_ITER (gh_iter, clusters) { + BMVert *v_dst = static_cast(BLI_ghashIterator_getKey(&gh_iter)); + blender::Vector *cluster = static_cast *>( + BLI_ghashIterator_getValue(&gh_iter)); + + float centroid[3]; + copy_v3_v3(centroid, v_dst->co); + int count = 1; /* Include `v_dst`. */ + + for (BMVert *v_duplicate : *cluster) { + add_v3_v3(centroid, v_duplicate->co); + count++; + } + + mul_v3_fl(centroid, 1.0f / float(count)); + copy_v3_v3(v_dst->co, centroid); + } + + /* Free temporary cluster storage. */ + GHASH_ITER (gh_iter, clusters) { + blender::Vector *cluster = static_cast *>( + BLI_ghashIterator_getValue(&gh_iter)); + MEM_delete(cluster); + } + BLI_ghash_free(clusters, nullptr, nullptr); + } + /* mark merge verts for deletion */ BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { BMVert *v_dst = static_cast(BMO_slot_map_elem_get(slot_targetmap, v)); diff --git a/source/blender/editors/mesh/editmesh_tools.cc b/source/blender/editors/mesh/editmesh_tools.cc index 089efd813c9..e1a9b7a56ab 100644 --- a/source/blender/editors/mesh/editmesh_tools.cc +++ b/source/blender/editors/mesh/editmesh_tools.cc @@ -3581,7 +3581,13 @@ static wmOperatorStatus edbm_remove_doubles_exec(bContext *C, wmOperator *op) BMO_op_exec(em->bm, &bmop); - if (!EDBM_op_callf(em, op, "weld_verts targetmap=%S", &bmop, "targetmap.out")) { + if (!EDBM_op_callf(em, + op, + "weld_verts targetmap=%S use_centroid=%b", + &bmop, + "targetmap.out", + RNA_boolean_get(op->ptr, "use_centroid"))) + { BMO_op_finish(em->bm, &bmop); continue; } @@ -3640,6 +3646,13 @@ void MESH_OT_remove_doubles(wmOperatorType *ot) "Maximum distance between elements to merge", 1e-5f, 10.0f); + RNA_def_boolean(ot->srna, + "use_centroid", + true, + "Centroid Merge", + "Move vertices to the centroid of the duplicate cluster, " + "otherwise the vertex closest to the centroid is used."); + RNA_def_boolean(ot->srna, "use_unselected", false,