Blender V4.5
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_buffer.h"
12#include "BLI_linklist_stack.h"
13#include "BLI_math_geom.h"
14#include "BLI_math_vector.h"
15#include "BLI_memarena.h"
16#include "BLI_stack.h"
17#include "BLI_vector.hh"
18
19#include "BKE_context.hh"
20#include "BKE_editmesh.hh"
21#include "BKE_editmesh_bvh.hh"
22#include "BKE_layer.hh"
23#include "BKE_report.hh"
24
25#include "RNA_access.hh"
26#include "RNA_define.hh"
27
28#include "WM_types.hh"
29
30#include "UI_interface.hh"
31#include "UI_resources.hh"
32
33#include "ED_mesh.hh"
34#include "ED_screen.hh"
35
36#include "mesh_intern.hh" /* own include */
37
41
42/* detect isolated holes and fill them */
43#define USE_NET_ISLAND_CONNECT
44
45using blender::Vector;
46
50static int bm_face_isect_self(BMFace *f, void * /*user_data*/)
51{
53 return 0;
54 }
55 return -1;
56}
57
61static int bm_face_isect_pair(BMFace *f, void * /*user_data*/)
62{
64 return -1;
65 }
67 return 1;
68 }
69 return 0;
70}
71
76static int bm_face_isect_pair_swap(BMFace *f, void * /*user_data*/)
77{
79 return -1;
80 }
82 return 0;
83 }
84 return 1;
85}
86
90static void edbm_intersect_select(BMEditMesh *em, Mesh *mesh, bool do_select)
91{
92 if (do_select) {
94
96 BMIter iter;
97 BMEdge *e;
98
99 BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) {
101 BM_edge_select_set(em->bm, e, true);
102 }
103 }
105 }
106 }
107
109 params.calc_looptris = true;
110 params.calc_normals = true;
111 params.is_destructive = true;
112 EDBM_update(mesh, &params);
113}
114
115/* -------------------------------------------------------------------- */
120
121enum {
124};
125
126enum {
130};
131
132enum {
135};
136
138{
139 const int mode = RNA_enum_get(op->ptr, "mode");
140 int (*test_fn)(BMFace *, void *);
141 bool use_separate_all = false;
142 bool use_separate_cut = false;
143 const int separate_mode = RNA_enum_get(op->ptr, "separate_mode");
144 const float eps = RNA_float_get(op->ptr, "threshold");
145#ifdef WITH_GMP
146 const bool exact = RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT;
147#else
148 if (RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT) {
149 BKE_report(op->reports, RPT_WARNING, "Compiled without GMP, using fast solver");
150 }
151 const bool exact = false;
152#endif
153 bool use_self;
154 bool has_isect;
155
156 switch (mode) {
157 case ISECT_SEL:
158 test_fn = bm_face_isect_self;
159 use_self = true;
160 break;
161 default: /* ISECT_SEL_UNSEL */
162 test_fn = bm_face_isect_pair;
163 use_self = false;
164 break;
165 }
166
167 switch (separate_mode) {
169 use_separate_all = true;
170 break;
172 if (use_self == false) {
173 use_separate_cut = true;
174 }
175 else {
176 /* we could support this but would require more advanced logic inside 'BM_mesh_intersect'
177 * for now just separate all */
178 use_separate_all = true;
179 }
180 break;
181 default: /* ISECT_SEPARATE_NONE */
182 break;
183 }
184 const Scene *scene = CTX_data_scene(C);
185 ViewLayer *view_layer = CTX_data_view_layer(C);
186 uint isect_len = 0;
188 scene, view_layer, CTX_wm_view3d(C));
189 for (Object *obedit : objects) {
191
192 if (em->bm->totfacesel == 0) {
193 continue;
194 }
195
196 if (exact) {
197 int nshapes = use_self ? 1 : 2;
198 has_isect = BM_mesh_boolean_knife(em->bm,
199 em->looptris,
200 test_fn,
201 nullptr,
202 nshapes,
203 use_self,
204 use_separate_all,
205 false,
206 true);
207 }
208 else {
209 has_isect = BM_mesh_intersect(em->bm,
210 em->looptris,
211 test_fn,
212 nullptr,
213 use_self,
214 use_separate_all,
215 true,
216 true,
217 true,
218 true,
219 -1,
220 eps);
221 }
222
223 if (use_separate_cut) {
224 /* detach selected/un-selected faces */
227 }
228
229 edbm_intersect_select(em, static_cast<Mesh *>(obedit->data), has_isect);
230
231 if (!has_isect) {
232 isect_len++;
233 }
234 }
235
236 if (isect_len == objects.size()) {
237 BKE_report(op->reports, RPT_WARNING, "No intersections found");
238 }
239 return OPERATOR_FINISHED;
240}
241
242static void edbm_intersect_ui(bContext * /*C*/, wmOperator *op)
243{
244 uiLayout *layout = op->layout;
245 uiLayout *row;
246
247 bool use_exact = RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT;
248
249 uiLayoutSetPropSep(layout, true);
250 uiLayoutSetPropDecorate(layout, false);
251 row = &layout->row(false);
252 row->prop(op->ptr, "mode", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
253 layout->separator();
254 row = &layout->row(false);
255 row->prop(op->ptr, "separate_mode", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
256 layout->separator();
257
258 row = &layout->row(false);
259 row->prop(op->ptr, "solver", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
260 layout->separator();
261
262 if (!use_exact) {
263 layout->prop(op->ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
264 }
265}
266
268{
269 static const EnumPropertyItem isect_mode_items[] = {
270 {ISECT_SEL, "SELECT", 0, "Self Intersect", "Self intersect selected faces"},
272 "SELECT_UNSELECT",
273 0,
274 "Selected/Unselected",
275 "Intersect selected with unselected faces"},
276 {0, nullptr, 0, nullptr, nullptr},
277 };
278
279 static const EnumPropertyItem isect_separate_items[] = {
280 {ISECT_SEPARATE_ALL, "ALL", 0, "All", "Separate all geometry from intersections"},
282 "CUT",
283 0,
284 "Cut",
285 "Cut into geometry keeping each side separate (Selected/Unselected only)"},
286 {ISECT_SEPARATE_NONE, "NONE", 0, "Merge", "Merge all geometry from the intersection"},
287 {0, nullptr, 0, nullptr, nullptr},
288 };
289
290 static const EnumPropertyItem isect_intersect_solver_items[] = {
292 "FAST",
293 0,
294 "Float",
295 "Simple solver with good performance, without support for overlapping geometry"},
297 "EXACT",
298 0,
299 "Exact",
300 "Slower solver with the best results for coplanar faces"},
301 {0, nullptr, 0, nullptr, nullptr},
302 };
303
304 /* identifiers */
305 ot->name = "Intersect (Knife)";
306 ot->description = "Cut an intersection into faces";
307 ot->idname = "MESH_OT_intersect";
308
309 /* API callbacks. */
310 ot->exec = edbm_intersect_exec;
311 ot->poll = ED_operator_editmesh;
312 ot->ui = edbm_intersect_ui;
313
314 /* props */
315 RNA_def_enum(ot->srna, "mode", isect_mode_items, ISECT_SEL_UNSEL, "Source", "");
317 ot->srna, "separate_mode", isect_separate_items, ISECT_SEPARATE_CUT, "Separate Mode", "");
319 ot->srna, "threshold", 0.000001f, 0.0, 0.01, "Merge Threshold", "", 0.0, 0.001);
320 RNA_def_enum(ot->srna,
321 "solver",
322 isect_intersect_solver_items,
324 "Solver",
325 "Which Intersect solver to use");
326
327 /* flags */
329}
330
332
333/* -------------------------------------------------------------------- */
339
341{
342 const int boolean_operation = RNA_enum_get(op->ptr, "operation");
343 bool use_swap = RNA_boolean_get(op->ptr, "use_swap");
344 bool use_self = RNA_boolean_get(op->ptr, "use_self");
345#ifdef WITH_GMP
346 const bool use_exact = RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT;
347#else
348 if (RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT) {
349 BKE_report(op->reports, RPT_WARNING, "Compiled without GMP, using fast solver");
350 }
351 const bool use_exact = false;
352#endif
353 const float eps = RNA_float_get(op->ptr, "threshold");
354 int (*test_fn)(BMFace *, void *);
355 bool has_isect;
356
357 test_fn = use_swap ? bm_face_isect_pair_swap : bm_face_isect_pair;
358 const Scene *scene = CTX_data_scene(C);
359 ViewLayer *view_layer = CTX_data_view_layer(C);
360 uint isect_len = 0;
362 scene, view_layer, CTX_wm_view3d(C));
363 for (Object *obedit : objects) {
365
366 if (em->bm->totfacesel == 0) {
367 continue;
368 }
369
370 if (use_exact) {
371 has_isect = BM_mesh_boolean(
372 em->bm, em->looptris, test_fn, nullptr, 2, use_self, true, false, boolean_operation);
373 }
374 else {
375 has_isect = BM_mesh_intersect(em->bm,
376 em->looptris,
377 test_fn,
378 nullptr,
379 false,
380 false,
381 true,
382 true,
383 false,
384 true,
385 boolean_operation,
386 eps);
387 }
388
389 edbm_intersect_select(em, static_cast<Mesh *>(obedit->data), has_isect);
390
391 if (!has_isect) {
392 isect_len++;
393 }
394 }
395
396 if (isect_len == objects.size()) {
397 BKE_report(op->reports, RPT_WARNING, "No intersections found");
398 }
399 return OPERATOR_FINISHED;
400}
401
403{
404 uiLayout *layout = op->layout;
405 uiLayout *row;
406
407 bool use_exact = RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT;
408
409 uiLayoutSetPropSep(layout, true);
410 uiLayoutSetPropDecorate(layout, false);
411
412 row = &layout->row(false);
413 row->prop(op->ptr, "operation", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
414 layout->separator();
415
416 row = &layout->row(false);
417 row->prop(op->ptr, "solver", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
418 layout->separator();
419
420 layout->prop(op->ptr, "use_swap", UI_ITEM_NONE, std::nullopt, ICON_NONE);
421 layout->prop(op->ptr, "use_self", UI_ITEM_NONE, std::nullopt, ICON_NONE);
422 if (!use_exact) {
423 layout->prop(op->ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
424 }
425}
426
428{
429 static const EnumPropertyItem isect_boolean_operation_items[] = {
430 {BMESH_ISECT_BOOLEAN_ISECT, "INTERSECT", 0, "Intersect", ""},
431 {BMESH_ISECT_BOOLEAN_UNION, "UNION", 0, "Union", ""},
432 {BMESH_ISECT_BOOLEAN_DIFFERENCE, "DIFFERENCE", 0, "Difference", ""},
433 {0, nullptr, 0, nullptr, nullptr},
434 };
435
436 static const EnumPropertyItem isect_boolean_solver_items[] = {
437 {ISECT_SOLVER_FAST, "FAST", 0, "Fast", "Faster solver, some limitations"},
438 {ISECT_SOLVER_EXACT, "EXACT", 0, "Exact", "Exact solver, slower, handles more cases"},
439 {0, nullptr, 0, nullptr, nullptr},
440 };
441
442 /* identifiers */
443 ot->name = "Intersect (Boolean)";
444 ot->description = "Cut solid geometry from selected to unselected";
445 ot->idname = "MESH_OT_intersect_boolean";
446
447 /* API callbacks. */
449 ot->poll = ED_operator_editmesh;
451
452 /* props */
453 RNA_def_enum(ot->srna,
454 "operation",
455 isect_boolean_operation_items,
457 "Boolean Operation",
458 "Which boolean operation to apply");
459 RNA_def_boolean(ot->srna,
460 "use_swap",
461 false,
462 "Swap",
463 "Use with difference intersection to swap which side is kept");
465 ot->srna, "use_self", false, "Self Intersection", "Do self-union or self-intersection");
467 ot->srna, "threshold", 0.000001f, 0.0, 0.01, "Merge Threshold", "", 0.0, 0.001);
468 RNA_def_enum(ot->srna,
469 "solver",
470 isect_boolean_solver_items,
472 "Solver",
473 "Which Boolean solver to use");
474
475 /* flags */
477}
478
480
481/* -------------------------------------------------------------------- */
484
486 BMFace *f,
487 const char hflag, /* reusable memory buffer */
488 BLI_Buffer *edge_net_temp_buf)
489{
490 const int f_index = BM_elem_index_get(f);
491
492 BMLoop *l_iter;
493 BMLoop *l_first;
494 BMVert *v;
495
496 /* likely this will stay very small
497 * all verts pushed into this stack _must_ have their previous edges set! */
498 BLI_SMALLSTACK_DECLARE(vert_stack, BMVert *);
499 BLI_SMALLSTACK_DECLARE(vert_stack_next, BMVert *);
500
501 BLI_assert(edge_net_temp_buf->count == 0);
502
503 /* collect all edges */
504 l_iter = l_first = BM_FACE_FIRST_LOOP(f);
505 do {
506 BMIter iter;
507 BMEdge *e;
508
509 BM_ITER_ELEM (e, &iter, l_iter->v, BM_EDGES_OF_VERT) {
510 if (BM_elem_flag_test(e, hflag) && (BM_elem_index_get(e) == f_index)) {
511 v = BM_edge_other_vert(e, l_iter->v);
512 v->e = e;
513
514 BLI_SMALLSTACK_PUSH(vert_stack, v);
515 BLI_buffer_append(edge_net_temp_buf, BMEdge *, e);
516 }
517 }
518 } while ((l_iter = l_iter->next) != l_first);
519
520 /* now assign all */
521 /* pop free values into the next stack */
522 while ((v = static_cast<BMVert *>(BLI_SMALLSTACK_POP_EX(vert_stack, vert_stack_next)))) {
523 BMIter eiter;
524 BMEdge *e_next;
525
526 BM_ITER_ELEM (e_next, &eiter, v, BM_EDGES_OF_VERT) {
527 if (BM_elem_flag_test(e_next, hflag) && (BM_elem_index_get(e_next) == -1)) {
528 BMVert *v_next;
529 v_next = BM_edge_other_vert(e_next, v);
530 BM_elem_index_set(e_next, f_index);
531 BLI_SMALLSTACK_PUSH(vert_stack_next, v_next);
532 BLI_buffer_append(edge_net_temp_buf, BMEdge *, e_next);
533 }
534 }
535
536 if (BLI_SMALLSTACK_IS_EMPTY(vert_stack)) {
537 BLI_SMALLSTACK_SWAP(vert_stack, vert_stack_next);
538 }
539 }
540
541 Vector<BMFace *> face_arr;
543 bm, f, static_cast<BMEdge **>(edge_net_temp_buf->data), edge_net_temp_buf->count, &face_arr);
544
545 BLI_buffer_clear(edge_net_temp_buf);
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 BLI_buffer_declare_static(BMEdge **, edge_net_temp_buf, 0, 128);
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 BLI_buffer_free(&edge_net_temp_buf);
939 }
940
941#ifdef USE_NET_ISLAND_CONNECT
942 /* before overwriting edge index values, collect edges left untouched */
943 BLI_Stack *edges_loose = BLI_stack_new(sizeof(BMEdge *), __func__);
944 BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
946 BLI_stack_push(edges_loose, &e);
947 }
948 }
949#endif
950
952 params.calc_looptris = true;
953 params.calc_normals = true;
954 params.is_destructive = true;
955 EDBM_update(static_cast<Mesh *>(obedit->data), &params);
956
957#ifdef USE_NET_ISLAND_CONNECT
958 /* we may have remaining isolated regions remaining,
959 * these will need to have connecting edges created */
960 if (!BLI_stack_is_empty(edges_loose)) {
961 GHash *face_edge_map = BLI_ghash_ptr_new(__func__);
962
964
966
967 {
968 BMBVHTree *bmbvh = BKE_bmbvh_new(bm, em->looptris, BMBVH_RESPECT_SELECT, nullptr, false);
969
970 BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
971 BM_elem_index_set(e, -1); /* set_dirty */
972 }
973
974 while (!BLI_stack_is_empty(edges_loose)) {
975 BLI_stack_pop(edges_loose, &e);
976 float e_center[3];
977 mid_v3_v3v3(e_center, e->v1->co, e->v2->co);
978
979 BMFace *f = BKE_bmbvh_find_face_closest(bmbvh, e_center, FLT_MAX);
980 if (f) {
981 ghash_insert_face_edge_link(face_edge_map, f, e, mem_arena);
982 BM_elem_index_set(e, BM_elem_index_get(f)); /* set_dirty */
983 }
984 }
985
986 BKE_bmbvh_free(bmbvh);
987 }
988
989 bm->elem_index_dirty |= BM_EDGE;
990
992
993 /* detect edges chains that span faces
994 * and splice vertices into the closest edges */
995 {
996 GHashIterator gh_iter;
997
998 GHASH_ITER (gh_iter, face_edge_map) {
999 BMFace *f = static_cast<BMFace *>(BLI_ghashIterator_getKey(&gh_iter));
1000 LinkBase *e_ls_base = static_cast<LinkBase *>(BLI_ghashIterator_getValue(&gh_iter));
1001 LinkNode *e_link = e_ls_base->list;
1002
1003 do {
1004 e = static_cast<BMEdge *>(e_link->link);
1005
1006 for (int j = 0; j < 2; j++) {
1007 BMVert *v_pivot = (&e->v1)[j];
1008 /* checking that \a v_pivot isn't in the face prevents attempting
1009 * to splice the same vertex into an edge from multiple faces */
1010 if (!BM_vert_in_face(v_pivot, f)) {
1011 float v_pivot_co[3];
1012 float v_pivot_fac;
1014 e, f, v_pivot, bm->ftable, bm->totface, v_pivot_co, &v_pivot_fac);
1015
1016 if (e_split) {
1017 /* for degenerate cases this vertex may be in one
1018 * of this edges radial faces */
1019 if (!bm_vert_in_faces_radial(v_pivot, e_split, f)) {
1020 BMEdge *e_new;
1021 BMVert *v_new = BM_edge_split(bm, e_split, e_split->v1, &e_new, v_pivot_fac);
1022 if (v_new) {
1023 /* we _know_ these don't share an edge */
1024 BM_vert_splice(bm, v_pivot, v_new);
1025 BM_elem_index_set(e_new, BM_elem_index_get(e_split));
1026 }
1027 }
1028 }
1029 }
1030 }
1031
1032 } while ((e_link = e_link->next));
1033 }
1034 }
1035
1036 {
1037 MemArena *mem_arena_edgenet = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
1038
1039 GHashIterator gh_iter;
1040
1041 GHASH_ITER (gh_iter, face_edge_map) {
1042 BMFace *f = static_cast<BMFace *>(BLI_ghashIterator_getKey(&gh_iter));
1043 LinkBase *e_ls_base = static_cast<LinkBase *>(BLI_ghashIterator_getValue(&gh_iter));
1044
1046 bm, f, e_ls_base->list, e_ls_base->list_len, mem_arena_edgenet);
1047
1048 BLI_memarena_clear(mem_arena_edgenet);
1049 }
1050
1051 BLI_memarena_free(mem_arena_edgenet);
1052 }
1053
1055
1056 BLI_ghash_free(face_edge_map, nullptr, nullptr);
1057
1059 params.calc_looptris = true;
1060 params.calc_normals = true;
1061 params.is_destructive = true;
1062 EDBM_update(static_cast<Mesh *>(obedit->data), &params);
1063 }
1064
1065 BLI_stack_free(edges_loose);
1066#endif /* USE_NET_ISLAND_CONNECT */
1067 }
1068 return OPERATOR_FINISHED;
1069}
1070
1072{
1073 /* identifiers */
1074 ot->name = "Weld Edges into Faces";
1075 ot->description = "Weld loose edges into faces (splitting them into new faces)";
1076 ot->idname = "MESH_OT_face_split_by_edges";
1077
1078 /* API callbacks. */
1080 ot->poll = ED_operator_editmesh;
1081
1082 /* flags */
1083 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1084}
1085
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)
@ BMBVH_RESPECT_SELECT
void BKE_bmbvh_free(BMBVHTree *tree)
blender::Vector< Object * > BKE_view_layer_array_from_objects_in_edit_mode_unique_data(const Scene *scene, ViewLayer *view_layer, const View3D *v3d)
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define BLI_buffer_append(buffer_, type_, val_)
Definition BLI_buffer.h:50
#define BLI_buffer_declare_static(type_, name_, flag_, static_count_)
Definition BLI_buffer.h:25
#define BLI_buffer_clear(buffer_)
Definition BLI_buffer.h:54
#define BLI_buffer_free(name_)
Definition BLI_buffer.h:94
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)
void EDBM_selectmode_flush(BMEditMesh *em)
bool ED_operator_editmesh(bContext *C)
#define C
Definition RandGen.cpp:29
@ UI_ITEM_R_EXPAND
void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
#define UI_ITEM_NONE
void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
@ 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)
@ BM_ELEM_HIDDEN
@ BM_ELEM_SELECT
@ BM_ELEM_INTERNAL_TAG
@ BM_ELEM_TAG
#define BM_DISK_EDGE_NEXT(e, v)
#define BM_FACE_FIRST_LOOP(p)
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
#define fabsf(x)
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 void bm_face_split_by_edges(BMesh *bm, BMFace *f, const char hflag, BLI_Buffer *edge_net_temp_buf)
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 *)
static void edbm_intersect_ui(bContext *, wmOperator *op)
@ ISECT_SEPARATE_ALL
@ ISECT_SEPARATE_NONE
@ ISECT_SEPARATE_CUT
@ ISECT_SOLVER_EXACT
@ ISECT_SOLVER_FAST
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)
@ ISECT_SEL_UNSEL
static wmOperatorStatus edbm_face_split_by_edges_exec(bContext *C, wmOperator *)
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
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
void * data
Definition BLI_buffer.h:14
size_t count
Definition BLI_buffer.h:16
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 separator(float factor=1.0f, LayoutSeparatorType type=LayoutSeparatorType::Auto)
uiLayout & row(bool align)
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:4226