Blender V5.0
editmesh_intersect.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "DNA_object_types.h"
10
11#include "BLI_linklist_stack.h"
12#include "BLI_math_geom.h"
13#include "BLI_math_vector.h"
14#include "BLI_memarena.h"
15#include "BLI_stack.h"
16#include "BLI_vector.hh"
17
18#include "BKE_context.hh"
19#include "BKE_editmesh.hh"
20#include "BKE_editmesh_bvh.hh"
21#include "BKE_layer.hh"
22#include "BKE_report.hh"
23
24#include "RNA_access.hh"
25#include "RNA_define.hh"
26
27#include "WM_types.hh"
28
30#include "UI_resources.hh"
31
32#include "ED_mesh.hh"
33#include "ED_screen.hh"
34
35#include "mesh_intern.hh" /* own include */
36
40
41/* detect isolated holes and fill them */
42#define USE_NET_ISLAND_CONNECT
43
44using blender::Vector;
45
49static int bm_face_isect_self(BMFace *f, void * /*user_data*/)
50{
52 return 0;
53 }
54 return -1;
55}
56
60static int bm_face_isect_pair(BMFace *f, void * /*user_data*/)
61{
63 return -1;
64 }
66 return 1;
67 }
68 return 0;
69}
70
75static int bm_face_isect_pair_swap(BMFace *f, void * /*user_data*/)
76{
78 return -1;
79 }
81 return 0;
82 }
83 return 1;
84}
85
89static void edbm_intersect_select(BMEditMesh *em, Mesh *mesh, bool do_select)
90{
91 if (do_select) {
93
95 BMIter iter;
96 BMEdge *e;
97
98 BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) {
100 BM_edge_select_set(em->bm, e, true);
101 }
102 }
103
106 }
107 }
108
110 params.calc_looptris = true;
111 params.calc_normals = true;
112 params.is_destructive = true;
113 EDBM_update(mesh, &params);
114}
115
116/* -------------------------------------------------------------------- */
121
122enum {
125};
126
127enum {
131};
132
133enum {
136};
137
139{
140 const int mode = RNA_enum_get(op->ptr, "mode");
141 int (*test_fn)(BMFace *, void *);
142 bool use_separate_all = false;
143 bool use_separate_cut = false;
144 const int separate_mode = RNA_enum_get(op->ptr, "separate_mode");
145 const float eps = RNA_float_get(op->ptr, "threshold");
146#ifdef WITH_GMP
147 const bool exact = RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT;
148#else
149 if (RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT) {
150 BKE_report(op->reports, RPT_WARNING, "Compiled without GMP, using fast solver");
151 }
152 const bool exact = false;
153#endif
154 bool use_self;
155 bool has_isect;
156
157 switch (mode) {
158 case ISECT_SEL:
159 test_fn = bm_face_isect_self;
160 use_self = true;
161 break;
162 default: /* ISECT_SEL_UNSEL */
163 test_fn = bm_face_isect_pair;
164 use_self = false;
165 break;
166 }
167
168 switch (separate_mode) {
170 use_separate_all = true;
171 break;
173 if (use_self == false) {
174 use_separate_cut = true;
175 }
176 else {
177 /* we could support this but would require more advanced logic inside 'BM_mesh_intersect'
178 * for now just separate all */
179 use_separate_all = true;
180 }
181 break;
182 default: /* ISECT_SEPARATE_NONE */
183 break;
184 }
185 const Scene *scene = CTX_data_scene(C);
186 ViewLayer *view_layer = CTX_data_view_layer(C);
187 uint isect_len = 0;
189 scene, view_layer, CTX_wm_view3d(C));
190 for (Object *obedit : objects) {
192
193 if (em->bm->totfacesel == 0) {
194 continue;
195 }
196
197 if (exact) {
198 int nshapes = use_self ? 1 : 2;
199 has_isect = BM_mesh_boolean_knife(em->bm,
200 em->looptris,
201 test_fn,
202 nullptr,
203 nshapes,
204 use_self,
205 use_separate_all,
206 false,
207 true);
208 }
209 else {
210 has_isect = BM_mesh_intersect(em->bm,
211 em->looptris,
212 test_fn,
213 nullptr,
214 use_self,
215 use_separate_all,
216 true,
217 true,
218 true,
219 true,
220 -1,
221 eps);
222 }
223
224 if (use_separate_cut) {
225 /* detach selected/un-selected faces */
228 }
229
230 edbm_intersect_select(em, static_cast<Mesh *>(obedit->data), has_isect);
231
232 if (!has_isect) {
233 isect_len++;
234 }
235 }
236
237 if (isect_len == objects.size()) {
238 BKE_report(op->reports, RPT_WARNING, "No intersections found");
239 }
240 return OPERATOR_FINISHED;
241}
242
243static void edbm_intersect_ui(bContext * /*C*/, wmOperator *op)
244{
245 uiLayout *layout = op->layout;
246 uiLayout *row;
247
248 bool use_exact = RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT;
249
250 layout->use_property_split_set(true);
251 layout->use_property_decorate_set(false);
252 row = &layout->row(false);
253 row->prop(op->ptr, "mode", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
254 layout->separator();
255 row = &layout->row(false);
256 row->prop(op->ptr, "separate_mode", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
257 layout->separator();
258
259 row = &layout->row(false);
260 row->prop(op->ptr, "solver", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
261 layout->separator();
262
263 if (!use_exact) {
264 layout->prop(op->ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
265 }
266}
267
269{
270 static const EnumPropertyItem isect_mode_items[] = {
271 {ISECT_SEL, "SELECT", 0, "Self Intersect", "Self intersect selected faces"},
273 "SELECT_UNSELECT",
274 0,
275 "Selected/Unselected",
276 "Intersect selected with unselected faces"},
277 {0, nullptr, 0, nullptr, nullptr},
278 };
279
280 static const EnumPropertyItem isect_separate_items[] = {
281 {ISECT_SEPARATE_ALL, "ALL", 0, "All", "Separate all geometry from intersections"},
283 "CUT",
284 0,
285 "Cut",
286 "Cut into geometry keeping each side separate (Selected/Unselected only)"},
287 {ISECT_SEPARATE_NONE, "NONE", 0, "Merge", "Merge all geometry from the intersection"},
288 {0, nullptr, 0, nullptr, nullptr},
289 };
290
291 static const EnumPropertyItem isect_intersect_solver_items[] = {
293 "FLOAT",
294 0,
295 "Float",
296 "Simple solver with good performance, without support for overlapping geometry"},
298 "EXACT",
299 0,
300 "Exact",
301 "Slower solver with the best results for coplanar faces"},
302 {0, nullptr, 0, nullptr, nullptr},
303 };
304
305 /* identifiers */
306 ot->name = "Intersect (Knife)";
307 ot->description = "Cut an intersection into faces";
308 ot->idname = "MESH_OT_intersect";
309
310 /* API callbacks. */
311 ot->exec = edbm_intersect_exec;
312 ot->poll = ED_operator_editmesh;
313 ot->ui = edbm_intersect_ui;
314
315 /* props */
316 RNA_def_enum(ot->srna, "mode", isect_mode_items, ISECT_SEL_UNSEL, "Source", "");
318 ot->srna, "separate_mode", isect_separate_items, ISECT_SEPARATE_CUT, "Separate Mode", "");
320 ot->srna, "threshold", 0.000001f, 0.0, 0.01, "Merge Threshold", "", 0.0, 0.001);
321 RNA_def_enum(ot->srna,
322 "solver",
323 isect_intersect_solver_items,
325 "Solver",
326 "Which Intersect solver to use");
327
328 /* flags */
330}
331
333
334/* -------------------------------------------------------------------- */
340
342{
343 const int boolean_operation = RNA_enum_get(op->ptr, "operation");
344 bool use_swap = RNA_boolean_get(op->ptr, "use_swap");
345 bool use_self = RNA_boolean_get(op->ptr, "use_self");
346#ifdef WITH_GMP
347 const bool use_exact = RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT;
348#else
349 if (RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT) {
350 BKE_report(op->reports, RPT_WARNING, "Compiled without GMP, using fast solver");
351 }
352 const bool use_exact = false;
353#endif
354 const float eps = RNA_float_get(op->ptr, "threshold");
355 int (*test_fn)(BMFace *, void *);
356 bool has_isect;
357
358 test_fn = use_swap ? bm_face_isect_pair_swap : bm_face_isect_pair;
359 const Scene *scene = CTX_data_scene(C);
360 ViewLayer *view_layer = CTX_data_view_layer(C);
361 uint isect_len = 0;
363 scene, view_layer, CTX_wm_view3d(C));
364 for (Object *obedit : objects) {
366
367 if (em->bm->totfacesel == 0) {
368 continue;
369 }
370
371 if (use_exact) {
372 has_isect = BM_mesh_boolean(
373 em->bm, em->looptris, test_fn, nullptr, 2, use_self, true, false, boolean_operation);
374 }
375 else {
376 has_isect = BM_mesh_intersect(em->bm,
377 em->looptris,
378 test_fn,
379 nullptr,
380 false,
381 false,
382 true,
383 true,
384 false,
385 true,
386 boolean_operation,
387 eps);
388 }
389
390 edbm_intersect_select(em, static_cast<Mesh *>(obedit->data), has_isect);
391
392 if (!has_isect) {
393 isect_len++;
394 }
395 }
396
397 if (isect_len == objects.size()) {
398 BKE_report(op->reports, RPT_WARNING, "No intersections found");
399 }
400 return OPERATOR_FINISHED;
401}
402
404{
405 uiLayout *layout = op->layout;
406 uiLayout *row;
407
408 bool use_exact = RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT;
409
410 layout->use_property_split_set(true);
411 layout->use_property_decorate_set(false);
412
413 row = &layout->row(false);
414 row->prop(op->ptr, "operation", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
415 layout->separator();
416
417 row = &layout->row(false);
418 row->prop(op->ptr, "solver", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
419 layout->separator();
420
421 layout->prop(op->ptr, "use_swap", UI_ITEM_NONE, std::nullopt, ICON_NONE);
422 layout->prop(op->ptr, "use_self", UI_ITEM_NONE, std::nullopt, ICON_NONE);
423 if (!use_exact) {
424 layout->prop(op->ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
425 }
426}
427
429{
430 static const EnumPropertyItem isect_boolean_operation_items[] = {
431 {BMESH_ISECT_BOOLEAN_ISECT, "INTERSECT", 0, "Intersect", ""},
432 {BMESH_ISECT_BOOLEAN_UNION, "UNION", 0, "Union", ""},
433 {BMESH_ISECT_BOOLEAN_DIFFERENCE, "DIFFERENCE", 0, "Difference", ""},
434 {0, nullptr, 0, nullptr, nullptr},
435 };
436
437 static const EnumPropertyItem isect_boolean_solver_items[] = {
438 {ISECT_SOLVER_FAST, "FLOAT", 0, "Float", "Faster solver, some limitations"},
439 {ISECT_SOLVER_EXACT, "EXACT", 0, "Exact", "Exact solver, slower, handles more cases"},
440 {0, nullptr, 0, nullptr, nullptr},
441 };
442
443 /* identifiers */
444 ot->name = "Intersect (Boolean)";
445 ot->description = "Cut solid geometry from selected to unselected";
446 ot->idname = "MESH_OT_intersect_boolean";
447
448 /* API callbacks. */
450 ot->poll = ED_operator_editmesh;
452
453 /* props */
454 RNA_def_enum(ot->srna,
455 "operation",
456 isect_boolean_operation_items,
458 "Boolean Operation",
459 "Which boolean operation to apply");
460 RNA_def_boolean(ot->srna,
461 "use_swap",
462 false,
463 "Swap",
464 "Use with difference intersection to swap which side is kept");
466 ot->srna, "use_self", false, "Self Intersection", "Do self-union or self-intersection");
468 ot->srna, "threshold", 0.000001f, 0.0, 0.01, "Merge Threshold", "", 0.0, 0.001);
469 RNA_def_enum(ot->srna,
470 "solver",
471 isect_boolean_solver_items,
473 "Solver",
474 "Which Boolean solver to use");
475
476 /* flags */
478}
479
481
482/* -------------------------------------------------------------------- */
485
487 BMFace *f,
488 const char hflag, /* reusable memory buffer */
489 Vector<BMEdge *, 128> *edge_net_temp_buf)
490{
491 const int f_index = BM_elem_index_get(f);
492
493 BMLoop *l_iter;
494 BMLoop *l_first;
495 BMVert *v;
496
497 /* likely this will stay very small
498 * all verts pushed into this stack _must_ have their previous edges set! */
499 BLI_SMALLSTACK_DECLARE(vert_stack, BMVert *);
500 BLI_SMALLSTACK_DECLARE(vert_stack_next, BMVert *);
501
502 BLI_assert(edge_net_temp_buf->is_empty());
503
504 /* collect all edges */
505 l_iter = l_first = BM_FACE_FIRST_LOOP(f);
506 do {
507 BMIter iter;
508 BMEdge *e;
509
510 BM_ITER_ELEM (e, &iter, l_iter->v, BM_EDGES_OF_VERT) {
511 if (BM_elem_flag_test(e, hflag) && (BM_elem_index_get(e) == f_index)) {
512 v = BM_edge_other_vert(e, l_iter->v);
513 v->e = e;
514
515 BLI_SMALLSTACK_PUSH(vert_stack, v);
516 edge_net_temp_buf->append(e);
517 }
518 }
519 } while ((l_iter = l_iter->next) != l_first);
520
521 /* now assign all */
522 /* pop free values into the next stack */
523 while ((v = static_cast<BMVert *>(BLI_SMALLSTACK_POP_EX(vert_stack, vert_stack_next)))) {
524 BMIter eiter;
525 BMEdge *e_next;
526
527 BM_ITER_ELEM (e_next, &eiter, v, BM_EDGES_OF_VERT) {
528 if (BM_elem_flag_test(e_next, hflag) && (BM_elem_index_get(e_next) == -1)) {
529 BMVert *v_next;
530 v_next = BM_edge_other_vert(e_next, v);
531 BM_elem_index_set(e_next, f_index);
532 BLI_SMALLSTACK_PUSH(vert_stack_next, v_next);
533 edge_net_temp_buf->append(e_next);
534 }
535 }
536
537 if (BLI_SMALLSTACK_IS_EMPTY(vert_stack)) {
538 BLI_SMALLSTACK_SWAP(vert_stack, vert_stack_next);
539 }
540 }
541
542 Vector<BMFace *> face_arr;
543 BM_face_split_edgenet(bm, f, edge_net_temp_buf->data(), edge_net_temp_buf->size(), &face_arr);
544
545 edge_net_temp_buf->clear();
546
547 for (BMFace *face : face_arr) {
548 BM_face_select_set(bm, face, true);
549 BM_elem_flag_disable(face, hflag);
550 }
551}
552
557static bool bm_vert_in_faces_radial(BMVert *v, BMEdge *e_radial, BMFace *f_ignore)
558{
559 BLI_assert(BM_vert_in_face(v, f_ignore) == false);
560 if (e_radial->l) {
561 BMLoop *l_iter = e_radial->l;
562 do {
563 if (l_iter->f != f_ignore) {
564 if (BM_vert_in_face(v, l_iter->f)) {
565 return true;
566 }
567 }
568 } while ((l_iter = l_iter->radial_next) != e_radial->l);
569 }
570 return false;
571}
572
573#ifdef USE_NET_ISLAND_CONNECT
574
575struct LinkBase {
576 LinkNode *list;
578};
579
581 BMFace *f_key,
582 BMEdge *e_val,
584{
585 void **ls_base_p;
586 LinkBase *ls_base;
587 LinkNode *ls;
588
589 if (!BLI_ghash_ensure_p(gh, f_key, &ls_base_p)) {
590 ls_base = static_cast<LinkBase *>(*ls_base_p = BLI_memarena_alloc(mem_arena,
591 sizeof(*ls_base)));
592 ls_base->list = nullptr;
593 ls_base->list_len = 0;
594 }
595 else {
596 ls_base = static_cast<LinkBase *>(*ls_base_p);
597 }
598
599 ls = static_cast<LinkNode *>(BLI_memarena_alloc(mem_arena, sizeof(*ls)));
600 ls->next = ls_base->list;
601 ls->link = e_val;
602 ls_base->list = ls;
603 ls_base->list_len += 1;
604}
605
606static int bm_edge_sort_length_cb(const void *e_a_v, const void *e_b_v)
607{
608 const float val_a = -BM_edge_calc_length_squared(*((BMEdge **)e_a_v));
609 const float val_b = -BM_edge_calc_length_squared(*((BMEdge **)e_b_v));
610
611 if (val_a > val_b) {
612 return 1;
613 }
614 if (val_a < val_b) {
615 return -1;
616 }
617 return 0;
618}
619
621 BMesh *bm, BMFace *f, LinkNode *e_link, const int e_link_len, MemArena *mem_arena_edgenet)
622{
623 BMEdge **edge_arr = static_cast<BMEdge **>(
624 BLI_memarena_alloc(mem_arena_edgenet, sizeof(*edge_arr) * e_link_len));
625 int edge_arr_len = 0;
626
627 while (e_link) {
628 edge_arr[edge_arr_len++] = static_cast<BMEdge *>(e_link->link);
629 e_link = e_link->next;
630 }
631
632 {
633 uint edge_arr_holes_len;
634 BMEdge **edge_arr_holes;
636 f,
637 edge_arr,
638 e_link_len,
639 true,
640 mem_arena_edgenet,
641 &edge_arr_holes,
642 &edge_arr_holes_len))
643 {
644 edge_arr_len = edge_arr_holes_len;
645 edge_arr = edge_arr_holes; /* owned by the arena */
646 }
647 }
648
649 BM_face_split_edgenet(bm, f, edge_arr, edge_arr_len, nullptr);
650
651 for (int i = e_link_len; i < edge_arr_len; i++) {
652 BM_edge_select_set(bm, edge_arr[i], true);
653 }
654
655 if (e_link_len != edge_arr_len) {
656 /* connecting partial islands can add redundant edges
657 * sort before removal to give deterministic outcome */
658 qsort(edge_arr, edge_arr_len - e_link_len, sizeof(*edge_arr), bm_edge_sort_length_cb);
659 for (int i = e_link_len; i < edge_arr_len; i++) {
660 BMFace *f_pair[2];
661 if (BM_edge_face_pair(edge_arr[i], &f_pair[0], &f_pair[1])) {
662 if (BM_face_share_vert_count(f_pair[0], f_pair[1]) == 2) {
663 BMFace *f_double;
664 BMFace *f_new = BM_faces_join(bm, f_pair, 2, true, &f_double);
665 /* See #BM_faces_join note on callers asserting when `r_double` is non-null. */
666 BLI_assert_msg(f_double == nullptr,
667 "Doubled face detected at " AT ". Resulting mesh may be corrupt.");
668
669 if (f_new) {
670 BM_face_select_set(bm, f_new, true);
671 }
672 }
673 }
674 }
675 }
676}
677
697 BMFace *f_a,
698 BMVert *v_pivot,
699 BMFace **ftable,
700 const int ftable_len,
701 float r_v_pivot_co[3],
702 float *r_v_pivot_fac)
703{
704 const int f_a_index = BM_elem_index_get(e_a);
705 bool found_other_self = false;
706 int found_other_face = 0;
707 BLI_SMALLSTACK_DECLARE(face_stack, BMFace *);
708
709 /* loop over surrounding edges to check if we're part of a chain or a delimiter vertex */
710 BMEdge *e_b = v_pivot->e;
711 do {
712 if (e_b != e_a) {
713 const int f_b_index = BM_elem_index_get(e_b);
714 if (f_b_index == f_a_index) {
715 /* not an endpoint */
716 found_other_self = true;
717 }
718 else if (f_b_index != -1) {
719 BLI_assert(f_b_index < ftable_len);
720 UNUSED_VARS_NDEBUG(ftable_len);
721
722 /* 'v_pivot' spans 2+ faces,
723 * tag to ensure we pick an edge that includes this face */
724 BMFace *f_b = ftable[f_b_index];
727 BLI_SMALLSTACK_PUSH(face_stack, f_b);
728 found_other_face++;
729 }
730 }
731 }
732 } while ((e_b = BM_DISK_EDGE_NEXT(e_b, v_pivot)) != v_pivot->e);
733
734 BMEdge *e_split = nullptr;
735
736 /* if we have no others or the other edge is outside this face,
737 * we're an endpoint to connect to a boundary */
738 if ((found_other_self == false) || found_other_face) {
739
740 BMLoop *l_iter, *l_first;
741 l_iter = l_first = BM_FACE_FIRST_LOOP(f_a);
742 float dist_best_sq = FLT_MAX;
743
744 do {
745 float v_pivot_co_test[3];
746 float v_pivot_fac = line_point_factor_v3(v_pivot->co, l_iter->e->v1->co, l_iter->e->v2->co);
747 CLAMP(v_pivot_fac, 0.0f, 1.0f);
748 interp_v3_v3v3(v_pivot_co_test, l_iter->e->v1->co, l_iter->e->v2->co, v_pivot_fac);
749
750 float dist_test_sq = len_squared_v3v3(v_pivot_co_test, v_pivot->co);
751 if ((dist_test_sq < dist_best_sq) || (e_split == nullptr)) {
752 bool ok = true;
753
754 if (UNLIKELY(BM_edge_exists(v_pivot, l_iter->e->v1) ||
755 BM_edge_exists(v_pivot, l_iter->e->v2)))
756 {
757 /* very unlikely but will cause complications splicing the verts together,
758 * so just skip this case */
759 ok = false;
760 }
761 else if (found_other_face) {
762 /* double check that _all_ the faces used by v_pivot's edges are attached
763 * to this edge otherwise don't attempt the split since it will give
764 * non-deterministic results */
765 BMLoop *l_radial_iter = l_iter->radial_next;
766 int other_face_shared = 0;
767 if (l_radial_iter != l_iter) {
768 do {
769 if (BM_elem_flag_test(l_radial_iter->f, BM_ELEM_INTERNAL_TAG)) {
770 other_face_shared++;
771 }
772 } while ((l_radial_iter = l_radial_iter->radial_next) != l_iter);
773 }
774 if (other_face_shared != found_other_face) {
775 ok = false;
776 }
777 }
778
779 if (ok) {
780 e_split = l_iter->e;
781 dist_best_sq = dist_test_sq;
782 copy_v3_v3(r_v_pivot_co, v_pivot_co_test);
783 *r_v_pivot_fac = v_pivot_fac;
784 }
785 }
786 } while ((l_iter = l_iter->next) != l_first);
787 }
788
789 {
790 /* reset the flag, for future use */
791 BMFace *f;
792 while ((f = static_cast<BMFace *>(BLI_SMALLSTACK_POP(face_stack)))) {
794 }
795 }
796
797 return e_split;
798}
799
800#endif /* USE_NET_ISLAND_CONNECT */
801
803{
804 const char hflag = BM_ELEM_TAG;
805
806 BMEdge *e;
807 BMIter iter;
808
809 BLI_SMALLSTACK_DECLARE(loop_stack, BMLoop *);
810
811 const Scene *scene = CTX_data_scene(C);
812 ViewLayer *view_layer = CTX_data_view_layer(C);
814 scene, view_layer, CTX_wm_view3d(C));
815 for (Object *obedit : objects) {
817 BMesh *bm = em->bm;
818
819 if ((bm->totedgesel == 0) || (bm->totfacesel == 0)) {
820 continue;
821 }
822
823 {
824 BMVert *v;
825 BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
826 BM_elem_flag_disable(v, hflag);
827 }
828 }
829
830 /* edge index is set to -1 then used to associate them with faces */
831 BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
833 BM_elem_flag_enable(e, hflag);
834
835 BM_elem_flag_enable(e->v1, hflag);
836 BM_elem_flag_enable(e->v2, hflag);
837 }
838 else {
839 BM_elem_flag_disable(e, hflag);
840 }
841 BM_elem_index_set(e, -1); /* set_dirty */
842 }
843 bm->elem_index_dirty |= BM_EDGE;
844
845 {
846 BMFace *f;
847 int i;
850 BM_elem_flag_enable(f, hflag);
851 }
852 else {
853 BM_elem_flag_disable(f, hflag);
854 }
856 BM_elem_index_set(f, i); /* set_ok */
857 }
858 }
859 bm->elem_index_dirty &= ~BM_FACE;
860
861 BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
862 if (BM_elem_flag_test(e, hflag)) {
863 BMIter viter;
864 BMVert *v;
865 BM_ITER_ELEM (v, &viter, e, BM_VERTS_OF_EDGE) {
866 BMIter liter;
867 BMLoop *l;
868
869 uint loop_stack_len;
870 BMLoop *l_best = nullptr;
871
873 loop_stack_len = 0;
874
875 BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) {
876 if (BM_elem_flag_test(l->f, hflag)) {
877 BLI_SMALLSTACK_PUSH(loop_stack, l);
878 loop_stack_len++;
879 }
880 }
881
882 if (loop_stack_len == 0) {
883 /* pass */
884 }
885 else if (loop_stack_len == 1) {
886 l_best = static_cast<BMLoop *>(BLI_SMALLSTACK_POP(loop_stack));
887 }
888 else {
889 /* complicated case, match the edge with a face-loop */
890
891 BMVert *v_other = BM_edge_other_vert(e, v);
892 float e_dir[3];
893
894 /* we want closest to zero */
895 float dot_best = FLT_MAX;
896
897 sub_v3_v3v3(e_dir, v_other->co, v->co);
898 normalize_v3(e_dir);
899
900 while ((l = static_cast<BMLoop *>(BLI_SMALLSTACK_POP(loop_stack)))) {
901 float dot_test;
902
903 /* Check dot first to save on expensive angle-comparison.
904 * ideal case is 90d difference == 0.0 dot */
905 dot_test = fabsf(dot_v3v3(e_dir, l->f->no));
906 if (dot_test < dot_best) {
907
908 /* check we're in the correct corner
909 * (works with convex loops too) */
911 l->prev->v->co, l->v->co, v_other->co, l->f->no) <
913 l->prev->v->co, l->v->co, l->next->v->co, l->f->no))
914 {
915 dot_best = dot_test;
916 l_best = l;
917 }
918 }
919 }
920 }
921
922 if (l_best) {
923 BM_elem_index_set(e, BM_elem_index_get(l_best->f)); /* set_dirty */
924 }
925 }
926 }
927 }
928
929 {
930 BMFace *f;
931 Vector<BMEdge *, 128> edge_net_temp_buf;
932
933 BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
934 if (BM_elem_flag_test(f, hflag)) {
935 bm_face_split_by_edges(bm, f, hflag, &edge_net_temp_buf);
936 }
937 }
938 }
939
940#ifdef USE_NET_ISLAND_CONNECT
941 /* before overwriting edge index values, collect edges left untouched */
942 BLI_Stack *edges_loose = BLI_stack_new(sizeof(BMEdge *), __func__);
943 BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
945 BLI_stack_push(edges_loose, &e);
946 }
947 }
948#endif
949
951 params.calc_looptris = true;
952 params.calc_normals = true;
953 params.is_destructive = true;
954 EDBM_update(static_cast<Mesh *>(obedit->data), &params);
955
956#ifdef USE_NET_ISLAND_CONNECT
957 /* we may have remaining isolated regions remaining,
958 * these will need to have connecting edges created */
959 if (!BLI_stack_is_empty(edges_loose)) {
960 GHash *face_edge_map = BLI_ghash_ptr_new(__func__);
961
963
965
966 {
967 BMBVHTree *bmbvh = BKE_bmbvh_new(bm, em->looptris, BMBVH_RESPECT_SELECT, nullptr, false);
968
969 BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
970 BM_elem_index_set(e, -1); /* set_dirty */
971 }
972
973 while (!BLI_stack_is_empty(edges_loose)) {
974 BLI_stack_pop(edges_loose, &e);
975 float e_center[3];
976 mid_v3_v3v3(e_center, e->v1->co, e->v2->co);
977
978 BMFace *f = BKE_bmbvh_find_face_closest(bmbvh, e_center, FLT_MAX);
979 if (f) {
980 ghash_insert_face_edge_link(face_edge_map, f, e, mem_arena);
981 BM_elem_index_set(e, BM_elem_index_get(f)); /* set_dirty */
982 }
983 }
984
985 BKE_bmbvh_free(bmbvh);
986 }
987
988 bm->elem_index_dirty |= BM_EDGE;
989
991
992 /* detect edges chains that span faces
993 * and splice vertices into the closest edges */
994 {
995 GHashIterator gh_iter;
996
997 GHASH_ITER (gh_iter, face_edge_map) {
998 BMFace *f = static_cast<BMFace *>(BLI_ghashIterator_getKey(&gh_iter));
999 LinkBase *e_ls_base = static_cast<LinkBase *>(BLI_ghashIterator_getValue(&gh_iter));
1000 LinkNode *e_link = e_ls_base->list;
1001
1002 do {
1003 e = static_cast<BMEdge *>(e_link->link);
1004
1005 for (int j = 0; j < 2; j++) {
1006 BMVert *v_pivot = (&e->v1)[j];
1007 /* checking that \a v_pivot isn't in the face prevents attempting
1008 * to splice the same vertex into an edge from multiple faces */
1009 if (!BM_vert_in_face(v_pivot, f)) {
1010 float v_pivot_co[3];
1011 float v_pivot_fac;
1013 e, f, v_pivot, bm->ftable, bm->totface, v_pivot_co, &v_pivot_fac);
1014
1015 if (e_split) {
1016 /* for degenerate cases this vertex may be in one
1017 * of this edges radial faces */
1018 if (!bm_vert_in_faces_radial(v_pivot, e_split, f)) {
1019 BMEdge *e_new;
1020 BMVert *v_new = BM_edge_split(bm, e_split, e_split->v1, &e_new, v_pivot_fac);
1021 if (v_new) {
1022 /* we _know_ these don't share an edge */
1023 BM_vert_splice(bm, v_pivot, v_new);
1024 BM_elem_index_set(e_new, BM_elem_index_get(e_split));
1025 }
1026 }
1027 }
1028 }
1029 }
1030
1031 } while ((e_link = e_link->next));
1032 }
1033 }
1034
1035 {
1036 MemArena *mem_arena_edgenet = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
1037
1038 GHashIterator gh_iter;
1039
1040 GHASH_ITER (gh_iter, face_edge_map) {
1041 BMFace *f = static_cast<BMFace *>(BLI_ghashIterator_getKey(&gh_iter));
1042 LinkBase *e_ls_base = static_cast<LinkBase *>(BLI_ghashIterator_getValue(&gh_iter));
1043
1045 bm, f, e_ls_base->list, e_ls_base->list_len, mem_arena_edgenet);
1046
1047 BLI_memarena_clear(mem_arena_edgenet);
1048 }
1049
1050 BLI_memarena_free(mem_arena_edgenet);
1051 }
1052
1054
1055 BLI_ghash_free(face_edge_map, nullptr, nullptr);
1056
1058 params.calc_looptris = true;
1059 params.calc_normals = true;
1060 params.is_destructive = true;
1061 EDBM_update(static_cast<Mesh *>(obedit->data), &params);
1062 }
1063
1064 BLI_stack_free(edges_loose);
1065#endif /* USE_NET_ISLAND_CONNECT */
1066 }
1067 return OPERATOR_FINISHED;
1068}
1069
1071{
1072 /* identifiers */
1073 ot->name = "Weld Edges into Faces";
1074 ot->description = "Weld loose edges into faces (splitting them into new faces)";
1075 ot->idname = "MESH_OT_face_split_by_edges";
1076
1077 /* API callbacks. */
1079 ot->poll = ED_operator_editmesh;
1080
1081 /* flags */
1082 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1083}
1084
Scene * CTX_data_scene(const bContext *C)
View3D * CTX_wm_view3d(const bContext *C)
ViewLayer * CTX_data_view_layer(const bContext *C)
BMEditMesh * BKE_editmesh_from_object(Object *ob)
Return the BMEditMesh for a given object.
Definition editmesh.cc:61
struct BMFace * BKE_bmbvh_find_face_closest(const BMBVHTree *tree, const float co[3], float dist_max)
BMBVHTree * BKE_bmbvh_new(struct BMesh *bm, blender::Span< std::array< BMLoop *, 3 > > looptris, int flag, const blender::float3 *cos_cage, bool cos_cage_free)
void BKE_bmbvh_free(BMBVHTree *tree)
@ BMBVH_RESPECT_SELECT
blender::Vector< Object * > BKE_view_layer_array_from_objects_in_edit_mode_unique_data(const Scene *scene, ViewLayer *view_layer, const View3D *v3d)
@ RPT_WARNING
Definition BKE_report.hh:38
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
BLI_INLINE void * BLI_ghashIterator_getKey(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.h:295
BLI_INLINE void * BLI_ghashIterator_getValue(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.h:299
#define GHASH_ITER(gh_iter_, ghash_)
Definition BLI_ghash.h:318
GHash * BLI_ghash_ptr_new(const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
void BLI_ghash_free(GHash *gh, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp)
Definition BLI_ghash.cc:860
bool BLI_ghash_ensure_p(GHash *gh, void *key, void ***r_val) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.cc:752
float line_point_factor_v3(const float p[3], const float l1[3], const float l2[3])
MINLINE float len_squared_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
float angle_signed_on_axis_v3v3v3_v3(const float v1[3], const float v2[3], const float v3[3], const float axis[3]) ATTR_WARN_UNUSED_RESULT
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE float dot_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
void interp_v3_v3v3(float r[3], const float a[3], const float b[3], float t)
void mid_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE float normalize_v3(float n[3])
#define BLI_MEMARENA_STD_BUFSIZE
MemArena * BLI_memarena_new(size_t bufsize, const char *name) ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL ATTR_NONNULL(2) ATTR_MALLOC
void BLI_memarena_free(MemArena *ma) ATTR_NONNULL(1)
void * BLI_memarena_alloc(MemArena *ma, size_t size) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC ATTR_ALLOC_SIZE(2)
void BLI_memarena_clear(MemArena *ma) ATTR_NONNULL(1)
void BLI_stack_pop(BLI_Stack *stack, void *dst) ATTR_NONNULL()
Definition stack.cc:138
void BLI_stack_push(BLI_Stack *stack, const void *src) ATTR_NONNULL()
Definition stack.cc:132
bool BLI_stack_is_empty(const BLI_Stack *stack) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition stack.cc:250
void BLI_stack_free(BLI_Stack *stack) ATTR_NONNULL()
Definition stack.cc:96
#define BLI_stack_new(esize, descr)
unsigned int uint
#define CLAMP(a, b, c)
#define UNUSED_VARS_NDEBUG(...)
#define UNLIKELY(x)
#define AT
Object is a sort of wrapper for general info.
@ SCE_SELECT_VERTEX
@ SCE_SELECT_EDGE
@ OPERATOR_FINISHED
void EDBM_update(Mesh *mesh, const EDBMUpdate_Params *params)
bool EDBM_uvselect_clear(BMEditMesh *em)
void EDBM_selectmode_flush(BMEditMesh *em)
bool ED_operator_editmesh(bContext *C)
#define C
Definition RandGen.cpp:29
@ UI_ITEM_R_EXPAND
#define UI_ITEM_NONE
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
bool BM_mesh_boolean_knife(BMesh *, blender::Span< std::array< BMLoop *, 3 > > looptris, int(*test_fn)(BMFace *, void *), void *, const int, const bool, const bool, const bool, const bool)
bool BM_mesh_boolean(BMesh *, blender::Span< std::array< BMLoop *, 3 > > looptris, int(*test_fn)(BMFace *, void *), void *, const int, const bool, const bool, const bool, const int)
#define BM_elem_cb_check_hflag_enabled_simple(type, hflag_p)
#define BM_DISK_EDGE_NEXT(e, v)
#define BM_FACE_FIRST_LOOP(p)
@ BM_ELEM_HIDDEN
@ BM_ELEM_SELECT
@ BM_ELEM_INTERNAL_TAG
@ BM_ELEM_TAG
BMFace * BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del, BMFace **r_double)
Join Connected Faces.
bool BM_vert_splice(BMesh *bm, BMVert *v_dst, BMVert *v_src)
Splice Vert.
#define BM_elem_index_get(ele)
#define BM_elem_flag_disable(ele, hflag)
#define BM_elem_index_set(ele, index)
#define BM_elem_flag_test(ele, hflag)
#define BM_elem_flag_enable(ele, hflag)
bool BM_mesh_intersect(BMesh *bm, const blender::Span< std::array< BMLoop *, 3 > > looptris, int(*test_fn)(BMFace *f, void *user_data), void *user_data, const bool use_self, const bool use_separate, const bool use_dissolve, const bool use_island_connect, const bool use_partial_connect, const bool use_edge_tag, const int boolean_mode, const float eps)
@ BMESH_ISECT_BOOLEAN_DIFFERENCE
@ BMESH_ISECT_BOOLEAN_UNION
@ BMESH_ISECT_BOOLEAN_ISECT
#define BM_ITER_ELEM(ele, iter, data, itype)
#define BM_ITER_MESH(ele, iter, bm, itype)
#define BM_ITER_MESH_INDEX(ele, iter, bm, itype, indexvar)
@ BM_EDGES_OF_MESH
@ BM_VERTS_OF_MESH
@ BM_VERTS_OF_EDGE
@ BM_FACES_OF_MESH
@ BM_LOOPS_OF_VERT
@ BM_EDGES_OF_VERT
BMesh * bm
void BM_face_select_set(BMesh *bm, BMFace *f, const bool select)
Select Face.
void BM_edge_select_set(BMesh *bm, BMEdge *e, const bool select)
Select Edge.
void BM_mesh_elem_hflag_disable_all(BMesh *bm, const char htype, const char hflag, const bool respecthide)
void BM_mesh_elem_table_ensure(BMesh *bm, const char htype)
void BM_mesh_elem_index_ensure(BMesh *bm, const char htype)
BMVert * BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac)
Edge Split.
#define BM_FACE
#define BM_EDGE
#define BM_VERT
bool BM_face_split_edgenet_connect_islands(BMesh *bm, BMFace *f, BMEdge **edge_net_init, const uint edge_net_init_len, bool use_partial_connect, MemArena *mem_arena, BMEdge ***r_edge_net_new, uint *r_edge_net_new_len)
bool BM_face_split_edgenet(BMesh *bm, BMFace *f, BMEdge **edge_net, const int edge_net_len, blender::Vector< BMFace * > *r_face_arr)
int BM_face_share_vert_count(BMFace *f_a, BMFace *f_b)
float BM_edge_calc_length_squared(const BMEdge *e)
bool BM_edge_face_pair(BMEdge *e, BMFace **r_fa, BMFace **r_fb)
BMEdge * BM_edge_exists(BMVert *v_a, BMVert *v_b)
bool BM_vert_in_face(BMVert *v, BMFace *f)
BLI_INLINE BMVert * BM_edge_other_vert(BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
BLI_INLINE bool BM_edge_is_wire(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
ATTR_WARN_UNUSED_RESULT const BMLoop * l
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
ATTR_WARN_UNUSED_RESULT const BMVert * v
void BM_mesh_separate_faces(BMesh *bm, BMFaceFilterFunc filter_fn, void *user_data)
int64_t size() const
void append(const T &value)
bool is_empty() const
void MESH_OT_intersect(wmOperatorType *ot)
static bool bm_vert_in_faces_radial(BMVert *v, BMEdge *e_radial, BMFace *f_ignore)
void MESH_OT_intersect_boolean(wmOperatorType *ot)
static BMEdge * bm_face_split_edge_find(BMEdge *e_a, BMFace *f_a, BMVert *v_pivot, BMFace **ftable, const int ftable_len, float r_v_pivot_co[3], float *r_v_pivot_fac)
static void bm_face_split_by_edges_island_connect(BMesh *bm, BMFace *f, LinkNode *e_link, const int e_link_len, MemArena *mem_arena_edgenet)
static int bm_face_isect_self(BMFace *f, void *)
@ ISECT_SOLVER_EXACT
@ ISECT_SOLVER_FAST
@ ISECT_SEPARATE_ALL
@ ISECT_SEPARATE_NONE
@ ISECT_SEPARATE_CUT
static void bm_face_split_by_edges(BMesh *bm, BMFace *f, const char hflag, Vector< BMEdge *, 128 > *edge_net_temp_buf)
static void edbm_intersect_ui(bContext *, wmOperator *op)
static wmOperatorStatus edbm_intersect_exec(bContext *C, wmOperator *op)
static void ghash_insert_face_edge_link(GHash *gh, BMFace *f_key, BMEdge *e_val, MemArena *mem_arena)
static void edbm_intersect_select(BMEditMesh *em, Mesh *mesh, bool do_select)
void MESH_OT_face_split_by_edges(wmOperatorType *ot)
static int bm_face_isect_pair_swap(BMFace *f, void *)
static wmOperatorStatus edbm_intersect_boolean_exec(bContext *C, wmOperator *op)
static void edbm_intersect_boolean_ui(bContext *, wmOperator *op)
static wmOperatorStatus edbm_face_split_by_edges_exec(bContext *C, wmOperator *)
@ ISECT_SEL_UNSEL
static int bm_edge_sort_length_cb(const void *e_a_v, const void *e_b_v)
static int bm_face_isect_pair(BMFace *f, void *)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
static MemArena * mem_arena
Definition makesdna.cc:62
const btScalar eps
Definition poly34.cpp:11
#define fabsf
float RNA_float_get(PointerRNA *ptr, const char *name)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
int RNA_enum_get(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_def_float_distance(StructOrFunctionRNA *cont_, const char *identifier, const float default_value, const float hardmin, const float hardmax, const char *ui_name, const char *ui_description, const float softmin, const float softmax)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_boolean(StructOrFunctionRNA *cont_, const char *identifier, const bool default_value, const char *ui_name, const char *ui_description)
#define FLT_MAX
Definition stdcycles.h:14
BMVert * v1
BMVert * v2
struct BMLoop * l
blender::Array< std::array< BMLoop *, 3 > > looptris
struct BMVert * v
struct BMEdge * e
struct BMLoop * radial_next
struct BMFace * f
struct BMLoop * next
float co[3]
struct BMEdge * e
int totfacesel
short selectmode
LinkNode * list
void * link
struct LinkNode * next
void use_property_decorate_set(bool is_sep)
void separator(float factor=1.0f, LayoutSeparatorType type=LayoutSeparatorType::Auto)
uiLayout & row(bool align)
void use_property_split_set(bool value)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
struct ReportList * reports
struct uiLayout * layout
struct PointerRNA * ptr
i
Definition text_draw.cc:230
wmOperatorType * ot
Definition wm_files.cc:4237