BLI_stackdefines
[blender.git] / source / blender / bmesh / operators / bmo_removedoubles.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Contributor(s): Joseph Eagar.
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/bmesh/operators/bmo_removedoubles.c
24  *  \ingroup bmesh
25  *
26  * Welding and merging functionality.
27  */
28
29 #include "MEM_guardedalloc.h"
30
31 #include "BLI_math.h"
32 #include "BLI_array.h"
33 #include "BLI_alloca.h"
34 #include "BLI_stackdefines.h"
35
36 #include "BKE_customdata.h"
37
38 #include "bmesh.h"
39 #include "intern/bmesh_operators_private.h"
40
41
42 static void remdoubles_splitface(BMFace *f, BMesh *bm, BMOperator *op, BMOpSlot *slot_targetmap)
43 {
44         BMIter liter;
45         BMLoop *l, *l_tar, *l_double;
46         bool split = false;
47
48         BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
49                 BMVert *v_tar = BMO_slot_map_elem_get(slot_targetmap, l->v);
50                 /* ok: if v_tar is NULL (e.g. not in the map) then it's
51                  *     a target vert, otherwise it's a double */
52                 if (v_tar) {
53                         l_tar = BM_face_vert_share_loop(f, v_tar);
54
55                         if (l_tar && (l_tar != l) && !BM_loop_is_adjacent(l_tar, l)) {
56                                 l_double = l;
57                                 split = true;
58                                 break;
59                         }
60                 }
61         }
62
63         if (split) {
64                 BMLoop *l_new;
65                 BMFace *f_new;
66
67                 f_new = BM_face_split(bm, f, l_double, l_tar, &l_new, NULL, false);
68
69                 remdoubles_splitface(f,     bm, op, slot_targetmap);
70                 remdoubles_splitface(f_new, bm, op, slot_targetmap);
71         }
72 }
73
74 #define ELE_DEL         1
75 #define EDGE_COL        2
76 #define FACE_MARK       2
77
78 /**
79  * helper function for bmo_weld_verts_exec so we can use stack memory
80  */
81 static void remdoubles_createface(BMesh *bm, BMFace *f, BMOpSlot *slot_targetmap)
82 {
83         BMIter liter;
84         BMFace *f_new;
85         BMEdge *e_new;
86
87         BMLoop *l;
88
89         BMEdge **edges = BLI_array_alloca(edges, f->len);
90         BMLoop **loops = BLI_array_alloca(loops, f->len);
91
92         STACK_DECLARE(edges);
93         STACK_DECLARE(loops);
94
95         STACK_INIT(edges, f->len);
96         STACK_INIT(loops, f->len);
97
98         BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
99                 BMVert *v1 = l->v;
100                 BMVert *v2 = l->next->v;
101
102                 const bool is_del_v1 = BMO_elem_flag_test_bool(bm, v1, ELE_DEL);
103                 const bool is_del_v2 = BMO_elem_flag_test_bool(bm, v2, ELE_DEL);
104
105                 /* only search for a new edge if one of the verts is mapped */
106                 if (is_del_v1 || is_del_v2) {
107                         if (is_del_v1)
108                                 v1 = BMO_slot_map_elem_get(slot_targetmap, v1);
109                         if (is_del_v2)
110                                 v2 = BMO_slot_map_elem_get(slot_targetmap, v2);
111
112                         e_new = (v1 != v2) ? BM_edge_exists(v1, v2) : NULL;
113                 }
114                 else {
115                         e_new = l->e;
116                 }
117
118                 if (e_new) {
119                         unsigned int i;
120                         for (i = 0; i < STACK_SIZE(edges); i++) {
121                                 if (edges[i] == e_new) {
122                                         break;
123                                 }
124                         }
125                         if (UNLIKELY(i != STACK_SIZE(edges))) {
126                                 continue;
127                         }
128
129                         STACK_PUSH(edges, e_new);
130                         STACK_PUSH(loops, l);
131                 }
132         }
133
134         if (STACK_SIZE(edges) >= 3) {
135                 BMVert *v1 = loops[0]->v;
136                 BMVert *v2 = loops[1]->v;
137
138                 if (BMO_elem_flag_test(bm, v1, ELE_DEL)) {
139                         v1 = BMO_slot_map_elem_get(slot_targetmap, v1);
140                 }
141                 if (BMO_elem_flag_test(bm, v2, ELE_DEL)) {
142                         v2 = BMO_slot_map_elem_get(slot_targetmap, v2);
143                 }
144
145                 f_new = BM_face_create_ngon(bm, v1, v2, edges, STACK_SIZE(edges), f, BM_CREATE_NO_DOUBLE);
146                 BLI_assert(f_new != f);
147
148                 if (f_new) {
149                         unsigned int i;
150                         BM_ITER_ELEM_INDEX (l, &liter, f_new, BM_LOOPS_OF_FACE, i) {
151                                 BM_elem_attrs_copy(bm, bm, loops[i], l);
152                         }
153                 }
154         }
155 }
156
157 /**
158  * \note with 'targetmap', multiple 'keys' are currently supported, though no callers should be using.
159  * (because slot maps currently use GHash without the GHASH_FLAG_ALLOW_DUPES flag set)
160  *
161  */
162 void bmo_weld_verts_exec(BMesh *bm, BMOperator *op)
163 {
164         BMIter iter, liter;
165         BMVert *v1, *v2;
166         BMEdge *e;
167         BMLoop *l;
168         BMFace *f;
169         BMOpSlot *slot_targetmap = BMO_slot_get(op->slots_in, "targetmap");
170
171         /* mark merge verts for deletion */
172         BM_ITER_MESH (v1, &iter, bm, BM_VERTS_OF_MESH) {
173                 if ((v2 = BMO_slot_map_elem_get(slot_targetmap, v1))) {
174                         BMO_elem_flag_enable(bm, v1, ELE_DEL);
175
176                         /* merge the vertex flags, else we get randomly selected/unselected verts */
177                         BM_elem_flag_merge(v1, v2);
178                 }
179         }
180
181         /* check if any faces are getting their own corners merged
182          * together, split face if so */
183         BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
184                 remdoubles_splitface(f, bm, op, slot_targetmap);
185         }
186
187         BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
188                 const bool is_del_v1 = BMO_elem_flag_test_bool(bm, (v1 = e->v1), ELE_DEL);
189                 const bool is_del_v2 = BMO_elem_flag_test_bool(bm, (v2 = e->v2), ELE_DEL);
190
191                 if (is_del_v1 || is_del_v2) {
192                         if (is_del_v1)
193                                 v1 = BMO_slot_map_elem_get(slot_targetmap, v1);
194                         if (is_del_v2)
195                                 v2 = BMO_slot_map_elem_get(slot_targetmap, v2);
196
197                         if (v1 == v2) {
198                                 BMO_elem_flag_enable(bm, e, EDGE_COL);
199                         }
200                         else if (!BM_edge_exists(v1, v2)) {
201                                 BM_edge_create(bm, v1, v2, e, BM_CREATE_NO_DOUBLE);
202                         }
203
204                         BMO_elem_flag_enable(bm, e, ELE_DEL);
205                 }
206         }
207
208         /* BMESH_TODO, stop abusing face index here */
209         BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
210                 BM_elem_index_set(f, 0); /* set_dirty! */
211                 BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
212                         if (BMO_elem_flag_test(bm, l->v, ELE_DEL)) {
213                                 BMO_elem_flag_enable(bm, f, FACE_MARK | ELE_DEL);
214                         }
215                         if (BMO_elem_flag_test(bm, l->e, EDGE_COL)) {
216                                 BM_elem_index_set(f, BM_elem_index_get(f) + 1); /* set_dirty! */
217                         }
218                 }
219         }
220         bm->elem_index_dirty |= BM_FACE | BM_LOOP;
221
222         /* faces get "modified" by creating new faces here, then at the
223          * end the old faces are deleted */
224         BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
225                 if (!BMO_elem_flag_test(bm, f, FACE_MARK))
226                         continue;
227
228                 if (f->len - BM_elem_index_get(f) < 3) {
229                         BMO_elem_flag_enable(bm, f, ELE_DEL);
230                         continue;
231                 }
232
233                 remdoubles_createface(bm, f, slot_targetmap);
234         }
235
236         BMO_mesh_delete_oflag_context(bm, ELE_DEL, DEL_ONLYTAGGED);
237 }
238
239 static int vergaverco(const void *e1, const void *e2)
240 {
241         const BMVert *v1 = *(void **)e1, *v2 = *(void **)e2;
242         float x1 = v1->co[0] + v1->co[1] + v1->co[2];
243         float x2 = v2->co[0] + v2->co[1] + v2->co[2];
244
245         if      (x1 > x2) return  1;
246         else if (x1 < x2) return -1;
247         else return 0;
248 }
249
250 // #define VERT_TESTED  1 // UNUSED
251 #define VERT_DOUBLE     2
252 #define VERT_TARGET     4
253 #define VERT_KEEP       8
254 // #define VERT_MARK    16 // UNUSED
255 #define VERT_IN         32
256
257 #define EDGE_MARK       1
258
259 void bmo_pointmerge_facedata_exec(BMesh *bm, BMOperator *op)
260 {
261         BMOIter siter;
262         BMIter iter;
263         BMVert *v, *vert_snap;
264         BMLoop *l, *firstl = NULL;
265         float fac;
266         int i, tot;
267
268         vert_snap = BMO_slot_buffer_get_single(BMO_slot_get(op->slots_in, "vert_snap"));
269         tot = BM_vert_face_count(vert_snap);
270
271         if (!tot)
272                 return;
273
274         fac = 1.0f / tot;
275         BM_ITER_ELEM (l, &iter, vert_snap, BM_LOOPS_OF_VERT) {
276                 if (!firstl) {
277                         firstl = l;
278                 }
279                 
280                 for (i = 0; i < bm->ldata.totlayer; i++) {
281                         if (CustomData_layer_has_math(&bm->ldata, i)) {
282                                 int type = bm->ldata.layers[i].type;
283                                 void *e1, *e2;
284
285                                 e1 = CustomData_bmesh_get_layer_n(&bm->ldata, firstl->head.data, i);
286                                 e2 = CustomData_bmesh_get_layer_n(&bm->ldata, l->head.data, i);
287                                 
288                                 CustomData_data_multiply(type, e2, fac);
289
290                                 if (l != firstl)
291                                         CustomData_data_add(type, e1, e2);
292                         }
293                 }
294         }
295
296         BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
297                 BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) {
298                         if (l == firstl) {
299                                 continue;
300                         }
301
302                         CustomData_bmesh_copy_data(&bm->ldata, &bm->ldata, firstl->head.data, &l->head.data);
303                 }
304         }
305 }
306
307 void bmo_average_vert_facedata_exec(BMesh *bm, BMOperator *op)
308 {
309         BMOIter siter;
310         BMIter iter;
311         BMVert *v;
312         BMLoop *l /* , *firstl = NULL */;
313         CDBlockBytes min, max;
314         void *block;
315         int i, type;
316
317         for (i = 0; i < bm->ldata.totlayer; i++) {
318                 if (!CustomData_layer_has_math(&bm->ldata, i))
319                         continue;
320                 
321                 type = bm->ldata.layers[i].type;
322                 CustomData_data_initminmax(type, &min, &max);
323
324                 BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
325                         BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) {
326                                 block = CustomData_bmesh_get_layer_n(&bm->ldata, l->head.data, i);
327                                 CustomData_data_dominmax(type, block, &min, &max);
328                         }
329                 }
330
331                 CustomData_data_multiply(type, &min, 0.5f);
332                 CustomData_data_multiply(type, &max, 0.5f);
333                 CustomData_data_add(type, &min, &max);
334
335                 BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
336                         BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) {
337                                 block = CustomData_bmesh_get_layer_n(&bm->ldata, l->head.data, i);
338                                 CustomData_data_copy_value(type, &min, block);
339                         }
340                 }
341         }
342 }
343
344 void bmo_pointmerge_exec(BMesh *bm, BMOperator *op)
345 {
346         BMOperator weldop;
347         BMOIter siter;
348         BMVert *v, *vert_snap = NULL;
349         float vec[3];
350         BMOpSlot *slot_targetmap;
351         
352         BMO_slot_vec_get(op->slots_in, "merge_co", vec);
353
354         //BMO_op_callf(bm, op->flag, "collapse_uvs edges=%s", op, "edges");
355         BMO_op_init(bm, &weldop, op->flag, "weld_verts");
356
357         slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
358
359         BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
360                 if (!vert_snap) {
361                         vert_snap = v;
362                         copy_v3_v3(vert_snap->co, vec);
363                 }
364                 else {
365                         BMO_slot_map_elem_insert(&weldop, slot_targetmap, v, vert_snap);
366                 }
367         }
368
369         BMO_op_exec(bm, &weldop);
370         BMO_op_finish(bm, &weldop);
371 }
372
373 void bmo_collapse_exec(BMesh *bm, BMOperator *op)
374 {
375         BMOperator weldop;
376         BMWalker walker;
377         BMIter iter;
378         BMEdge *e, **edges = NULL;
379         BLI_array_declare(edges);
380         float min[3], max[3], center[3];
381         unsigned int i, tot;
382         BMOpSlot *slot_targetmap;
383         
384         BMO_op_callf(bm, op->flag, "collapse_uvs edges=%s", op, "edges");
385         BMO_op_init(bm, &weldop, op->flag, "weld_verts");
386         slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
387
388         BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);
389
390         BMW_init(&walker, bm, BMW_VERT_SHELL,
391                  BMW_MASK_NOP, EDGE_MARK, BMW_MASK_NOP,
392                  BMW_FLAG_NOP, /* no need to use BMW_FLAG_TEST_HIDDEN, already marked data */
393                  BMW_NIL_LAY);
394
395         BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
396                 BMVert *v_tar;
397
398                 if (!BMO_elem_flag_test(bm, e, EDGE_MARK))
399                         continue;
400
401                 BLI_array_empty(edges);
402
403                 INIT_MINMAX(min, max);
404                 for (e = BMW_begin(&walker, e->v1), tot = 0; e; e = BMW_step(&walker), tot++) {
405                         BLI_array_grow_one(edges);
406                         edges[tot] = e;
407
408                         minmax_v3v3_v3(min, max, e->v1->co);
409                         minmax_v3v3_v3(min, max, e->v2->co);
410
411                         /* prevent adding to slot_targetmap multiple times */
412                         BM_elem_flag_disable(e->v1, BM_ELEM_TAG);
413                         BM_elem_flag_disable(e->v2, BM_ELEM_TAG);
414                 }
415
416                 mid_v3_v3v3(center, min, max);
417
418                 /* snap edges to a point.  for initial testing purposes anyway */
419                 v_tar = edges[0]->v1;
420
421                 for (i = 0; i < tot; i++) {
422                         unsigned int j;
423
424                         for (j = 0; j < 2; j++) {
425                                 BMVert *v_src = *((&edges[i]->v1) + j);
426
427                                 copy_v3_v3(v_src->co, center);
428                                 if ((v_src != v_tar) && !BM_elem_flag_test(v_src, BM_ELEM_TAG)) {
429                                         BM_elem_flag_enable(v_src, BM_ELEM_TAG);
430                                         BMO_slot_map_elem_insert(&weldop, slot_targetmap, v_src, v_tar);
431                                 }
432                         }
433                 }
434         }
435         
436         BMO_op_exec(bm, &weldop);
437         BMO_op_finish(bm, &weldop);
438
439         BMW_end(&walker);
440         BLI_array_free(edges);
441 }
442
443 /* uv collapse function */
444 static void bmo_collapsecon_do_layer(BMesh *bm, const int layer, const short oflag)
445 {
446         BMIter iter, liter;
447         BMFace *f;
448         BMLoop *l, *l2;
449         BMWalker walker;
450         void **blocks = NULL;
451         BLI_array_declare(blocks);
452         CDBlockBytes min, max;
453         int i, tot, type = bm->ldata.layers[layer].type;
454
455         BMW_init(&walker, bm, BMW_LOOPDATA_ISLAND,
456                  BMW_MASK_NOP, oflag, BMW_MASK_NOP,
457                  BMW_FLAG_NOP, /* no need to use BMW_FLAG_TEST_HIDDEN, already marked data */
458                  layer);
459
460         BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
461                 BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
462                         if (BMO_elem_flag_test(bm, l->e, oflag)) {
463                                 /* walk */
464                                 BLI_array_empty(blocks);
465
466                                 CustomData_data_initminmax(type, &min, &max);
467                                 for (l2 = BMW_begin(&walker, l), tot = 0; l2; l2 = BMW_step(&walker), tot++) {
468                                         BLI_array_grow_one(blocks);
469                                         blocks[tot] = CustomData_bmesh_get_layer_n(&bm->ldata, l2->head.data, layer);
470                                         CustomData_data_dominmax(type, blocks[tot], &min, &max);
471                                 }
472
473                                 if (tot) {
474                                         CustomData_data_multiply(type, &min, 0.5f);
475                                         CustomData_data_multiply(type, &max, 0.5f);
476                                         CustomData_data_add(type, &min, &max);
477
478                                         /* snap CD (uv, vcol) points to their centroid */
479                                         for (i = 0; i < tot; i++) {
480                                                 CustomData_data_copy_value(type, &min, blocks[i]);
481                                         }
482                                 }
483                         }
484                 }
485         }
486
487         BMW_end(&walker);
488         BLI_array_free(blocks);
489 }
490
491 void bmo_collapse_uvs_exec(BMesh *bm, BMOperator *op)
492 {
493         const short oflag = EDGE_MARK;
494         int i;
495
496         /* check flags dont change once set */
497 #ifndef NDEBUG
498         int tot_test;
499 #endif
500
501         if (!CustomData_has_math(&bm->ldata)) {
502                 return;
503         }
504
505         BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, oflag);
506
507 #ifndef NDEBUG
508         tot_test = BM_iter_mesh_count_flag(BM_EDGES_OF_MESH, bm, oflag, true);
509 #endif
510
511         for (i = 0; i < bm->ldata.totlayer; i++) {
512                 if (CustomData_layer_has_math(&bm->ldata, i))
513                         bmo_collapsecon_do_layer(bm, i, oflag);
514         }
515
516 #ifndef NDEBUG
517         BLI_assert(tot_test == BM_iter_mesh_count_flag(BM_EDGES_OF_MESH, bm, EDGE_MARK, true));
518 #endif
519
520 }
521
522 static void bmesh_find_doubles_common(BMesh *bm, BMOperator *op,
523                                       BMOperator *optarget, BMOpSlot *optarget_slot)
524 {
525         BMVert  **verts;
526         int       verts_len;
527
528         int i, j, keepvert = 0;
529
530         const float dist  = BMO_slot_float_get(op->slots_in, "dist");
531         const float dist3 = dist * 3.0f;
532
533         /* Test whether keep_verts arg exists and is non-empty */
534         if (BMO_slot_exists(op->slots_in, "keep_verts")) {
535                 BMOIter oiter;
536                 keepvert = BMO_iter_new(&oiter, op->slots_in, "keep_verts", BM_VERT) != NULL;
537         }
538
539         /* get the verts as an array we can sort */
540         verts = BMO_slot_as_arrayN(op->slots_in, "verts", &verts_len);
541
542         /* sort by vertex coordinates added together */
543         qsort(verts, verts_len, sizeof(BMVert *), vergaverco);
544
545         /* Flag keep_verts */
546         if (keepvert) {
547                 BMO_slot_buffer_flag_enable(bm, op->slots_in, "keep_verts", BM_VERT, VERT_KEEP);
548         }
549
550         for (i = 0; i < verts_len; i++) {
551                 BMVert *v_check = verts[i];
552
553                 if (BMO_elem_flag_test(bm, v_check, VERT_DOUBLE | VERT_TARGET)) {
554                         continue;
555                 }
556
557                 for (j = i + 1; j < verts_len; j++) {
558                         BMVert *v_other = verts[j];
559
560                         /* a match has already been found, (we could check which is best, for now don't) */
561                         if (BMO_elem_flag_test(bm, v_other, VERT_DOUBLE | VERT_TARGET)) {
562                                 continue;
563                         }
564
565                         /* Compare sort values of the verts using 3x tolerance (allowing for the tolerance
566                          * on each of the three axes). This avoids the more expensive length comparison
567                          * for most vertex pairs. */
568                         if ((v_other->co[0] + v_other->co[1] + v_other->co[2]) -
569                             (v_check->co[0] + v_check->co[1] + v_check->co[2]) > dist3)
570                         {
571                                 break;
572                         }
573
574                         if (keepvert) {
575                                 if (BMO_elem_flag_test(bm, v_other, VERT_KEEP) == BMO_elem_flag_test(bm, v_check, VERT_KEEP))
576                                         continue;
577                         }
578
579                         if (compare_len_v3v3(v_check->co, v_other->co, dist)) {
580
581                                 /* If one vert is marked as keep, make sure it will be the target */
582                                 if (BMO_elem_flag_test(bm, v_other, VERT_KEEP)) {
583                                         SWAP(BMVert *, v_check, v_other);
584                                 }
585
586                                 BMO_elem_flag_enable(bm, v_other, VERT_DOUBLE);
587                                 BMO_elem_flag_enable(bm, v_check, VERT_TARGET);
588
589                                 BMO_slot_map_elem_insert(optarget, optarget_slot, v_other, v_check);
590                         }
591                 }
592         }
593
594         MEM_freeN(verts);
595 }
596
597 void bmo_remove_doubles_exec(BMesh *bm, BMOperator *op)
598 {
599         BMOperator weldop;
600         BMOpSlot *slot_targetmap;
601
602         BMO_op_init(bm, &weldop, op->flag, "weld_verts");
603         slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
604         bmesh_find_doubles_common(bm, op,
605                                   &weldop, slot_targetmap);
606         BMO_op_exec(bm, &weldop);
607         BMO_op_finish(bm, &weldop);
608 }
609
610
611 void bmo_find_doubles_exec(BMesh *bm, BMOperator *op)
612 {
613         BMOpSlot *slot_targetmap_out;
614         slot_targetmap_out = BMO_slot_get(op->slots_out, "targetmap.out");
615         bmesh_find_doubles_common(bm, op,
616                                   op, slot_targetmap_out);
617 }
618
619 void bmo_automerge_exec(BMesh *bm, BMOperator *op)
620 {
621         BMOperator findop, weldop;
622         BMIter viter;
623         BMVert *v;
624
625         /* The "verts" input sent to this op is the set of verts that
626          * can be merged away into any other verts. Mark all other verts
627          * as VERT_KEEP. */
628         BMO_slot_buffer_flag_enable(bm, op->slots_in, "verts", BM_VERT, VERT_IN);
629         BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
630                 if (!BMO_elem_flag_test(bm, v, VERT_IN)) {
631                         BMO_elem_flag_enable(bm, v, VERT_KEEP);
632                 }
633         }
634
635         /* Search for doubles among all vertices, but only merge non-VERT_KEEP
636          * vertices into VERT_KEEP vertices. */
637         BMO_op_initf(bm, &findop, op->flag, "find_doubles verts=%av keep_verts=%fv", VERT_KEEP);
638         BMO_slot_copy(op,      slots_in, "dist",
639                       &findop, slots_in, "dist");
640         BMO_op_exec(bm, &findop);
641
642         /* weld the vertices */
643         BMO_op_init(bm, &weldop, op->flag, "weld_verts");
644         BMO_slot_copy(&findop, slots_out, "targetmap.out",
645                       &weldop, slots_in,  "targetmap");
646         BMO_op_exec(bm, &weldop);
647
648         BMO_op_finish(bm, &findop);
649         BMO_op_finish(bm, &weldop);
650 }