Modeling: Support interpolation of vertex custom data when merging vertices

Implements support for merged vertices to properly interpolate vertex
custom data when merged. Merging at center averages the data but in the
case of merging at last or at first, it keeps the data of the surviving
vertex.

Pull Request: https://projects.blender.org/blender/blender/pulls/147755
This commit is contained in:
Tariq-Sulley 2026-06-01 14:28:20 +02:00 • committed by Tariq Sulley
parent 0f4b405089
commit 672c2cac52
3 changed files with 53 additions and 17 deletions

View file

@ -612,8 +612,8 @@ static BMOpDefine bmo_pointmerge_facedata_def = {
{
/* Input vertices. */
{"verts", BMO_OP_SLOT_ELEMENT_BUF, {BM_VERT}},
/* Snap vertex. */
{"vert_snap", BMO_OP_SLOT_ELEMENT_BUF, {BM_VERT | BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE}},
/* Target vertex to merge into. */
{"vert_target", BMO_OP_SLOT_ELEMENT_BUF, {BM_VERT | BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE}},
{{'\0'}},
},
/*slot_types_out*/
@ -657,6 +657,9 @@ static BMOpDefine bmo_pointmerge_def = {
{"verts", BMO_OP_SLOT_ELEMENT_BUF, {BM_VERT}},
/* Position to merge at. */
{"merge_co", BMO_OP_SLOT_VEC},
/* Optional target vertex to merge into. Does not override merge_co.
* Set this to preserve the custom data of the target vertex. */
{"vert_target", BMO_OP_SLOT_ELEMENT_BUF, {BM_VERT | BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE}},
{{'\0'}},
},
/*slot_types_out*/
@ -704,6 +707,8 @@ static BMOpDefine bmo_weld_verts_def = {
/* Merge vertices to their centroid position,
* otherwise use the position of the target vertex. */
{"use_centroid", BMO_OP_SLOT_BOOL},
/* Whether to average custom data of merged vertices. */
{"average_vert_data", BMO_OP_SLOT_BOOL},
{{'\0'}},
},
/*slot_types_out*/

View file

@ -194,13 +194,15 @@ void bmo_weld_verts_exec(BMesh *bm, BMOperator *op)
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");
const bool average_vert_data = BMO_slot_bool_get(op->slots_in, "average_vert_data") ||
use_centroid;
/* Maintain selection history. */
const bool has_selected = !bm->selected.is_empty();
const bool use_targetmap_all = has_selected;
Map<void *, void *> targetmap_all;
/* Used when use_centroid is true. */
/* Used when use_centroid or average_vert_data is true. */
MultiValueMap<BMVert *, BMVert *> clusters;
/* Mark merge verts for deletion. */
@ -221,7 +223,7 @@ void bmo_weld_verts_exec(BMesh *bm, BMOperator *op)
}
/* Group vertices by their survivor. */
if (use_centroid && LIKELY(v_dst != v)) {
if (average_vert_data && LIKELY(v_dst != v)) {
clusters.add(v_dst, v);
}
}
@ -246,6 +248,21 @@ void bmo_weld_verts_exec(BMesh *bm, BMOperator *op)
}
}
if (average_vert_data) {
for (const auto &item : clusters.items()) {
BMVert *v_dst = item.key;
Span<BMVert *> merged_verts = item.value;
Array<const void *> src_blocks(merged_verts.size() + 1);
src_blocks[0] = v_dst->head.data;
for (const int i : merged_verts.index_range()) {
src_blocks[i + 1] = merged_verts[i]->head.data;
}
CustomData_bmesh_interp(
&bm->vdata, src_blocks.data(), nullptr, src_blocks.size(), v_dst->head.data);
}
}
/* Check if any faces are getting their own corners merged
* together, split face if so. */
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
@ -356,21 +373,21 @@ void bmo_pointmerge_facedata_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMIter iter;
BMVert *v, *vert_snap;
BMVert *v, *vert_target;
BMLoop *l, *l_first = nullptr;
float fac;
int i, tot;
vert_snap = static_cast<BMVert *>(
BMO_slot_buffer_get_single(BMO_slot_get(op->slots_in, "vert_snap")));
tot = BM_vert_face_count(vert_snap);
vert_target = static_cast<BMVert *>(
BMO_slot_buffer_get_single(BMO_slot_get(op->slots_in, "vert_target")));
tot = BM_vert_face_count(vert_target);
if (!tot) {
return;
}
fac = 1.0f / tot;
BM_ITER_ELEM (l, &iter, vert_snap, BM_LOOPS_OF_VERT) {
BM_ITER_ELEM (l, &iter, vert_target, BM_LOOPS_OF_VERT) {
if (l_first == nullptr) {
l_first = l;
}
@ -447,7 +464,7 @@ void bmo_pointmerge_exec(BMesh *bm, BMOperator *op)
{
BMOperator weldop;
BMOIter siter;
BMVert *v, *vert_snap = nullptr;
BMVert *v, *vert_target = nullptr;
float vec[3];
BMOpSlot *slot_targetmap;
@ -458,16 +475,25 @@ void bmo_pointmerge_exec(BMesh *bm, BMOperator *op)
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
vert_target = static_cast<BMVert *>(
BMO_slot_buffer_get_single(BMO_slot_get(op->slots_in, "vert_target")));
const bool is_explicit_snap = vert_target != nullptr;
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
if (!vert_snap) {
vert_snap = v;
copy_v3_v3(vert_snap->co, vec);
if (!vert_target) {
vert_target = v;
copy_v3_v3(vert_target->co, vec);
}
else {
BMO_slot_map_elem_insert(&weldop, slot_targetmap, v, vert_snap);
else if (v != vert_target) {
BMO_slot_map_elem_insert(&weldop, slot_targetmap, v, vert_target);
}
}
if (!is_explicit_snap) {
BMO_slot_bool_set(weldop.slots_in, "average_vert_data", true);
}
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &weldop);
}

View file

@ -3412,13 +3412,18 @@ static bool merge_firstlast(BMEditMesh *em,
if (use_uvmerge) {
if (!EDBM_op_callf(
em, wmop, "pointmerge_facedata verts=%hv vert_snap=%e", BM_ELEM_SELECT, mergevert))
em, wmop, "pointmerge_facedata verts=%hv vert_target=%e", BM_ELEM_SELECT, mergevert))
{
return false;
}
}
if (!EDBM_op_callf(em, wmop, "pointmerge verts=%hv merge_co=%v", BM_ELEM_SELECT, mergevert->co))
if (!EDBM_op_callf(em,
wmop,
"pointmerge verts=%hv merge_co=%v vert_target=%e",
BM_ELEM_SELECT,
mergevert->co,
mergevert))
{
return false;
}