Blender V5.0
uvedit_rip.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 <cmath>
10#include <cstdlib>
11#include <cstring>
12
13#include "MEM_guardedalloc.h"
14
15#include "BLI_ghash.h"
16#include "BLI_linklist_stack.h"
17#include "BLI_math_vector.h"
18#include "BLI_utildefines.h"
19
20#include "DNA_mesh_types.h"
21#include "DNA_object_types.h"
22#include "DNA_scene_types.h"
23#include "DNA_space_types.h"
24
25#include "BKE_context.hh"
26#include "BKE_customdata.hh"
27#include "BKE_editmesh.hh"
28#include "BKE_layer.hh"
29#include "BKE_mesh_types.hh"
30#include "BKE_report.hh"
31
32#include "DEG_depsgraph.hh"
33
34#include "ED_mesh.hh"
35#include "ED_screen.hh"
36#include "ED_transform.hh"
37#include "ED_uvedit.hh"
38
39#include "RNA_access.hh"
40#include "RNA_define.hh"
41
42#include "WM_api.hh"
43#include "WM_types.hh"
44
45#include "UI_view2d.hh"
46
47#include "uvedit_intern.hh"
48
49using blender::Vector;
50
51/* -------------------------------------------------------------------- */
54
87
89BLI_STATIC_ASSERT(sizeof(ULData) <= sizeof(int), "");
90
92{
93 return (ULData *)&l->head.index;
94}
95
97
98/* -------------------------------------------------------------------- */
101
103 const int cd_loop_uv_offset)
104{
105 BMLoop *l_other = nullptr;
106 BMLoop *l_iter = l_src->radial_next;
107 if (l_iter != l_src) {
108 do {
109 if (BM_elem_flag_test(l_iter->f, BM_ELEM_TAG) && UL(l_iter)->is_select_edge &&
110 BM_loop_uv_share_edge_check(l_src, l_iter, cd_loop_uv_offset))
111 {
112 /* Check UVs are contiguous. */
113 if (l_other == nullptr) {
114 l_other = l_iter;
115 }
116 else {
117 /* Only use when there is a single alternative. */
118 l_other = nullptr;
119 break;
120 }
121 }
122 } while ((l_iter = l_iter->radial_next) != l_src);
123 }
124 return l_other;
125}
126
128 BMVert *v_src,
129 const int cd_loop_uv_offset)
130{
131 BLI_assert(BM_vert_in_edge(l_src->e, v_src));
132 BMLoop *l_other = nullptr;
133 BMLoop *l_iter = l_src->radial_next;
134 if (l_iter != l_src) {
135 do {
136 if (BM_elem_flag_test(l_iter->f, BM_ELEM_TAG) &&
137 BM_loop_uv_share_edge_check(l_src, l_iter, cd_loop_uv_offset))
138 {
139 /* Check UVs are contiguous. */
140 if (l_other == nullptr) {
141 l_other = l_iter;
142 }
143 else {
144 /* Only use when there is a single alternative. */
145 l_other = nullptr;
146 break;
147 }
148 }
149 } while ((l_iter = l_iter->radial_next) != l_src);
150 }
151 if (l_other != nullptr) {
152 if (l_other->v == v_src) {
153 /* do nothing. */
154 }
155 else if (l_other->next->v == v_src) {
156 l_other = l_other->next;
157 }
158 else if (l_other->prev->v == v_src) {
159 l_other = l_other->prev;
160 }
161 else {
163 }
164 }
165 return l_other;
166}
167
171static BMLoop *bm_vert_step_fan_loop_uv(BMLoop *l, BMEdge **e_step, const int cd_loop_uv_offset)
172{
173 BMEdge *e_prev = *e_step;
174 BMLoop *l_next;
175 if (l->e == e_prev) {
176 l_next = l->prev;
177 }
178 else if (l->prev->e == e_prev) {
179 l_next = l;
180 }
181 else {
183 return nullptr;
184 }
185
186 *e_step = l_next->e;
187
188 return bm_loop_find_other_fan_loop_with_visible_face(l_next, l->v, cd_loop_uv_offset);
189}
190
191static void bm_loop_uv_select_single_vert_validate(BMLoop *l_init, const int cd_loop_uv_offset)
192{
193 const float *luv_init = BM_ELEM_CD_GET_FLOAT_P(l_init, cd_loop_uv_offset);
194 BMIter liter;
195 BMLoop *l;
196 bool is_single_vert = true;
197 BM_ITER_ELEM (l, &liter, l_init->v, BM_LOOPS_OF_VERT) {
198 const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
199 if (equals_v2v2(luv_init, luv)) {
200 if (UL(l->prev)->is_select_edge || UL(l)->is_select_edge) {
201 is_single_vert = false;
202 break;
203 }
204 }
205 }
206 if (is_single_vert == false) {
207 BM_ITER_ELEM (l, &liter, l_init->v, BM_LOOPS_OF_VERT) {
208 if (UL(l)->is_select_vert_single) {
209 const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
210 if (equals_v2v2(luv_init, luv)) {
211 UL(l)->is_select_vert_single = false;
212 }
213 }
214 }
215 }
216}
217
225 const float dir[2],
226 const float aspect_y,
227 const int cd_loop_uv_offset,
228 float *r_corner_angle,
229 float *r_edge_angle,
230 int *r_edge_index)
231{
232 /* Calculate 3 directions, return the shortest angle. */
233 blender::float2 dir_test[3];
234 const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
235 const float *luv_prev = BM_ELEM_CD_GET_FLOAT_P(l->prev, cd_loop_uv_offset);
236 const float *luv_next = BM_ELEM_CD_GET_FLOAT_P(l->next, cd_loop_uv_offset);
237
238 sub_v2_v2v2(dir_test[0], luv, luv_prev);
239 sub_v2_v2v2(dir_test[2], luv, luv_next);
240 dir_test[0][1] /= aspect_y;
241 dir_test[2][1] /= aspect_y;
242
243 normalize_v2(dir_test[0]);
244 normalize_v2(dir_test[2]);
245
246 /* Calculate the orthogonal line (same as negating one, then adding). */
247 sub_v2_v2v2(dir_test[1], dir_test[0], dir_test[2]);
248 normalize_v2(dir_test[1]);
249
250 /* Rotate 90 degrees. */
251 std::swap(dir_test[1][0], dir_test[1][1]);
252 dir_test[1][1] *= -1.0f;
253
254 if (BM_face_uv_calc_cross(l->f, cd_loop_uv_offset) > 0.0f) {
255 dir_test[1] *= -1.0f;
256 }
257
258 const float angles[3] = {
259 angle_v2v2(dir, dir_test[0]),
260 angle_v2v2(dir, dir_test[1]),
261 angle_v2v2(dir, dir_test[2]),
262 };
263
264 /* Set the corner values. */
265 *r_corner_angle = angles[1];
266
267 /* Set the edge values. */
268 if (angles[0] < angles[2]) {
269 *r_edge_angle = angles[0];
270 *r_edge_index = -1;
271 }
272 else {
273 *r_edge_angle = angles[2];
274 *r_edge_index = 1;
275 }
276}
277
279
280/* -------------------------------------------------------------------- */
283
287};
288
304 const float co[2],
305 const float aspect_y,
306 const int cd_loop_uv_offset)
307{
308 UVRipSingle *rip = MEM_callocN<UVRipSingle>(__func__);
309 const float *co_center = BM_ELEM_CD_GET_FLOAT_P(l_init_orig, cd_loop_uv_offset);
310 rip->loops = BLI_gset_ptr_new(__func__);
311
312 /* Track the closest loop, start walking from this so in the event we have multiple
313 * disconnected fans, we can rip away loops connected to this one. */
314 BMLoop *l_init = nullptr;
315 BMLoop *l_init_edge = nullptr;
316 float corner_angle_best = FLT_MAX;
317 float edge_angle_best = FLT_MAX;
318 int edge_index_best = 0; /* -1 or +1 (never center). */
319
320 /* Calculate the direction from the cursor with aspect correction. */
321 float dir_co[2];
322 sub_v2_v2v2(dir_co, co_center, co);
323 dir_co[1] /= aspect_y;
324 if (UNLIKELY(normalize_v2(dir_co) == 0.0)) {
325 dir_co[1] = 1.0f;
326 }
327
328 int uv_fan_count_all = 0;
329 {
330 BMIter liter;
331 BMLoop *l;
332 BM_ITER_ELEM (l, &liter, l_init_orig->v, BM_LOOPS_OF_VERT) {
334 const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
335 if (equals_v2v2(co_center, luv)) {
336 uv_fan_count_all += 1;
337 /* Clear at the same time. */
338 UL(l)->is_select_vert_single = true;
339 UL(l)->side = 0;
340 BLI_gset_add(rip->loops, l);
341
342 /* Update `l_init_close` */
343 float corner_angle_test;
344 float edge_angle_test;
345 int edge_index_test;
347 dir_co,
348 aspect_y,
349 cd_loop_uv_offset,
350 &corner_angle_test,
351 &edge_angle_test,
352 &edge_index_test);
353 if ((corner_angle_best == FLT_MAX) || (corner_angle_test < corner_angle_best)) {
354 corner_angle_best = corner_angle_test;
355 l_init = l;
356 }
357
358 /* Trick so we don't consider concave corners further away than they should be. */
359 edge_angle_test = min_ff(corner_angle_test, edge_angle_test);
360
361 if ((edge_angle_best == FLT_MAX) || (edge_angle_test < edge_angle_best)) {
362 edge_angle_best = edge_angle_test;
363 edge_index_best = edge_index_test;
364 l_init_edge = l;
365 }
366 }
367 }
368 }
369 }
370
371 /* Walk around the `l_init` in both directions of the UV fan. */
372 int uv_fan_count_contiguous = 1;
373 UL(l_init)->side = 1;
374 for (int i = 0; i < 2; i += 1) {
375 BMEdge *e_prev = i ? l_init->e : l_init->prev->e;
376 BMLoop *l_iter = l_init;
377 while (((l_iter = bm_vert_step_fan_loop_uv(l_iter, &e_prev, cd_loop_uv_offset)) != l_init) &&
378 (l_iter != nullptr) && (UL(l_iter)->side == 0))
379 {
380 uv_fan_count_contiguous += 1;
381 /* Keep. */
382 UL(l_iter)->side = 1;
383 }
384 /* May be useful to know if the fan is closed, currently it's not needed. */
385#if 0
386 if (l_iter == l_init) {
387 is_closed = true;
388 }
389#endif
390 }
391
392 if (uv_fan_count_contiguous != uv_fan_count_all) {
393 /* Simply rip off the current fan, all tagging is done. */
394 }
395 else {
396 GSetIterator gs_iter;
397 GSET_ITER (gs_iter, rip->loops) {
398 BMLoop *l = static_cast<BMLoop *>(BLI_gsetIterator_getKey(&gs_iter));
399 UL(l)->side = 0;
400 }
401
402 if (uv_fan_count_contiguous <= 2) {
403 /* Simple case, rip away the closest loop. */
404 UL(l_init)->side = 1;
405 }
406 else {
407 /* Rip away from the closest edge. */
408 BMLoop *l_radial_init = (edge_index_best == -1) ? l_init_edge->prev : l_init_edge;
409 BMLoop *l_radial_iter = l_radial_init;
410 do {
411 if (BM_loop_uv_share_edge_check(l_radial_init, l_radial_iter, cd_loop_uv_offset)) {
412 BMLoop *l = (l_radial_iter->v == l_init->v) ? l_radial_iter : l_radial_iter->next;
413 BLI_assert(l->v == l_init->v);
414 /* Keep. */
415 UL(l)->side = 1;
416 }
417 } while ((l_radial_iter = l_radial_iter->radial_next) != l_radial_init);
418 }
419 }
420
421 return rip;
422}
423
425{
426 BLI_gset_free(rip->loops, nullptr);
427 MEM_freeN(rip);
428}
429
431
432/* -------------------------------------------------------------------- */
435
439};
440
442{
443 ULData *ul = UL(l);
445 BLI_assert(ul->in_rip_pairs == false);
446 ul->in_rip_pairs = true;
447 BLI_gset_add(rip->loops, l);
448}
449
451{
452 ULData *ul = UL(l);
454 BLI_assert(ul->in_rip_pairs == true);
455 ul->in_rip_pairs = false;
456 BLI_gset_remove(rip->loops, l, nullptr);
457}
458
464 uint side,
465 const float aspect_y,
466 const int cd_loop_uv_offset)
467{
468 BMIter liter;
469 const float *luv_init = BM_ELEM_CD_GET_FLOAT_P(l_init, cd_loop_uv_offset);
470 float angle_of_side = 0.0f;
471 BMLoop *l;
472 BM_ITER_ELEM (l, &liter, l_init->v, BM_LOOPS_OF_VERT) {
473 if (UL(l)->in_rip_pairs) {
474 if (UL(l)->side == side) {
475 const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
476 if (equals_v2v2(luv_init, luv)) {
477 const float *luv_prev = BM_ELEM_CD_GET_FLOAT_P(l->prev, cd_loop_uv_offset);
478 const float *luv_next = BM_ELEM_CD_GET_FLOAT_P(l->next, cd_loop_uv_offset);
479 float dir_prev[2], dir_next[2];
480 sub_v2_v2v2(dir_prev, luv_prev, luv);
481 sub_v2_v2v2(dir_next, luv_next, luv);
482 dir_prev[1] /= aspect_y;
483 dir_next[1] /= aspect_y;
484 const float luv_angle = angle_v2v2(dir_prev, dir_next);
485 if (LIKELY(isfinite(luv_angle))) {
486 angle_of_side += luv_angle;
487 }
488 }
489 }
490 }
491 }
492 return angle_of_side;
493}
494
495static int uv_rip_pairs_loop_count_on_side(BMLoop *l_init, uint side, const int cd_loop_uv_offset)
496{
497 const float *luv_init = BM_ELEM_CD_GET_FLOAT_P(l_init, cd_loop_uv_offset);
498 int count = 0;
499 BMIter liter;
500 BMLoop *l;
501 BM_ITER_ELEM (l, &liter, l_init->v, BM_LOOPS_OF_VERT) {
502 if (UL(l)->in_rip_pairs) {
503 if (UL(l)->side == side) {
504 const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
505 if (equals_v2v2(luv_init, luv)) {
506 count += 1;
507 }
508 }
509 }
510 }
511 return count;
512}
513
515 BMLoop *l_target,
516 const float aspect_y,
517 const int cd_loop_uv_offset)
518{
519 const int side_a = UL(l_switch)->side;
520 const int side_b = UL(l_target)->side;
521
522 BLI_assert(UL(l_switch)->side != UL(l_target)->side);
523
524 /* First, check if this is a simple grid topology,
525 * in that case always choose the adjacent edge. */
526 const int count_a = uv_rip_pairs_loop_count_on_side(l_switch, side_a, cd_loop_uv_offset);
527 const int count_b = uv_rip_pairs_loop_count_on_side(l_target, side_b, cd_loop_uv_offset);
528 if (count_a + count_b == 4) {
529 return count_a > count_b;
530 }
531
532 const float angle_a_before = uv_rip_pairs_calc_uv_angle(
533 l_switch, side_a, aspect_y, cd_loop_uv_offset);
534 const float angle_b_before = uv_rip_pairs_calc_uv_angle(
535 l_target, side_b, aspect_y, cd_loop_uv_offset);
536
537 UL(l_switch)->side = side_b;
538
539 const float angle_a_after = uv_rip_pairs_calc_uv_angle(
540 l_switch, side_a, aspect_y, cd_loop_uv_offset);
541 const float angle_b_after = uv_rip_pairs_calc_uv_angle(
542 l_target, side_b, aspect_y, cd_loop_uv_offset);
543
544 UL(l_switch)->side = side_a;
545
546 return fabsf(angle_a_before - angle_b_before) > fabsf(angle_a_after - angle_b_after);
547}
548
558 const float aspect_y,
559 const int cd_loop_uv_offset)
560{
561 UVRipPairs *rip = MEM_callocN<UVRipPairs>(__func__);
562 rip->loops = BLI_gset_ptr_new(__func__);
563
564 /* We can rely on this stack being small, as we're walking down two sides of an edge loop,
565 * so the stack won't be much larger than the total number of fans at any one vertex. */
567
568 /* Needed for cases when we walk onto loops which already have a side assigned,
569 * in this case we need to pick a better side (see #uv_rip_pairs_loop_change_sides_test)
570 * and put the loop back in the stack,
571 * which is needed in the case adjacent loops should also switch sides. */
572#define UV_SET_SIDE_AND_REMOVE_FROM_RAIL(loop, side_value) \
573 { \
574 BLI_assert(UL(loop)->side_was_swapped == false); \
575 BLI_assert(UL(loop)->side != side_value); \
576 if (!UL(loop)->in_stack) { \
577 BLI_SMALLSTACK_PUSH(stack, loop); \
578 UL(loop)->in_stack = true; \
579 } \
580 if (UL(loop)->in_rip_pairs) { \
581 uv_rip_pairs_remove(rip, loop); \
582 } \
583 UL(loop)->side = side_value; \
584 UL(loop)->side_was_swapped = true; \
585 }
586
587 /* Initialize the stack. */
588 BLI_SMALLSTACK_PUSH(stack, l_init);
589 UL(l_init)->in_stack = true;
590
591 BMLoop *l_step;
592 while ((l_step = static_cast<BMLoop *>(BLI_SMALLSTACK_POP(stack)))) {
593 int side = UL(l_step)->side;
594 UL(l_step)->in_stack = false;
595
596 /* Note that we could add all loops into the rip-pairs when adding into the stack,
597 * however this complicates removal, so add into the rip-pairs when popping from the stack. */
598 uv_rip_pairs_add(rip, l_step);
599
600 /* Add to the other side if it exists. */
601 if (UL(l_step)->is_select_edge) {
603 cd_loop_uv_offset);
604 if (l_other != nullptr) {
605 if (!UL(l_other)->in_rip_pairs && !UL(l_other)->in_stack) {
606 BLI_SMALLSTACK_PUSH(stack, l_other);
607 UL(l_other)->in_stack = true;
608 UL(l_other)->side = !side;
609 }
610 else {
611 if (UL(l_other)->side == side) {
612 if (UL(l_other)->side_was_swapped == false) {
613 UV_SET_SIDE_AND_REMOVE_FROM_RAIL(l_other, !side);
614 }
615 }
616 }
617 }
618
619 /* Add the next loop along the edge on the same side. */
620 l_other = l_step->next;
621 if (!UL(l_other)->in_rip_pairs && !UL(l_other)->in_stack) {
622 BLI_SMALLSTACK_PUSH(stack, l_other);
623 UL(l_other)->in_stack = true;
624 UL(l_other)->side = side;
625 }
626 else {
627 if (UL(l_other)->side != side) {
628 if ((UL(l_other)->side_was_swapped == false) &&
629 uv_rip_pairs_loop_change_sides_test(l_other, l_step, aspect_y, cd_loop_uv_offset))
630 {
632 }
633 }
634 }
635 }
636
637 /* Walk over the fan of loops, starting from `l_step` in both directions. */
638 for (int i = 0; i < 2; i++) {
639 BMLoop *l_radial_first = i ? l_step : l_step->prev;
640 if (l_radial_first != l_radial_first->radial_next) {
641 BMEdge *e_radial = l_radial_first->e;
642 BMLoop *l_radial_iter = l_radial_first->radial_next;
643 do {
644 /* Not a boundary and visible. */
645 if (!UL(l_radial_iter)->is_select_edge &&
646 BM_elem_flag_test(l_radial_iter->f, BM_ELEM_TAG))
647 {
648 BMLoop *l_other = (l_radial_iter->v == l_step->v) ? l_radial_iter :
649 l_radial_iter->next;
650 BLI_assert(l_other->v == l_step->v);
651 if (BM_edge_uv_share_vert_check(e_radial, l_other, l_step, cd_loop_uv_offset)) {
652 if (!UL(l_other)->in_rip_pairs && !UL(l_other)->in_stack) {
653 BLI_SMALLSTACK_PUSH(stack, l_other);
654 UL(l_other)->in_stack = true;
655 UL(l_other)->side = side;
656 }
657 else {
658 if (UL(l_other)->side != side) {
659 if ((UL(l_other)->side_was_swapped == false) &&
661 l_other, l_step, aspect_y, cd_loop_uv_offset))
662 {
664 }
665 }
666 }
667 }
668 }
669 } while ((l_radial_iter = l_radial_iter->radial_next) != l_radial_first);
670 }
671 }
672 }
673
674#undef UV_SET_SIDE_AND_REMOVE_FROM_RAIL
675
676 return rip;
677}
678
680{
681 BLI_gset_free(rip->loops, nullptr);
682 MEM_freeN(rip);
683}
684
689 const int cd_loop_uv_offset,
690 float r_center[2],
691 float r_dir_side[2][2])
692{
693 zero_v2(r_center);
694 int center_total = 0;
695 int side_total[2] = {0, 0};
696
697 for (int i = 0; i < 2; i++) {
698 zero_v2(r_dir_side[i]);
699 }
700 GSetIterator gs_iter;
701 GSET_ITER (gs_iter, rip->loops) {
702 BMLoop *l = static_cast<BMLoop *>(BLI_gsetIterator_getKey(&gs_iter));
703 int side = UL(l)->side;
704 const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
705 add_v2_v2(r_center, luv);
706
707 float dir[2];
708 if (!UL(l)->is_select_edge) {
709 const float *luv_next = BM_ELEM_CD_GET_FLOAT_P(l->next, cd_loop_uv_offset);
710 sub_v2_v2v2(dir, luv_next, luv);
711 add_v2_v2(r_dir_side[side], dir);
712 }
713 if (!UL(l->prev)->is_select_edge) {
714 const float *luv_prev = BM_ELEM_CD_GET_FLOAT_P(l->prev, cd_loop_uv_offset);
715 sub_v2_v2v2(dir, luv_prev, luv);
716 add_v2_v2(r_dir_side[side], dir);
717 }
718 side_total[side] += 1;
719 }
720 center_total += BLI_gset_len(rip->loops);
721
722 for (int i = 0; i < 2; i++) {
723 normalize_v2(r_dir_side[i]);
724 }
725 mul_v2_fl(r_center, 1.0f / center_total);
726
727 /* If only a single side is selected, don't handle this rip-pairs. */
728 return side_total[0] && side_total[1];
729}
730
732
733/* -------------------------------------------------------------------- */
736
740static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const float aspect_y)
741{
742 const ToolSettings *ts = scene->toolsettings;
743
745 BMesh *bm = em->bm;
746
747 if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
749 BLI_assert(bm->uv_select_sync_valid);
750 }
751 else {
753 }
754
755 const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
756
757 BMFace *efa;
758 BMIter iter, liter;
759 BMLoop *l;
760
761 const ULData ul_clear = {0};
762
763 bool changed = false;
764
765 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
767 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
768 ULData *ul = UL(l);
769 *ul = ul_clear;
770 }
771 }
772 bm->elem_index_dirty |= BM_LOOP;
773
774 bool is_select_all_any = false;
775 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
776 if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
777 bool is_all = true;
778 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
779 if (uvedit_loop_vert_select_get(ts, bm, l)) {
780 if (uvedit_loop_edge_select_get(ts, bm, l)) {
781 UL(l)->is_select_edge = true;
782 }
783 else if (!uvedit_loop_edge_select_get(ts, bm, l->prev)) {
784 /* #bm_loop_uv_select_single_vert_validate validates below. */
785 UL(l)->is_select_vert_single = true;
786 is_all = false;
787 }
788 else {
789 /* Cases where all vertices of a face are selected but not all edges are selected. */
790 is_all = false;
791 }
792 }
793 else {
794 is_all = false;
795 }
796 }
797 if (is_all) {
798 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
799 UL(l)->is_select_all = true;
800 }
801 is_select_all_any = true;
802 }
803 }
804 }
805
806 /* Remove #ULData.is_select_vert_single when connected to selected edges. */
807 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
808 if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
809 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
810 if (UL(l)->is_select_vert_single) {
812 }
813 }
814 }
815 }
816
817 /* Special case: if we have selected faces, isolate them.
818 * This isn't a rip, however it's useful for users as a quick way
819 * to detach the selection.
820 *
821 * We could also extract an edge loop from the boundary
822 * however in practice it's not that useful, see #78751. */
823 if (is_select_all_any) {
824 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
825 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
826 if (!UL(l)->is_select_all) {
827 if (uvedit_loop_vert_select_get(ts, bm, l)) {
828 uvedit_loop_vert_select_set(ts, bm, l, false);
829 changed = true;
830 }
831 if (uvedit_loop_edge_select_get(ts, bm, l)) {
832 uvedit_loop_edge_select_set(ts, bm, l, false);
833 changed = true;
834 }
835 }
836 }
837 }
838 return changed;
839 }
840
841 /* Extract loop pairs or single loops. */
842 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
843 if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
844 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
845 if (UL(l)->is_select_edge) {
846 if (!UL(l)->in_rip_pairs) {
847 UVRipPairs *rip = uv_rip_pairs_from_loop(l, aspect_y, offsets.uv);
848 float center[2];
849 float dir_cursor[2];
850 float dir_side[2][2];
851 int side_from_cursor = -1;
852 if (uv_rip_pairs_calc_center_and_direction(rip, offsets.uv, center, dir_side)) {
853 for (int i = 0; i < 2; i++) {
854 sub_v2_v2v2(dir_cursor, center, co);
855 normalize_v2(dir_cursor);
856 }
857 side_from_cursor = (dot_v2v2(dir_side[0], dir_cursor) -
858 dot_v2v2(dir_side[1], dir_cursor)) < 0.0f;
859 }
860 GSetIterator gs_iter;
861 GSET_ITER (gs_iter, rip->loops) {
862 BMLoop *l_iter = static_cast<BMLoop *>(BLI_gsetIterator_getKey(&gs_iter));
863 ULData *ul = UL(l_iter);
864 if (ul->side == side_from_cursor) {
865 uvedit_uv_select_disable(scene, bm, l_iter);
866 changed = true;
867 }
868 /* Ensure we don't operate on these again. */
869 *ul = ul_clear;
870 }
872 }
873 }
874 else if (UL(l)->is_select_vert_single) {
875 UVRipSingle *rip = uv_rip_single_from_loop(l, co, aspect_y, offsets.uv);
876 /* We only ever use one side. */
877 const int side_from_cursor = 0;
878 GSetIterator gs_iter;
879 GSET_ITER (gs_iter, rip->loops) {
880 BMLoop *l_iter = static_cast<BMLoop *>(BLI_gsetIterator_getKey(&gs_iter));
881 ULData *ul = UL(l_iter);
882 if (ul->side == side_from_cursor) {
883 uvedit_uv_select_disable(scene, bm, l_iter);
884 changed = true;
885 }
886 /* Ensure we don't operate on these again. */
887 *ul = ul_clear;
888 }
890 }
891 }
892 }
893 }
894 if (changed) {
895 if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
897 }
898 else {
899 uvedit_select_flush_from_verts(scene, bm, false);
900 }
901 }
902 return changed;
903}
904
906
907/* -------------------------------------------------------------------- */
910
912{
914 Scene *scene = CTX_data_scene(C);
915 const ToolSettings *ts = scene->toolsettings;
916 ViewLayer *view_layer = CTX_data_view_layer(C);
917
918 if (ts->uv_sticky == UV_STICKY_VERT) {
919 /* "Rip" is logically incompatible with sync-select.
920 * Report an error instead of "poll" so this is reported when the tool is used,
921 * with #131642 implemented, this can be made to work. */
922 BKE_report(op->reports, RPT_ERROR, "Rip is not compatible with vertex sticky selection");
923 return OPERATOR_CANCELLED;
924 }
925
926 if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
927 /* Important because in sync selection we *must* be able to de-select individual loops. */
930 RPT_ERROR,
931 "Rip is only compatible with sync-select with vertex/edge selection");
932 return OPERATOR_CANCELLED;
933 }
934 }
935
936 bool changed_multi = false;
937
938 float co[2];
939 RNA_float_get_array(op->ptr, "location", co);
940
941 float aspx, aspy;
942 {
943 /* Note that we only want to run this on the active object as this defines the UV image. */
944 Object *obedit = CTX_data_edit_object(C);
945 ED_uvedit_get_aspect(obedit, &aspx, &aspy);
946 }
947 const float aspect_y = aspx / aspy;
948
950 scene, view_layer, nullptr);
951
952 if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
953 /* While this is almost always true, any mis-match (from multiple scenes for example).
954 * Will not work properly. */
955 EDBM_selectmode_set_multi_ex(scene, objects, ts->selectmode);
956 }
957
958 for (Object *obedit : objects) {
959 if (uv_rip_object(scene, obedit, co, aspect_y)) {
960 changed_multi = true;
961 uvedit_live_unwrap_update(sima, scene, obedit);
962 DEG_id_tag_update(static_cast<ID *>(obedit->data), 0);
963 WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
964 }
965 }
966
967 if (!changed_multi) {
968 BKE_report(op->reports, RPT_ERROR, "Rip failed");
969 return OPERATOR_CANCELLED;
970 }
971 return OPERATOR_FINISHED;
972}
973
975{
976 ARegion *region = CTX_wm_region(C);
977 float co[2];
978
979 UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
980 RNA_float_set_array(op->ptr, "location", co);
981
982 return uv_rip_exec(C, op);
983}
984
986{
987 /* identifiers */
988 ot->name = "UV Rip";
989 ot->description = "Rip selected vertices or a selected region";
990 ot->idname = "UV_OT_rip";
992
993 /* API callbacks. */
994 ot->exec = uv_rip_exec;
995 ot->invoke = uv_rip_invoke;
996 ot->poll = ED_operator_uvedit;
997
998 /* translation data */
1000
1001 /* properties */
1003 ot->srna,
1004 "location",
1005 2,
1006 nullptr,
1007 -FLT_MAX,
1008 FLT_MAX,
1009 "Location",
1010 "Mouse location in normalized coordinates, 0.0 to 1.0 is within the image bounds",
1011 -100.0f,
1012 100.0f);
1013}
1014
SpaceImage * CTX_wm_space_image(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Object * CTX_data_edit_object(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
ViewLayer * CTX_data_view_layer(const bContext *C)
CustomData interface, see also DNA_customdata_types.h.
BMEditMesh * BKE_editmesh_from_object(Object *ob)
Return the BMEditMesh for a given object.
Definition editmesh.cc:61
blender::Vector< Object * > BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(const Scene *scene, ViewLayer *view_layer, const View3D *v3d)
@ RPT_ERROR
Definition BKE_report.hh:39
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_STATIC_ASSERT(a, msg)
Definition BLI_assert.h:83
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_INLINE
struct GSet GSet
Definition BLI_ghash.h:337
GSet * BLI_gset_ptr_new(const char *info)
bool BLI_gset_haskey(const GSet *gs, const void *key) ATTR_WARN_UNUSED_RESULT
unsigned int BLI_gset_len(const GSet *gs) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.cc:954
BLI_INLINE void * BLI_gsetIterator_getKey(GSetIterator *gsi)
Definition BLI_ghash.h:455
#define GSET_ITER(gs_iter_, gset_)
Definition BLI_ghash.h:468
void BLI_gset_free(GSet *gs, GSetKeyFreeFP keyfreefp)
bool BLI_gset_add(GSet *gs, void *key)
Definition BLI_ghash.cc:966
bool BLI_gset_remove(GSet *gs, const void *key, GSetKeyFreeFP keyfreefp)
Definition BLI_ghash.cc:999
MINLINE float min_ff(float a, float b)
float angle_v2v2(const float a[2], const float b[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void mul_v2_fl(float r[2], float f)
MINLINE void add_v2_v2(float r[2], const float a[2])
MINLINE bool equals_v2v2(const float v1[2], const float v2[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void sub_v2_v2v2(float r[2], const float a[2], const float b[2])
MINLINE void zero_v2(float r[2])
MINLINE float dot_v2v2(const float a[2], const float b[2]) ATTR_WARN_UNUSED_RESULT
MINLINE float normalize_v2(float n[2])
unsigned int uint
#define UNLIKELY(x)
#define LIKELY(x)
void DEG_id_tag_update(ID *id, unsigned int flags)
Object is a sort of wrapper for general info.
@ UV_STICKY_VERT
@ UV_FLAG_SELECT_SYNC
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
bool EDBM_selectmode_set_multi_ex(Scene *scene, blender::Span< Object * > objects, const short selectmode)
bool ED_operator_uvedit(bContext *C)
#define P_MIRROR_DUMMY
bool uvedit_loop_vert_select_get(const ToolSettings *ts, const BMesh *bm, const BMLoop *l)
bool ED_uvedit_sync_uvselect_ignore(const ToolSettings *ts)
void uvedit_select_flush_from_verts(const Scene *scene, BMesh *bm, bool select)
void uvedit_uv_select_disable(const Scene *scene, BMesh *bm, BMLoop *l)
void uvedit_loop_vert_select_set(const ToolSettings *ts, const BMesh *bm, BMLoop *l, const bool select)
bool uvedit_loop_edge_select_get(const ToolSettings *ts, const BMesh *bm, const BMLoop *l)
void ED_uvedit_get_aspect(Object *obedit, float *r_aspx, float *r_aspy)
void uvedit_loop_edge_select_set(const ToolSettings *ts, const BMesh *bm, BMLoop *l, const bool select)
bool uvedit_face_visible_test(const Scene *scene, const BMFace *efa)
Read Guarded memory(de)allocation.
#define C
Definition RandGen.cpp:29
void UI_view2d_region_to_view(const View2D *v2d, float x, float y, float *r_view_x, float *r_view_y) ATTR_NONNULL()
Definition view2d.cc:1668
#define NC_GEOM
Definition WM_types.hh:393
#define ND_DATA
Definition WM_types.hh:509
@ OPTYPE_DEPENDS_ON_CURSOR
Definition WM_types.hh:218
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#define BM_ELEM_CD_GET_FLOAT_P(ele, offset)
@ BM_ELEM_TAG
@ BM_LOOP
#define BM_elem_flag_set(ele, hflag, val)
#define BM_elem_flag_test(ele, hflag)
#define BM_ITER_ELEM(ele, iter, data, itype)
#define BM_ITER_MESH(ele, iter, bm, itype)
@ BM_FACES_OF_MESH
@ BM_LOOPS_OF_VERT
@ BM_LOOPS_OF_FACE
BMesh * bm
BLI_INLINE bool BM_vert_in_edge(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
ATTR_WARN_UNUSED_RESULT const BMLoop * l
BMUVOffsets BM_uv_map_offsets_get(const BMesh *bm)
bool BM_loop_uv_share_edge_check(const BMLoop *l_a, const BMLoop *l_b, const int cd_loop_uv_offset)
bool BM_edge_uv_share_vert_check(const BMEdge *e, const BMLoop *l_a, const BMLoop *l_b, const int cd_loop_uv_offset)
float BM_face_uv_calc_cross(const BMFace *f, const int cd_loop_uv_offset)
void BM_mesh_uvselect_flush_from_loop_verts(BMesh *bm)
int count
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
void properties_register(wmOperatorType *ot, int flags)
VecBase< float, 2 > float2
#define fabsf
void RNA_float_get_array(PointerRNA *ptr, const char *name, float *values)
void RNA_float_set_array(PointerRNA *ptr, const char *name, const float *values)
PropertyRNA * RNA_def_float_vector(StructOrFunctionRNA *cont_, const char *identifier, const int len, const float *default_value, const float hardmin, const float hardmax, const char *ui_name, const char *ui_description, const float softmin, const float softmax)
#define FLT_MAX
Definition stdcycles.h:14
struct BMVert * v
struct BMEdge * e
struct BMLoop * radial_next
struct BMLoop * prev
struct BMFace * f
struct BMLoop * next
Definition DNA_ID.h:414
struct ToolSettings * toolsettings
uint side_was_swapped
Definition uvedit_rip.cc:85
uint in_rip_pairs
Definition uvedit_rip.cc:77
uint is_select_vert_single
Definition uvedit_rip.cc:71
uint in_stack
Definition uvedit_rip.cc:75
uint is_select_edge
Definition uvedit_rip.cc:58
uint side
Definition uvedit_rip.cc:79
uint is_select_all
Definition uvedit_rip.cc:73
GSet * loops
int mval[2]
Definition WM_types.hh:763
struct ReportList * reports
struct PointerRNA * ptr
i
Definition text_draw.cc:230
void uvedit_select_prepare_sync_select(const Scene *scene, BMesh *bm)
void uvedit_live_unwrap_update(SpaceImage *sima, Scene *scene, Object *obedit)
void uvedit_select_prepare_custom_data(const Scene *scene, BMesh *bm)
static wmOperatorStatus uv_rip_exec(bContext *C, wmOperator *op)
static wmOperatorStatus uv_rip_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static BMLoop * bm_loop_find_other_fan_loop_with_visible_face(BMLoop *l_src, BMVert *v_src, const int cd_loop_uv_offset)
#define UV_SET_SIDE_AND_REMOVE_FROM_RAIL(loop, side_value)
static void bm_loop_uv_select_single_vert_validate(BMLoop *l_init, const int cd_loop_uv_offset)
static void uv_rip_pairs_free(UVRipPairs *rip)
BLI_INLINE ULData * UL(BMLoop *l)
Definition uvedit_rip.cc:91
static void uv_rip_single_free(UVRipSingle *rip)
static void uv_rip_pairs_remove(UVRipPairs *rip, BMLoop *l)
static void uv_rip_pairs_add(UVRipPairs *rip, BMLoop *l)
static int uv_rip_pairs_loop_count_on_side(BMLoop *l_init, uint side, const int cd_loop_uv_offset)
static UVRipSingle * uv_rip_single_from_loop(BMLoop *l_init_orig, const float co[2], const float aspect_y, const int cd_loop_uv_offset)
static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const float aspect_y)
static BMLoop * bm_loop_find_other_radial_loop_with_visible_face(BMLoop *l_src, const int cd_loop_uv_offset)
void UV_OT_rip(wmOperatorType *ot)
static UVRipPairs * uv_rip_pairs_from_loop(BMLoop *l_init, const float aspect_y, const int cd_loop_uv_offset)
static float uv_rip_pairs_calc_uv_angle(BMLoop *l_init, uint side, const float aspect_y, const int cd_loop_uv_offset)
static bool uv_rip_pairs_calc_center_and_direction(UVRipPairs *rip, const int cd_loop_uv_offset, float r_center[2], float r_dir_side[2][2])
static bool uv_rip_pairs_loop_change_sides_test(BMLoop *l_switch, BMLoop *l_target, const float aspect_y, const int cd_loop_uv_offset)
static void bm_loop_calc_uv_angle_from_dir(BMLoop *l, const float dir[2], const float aspect_y, const int cd_loop_uv_offset, float *r_corner_angle, float *r_edge_angle, int *r_edge_index)
static BMLoop * bm_vert_step_fan_loop_uv(BMLoop *l, BMEdge **e_step, const int cd_loop_uv_offset)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
wmOperatorType * ot
Definition wm_files.cc:4237