Blender V4.5
uvedit_unwrap_ops.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
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 "DNA_defaults.h"
16#include "DNA_meshdata_types.h"
17#include "DNA_modifier_types.h"
18#include "DNA_object_types.h"
19#include "DNA_scene_types.h"
20
21#include "BKE_global.hh"
22
23#include "BLI_array.hh"
24#include "BLI_linklist.h"
25#include "BLI_listbase.h"
26#include "BLI_math_geom.h"
27#include "BLI_math_matrix.h"
28#include "BLI_math_rotation.h"
29#include "BLI_math_vector.h"
30#include "BLI_memarena.h"
31#include "BLI_string.h"
32#include "BLI_time.h"
33#include "BLI_utildefines.h"
34#include "BLI_vector.hh"
35
36#include "BLT_translation.hh"
37
38#include "BKE_context.hh"
39#include "BKE_customdata.hh"
40#include "BKE_deform.hh"
41#include "BKE_editmesh.hh"
42#include "BKE_image.hh"
43#include "BKE_layer.hh"
44#include "BKE_lib_id.hh"
45#include "BKE_main.hh"
46#include "BKE_mesh.hh"
47#include "BKE_object_types.hh"
48#include "BKE_report.hh"
49#include "BKE_screen.hh"
50#include "BKE_subdiv.hh"
51#include "BKE_subdiv_mesh.hh"
53#include "BKE_uvproject.h"
54
55#include "DEG_depsgraph.hh"
56
57#include "GEO_uv_pack.hh"
59
60#include "UI_interface.hh"
61#include "UI_resources.hh"
62
63#include "ED_image.hh"
64#include "ED_mesh.hh"
65#include "ED_screen.hh"
66#include "ED_undo.hh"
67#include "ED_uvedit.hh"
68#include "ED_view3d.hh"
69
70#include "RNA_access.hh"
71#include "RNA_define.hh"
72
73#include "WM_api.hh"
74#include "WM_types.hh"
75
76#include "uvedit_intern.hh"
77
78using blender::Span;
79using blender::Vector;
83
84/* -------------------------------------------------------------------- */
87
88static bool uvedit_ensure_uvs(Object *obedit)
89{
90 if (ED_uvedit_test(obedit)) {
91 return true;
92 }
93
95 BMFace *efa;
96 BMIter iter;
97
98 if (em && em->bm->totface && !CustomData_has_layer(&em->bm->ldata, CD_PROP_FLOAT2)) {
99 ED_mesh_uv_add(static_cast<Mesh *>(obedit->data), nullptr, true, true, nullptr);
100 }
101
102 /* Happens when there are no faces. */
103 if (!ED_uvedit_test(obedit)) {
104 return false;
105 }
106
107 const char *active_uv_name = CustomData_get_active_layer_name(&em->bm->ldata, CD_PROP_FLOAT2);
108 BM_uv_map_attr_vert_select_ensure(em->bm, active_uv_name);
109 BM_uv_map_attr_edge_select_ensure(em->bm, active_uv_name);
110 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
111
112 /* select new UVs (ignore UV_SYNC_SELECTION in this case) */
113 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
114 BMIter liter;
115 BMLoop *l;
116
117 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
118 BM_ELEM_CD_SET_BOOL(l, offsets.select_vert, true);
119 BM_ELEM_CD_SET_BOOL(l, offsets.select_edge, true);
120 }
121 }
122
123 return true;
124}
125
127
128/* -------------------------------------------------------------------- */
131
133{
135 ot->srna,
136 "correct_aspect",
137 true,
138 "Correct Aspect",
139 "Map UVs taking aspect ratio of the image associated with the material into account");
140}
141
143
144/* -------------------------------------------------------------------- */
147
149{
150 if (!sima) {
151 return; /* Nothing to do. */
152 }
153
154 /* NOTE: Presently, when UDIM grid and tiled image are present together, only active tile for
155 * the tiled image is considered. */
156 const Image *image = sima->image;
157 if (image && image->source == IMA_SRC_TILED) {
158 ImageTile *active_tile = static_cast<ImageTile *>(
159 BLI_findlink(&image->tiles, image->active_tile_index));
160 if (active_tile) {
161 udim_base_offset[0] = (active_tile->tile_number - 1001) % 10;
162 udim_base_offset[1] = (active_tile->tile_number - 1001) / 10;
163 }
164 return;
165 }
166
167 /* TODO: Support storing an active UDIM when there are no tiles present.
168 * Until then, use 2D cursor to find the active tile index for the UDIM grid. */
169 if (uv_coords_isect_udim(sima->image, sima->tile_grid_shape, sima->cursor)) {
170 udim_base_offset[0] = floorf(sima->cursor[0]);
171 udim_base_offset[1] = floorf(sima->cursor[1]);
172 }
173}
174
175
177{
178 if (stop) {
179 return *stop;
180 }
181 return false;
182}
183
184/* -------------------------------------------------------------------- */
187
217
219{
220 only_selected_uvs = options.only_selected_uvs;
221 only_selected_faces = options.only_selected_faces;
222 use_seams = !options.topology_from_uvs || options.topology_from_uvs_use_seams;
223 correct_aspect = options.correct_aspect;
224 pin_unselected = options.pin_unselected;
225}
226
227static void modifier_unwrap_state(Object *obedit,
228 const UnwrapOptions *options,
229 bool *r_use_subsurf)
230{
231 ModifierData *md;
232 bool subsurf = options->use_subsurf;
233
234 md = static_cast<ModifierData *>(obedit->modifiers.first);
235
236 /* Subdivision-surface will take the modifier settings
237 * only if modifier is first or right after mirror. */
238 if (subsurf) {
239 if (md && md->type == eModifierType_Subsurf) {
240 const SubsurfModifierData &smd = *reinterpret_cast<const SubsurfModifierData *>(md);
241 if (smd.levels > 0) {
242 /* Skip all calculation for zero subdivision levels, similar to the way the modifier is
243 * disabled in that case. */
244 subsurf = true;
245 }
246 else {
247 subsurf = false;
248 }
249 }
250 else {
251 subsurf = false;
252 }
253 }
254
255 *r_use_subsurf = subsurf;
256}
257
259{
261
262 /* To be set by the upper layer */
263 options.topology_from_uvs = false;
264 options.topology_from_uvs_use_seams = false;
265 options.only_selected_faces = false;
266 options.only_selected_uvs = false;
267 options.pin_unselected = false;
268
269 options.slim.skip_init = false;
270
271 if (ts) {
272 options.method = ts->unwrapper;
273 options.correct_aspect = (ts->uvcalc_flag & UVCALC_NO_ASPECT_CORRECT) == 0;
274 options.fill_holes = (ts->uvcalc_flag & UVCALC_FILLHOLES) != 0;
275 options.use_subsurf = (ts->uvcalc_flag & UVCALC_USESUBSURF) != 0;
276
278 STRNCPY(options.weight_group, ts->uvcalc_weight_group);
279 options.slim.weight_influence = ts->uvcalc_weight_factor;
280
281 options.slim.iterations = ts->uvcalc_iterations;
282 options.slim.no_flip = ts->uvcalc_flag & UVCALC_UNWRAP_NO_FLIP;
283 }
284 else {
285 options.method = RNA_enum_get(op->ptr, "method");
286 options.correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect");
287 options.fill_holes = RNA_boolean_get(op->ptr, "fill_holes");
288 options.use_subsurf = RNA_boolean_get(op->ptr, "use_subsurf_data");
289
290 options.use_weights = RNA_boolean_get(op->ptr, "use_weights");
291 RNA_string_get(op->ptr, "weight_group", options.weight_group);
292 options.slim.weight_influence = RNA_float_get(op->ptr, "weight_factor");
293
294 options.slim.iterations = RNA_int_get(op->ptr, "iterations");
295 options.slim.no_flip = RNA_boolean_get(op->ptr, "no_flip");
296 }
297
298#ifndef WITH_UV_SLIM
301 if (op) {
302 BKE_report(op->reports, RPT_WARNING, "Built without SLIM, falling back to conformal method");
303 }
304 }
305#endif /* !WITH_UV_SLIM */
306
307 if (options.weight_group[0] == '\0' || options.use_weights == false) {
308 options.slim.weight_influence = 0.0f;
309 }
310
311 options.use_abf = options.method == UVCALC_UNWRAP_METHOD_ANGLE;
313
314 /* SLIM requires hole filling */
315 if (options.use_slim) {
316 options.fill_holes = true;
317 }
318
319 if (ob) {
320 bool use_subsurf_final;
321 modifier_unwrap_state(ob, &options, &use_subsurf_final);
322 options.use_subsurf = use_subsurf_final;
323 }
324
325 return options;
326}
327
328/* Generic sync functions
329 *
330 * NOTE: these could be moved to a generic API.
331 */
332
334 PointerRNA *ptr, const char *prop_name, char flag, bool flipped, char *value_p)
335{
336 if (PropertyRNA *prop = RNA_struct_find_property(ptr, prop_name)) {
337 if (RNA_property_is_set(ptr, prop)) {
338 if (RNA_property_boolean_get(ptr, prop) ^ flipped) {
339 *value_p |= flag;
340 }
341 else {
342 *value_p &= ~flag;
343 }
344 return true;
345 }
346 RNA_property_boolean_set(ptr, prop, ((*value_p & flag) > 0) ^ flipped);
347 return false;
348 }
350 return false;
351}
352
353static bool rna_property_sync_enum(PointerRNA *ptr, const char *prop_name, int *value_p)
354{
355 if (PropertyRNA *prop = RNA_struct_find_property(ptr, prop_name)) {
356 if (RNA_property_is_set(ptr, prop)) {
357 *value_p = RNA_property_enum_get(ptr, prop);
358 return true;
359 }
360 RNA_property_enum_set(ptr, prop, *value_p);
361 return false;
362 }
364 return false;
365}
366
367static bool rna_property_sync_enum_char(PointerRNA *ptr, const char *prop_name, char *value_p)
368{
369 int value_i = *value_p;
370 if (rna_property_sync_enum(ptr, prop_name, &value_i)) {
371 *value_p = value_i;
372 return true;
373 }
374 return false;
375}
376
377static bool rna_property_sync_int(PointerRNA *ptr, const char *prop_name, int *value_p)
378{
379 if (PropertyRNA *prop = RNA_struct_find_property(ptr, prop_name)) {
380 if (RNA_property_is_set(ptr, prop)) {
381 *value_p = RNA_property_int_get(ptr, prop);
382 return true;
383 }
384 RNA_property_int_set(ptr, prop, *value_p);
385 return false;
386 }
388 return false;
389}
390
391static bool rna_property_sync_float(PointerRNA *ptr, const char *prop_name, float *value_p)
392{
393 if (PropertyRNA *prop = RNA_struct_find_property(ptr, prop_name)) {
394 if (RNA_property_is_set(ptr, prop)) {
395 *value_p = RNA_property_float_get(ptr, prop);
396 return true;
397 }
398 RNA_property_float_set(ptr, prop, *value_p);
399 return false;
400 }
402 return false;
403}
404
405static bool rna_property_sync_string(PointerRNA *ptr, const char *prop_name, char value_p[])
406{
407 if (PropertyRNA *prop = RNA_struct_find_property(ptr, prop_name)) {
408 if (RNA_property_is_set(ptr, prop)) {
409 RNA_property_string_get(ptr, prop, value_p);
410 return true;
411 }
412 RNA_property_string_set(ptr, prop, value_p);
413 return false;
414 }
416 return false;
417}
418
420{
421 /* Remember last method for live unwrap. */
422 rna_property_sync_enum_char(op->ptr, "method", &ts->unwrapper);
423
424 /* Remember packing margin. */
425 rna_property_sync_float(op->ptr, "margin", &ts->uvcalc_margin);
426
427 rna_property_sync_int(op->ptr, "iterations", &ts->uvcalc_iterations);
428
429 rna_property_sync_float(op->ptr, "weight_factor", &ts->uvcalc_weight_factor);
430
431 rna_property_sync_string(op->ptr, "weight_group", ts->uvcalc_weight_group);
432
433 rna_property_sync_flag(op->ptr, "fill_holes", UVCALC_FILLHOLES, false, &ts->uvcalc_flag);
435 op->ptr, "correct_aspect", UVCALC_NO_ASPECT_CORRECT, true, &ts->uvcalc_flag);
436 rna_property_sync_flag(op->ptr, "use_subsurf_data", UVCALC_USESUBSURF, false, &ts->uvcalc_flag);
437 rna_property_sync_flag(op->ptr, "no_flip", UVCALC_UNWRAP_NO_FLIP, false, &ts->uvcalc_flag);
438
440 op->ptr, "use_weights", UVCALC_UNWRAP_USE_WEIGHTS, false, &ts->uvcalc_flag);
441}
442
443static bool uvedit_have_selection(const Scene *scene, BMEditMesh *em, const UnwrapOptions *options)
444{
445 BMFace *efa;
446 BMLoop *l;
447 BMIter iter, liter;
448 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
449
450 if (offsets.uv == -1) {
451 return (em->bm->totfacesel != 0);
452 }
453
454 /* verify if we have any selected uv's before unwrapping,
455 * so we can cancel the operator early */
456 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
457 if (scene->toolsettings->uv_flag & UV_SYNC_SELECTION) {
459 continue;
460 }
461 }
462 else if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
463 continue;
464 }
465
466 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
467 if (uvedit_uv_select_test(scene, l, offsets)) {
468 break;
469 }
470 }
471
472 if (options->only_selected_uvs && !l) {
473 continue;
474 }
475
476 return true;
477 }
478
479 return false;
480}
481
482static bool uvedit_have_selection_multi(const Scene *scene,
483 const Span<Object *> objects,
484 const UnwrapOptions *options)
485{
486 bool have_select = false;
487 for (Object *obedit : objects) {
489 if (uvedit_have_selection(scene, em, options)) {
490 have_select = true;
491 break;
492 }
493 }
494 return have_select;
495}
496
498 const int material_index,
499 float *r_aspx,
500 float *r_aspy)
501{
502 if (UNLIKELY(material_index < 0 || material_index >= ob->totcol)) {
503 *r_aspx = 1.0f;
504 *r_aspy = 1.0f;
505 return;
506 }
507 Image *ima;
508 ED_object_get_active_image(ob, material_index + 1, &ima, nullptr, nullptr, nullptr);
509 ED_image_get_uv_aspect(ima, nullptr, r_aspx, r_aspy);
510}
511
512void ED_uvedit_get_aspect(Object *ob, float *r_aspx, float *r_aspy)
513{
515 BLI_assert(em != nullptr);
516 bool sloppy = true;
517 bool selected = false;
518 BMFace *efa = BM_mesh_active_face_get(em->bm, sloppy, selected);
519 if (!efa) {
520 *r_aspx = 1.0f;
521 *r_aspy = 1.0f;
522 return;
523 }
524
525 ED_uvedit_get_aspect_from_material(ob, efa->mat_nr, r_aspx, r_aspy);
526}
527
529{
530 float aspect[2];
531 ED_uvedit_get_aspect(ob, &aspect[0], &aspect[1]);
532 return aspect[0] / aspect[1];
533}
534
535static bool uvedit_is_face_affected(const Scene *scene,
536 BMFace *efa,
537 const UnwrapOptions *options,
538 const BMUVOffsets &offsets)
539{
541 return false;
542 }
543
544 if (options->only_selected_faces && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
545 return false;
546 }
547
548 if (options->only_selected_uvs) {
549 BMLoop *l;
550 BMIter iter;
551 BM_ITER_ELEM (l, &iter, efa, BM_LOOPS_OF_FACE) {
552 if (uvedit_uv_select_test(scene, l, offsets)) {
553 return true;
554 }
555 }
556 return false;
557 }
558
559 return true;
560}
561
562/* Prepare unique indices for each unique pinned UV, even if it shares a BMVert.
563 */
565 const Scene *scene,
566 BMFace *efa,
567 const UnwrapOptions *options,
568 const BMUVOffsets &offsets)
569{
570 BMIter liter;
571 BMLoop *l;
572 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
573 bool pin = BM_ELEM_CD_GET_BOOL(l, offsets.pin);
574 if (options->pin_unselected && !pin) {
575 pin = !uvedit_uv_select_test(scene, l, offsets);
576 }
577 if (pin) {
578 int bmvertindex = BM_elem_index_get(l->v);
579 const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
580 blender::geometry::uv_prepare_pin_index(handle, bmvertindex, luv);
581 }
582 }
583}
584
586 const Scene *scene,
587 BMFace *efa,
589 const UnwrapOptions *options,
590 const BMUVOffsets &offsets,
591 const int cd_weight_offset,
592 const int cd_weight_index)
593{
600
601 int i;
602
603 BMIter liter;
604 BMLoop *l;
605
606 /* let parametrizer split the ngon, it can make better decisions
607 * about which split is best for unwrapping than poly-fill. */
608 BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) {
609 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
610
612 co[i] = l->v->co;
613 uv[i] = luv;
614 pin[i] = BM_ELEM_CD_GET_BOOL(l, offsets.pin);
615 select[i] = uvedit_uv_select_test(scene, l, offsets);
616 if (options->pin_unselected && !select[i]) {
617 pin[i] = true;
618 }
619
620 /* Optional vertex group weighting. */
621 if (cd_weight_offset >= 0 && cd_weight_index >= 0) {
622 MDeformVert *dv = (MDeformVert *)BM_ELEM_CD_GET_VOID_P(l->v, cd_weight_offset);
623 weight[i] = BKE_defvert_find_weight(dv, cd_weight_index);
624 }
625 else {
626 weight[i] = 1.0f;
627 }
628 }
629
631 face_index,
632 i,
633 vkeys.data(),
634 co.data(),
635 uv.data(),
636 weight.data(),
637 pin.data(),
638 select.data());
639}
640
641/* Set seams on UV Parametrizer based on options. */
643 BMesh *bm,
644 const UnwrapOptions *options)
645{
646 if (options->topology_from_uvs && !options->topology_from_uvs_use_seams) {
647 return; /* Seams are not required with these options. */
648 }
649
650 const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
651 if (offsets.uv == -1) {
652 return; /* UVs aren't present on BMesh. Nothing to do. */
653 }
654
655 BMEdge *edge;
656 BMIter iter;
657 BM_ITER_MESH (edge, &iter, bm, BM_EDGES_OF_MESH) {
658 if (!BM_elem_flag_test(edge, BM_ELEM_SEAM)) {
659 continue; /* No seam on this edge, nothing to do. */
660 }
661
662 /* Pinned vertices might have more than one ParamKey per BMVert.
663 * Check all the BM_LOOPS_OF_EDGE to find all the ParamKeys.
664 */
665 BMLoop *l;
666 BMIter liter;
667 BM_ITER_ELEM (l, &liter, edge, BM_LOOPS_OF_EDGE) {
668 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
669 float *luv_next = BM_ELEM_CD_GET_FLOAT_P(l->next, offsets.uv);
670 ParamKey vkeys[2];
671 vkeys[0] = blender::geometry::uv_find_pin_index(handle, BM_elem_index_get(l->v), luv);
673 handle, BM_elem_index_get(l->next->v), luv_next);
674
675 /* Set the seam. */
677 }
678 }
679}
680
681/*
682 * Version of #construct_param_handle_multi with a separate BMesh parameter.
683 */
685 Object *ob,
686 BMesh *bm,
687 const UnwrapOptions *options,
688 int *r_count_failed = nullptr)
689{
690 BMFace *efa;
691 BMIter iter;
692 int i;
693
695
696 if (options->correct_aspect) {
698 }
699
700 /* we need the vert indices */
702
703 const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
704 const int cd_weight_offset = CustomData_get_offset(&bm->vdata, CD_MDEFORMVERT);
705 const int cd_weight_index = BKE_object_defgroup_name_index(ob, options->weight_group);
706
707 BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, i) {
708 if (uvedit_is_face_affected(scene, efa, options, offsets)) {
709 uvedit_prepare_pinned_indices(handle, scene, efa, options, offsets);
710 }
711 }
712
713 BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, i) {
714 if (uvedit_is_face_affected(scene, efa, options, offsets)) {
716 handle, scene, efa, i, options, offsets, cd_weight_offset, cd_weight_index);
717 }
718 }
719
721
723 handle, options->fill_holes, options->topology_from_uvs, r_count_failed);
724
725 return handle;
726}
727
732 const Span<Object *> objects,
733 const UnwrapOptions *options)
734{
735 BMFace *efa;
736 BMIter iter;
737 int i;
738
740
741 if (options->correct_aspect) {
742 Object *ob = objects[0];
744 }
745
746 /* we need the vert indices */
748
749 int offset = 0;
750
751 for (Object *obedit : objects) {
753 BMesh *bm = em->bm;
754
755 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
756
757 if (offsets.uv == -1) {
758 continue;
759 }
760
761 const int cd_weight_offset = CustomData_get_offset(&bm->vdata, CD_MDEFORMVERT);
762 const int cd_weight_index = BKE_object_defgroup_name_index(obedit, options->weight_group);
763
764 BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, i) {
765 if (uvedit_is_face_affected(scene, efa, options, offsets)) {
766 uvedit_prepare_pinned_indices(handle, scene, efa, options, offsets);
767 }
768 }
769
770 BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, i) {
771 if (uvedit_is_face_affected(scene, efa, options, offsets)) {
773 handle, scene, efa, i + offset, options, offsets, cd_weight_offset, cd_weight_index);
774 }
775 }
776
778
779 offset += bm->totface;
780 }
781
783 handle, options->fill_holes, options->topology_from_uvs, nullptr);
784
785 return handle;
786}
787
788static void texface_from_original_index(const Scene *scene,
789 const BMUVOffsets &offsets,
790 BMFace *efa,
791 int index,
792 float **r_uv,
793 bool *r_pin,
794 bool *r_select)
795{
796 BMLoop *l;
797 BMIter liter;
798
799 *r_uv = nullptr;
800 *r_pin = false;
801 *r_select = true;
802
803 if (index == ORIGINDEX_NONE) {
804 return;
805 }
806
807 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
808 if (BM_elem_index_get(l->v) == index) {
809 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
810 *r_uv = luv;
811 *r_pin = BM_ELEM_CD_GET_BOOL(l, offsets.pin);
812 *r_select = uvedit_uv_select_test(scene, l, offsets);
813 break;
814 }
815 }
816}
817
818static Mesh *subdivide_edit_mesh(const Object *object,
819 const BMEditMesh *em,
820 const SubsurfModifierData *smd)
821{
822 using namespace blender;
824 em->bm, nullptr, static_cast<const Mesh *>(object->data));
826
828 /* A zero level must be prevented by #modifier_unwrap_state
829 * since necessary data won't be available, see: #128958. */
830 BLI_assert(settings.level > 0);
831
832 /* Level 1 causes disconnected triangles, force level 2 to prevent this, see: #129503. */
833 if (settings.level == 1) {
834 settings.level = 2;
835 }
836
837 bke::subdiv::ToMeshSettings mesh_settings;
838 mesh_settings.resolution = (1 << smd->levels) + 1;
840
841 bke::subdiv::Subdiv *subdiv = bke::subdiv::new_from_mesh(&settings, me_from_em);
842 if (!subdiv) {
843 return nullptr;
844 }
845 Mesh *result = bke::subdiv::subdiv_to_mesh(subdiv, &mesh_settings, me_from_em);
846 BKE_id_free(nullptr, me_from_em);
847 bke::subdiv::free(subdiv);
848 return result;
849}
850
857 Object *ob,
858 BMEditMesh *em,
859 const UnwrapOptions *options,
860 int *r_count_failed = nullptr)
861{
862 /* pointers to modifier data for unwrap control */
863 SubsurfModifierData *smd_real;
864 /* Modifier initialization data, will control what type of subdivision will happen. */
865 SubsurfModifierData smd = {{nullptr}};
866
867 /* Holds a map to edit-faces for every subdivision-surface face.
868 * These will be used to get hidden/ selected flags etc. */
869 BMFace **faceMap;
870 /* Similar to the above, we need a way to map edges to their original ones. */
871 BMEdge **edgeMap;
872
873 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
874 const int cd_weight_index = BKE_object_defgroup_name_index(ob, options->weight_group);
875
877
878 if (options->correct_aspect) {
880 }
881
882 /* number of subdivisions to perform */
883 ModifierData *md = static_cast<ModifierData *>(ob->modifiers.first);
884 smd_real = (SubsurfModifierData *)md;
885
886 smd.levels = smd_real->levels;
887 smd.subdivType = smd_real->subdivType;
888 smd.flags = smd_real->flags;
889 smd.quality = smd_real->quality;
890
891 Mesh *subdiv_mesh = subdivide_edit_mesh(ob, em, &smd);
892
893 const blender::Span<blender::float3> subsurf_positions = subdiv_mesh->vert_positions();
894 const blender::Span<blender::int2> subsurf_edges = subdiv_mesh->edges();
895 const blender::OffsetIndices subsurf_facess = subdiv_mesh->faces();
896 const blender::Span<int> subsurf_corner_verts = subdiv_mesh->corner_verts();
897 const blender::Span<MDeformVert> subsurf_deform_verts = subdiv_mesh->deform_verts();
898
899 const int *origVertIndices = static_cast<const int *>(
901 const int *origEdgeIndices = static_cast<const int *>(
903 const int *origPolyIndices = static_cast<const int *>(
905
906 faceMap = MEM_malloc_arrayN<BMFace *>(subdiv_mesh->faces_num, "unwrap_edit_face_map");
907
910
911 /* map subsurfed faces to original editFaces */
912 for (int i = 0; i < subdiv_mesh->faces_num; i++) {
913 faceMap[i] = BM_face_at_index(em->bm, origPolyIndices[i]);
914 }
915
916 edgeMap = MEM_malloc_arrayN<BMEdge *>(subdiv_mesh->edges_num, "unwrap_edit_edge_map");
917
918 /* map subsurfed edges to original editEdges */
919 for (int i = 0; i < subdiv_mesh->edges_num; i++) {
920 /* not all edges correspond to an old edge */
921 edgeMap[i] = (origEdgeIndices[i] != ORIGINDEX_NONE) ?
922 BM_edge_at_index(em->bm, origEdgeIndices[i]) :
923 nullptr;
924 }
925
926 /* Prepare and feed faces to the solver. */
927 for (const int i : subsurf_facess.index_range()) {
928 ParamKey key, vkeys[4];
929 bool pin[4], select[4];
930 const float *co[4];
931 float *uv[4];
932 float weight[4];
933 BMFace *origFace = faceMap[i];
934
935 if (scene->toolsettings->uv_flag & UV_SYNC_SELECTION) {
936 if (BM_elem_flag_test(origFace, BM_ELEM_HIDDEN)) {
937 continue;
938 }
939 }
940 else {
941 if (BM_elem_flag_test(origFace, BM_ELEM_HIDDEN) ||
942 (options->only_selected_faces && !BM_elem_flag_test(origFace, BM_ELEM_SELECT)))
943 {
944 continue;
945 }
946 }
947
948 const blender::Span<int> poly_corner_verts = subsurf_corner_verts.slice(subsurf_facess[i]);
949
950 /* We will not check for v4 here. Sub-surface faces always have 4 vertices. */
951 BLI_assert(poly_corner_verts.size() == 4);
952 key = (ParamKey)i;
953 vkeys[0] = (ParamKey)poly_corner_verts[0];
954 vkeys[1] = (ParamKey)poly_corner_verts[1];
955 vkeys[2] = (ParamKey)poly_corner_verts[2];
956 vkeys[3] = (ParamKey)poly_corner_verts[3];
957
958 co[0] = subsurf_positions[poly_corner_verts[0]];
959 co[1] = subsurf_positions[poly_corner_verts[1]];
960 co[2] = subsurf_positions[poly_corner_verts[2]];
961 co[3] = subsurf_positions[poly_corner_verts[3]];
962
963 /* Optional vertex group weights. */
964 if (cd_weight_index >= 0) {
965 weight[0] = BKE_defvert_find_weight(&subsurf_deform_verts[poly_corner_verts[0]],
966 cd_weight_index);
967 weight[1] = BKE_defvert_find_weight(&subsurf_deform_verts[poly_corner_verts[1]],
968 cd_weight_index);
969 weight[2] = BKE_defvert_find_weight(&subsurf_deform_verts[poly_corner_verts[2]],
970 cd_weight_index);
971 weight[3] = BKE_defvert_find_weight(&subsurf_deform_verts[poly_corner_verts[3]],
972 cd_weight_index);
973 }
974 else {
975 weight[0] = 1.0f;
976 weight[1] = 1.0f;
977 weight[2] = 1.0f;
978 weight[3] = 1.0f;
979 }
980
981 /* This is where all the magic is done.
982 * If the vertex exists in the, we pass the original uv pointer to the solver, thus
983 * flushing the solution to the edit mesh. */
985 offsets,
986 origFace,
987 origVertIndices[poly_corner_verts[0]],
988 &uv[0],
989 &pin[0],
990 &select[0]);
992 offsets,
993 origFace,
994 origVertIndices[poly_corner_verts[1]],
995 &uv[1],
996 &pin[1],
997 &select[1]);
999 offsets,
1000 origFace,
1001 origVertIndices[poly_corner_verts[2]],
1002 &uv[2],
1003 &pin[2],
1004 &select[2]);
1006 offsets,
1007 origFace,
1008 origVertIndices[poly_corner_verts[3]],
1009 &uv[3],
1010 &pin[3],
1011 &select[3]);
1012
1014 handle, key, 4, vkeys, co, uv, weight, pin, select);
1015 }
1016
1017 /* These are calculated from original mesh too. */
1018 for (const int64_t i : subsurf_edges.index_range()) {
1019 if ((edgeMap[i] != nullptr) && BM_elem_flag_test(edgeMap[i], BM_ELEM_SEAM)) {
1020 const blender::int2 &edge = subsurf_edges[i];
1021 ParamKey vkeys[2];
1022 vkeys[0] = (ParamKey)edge[0];
1023 vkeys[1] = (ParamKey)edge[1];
1025 }
1026 }
1027
1029 handle, options->fill_holes, options->topology_from_uvs, r_count_failed);
1030
1031 /* cleanup */
1033 MEM_freeN(edgeMap);
1034 BKE_id_free(nullptr, subdiv_mesh);
1035
1036 return handle;
1037}
1038
1040
1041/* -------------------------------------------------------------------- */
1044
1054
1056{
1057 const Scene *scene = CTX_data_scene(C);
1058 ViewLayer *view_layer = CTX_data_view_layer(C);
1059
1061 options.topology_from_uvs = true;
1062 options.fill_holes = RNA_boolean_get(op->ptr, "fill_holes");
1063 options.only_selected_faces = true;
1064 options.only_selected_uvs = true;
1065 options.correct_aspect = true;
1066
1068 scene, view_layer, CTX_wm_view3d(C));
1069
1070 if (!uvedit_have_selection_multi(scene, objects, &options)) {
1071 return false;
1072 }
1073
1074 MinStretch *ms = MEM_new<MinStretch>(__func__);
1075 ms->scene = scene;
1076 ms->objects_edit = objects;
1077 ms->blend = RNA_float_get(op->ptr, "blend");
1078 ms->iterations = RNA_int_get(op->ptr, "iterations");
1079 ms->i = 0;
1080 ms->handle = construct_param_handle_multi(scene, objects, &options);
1082
1084 if (ms->blend != 0.0f) {
1086 }
1087
1088 op->customdata = ms;
1089
1090 return true;
1091}
1092
1093static void minimize_stretch_iteration(bContext *C, wmOperator *op, bool interactive)
1094{
1095 MinStretch *ms = static_cast<MinStretch *>(op->customdata);
1096 ScrArea *area = CTX_wm_area(C);
1097 const Scene *scene = CTX_data_scene(C);
1098 ToolSettings *ts = scene->toolsettings;
1099 const bool synced_selection = (ts->uv_flag & UV_SYNC_SELECTION) != 0;
1100
1103
1104 ms->i++;
1105 RNA_int_set(op->ptr, "iterations", ms->i);
1106
1107 if (interactive && (BLI_time_now_seconds() - ms->lasttime > 0.5)) {
1108 char str[UI_MAX_DRAW_STR];
1109
1111
1112 if (area) {
1113 SNPRINTF(str, IFACE_("Minimize Stretch. Blend %.2f"), ms->blend);
1114 ED_area_status_text(area, str);
1115 ED_workspace_status_text(C, IFACE_("Press + and -, or scroll wheel to set blending"));
1116 }
1117
1119
1120 for (Object *obedit : ms->objects_edit) {
1122
1123 if (synced_selection && (em->bm->totfacesel == 0)) {
1124 continue;
1125 }
1126
1127 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
1129 }
1130 }
1131}
1132
1133static void minimize_stretch_exit(bContext *C, wmOperator *op, bool cancel)
1134{
1135 MinStretch *ms = static_cast<MinStretch *>(op->customdata);
1136 ScrArea *area = CTX_wm_area(C);
1137 const Scene *scene = CTX_data_scene(C);
1138 ToolSettings *ts = scene->toolsettings;
1139 const bool synced_selection = (ts->uv_flag & UV_SYNC_SELECTION) != 0;
1140
1141 ED_area_status_text(area, nullptr);
1142 ED_workspace_status_text(C, nullptr);
1143
1144 if (ms->timer) {
1146 }
1147
1148 if (cancel) {
1150 }
1151 else {
1153 }
1154
1156 delete (ms->handle);
1157
1158 for (Object *obedit : ms->objects_edit) {
1160
1161 if (synced_selection && (em->bm->totfacesel == 0)) {
1162 continue;
1163 }
1164
1165 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
1167 }
1168
1169 MEM_delete(ms);
1170 op->customdata = nullptr;
1171}
1172
1174{
1175 int i, iterations;
1176
1177 if (!minimize_stretch_init(C, op)) {
1178 return OPERATOR_CANCELLED;
1179 }
1180
1181 iterations = RNA_int_get(op->ptr, "iterations");
1182 for (i = 0; i < iterations; i++) {
1183 minimize_stretch_iteration(C, op, false);
1184 }
1185 minimize_stretch_exit(C, op, false);
1186
1187 return OPERATOR_FINISHED;
1188}
1189
1191 wmOperator *op,
1192 const wmEvent * /*event*/)
1193{
1194 if (!minimize_stretch_init(C, op)) {
1195 return OPERATOR_CANCELLED;
1196 }
1197
1198 minimize_stretch_iteration(C, op, true);
1199
1200 MinStretch *ms = static_cast<MinStretch *>(op->customdata);
1203
1205}
1206
1208{
1209 MinStretch *ms = static_cast<MinStretch *>(op->customdata);
1210
1211 switch (event->type) {
1212 case EVT_ESCKEY:
1213 case RIGHTMOUSE:
1214 minimize_stretch_exit(C, op, true);
1215 return OPERATOR_CANCELLED;
1216 case EVT_RETKEY:
1217 case EVT_PADENTER:
1218 case LEFTMOUSE:
1219 minimize_stretch_exit(C, op, false);
1220 return OPERATOR_FINISHED;
1221 case EVT_PADPLUSKEY:
1222 case WHEELUPMOUSE:
1223 if (event->val == KM_PRESS) {
1224 if (ms->blend < 0.95f) {
1225 ms->blend += 0.1f;
1226 ms->lasttime = 0.0f;
1227 RNA_float_set(op->ptr, "blend", ms->blend);
1228 minimize_stretch_iteration(C, op, true);
1229 }
1230 }
1231 break;
1232 case EVT_PADMINUS:
1233 case WHEELDOWNMOUSE:
1234 if (event->val == KM_PRESS) {
1235 if (ms->blend > 0.05f) {
1236 ms->blend -= 0.1f;
1237 ms->lasttime = 0.0f;
1238 RNA_float_set(op->ptr, "blend", ms->blend);
1239 minimize_stretch_iteration(C, op, true);
1240 }
1241 }
1242 break;
1243 case TIMER:
1244 if (ms->timer == event->customdata) {
1245 double start = BLI_time_now_seconds();
1246
1247 do {
1248 minimize_stretch_iteration(C, op, true);
1249 } while (BLI_time_now_seconds() - start < 0.01);
1250 }
1251 break;
1252 default: {
1253 break;
1254 }
1255 }
1256
1257 if (ms->iterations && ms->i >= ms->iterations) {
1258 minimize_stretch_exit(C, op, false);
1259 return OPERATOR_FINISHED;
1260 }
1261
1263}
1264
1266{
1267 minimize_stretch_exit(C, op, true);
1268}
1269
1271{
1272 /* identifiers */
1273 ot->name = "Minimize Stretch";
1274 ot->idname = "UV_OT_minimize_stretch";
1276 ot->description = "Reduce UV stretching by relaxing angles";
1277
1278 /* API callbacks. */
1279 ot->exec = minimize_stretch_exec;
1280 ot->invoke = minimize_stretch_invoke;
1281 ot->modal = minimize_stretch_modal;
1282 ot->cancel = minimize_stretch_cancel;
1283 ot->poll = ED_operator_uvedit;
1284
1285 /* properties */
1286 RNA_def_boolean(ot->srna,
1287 "fill_holes",
1288 true,
1289 "Fill Holes",
1290 "Virtually fill holes in mesh before unwrapping, to better avoid overlaps and "
1291 "preserve symmetry");
1293 "blend",
1294 0.0f,
1295 0.0f,
1296 1.0f,
1297 "Blend",
1298 "Blend factor between stretch minimized and original",
1299 0.0f,
1300 1.0f);
1301 RNA_def_int(ot->srna,
1302 "iterations",
1303 0,
1304 0,
1305 INT_MAX,
1306 "Iterations",
1307 "Number of iterations to run, 0 is unlimited when run interactively",
1308 0,
1309 100);
1310}
1311
1313
1315 const float matrix[2][2], /* Scale and rotation. */
1316 const float pre_translate[2] /* (pre) Translation. */
1317)
1318{
1319 /* Use a pre-transform to compute `A * (x+b)`
1320 *
1321 * \note Ordinarily, we'd use a post_transform like `A * x + b`
1322 * In general, post-transforms are easier to work with when using homogenous co-ordinates.
1323 *
1324 * When UV mapping into the unit square, post-transforms can lose precision on small islands.
1325 * Instead we're using a pre-transform to maintain precision.
1326 *
1327 * To convert post-transform to pre-transform, use `A * x + b == A * (x + c), c = A^-1 * b`
1328 */
1329
1330 const int cd_loop_uv_offset = island->offsets.uv;
1331 const int faces_len = island->faces_len;
1332 for (int i = 0; i < faces_len; i++) {
1333 BMFace *f = island->faces[i];
1334 BMLoop *l;
1335 BMIter iter;
1336 BM_ITER_ELEM (l, &iter, f, BM_LOOPS_OF_FACE) {
1337 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
1338 blender::geometry::mul_v2_m2_add_v2v2(luv, matrix, luv, pre_translate);
1339 }
1340 }
1341}
1342
1346static float uv_nearest_image_tile_distance(const Image *image,
1347 const float coords[2],
1348 float nearest_tile_co[2])
1349{
1350 BKE_image_find_nearest_tile_with_offset(image, coords, nearest_tile_co);
1351
1352 /* Add 0.5 to get tile center coordinates. */
1353 float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]};
1354 add_v2_fl(nearest_tile_center_co, 0.5f);
1355
1356 return len_squared_v2v2(coords, nearest_tile_center_co);
1357}
1358
1362static float uv_nearest_grid_tile_distance(const int udim_grid[2],
1363 const float coords[2],
1364 float nearest_tile_co[2])
1365{
1366 const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])};
1367
1368 if (coords[0] > udim_grid[0]) {
1369 nearest_tile_co[0] = udim_grid[0] - 1;
1370 }
1371 else if (coords[0] < 0) {
1372 nearest_tile_co[0] = 0;
1373 }
1374 else {
1375 nearest_tile_co[0] = coords_floor[0];
1376 }
1377
1378 if (coords[1] > udim_grid[1]) {
1379 nearest_tile_co[1] = udim_grid[1] - 1;
1380 }
1381 else if (coords[1] < 0) {
1382 nearest_tile_co[1] = 0;
1383 }
1384 else {
1385 nearest_tile_co[1] = coords_floor[1];
1386 }
1387
1388 /* Add 0.5 to get tile center coordinates. */
1389 float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]};
1390 add_v2_fl(nearest_tile_center_co, 0.5f);
1391
1392 return len_squared_v2v2(coords, nearest_tile_center_co);
1393}
1394
1395static bool island_has_pins(const Scene *scene,
1396 FaceIsland *island,
1398{
1399 const bool pin_unselected = params->pin_unselected;
1400 const bool only_selected_faces = params->only_selected_faces;
1401 BMLoop *l;
1402 BMIter iter;
1403 const int pin_offset = island->offsets.pin;
1404 for (int i = 0; i < island->faces_len; i++) {
1405 BMFace *efa = island->faces[i];
1406 if (pin_unselected && only_selected_faces && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
1407 return true;
1408 }
1409 BM_ITER_ELEM (l, &iter, island->faces[i], BM_LOOPS_OF_FACE) {
1410 if (BM_ELEM_CD_GET_BOOL(l, pin_offset)) {
1411 return true;
1412 }
1413 if (pin_unselected && !uvedit_uv_select_test(scene, l, island->offsets)) {
1414 return true;
1415 }
1416 }
1417 }
1418 return false;
1419}
1420
1435static void uvedit_pack_islands_multi(const Scene *scene,
1436 const Span<Object *> objects,
1437 BMesh **bmesh_override,
1438 const SpaceImage *udim_source_closest,
1439 const bool original_selection,
1440 const bool notify_wm,
1442{
1443 blender::Vector<FaceIsland *> island_vector;
1444 blender::Vector<bool> pinned_vector;
1445
1446 for (const int ob_index : objects.index_range()) {
1447 Object *obedit = objects[ob_index];
1448 BMesh *bm = nullptr;
1449 if (bmesh_override) {
1450 /* NOTE: obedit is still required for aspect ratio and ID_RECALC_GEOMETRY. */
1451 bm = bmesh_override[ob_index];
1452 }
1453 else {
1455 bm = em->bm;
1456 }
1457 BLI_assert(bm);
1458
1459 const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
1460 if (offsets.uv == -1) {
1461 continue;
1462 }
1463
1464 const float aspect_y = params->correct_aspect ? ED_uvedit_get_aspect_y(obedit) : 1.0f;
1465
1466 bool only_selected_faces = params->only_selected_faces;
1467 bool only_selected_uvs = params->only_selected_uvs;
1468 const bool ignore_pinned = params->pin_method == ED_UVPACK_PIN_IGNORE;
1469 if (ignore_pinned && params->pin_unselected) {
1470 only_selected_faces = false;
1471 only_selected_uvs = false;
1472 }
1473 ListBase island_list = {nullptr};
1475 bm,
1476 &island_list,
1477 only_selected_faces,
1478 only_selected_uvs,
1479 params->use_seams,
1480 aspect_y,
1481 offsets);
1482
1483 /* Remove from linked list and append to blender::Vector. */
1484 LISTBASE_FOREACH_MUTABLE (FaceIsland *, island, &island_list) {
1485 BLI_remlink(&island_list, island);
1486 const bool pinned = island_has_pins(scene, island, params);
1487 if (ignore_pinned && pinned) {
1488 MEM_freeN(island->faces);
1489 MEM_freeN(island);
1490 continue;
1491 }
1492 island_vector.append(island);
1493 pinned_vector.append(pinned);
1494 }
1495 }
1496
1497 if (island_vector.is_empty()) {
1498 return;
1499 }
1500
1501 /* Coordinates of bounding box containing all selected UVs. */
1502 float selection_min_co[2], selection_max_co[2];
1503 INIT_MINMAX2(selection_min_co, selection_max_co);
1504
1505 for (int index = 0; index < island_vector.size(); index++) {
1506 FaceIsland *island = island_vector[index];
1507
1508 for (int i = 0; i < island->faces_len; i++) {
1509 BMFace *f = island->faces[i];
1510 BM_face_uv_minmax(f, selection_min_co, selection_max_co, island->offsets.uv);
1511 }
1512 }
1513
1514 /* Center of bounding box containing all selected UVs. */
1515 float selection_center[2];
1516 mid_v2_v2v2(selection_center, selection_min_co, selection_max_co);
1517
1518 if (original_selection) {
1519 /* Protect against degenerate source AABB. */
1520 if ((selection_max_co[0] - selection_min_co[0]) * (selection_max_co[1] - selection_min_co[1]) >
1521 1e-40f)
1522 {
1523 copy_v2_v2(params->udim_base_offset, selection_min_co);
1524 params->target_extent = selection_max_co[1] - selection_min_co[1];
1525 params->target_aspect_y = (selection_max_co[0] - selection_min_co[0]) /
1526 (selection_max_co[1] - selection_min_co[1]);
1527 }
1528 }
1529
1531 Heap *heap = BLI_heap_new();
1532
1534 for (int i = 0; i < island_vector.size(); i++) {
1535 FaceIsland *face_island = island_vector[i];
1537 pack_island->caller_index = i;
1538 pack_island->aspect_y = face_island->aspect_y;
1539 pack_island->pinned = pinned_vector[i];
1540 pack_island_vector.append(pack_island);
1541
1542 for (int i = 0; i < face_island->faces_len; i++) {
1543 BMFace *f = face_island->faces[i];
1544
1545 /* Storage. */
1547
1548 /* Obtain UVs of face. */
1549 BMLoop *l;
1550 BMIter iter;
1551 int j;
1552 BM_ITER_ELEM_INDEX (l, &iter, f, BM_LOOPS_OF_FACE, j) {
1553 copy_v2_v2(uvs[j], BM_ELEM_CD_GET_FLOAT_P(l, face_island->offsets.uv));
1554 }
1555
1556 pack_island->add_polygon(uvs, arena, heap);
1557
1558 BLI_memarena_clear(arena);
1559 }
1560 }
1561 BLI_heap_free(heap, nullptr);
1562 BLI_memarena_free(arena);
1563
1564 const float scale = pack_islands(pack_island_vector, *params);
1565 const bool is_cancelled = params->isCancelled();
1566
1567 float base_offset[2] = {0.0f, 0.0f};
1568 copy_v2_v2(base_offset, params->udim_base_offset);
1569
1570 if (udim_source_closest) {
1571 const Image *image = udim_source_closest->image;
1572 const int *udim_grid = udim_source_closest->tile_grid_shape;
1573 /* Check if selection lies on a valid UDIM grid tile. */
1574 bool is_valid_udim = uv_coords_isect_udim(image, udim_grid, selection_center);
1575 if (is_valid_udim) {
1576 base_offset[0] = floorf(selection_center[0]);
1577 base_offset[1] = floorf(selection_center[1]);
1578 }
1579 /* If selection doesn't lie on any UDIM then find the closest UDIM grid or image tile. */
1580 else {
1581 float nearest_image_tile_co[2] = {FLT_MAX, FLT_MAX};
1582 float nearest_image_tile_dist = FLT_MAX, nearest_grid_tile_dist = FLT_MAX;
1583 if (image) {
1584 nearest_image_tile_dist = uv_nearest_image_tile_distance(
1585 image, selection_center, nearest_image_tile_co);
1586 }
1587
1588 float nearest_grid_tile_co[2] = {0.0f, 0.0f};
1589 nearest_grid_tile_dist = uv_nearest_grid_tile_distance(
1590 udim_grid, selection_center, nearest_grid_tile_co);
1591
1592 base_offset[0] = (nearest_image_tile_dist < nearest_grid_tile_dist) ?
1593 nearest_image_tile_co[0] :
1594 nearest_grid_tile_co[0];
1595 base_offset[1] = (nearest_image_tile_dist < nearest_grid_tile_dist) ?
1596 nearest_image_tile_co[1] :
1597 nearest_grid_tile_co[1];
1598 }
1599 }
1600
1601 float matrix[2][2];
1602 float matrix_inverse[2][2];
1603 float pre_translate[2];
1604 for (const int64_t i : pack_island_vector.index_range()) {
1605 if (is_cancelled) {
1606 continue;
1607 }
1608 blender::geometry::PackIsland *pack_island = pack_island_vector[i];
1609 FaceIsland *island = island_vector[pack_island->caller_index];
1610 const float island_scale = pack_island->can_scale_(*params) ? scale : 1.0f;
1611 pack_island->build_transformation(island_scale, pack_island->angle, matrix);
1612 invert_m2_m2(matrix_inverse, matrix);
1613
1614 /* Add base_offset, post transform. */
1615 if (!pinned_vector[i] || params->pin_method != ED_UVPACK_PIN_LOCK_ALL) {
1616 mul_v2_m2v2(pre_translate, matrix_inverse, base_offset);
1617
1618 /* Add pre-translation from #pack_islands. */
1619 pre_translate[0] += pack_island->pre_translate.x;
1620 pre_translate[1] += pack_island->pre_translate.y;
1621
1622 /* Perform the transformation. */
1623 island_uv_transform(island, matrix, pre_translate);
1624 }
1625 }
1626
1627 for (const int64_t i : pack_island_vector.index_range()) {
1628 blender::geometry::PackIsland *pack_island = pack_island_vector[i];
1629 /* Cleanup memory. */
1630 pack_island_vector[i] = nullptr;
1631 delete pack_island;
1632 }
1633
1634 if (notify_wm && !is_cancelled) {
1635 for (Object *obedit : objects) {
1636 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
1637 WM_main_add_notifier(NC_GEOM | ND_DATA, obedit->data);
1638 }
1639 }
1640
1641 for (FaceIsland *island : island_vector) {
1642 MEM_freeN(island->faces);
1643 MEM_freeN(island);
1644 }
1645}
1646
1647/* -------------------------------------------------------------------- */
1650
1651/* TODO: support this, interaction with the job-system needs to be handled carefully. */
1652// #define USE_INTERACTIVE_PACK
1653
1654/* Packing targets. */
1655enum {
1659};
1660
1676
1677static void pack_islands_startjob(void *pidv, wmJobWorkerStatus *worker_status)
1678{
1679 worker_status->progress = 0.02f;
1680
1681 UVPackIslandsData *pid = static_cast<UVPackIslandsData *>(pidv);
1682
1683 pid->pack_island_params.stop = &worker_status->stop;
1684 pid->pack_island_params.do_update = &worker_status->do_update;
1685 pid->pack_island_params.progress = &worker_status->progress;
1686
1688 pid->objects,
1689 nullptr,
1690 (pid->udim_source == PACK_UDIM_SRC_CLOSEST) ? pid->sima : nullptr,
1692 !pid->use_job,
1693 &pid->pack_island_params);
1694
1695 worker_status->progress = 0.99f;
1696 worker_status->do_update = true;
1697}
1698
1699static void pack_islands_endjob(void *pidv)
1700{
1701 UVPackIslandsData *pid = static_cast<UVPackIslandsData *>(pidv);
1702 for (Object *obedit : pid->objects) {
1703 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
1705 }
1707
1708 if (pid->undo_str) {
1710 }
1711}
1712
1713static void pack_islands_freejob(void *pidv)
1714{
1715 WM_cursor_wait(false);
1716 UVPackIslandsData *pid = static_cast<UVPackIslandsData *>(pidv);
1717 WM_set_locked_interface(pid->wm, false);
1718 MEM_delete(pid);
1719}
1720
1722{
1723 ViewLayer *view_layer = CTX_data_view_layer(C);
1724 const Scene *scene = CTX_data_scene(C);
1725 const SpaceImage *sima = CTX_wm_space_image(C);
1726
1728 options.topology_from_uvs = true;
1729 options.only_selected_faces = true;
1730 options.only_selected_uvs = true;
1731 options.fill_holes = false;
1732 options.correct_aspect = true;
1733
1735 scene, view_layer, CTX_wm_view3d(C));
1736
1737 /* Early exit in case no UVs are selected. */
1738 if (!uvedit_have_selection_multi(scene, objects, &options)) {
1739 return OPERATOR_CANCELLED;
1740 }
1741
1742 /* RNA props */
1743 const int udim_source = RNA_enum_get(op->ptr, "udim_source");
1744 if (RNA_struct_property_is_set(op->ptr, "margin")) {
1745 scene->toolsettings->uvcalc_margin = RNA_float_get(op->ptr, "margin");
1746 }
1747 else {
1748 RNA_float_set(op->ptr, "margin", scene->toolsettings->uvcalc_margin);
1749 }
1750
1751 UVPackIslandsData *pid = MEM_new<UVPackIslandsData>(__func__);
1752 pid->use_job = op->flag & OP_IS_INVOKE;
1753 pid->scene = scene;
1754 pid->objects = std::move(objects);
1755 pid->sima = sima;
1756 pid->udim_source = udim_source;
1757 pid->wm = CTX_wm_manager(C);
1758
1760 {
1761 /* Call default constructor and copy the defaults. */
1763 pack_island_params = default_params;
1764 }
1765
1766 pack_island_params.setFromUnwrapOptions(options);
1767 if (RNA_boolean_get(op->ptr, "rotate")) {
1768 pack_island_params.rotate_method = eUVPackIsland_RotationMethod(
1769 RNA_enum_get(op->ptr, "rotate_method"));
1770 }
1771 else {
1772 pack_island_params.rotate_method = ED_UVPACK_ROTATION_NONE;
1773 }
1774 pack_island_params.scale_to_fit = RNA_boolean_get(op->ptr, "scale");
1775 pack_island_params.merge_overlap = RNA_boolean_get(op->ptr, "merge_overlap");
1776
1777 if (RNA_boolean_get(op->ptr, "pin")) {
1778 pack_island_params.pin_method = eUVPackIsland_PinMethod(RNA_enum_get(op->ptr, "pin_method"));
1779 }
1780 else {
1781 pack_island_params.pin_method = ED_UVPACK_PIN_NONE;
1782 }
1783
1784 pack_island_params.margin_method = eUVPackIsland_MarginMethod(
1785 RNA_enum_get(op->ptr, "margin_method"));
1786 pack_island_params.margin = RNA_float_get(op->ptr, "margin");
1787 pack_island_params.shape_method = eUVPackIsland_ShapeMethod(
1788 RNA_enum_get(op->ptr, "shape_method"));
1789
1790 if (udim_source == PACK_UDIM_SRC_ACTIVE) {
1791 pack_island_params.setUDIMOffsetFromSpaceImage(sima);
1792 }
1793
1794 if (pid->use_job) {
1795 /* Setup job. */
1796 if (pid->wm->op_undo_depth == 0) {
1797 /* The job must do its own undo push. */
1798 pid->undo_context = C;
1799 pid->undo_str = op->type->name;
1800 }
1801
1802 wmJob *wm_job = WM_jobs_get(
1803 pid->wm, CTX_wm_window(C), scene, "Packing UVs", WM_JOB_PROGRESS, WM_JOB_TYPE_UV_PACK);
1805 WM_jobs_timer(wm_job, 0.1, 0, 0);
1808
1809 WM_cursor_wait(true);
1810 G.is_break = false;
1811 WM_jobs_start(CTX_wm_manager(C), wm_job);
1812 return OPERATOR_FINISHED;
1813 }
1814
1815 wmJobWorkerStatus worker_status = {};
1816 pack_islands_startjob(pid, &worker_status);
1819
1820 return OPERATOR_FINISHED;
1821}
1822
1825 "SCALED",
1826 0,
1827 "Scaled",
1828 "Use scale of existing UVs to multiply margin"},
1829 {ED_UVPACK_MARGIN_ADD, "ADD", 0, "Add", "Just add the margin, ignoring any UV scale"},
1831 "FRACTION",
1832 0,
1833 "Fraction",
1834 "Specify a precise fraction of final UV output"},
1835 {0, nullptr, 0, nullptr, nullptr},
1836};
1837
1839 {ED_UVPACK_ROTATION_ANY, "ANY", 0, "Any", "Any angle is allowed for rotation"},
1841 "CARDINAL",
1842 0,
1843 "Cardinal",
1844 "Only 90 degree rotations are allowed"},
1846
1847#define PACK_ROTATE_METHOD_AXIS_ALIGNED_OFFSET 3
1849 "AXIS_ALIGNED",
1850 0,
1851 "Axis-aligned",
1852 "Rotated to a minimal rectangle, either vertical or horizontal"},
1854 "AXIS_ALIGNED_X",
1855 0,
1856 "Axis-aligned (Horizontal)",
1857 "Rotate islands to be aligned horizontally"},
1859 "AXIS_ALIGNED_Y",
1860 0,
1861 "Axis-aligned (Vertical)",
1862 "Rotate islands to be aligned vertically"},
1863 {0, nullptr, 0, nullptr, nullptr},
1864};
1865
1867 {ED_UVPACK_SHAPE_CONCAVE, "CONCAVE", 0, "Exact Shape (Concave)", "Uses exact geometry"},
1868 {ED_UVPACK_SHAPE_CONVEX, "CONVEX", 0, "Boundary Shape (Convex)", "Uses convex hull"},
1870 {ED_UVPACK_SHAPE_AABB, "AABB", 0, "Bounding Box", "Uses bounding boxes"},
1871 {0, nullptr, 0, nullptr, nullptr},
1872};
1873
1881 {ED_UVPACK_PIN_LOCK_SCALE, "SCALE", 0, "Scale", "Pinned islands won't rescale"},
1882 {ED_UVPACK_PIN_LOCK_ROTATION, "ROTATION", 0, "Rotation", "Pinned islands won't rotate"},
1884 "ROTATION_SCALE",
1885 0,
1886 "Rotation and Scale",
1887 "Pinned islands will translate only"},
1888 {ED_UVPACK_PIN_LOCK_ALL, "LOCKED", 0, "All", "Pinned islands are locked in place"},
1889 {0, nullptr, 0, nullptr, nullptr},
1890};
1891
1892static void uv_pack_islands_ui(bContext * /*C*/, wmOperator *op)
1893{
1894 uiLayout *layout = op->layout;
1895 uiLayoutSetPropSep(layout, true);
1896 uiLayoutSetPropDecorate(layout, false);
1897 layout->prop(op->ptr, "shape_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1898 layout->prop(op->ptr, "scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1899 {
1900 layout->prop(op->ptr, "rotate", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1901 uiLayout *sub = &layout->row(true);
1902 uiLayoutSetActive(sub, RNA_boolean_get(op->ptr, "rotate"));
1903 sub->prop(op->ptr, "rotate_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1904 layout->separator();
1905 }
1906 layout->prop(op->ptr, "margin_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1907 layout->prop(op->ptr, "margin", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1908 layout->separator();
1909 {
1910 layout->prop(op->ptr, "pin", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1911 uiLayout *sub = &layout->row(true);
1912 uiLayoutSetActive(sub, RNA_boolean_get(op->ptr, "pin"));
1913 sub->prop(op->ptr, "pin_method", UI_ITEM_NONE, IFACE_("Lock Method"), ICON_NONE);
1914 layout->separator();
1915 }
1916 layout->prop(op->ptr, "merge_overlap", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1917 layout->prop(op->ptr, "udim_source", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1918 layout->separator();
1919}
1920
1922{
1923 return WM_operator_props_popup_confirm_ex(C, op, event, IFACE_("Pack Islands"), IFACE_("Pack"));
1924}
1925
1927{
1928 static const EnumPropertyItem pack_target[] = {
1929 {PACK_UDIM_SRC_CLOSEST, "CLOSEST_UDIM", 0, "Closest UDIM", "Pack islands to closest UDIM"},
1931 "ACTIVE_UDIM",
1932 0,
1933 "Active UDIM",
1934 "Pack islands to active UDIM image tile or UDIM grid tile where 2D cursor is located"},
1936 "ORIGINAL_AABB",
1937 0,
1938 "Original bounding box",
1939 "Pack to starting bounding box of islands"},
1940 {0, nullptr, 0, nullptr, nullptr},
1941 };
1942 /* identifiers */
1943 ot->name = "Pack Islands";
1944 ot->idname = "UV_OT_pack_islands";
1945 ot->description =
1946 "Transform all islands so that they fill up the UV/UDIM space as much as possible";
1947
1948#ifdef USE_INTERACTIVE_PACK
1949 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1950#else
1951 /* The operator will handle undo, so the job system can push() it after the job completes. */
1952 ot->flag = OPTYPE_REGISTER;
1953#endif
1954
1955 /* API callbacks. */
1956 ot->exec = pack_islands_exec;
1957
1958#ifdef USE_INTERACTIVE_PACK
1960#else
1961 ot->invoke = uv_pack_islands_invoke;
1962#endif
1963 ot->ui = uv_pack_islands_ui;
1964 ot->poll = ED_operator_uvedit;
1965
1966 /* properties */
1967 RNA_def_enum(ot->srna, "udim_source", pack_target, PACK_UDIM_SRC_CLOSEST, "Pack to", "");
1968 RNA_def_boolean(ot->srna, "rotate", true, "Rotate", "Rotate islands to improve layout");
1969 RNA_def_enum(ot->srna,
1970 "rotate_method",
1973 "Rotation Method",
1974 "");
1975 RNA_def_boolean(ot->srna, "scale", true, "Scale", "Scale islands to fill unit square");
1977 ot->srna, "merge_overlap", false, "Merge Overlapping", "Overlapping islands stick together");
1978 RNA_def_enum(ot->srna,
1979 "margin_method",
1982 "Margin Method",
1983 "");
1985 ot->srna, "margin", 0.001f, 0.0f, 1.0f, "Margin", "Space between islands", 0.0f, 1.0f);
1986 RNA_def_boolean(ot->srna,
1987 "pin",
1988 false,
1989 "Lock Pinned Islands",
1990 "Constrain islands containing any pinned UV's");
1991 RNA_def_enum(ot->srna,
1992 "pin_method",
1995 "Pin Method",
1996 "");
1997 RNA_def_enum(ot->srna,
1998 "shape_method",
2001 "Shape Method",
2002 "");
2003}
2004
2006
2007/* -------------------------------------------------------------------- */
2010
2012{
2013 const Scene *scene = CTX_data_scene(C);
2014 ViewLayer *view_layer = CTX_data_view_layer(C);
2015 ToolSettings *ts = scene->toolsettings;
2016 const bool synced_selection = (ts->uv_flag & UV_SYNC_SELECTION) != 0;
2017
2018 UnwrapOptions options = unwrap_options_get(nullptr, nullptr, ts);
2019 options.topology_from_uvs = true;
2020 options.only_selected_faces = true;
2021 options.only_selected_uvs = true;
2022 options.fill_holes = false;
2023 options.correct_aspect = true;
2024
2026 scene, view_layer, CTX_wm_view3d(C));
2027
2028 if (!uvedit_have_selection_multi(scene, objects, &options)) {
2029 return OPERATOR_CANCELLED;
2030 }
2031
2032 /* RNA props */
2033 const bool scale_uv = RNA_boolean_get(op->ptr, "scale_uv");
2034 const bool shear = RNA_boolean_get(op->ptr, "shear");
2035
2036 ParamHandle *handle = construct_param_handle_multi(scene, objects, &options);
2037 blender::geometry::uv_parametrizer_average(handle, false, scale_uv, shear);
2039 delete (handle);
2040
2041 for (Object *obedit : objects) {
2043
2044 if (synced_selection && (em->bm->totvertsel == 0)) {
2045 continue;
2046 }
2047
2048 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
2049 WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
2050 }
2051 return OPERATOR_FINISHED;
2052}
2053
2055{
2056 /* identifiers */
2057 ot->name = "Average Islands Scale";
2058 ot->idname = "UV_OT_average_islands_scale";
2059 ot->description = "Average the size of separate UV islands, based on their area in 3D space";
2060
2061 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2062
2063 /* API callbacks. */
2065 ot->poll = ED_operator_uvedit;
2066
2067 /* properties */
2068 RNA_def_boolean(ot->srna, "scale_uv", false, "Non-Uniform", "Scale U and V independently");
2069 RNA_def_boolean(ot->srna, "shear", false, "Shear", "Reduce shear within islands");
2070}
2071
2073
2074/* -------------------------------------------------------------------- */
2077
2078static struct {
2082} g_live_unwrap = {nullptr};
2083
2085{
2086 /* NOTE: don't validate the timer, assume the timer passed in is valid. */
2087 return g_live_unwrap.timer == timer;
2088}
2089
2097{
2098 if (g_live_unwrap.timer == nullptr) {
2099 return false;
2100 }
2101 if (BLI_findindex(&wm->timers, g_live_unwrap.timer) != -1) {
2102 return false;
2103 }
2104 g_live_unwrap.timer = nullptr;
2105 return true;
2106}
2107
2108void ED_uvedit_live_unwrap_begin(Scene *scene, Object *obedit, wmWindow *win_modal)
2109{
2110 ParamHandle *handle = nullptr;
2112
2113 if (!ED_uvedit_test(obedit)) {
2114 return;
2115 }
2116
2117 UnwrapOptions options = unwrap_options_get(nullptr, obedit, scene->toolsettings);
2118 options.topology_from_uvs = false;
2119 options.only_selected_faces = false;
2120 options.only_selected_uvs = false;
2121
2122 if (options.use_subsurf) {
2123 handle = construct_param_handle_subsurfed(scene, obedit, em, &options, nullptr);
2124 }
2125 else {
2126 handle = construct_param_handle(scene, obedit, em->bm, &options, nullptr);
2127 }
2128
2129 if (options.use_slim) {
2130 options.slim.no_flip = false;
2131 options.slim.skip_init = true;
2133
2134 if (win_modal) {
2135 wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
2136 /* Clear in the unlikely event this is still set. */
2138 BLI_assert(!g_live_unwrap.timer);
2139 g_live_unwrap.timer = WM_event_timer_add(wm, win_modal, TIMER, 0.01f);
2140 }
2141 }
2142 else {
2144 }
2145
2146 /* Create or increase size of g_live_unwrap.handles array */
2147 if (g_live_unwrap.handles == nullptr) {
2148 g_live_unwrap.len_alloc = 32;
2150 "uvedit_live_unwrap_liveHandles");
2151 g_live_unwrap.len = 0;
2152 }
2153 if (g_live_unwrap.len >= g_live_unwrap.len_alloc) {
2154 g_live_unwrap.len_alloc *= 2;
2155 g_live_unwrap.handles = static_cast<ParamHandle **>(
2156 MEM_reallocN(g_live_unwrap.handles, sizeof(ParamHandle *) * g_live_unwrap.len_alloc));
2157 }
2158 g_live_unwrap.handles[g_live_unwrap.len] = handle;
2159 g_live_unwrap.len++;
2160}
2161
2163{
2164 if (g_live_unwrap.handles) {
2165 for (int i = 0; i < g_live_unwrap.len; i++) {
2166 if (uv_parametrizer_is_slim(g_live_unwrap.handles[i])) {
2168 }
2169 else {
2171 }
2172
2174 }
2175 }
2176}
2177
2178void ED_uvedit_live_unwrap_end(const bool cancel)
2179{
2180 if (g_live_unwrap.timer) {
2181 wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
2183 if (g_live_unwrap.timer) {
2184 wmWindow *win = g_live_unwrap.timer->win;
2186 g_live_unwrap.timer = nullptr;
2187 }
2188 }
2189
2190 if (g_live_unwrap.handles) {
2191 for (int i = 0; i < g_live_unwrap.len; i++) {
2192 if (uv_parametrizer_is_slim(g_live_unwrap.handles[i])) {
2194 }
2195 else {
2197 }
2198
2199 if (cancel) {
2201 }
2202 delete (g_live_unwrap.handles[i]);
2203 }
2204 MEM_freeN(g_live_unwrap.handles);
2205 g_live_unwrap.handles = nullptr;
2206 g_live_unwrap.len = 0;
2207 g_live_unwrap.len_alloc = 0;
2208 }
2209}
2210
2212
2213/* -------------------------------------------------------------------- */
2216
2217#define VIEW_ON_EQUATOR 0
2218#define VIEW_ON_POLES 1
2219#define ALIGN_TO_OBJECT 2
2220
2221#define POLAR_ZX 0
2222#define POLAR_ZY 1
2223
2224enum {
2226 FAN = 1,
2227};
2228
2229static void uv_map_transform_calc_bounds(BMEditMesh *em, float r_min[3], float r_max[3])
2230{
2231 BMFace *efa;
2232 BMIter iter;
2233 INIT_MINMAX(r_min, r_max);
2234 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2236 BM_face_calc_bounds_expand(efa, r_min, r_max);
2237 }
2238 }
2239}
2240
2241static void uv_map_transform_calc_center_median(BMEditMesh *em, float r_center[3])
2242{
2243 BMFace *efa;
2244 BMIter iter;
2245 uint center_accum_num = 0;
2246 zero_v3(r_center);
2247 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2249 float center[3];
2250 BM_face_calc_center_median(efa, center);
2251 add_v3_v3(r_center, center);
2252 center_accum_num += 1;
2253 }
2254 }
2255 mul_v3_fl(r_center, 1.0f / float(center_accum_num));
2256}
2257
2258static void uv_map_transform_center(const Scene *scene,
2259 View3D *v3d,
2260 Object *ob,
2261 BMEditMesh *em,
2262 float r_center[3],
2263 float r_bounds[2][3])
2264{
2265 /* only operates on the edit object - this is all that's needed now */
2266 const int around = (v3d) ? scene->toolsettings->transform_pivot_point :
2268
2269 float bounds[2][3];
2270 INIT_MINMAX(bounds[0], bounds[1]);
2271 bool is_minmax_set = false;
2272
2273 switch (around) {
2274 case V3D_AROUND_CENTER_BOUNDS: /* bounding box center */
2275 {
2277 is_minmax_set = true;
2278 mid_v3_v3v3(r_center, bounds[0], bounds[1]);
2279 break;
2280 }
2283 break;
2284 }
2285 case V3D_AROUND_CURSOR: /* cursor center */
2286 {
2287 invert_m4_m4(ob->runtime->world_to_object.ptr(), ob->object_to_world().ptr());
2288 mul_v3_m4v3(r_center, ob->world_to_object().ptr(), scene->cursor.location);
2289 break;
2290 }
2291 case V3D_AROUND_ACTIVE: {
2292 BMEditSelection ese;
2293 if (BM_select_history_active_get(em->bm, &ese)) {
2294 BM_editselection_center(&ese, r_center);
2295 break;
2296 }
2298 }
2299 case V3D_AROUND_LOCAL_ORIGINS: /* object center */
2300 default:
2301 zero_v3(r_center);
2302 break;
2303 }
2304
2305 /* if this is passed, always set! */
2306 if (r_bounds) {
2307 if (!is_minmax_set) {
2309 }
2310 copy_v3_v3(r_bounds[0], bounds[0]);
2311 copy_v3_v3(r_bounds[1], bounds[1]);
2312 }
2313}
2314
2315static void uv_map_rotation_matrix_ex(float result[4][4],
2316 RegionView3D *rv3d,
2317 Object *ob,
2318 float upangledeg,
2319 float sideangledeg,
2320 float radius,
2321 const float offset[4])
2322{
2323 float rotup[4][4], rotside[4][4], viewmatrix[4][4], rotobj[4][4];
2324 float sideangle = 0.0f, upangle = 0.0f;
2325
2326 /* get rotation of the current view matrix */
2327 if (rv3d) {
2328 copy_m4_m4(viewmatrix, rv3d->viewmat);
2329 }
2330 else {
2331 unit_m4(viewmatrix);
2332 }
2333
2334 /* but shifting */
2335 zero_v3(viewmatrix[3]);
2336
2337 /* get rotation of the current object matrix */
2338 copy_m4_m4(rotobj, ob->object_to_world().ptr());
2339 zero_v3(rotobj[3]);
2340
2341 /* but shifting */
2342 add_v4_v4(rotobj[3], offset);
2343 rotobj[3][3] = 0.0f;
2344
2345 zero_m4(rotup);
2346 zero_m4(rotside);
2347
2348 /* Compensate front/side.. against opengl x,y,z world definition.
2349 * This is "a sledgehammer to crack a nut" (overkill), a few plus minus 1 will do here.
2350 * I wanted to keep the reason here, so we're rotating. */
2351 sideangle = float(M_PI) * (sideangledeg + 180.0f) / 180.0f;
2352 rotside[0][0] = cosf(sideangle);
2353 rotside[0][1] = -sinf(sideangle);
2354 rotside[1][0] = sinf(sideangle);
2355 rotside[1][1] = cosf(sideangle);
2356 rotside[2][2] = 1.0f;
2357
2358 upangle = float(M_PI) * upangledeg / 180.0f;
2359 rotup[1][1] = cosf(upangle) / radius;
2360 rotup[1][2] = -sinf(upangle) / radius;
2361 rotup[2][1] = sinf(upangle) / radius;
2362 rotup[2][2] = cosf(upangle) / radius;
2363 rotup[0][0] = 1.0f / radius;
2364
2365 /* Calculate transforms. */
2366 mul_m4_series(result, rotup, rotside, viewmatrix, rotobj);
2367}
2368
2369static void uv_map_transform(bContext *C, wmOperator *op, float rotmat[3][3])
2370{
2371 Object *obedit = CTX_data_edit_object(C);
2373
2374 const int align = RNA_enum_get(op->ptr, "align");
2375 const int direction = RNA_enum_get(op->ptr, "direction");
2376 const float radius = RNA_struct_find_property(op->ptr, "radius") ?
2377 RNA_float_get(op->ptr, "radius") :
2378 1.0f;
2379
2380 /* Be compatible to the "old" sphere/cylinder mode. */
2381 if (direction == ALIGN_TO_OBJECT) {
2382 unit_m3(rotmat);
2383
2384 if (align == POLAR_ZY) {
2385 rotmat[0][0] = 0.0f;
2386 rotmat[0][1] = 1.0f;
2387 rotmat[1][0] = -1.0f;
2388 rotmat[1][1] = 0.0f;
2389 }
2390 return;
2391 }
2392
2393 const float up_angle_deg = (direction == VIEW_ON_EQUATOR) ? 90.0f : 0.0f;
2394 const float side_angle_deg = (align == POLAR_ZY) == (direction == VIEW_ON_EQUATOR) ? 90.0f :
2395 0.0f;
2396 const float offset[4] = {0};
2397 float rotmat4[4][4];
2398 uv_map_rotation_matrix_ex(rotmat4, rv3d, obedit, up_angle_deg, side_angle_deg, radius, offset);
2399 copy_m3_m4(rotmat, rotmat4);
2400}
2401
2403{
2404 static const EnumPropertyItem direction_items[] = {
2405 {VIEW_ON_EQUATOR, "VIEW_ON_EQUATOR", 0, "View on Equator", "3D view is on the equator"},
2406 {VIEW_ON_POLES, "VIEW_ON_POLES", 0, "View on Poles", "3D view is on the poles"},
2408 "ALIGN_TO_OBJECT",
2409 0,
2410 "Align to Object",
2411 "Align according to object transform"},
2412 {0, nullptr, 0, nullptr, nullptr},
2413 };
2414 static const EnumPropertyItem align_items[] = {
2415 {POLAR_ZX, "POLAR_ZX", 0, "Polar ZX", "Polar 0 is X"},
2416 {POLAR_ZY, "POLAR_ZY", 0, "Polar ZY", "Polar 0 is Y"},
2417 {0, nullptr, 0, nullptr, nullptr},
2418 };
2419
2420 static const EnumPropertyItem pole_items[] = {
2421 {PINCH, "PINCH", 0, "Pinch", "UVs are pinched at the poles"},
2422 {FAN, "FAN", 0, "Fan", "UVs are fanned at the poles"},
2423 {0, nullptr, 0, nullptr, nullptr},
2424 };
2425
2426 RNA_def_enum(ot->srna,
2427 "direction",
2428 direction_items,
2430 "Direction",
2431 "Direction of the sphere or cylinder");
2432 RNA_def_enum(ot->srna,
2433 "align",
2434 align_items,
2435 POLAR_ZX,
2436 "Align",
2437 "How to determine rotation around the pole");
2438 RNA_def_enum(ot->srna, "pole", pole_items, PINCH, "Pole", "How to handle faces at the poles");
2439 RNA_def_boolean(ot->srna,
2440 "seam",
2441 false,
2442 "Preserve Seams",
2443 "Separate projections by islands isolated by seams");
2444
2445 if (radius) {
2446 RNA_def_float(ot->srna,
2447 "radius",
2448 1.0f,
2449 0.0f,
2450 FLT_MAX,
2451 "Radius",
2452 "Radius of the sphere or cylinder",
2453 0.0001f,
2454 100.0f);
2455 }
2456}
2457
2459 const int cd_loop_uv_offset,
2460 const float aspect_y)
2461{
2462 BLI_assert(aspect_y != 1.0f); /* Nothing to do, should be handled by caller. */
2463 BLI_assert(aspect_y > 0.0f); /* Negative aspect ratios are not supported. */
2464
2465 BMLoop *l;
2466 BMIter iter;
2467 BM_ITER_ELEM (l, &iter, efa, BM_LOOPS_OF_FACE) {
2468 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
2469 if (aspect_y > 1.0f) {
2470 /* Reduce round-off error, i.e. `u = (u - 0.5) / aspect_y + 0.5`. */
2471 luv[0] = luv[0] / aspect_y + (0.5f - 0.5f / aspect_y);
2472 }
2473 else {
2474 /* Reduce round-off error, i.e. `v = (v - 0.5) * aspect_y + 0.5`. */
2475 luv[1] = luv[1] * aspect_y + (0.5f - 0.5f * aspect_y);
2476 }
2477 }
2478}
2479
2481{
2482 const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2);
2483 const float aspect_y = ED_uvedit_get_aspect_y(ob);
2484 if (aspect_y == 1.0f) {
2485 /* Scaling by 1.0 has no effect. */
2486 return;
2487 }
2488 BMFace *efa;
2489 BMIter iter;
2490 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2492 shrink_loop_uv_by_aspect_ratio(efa, cd_loop_uv_offset, aspect_y);
2493 }
2494 }
2495}
2496
2498{
2499 const int materials_num = ob->totcol;
2500 if (materials_num == 0) {
2501 /* Without any materials, there is no aspect_y information and nothing to do. */
2502 return;
2503 }
2504
2505 blender::Array<float, 16> material_aspect_y(materials_num, -1);
2506 /* Lazily initialize aspect ratio for materials. */
2507
2508 const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2);
2509
2510 BMFace *efa;
2511 BMIter iter;
2512 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2513 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
2514 continue;
2515 }
2516
2517 const int material_index = efa->mat_nr;
2518 if (UNLIKELY(material_index < 0 || material_index >= materials_num)) {
2519 /* The index might be for a material slot which is not currently setup. */
2520 continue;
2521 }
2522
2523 float aspect_y = material_aspect_y[material_index];
2524 if (aspect_y == -1.0f) {
2525 /* Lazily initialize aspect ratio for materials. */
2526 float aspx, aspy;
2527 ED_uvedit_get_aspect_from_material(ob, material_index, &aspx, &aspy);
2528 aspect_y = aspx / aspy;
2529 material_aspect_y[material_index] = aspect_y;
2530 }
2531
2532 if (aspect_y == 1.0f) {
2533 /* Scaling by 1.0 has no effect. */
2534 continue;
2535 }
2536 shrink_loop_uv_by_aspect_ratio(efa, cd_loop_uv_offset, aspect_y);
2537 }
2538}
2539
2540#undef VIEW_ON_EQUATOR
2541#undef VIEW_ON_POLES
2542#undef ALIGN_TO_OBJECT
2543
2544#undef POLAR_ZX
2545#undef POLAR_ZY
2546
2548
2549/* -------------------------------------------------------------------- */
2552
2553static void uv_map_clip_correct_properties_ex(wmOperatorType *ot, bool clip_to_bounds)
2554{
2556 /* Optional, since not all unwrapping types need to be clipped. */
2557 if (clip_to_bounds) {
2558 RNA_def_boolean(ot->srna,
2559 "clip_to_bounds",
2560 false,
2561 "Clip to Bounds",
2562 "Clip UV coordinates to bounds after unwrapping");
2563 }
2564 RNA_def_boolean(ot->srna,
2565 "scale_to_bounds",
2566 false,
2567 "Scale to Bounds",
2568 "Scale UV coordinates to bounds after unwrapping");
2569}
2570
2575
2583static void uv_map_clip_correct(const Scene *scene,
2584 const Span<Object *> objects,
2585 wmOperator *op,
2586 bool per_face_aspect,
2587 bool only_selected_uvs)
2588{
2589 BMFace *efa;
2590 BMLoop *l;
2591 BMIter iter, liter;
2592 float dx, dy, min[2], max[2];
2593 const bool correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect");
2594 const bool clip_to_bounds = (RNA_struct_find_property(op->ptr, "clip_to_bounds") &&
2595 RNA_boolean_get(op->ptr, "clip_to_bounds"));
2596 const bool scale_to_bounds = RNA_boolean_get(op->ptr, "scale_to_bounds");
2597
2599
2600 for (Object *ob : objects) {
2602 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
2603
2604 /* Correct for image aspect ratio. */
2605 if (correct_aspect) {
2606 if (per_face_aspect) {
2608 }
2609 else {
2610 correct_uv_aspect(ob, em);
2611 }
2612 }
2613
2614 if (scale_to_bounds) {
2615 /* find uv limits */
2616 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2617 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
2618 continue;
2619 }
2620
2621 if (only_selected_uvs && !uvedit_face_select_test(scene, efa, offsets)) {
2622 continue;
2623 }
2624
2625 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
2626 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
2627 minmax_v2v2_v2(min, max, luv);
2628 }
2629 }
2630 }
2631 else if (clip_to_bounds) {
2632 /* clipping and wrapping */
2633 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2634 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
2635 continue;
2636 }
2637
2638 if (only_selected_uvs && !uvedit_face_select_test(scene, efa, offsets)) {
2639 continue;
2640 }
2641
2642 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
2643 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
2644 clamp_v2(luv, 0.0f, 1.0f);
2645 }
2646 }
2647 }
2648 }
2649
2650 if (scale_to_bounds) {
2651 /* rescale UV to be in 1/1 */
2652 dx = (max[0] - min[0]);
2653 dy = (max[1] - min[1]);
2654
2655 if (dx > 0.0f) {
2656 dx = 1.0f / dx;
2657 }
2658 if (dy > 0.0f) {
2659 dy = 1.0f / dy;
2660 }
2661
2662 if (dx == 1.0f && dy == 1.0f && min[0] == 0.0f && min[1] == 0.0f) {
2663 /* Scaling by 1.0, without translating, has no effect. */
2664 return;
2665 }
2666
2667 for (Object *ob : objects) {
2669 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
2670
2671 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2672 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
2673 continue;
2674 }
2675
2676 if (only_selected_uvs && !uvedit_face_select_test(scene, efa, offsets)) {
2677 continue;
2678 }
2679
2680 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
2681 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
2682
2683 luv[0] = (luv[0] - min[0]) * dx;
2684 luv[1] = (luv[1] - min[1]) * dy;
2685 }
2686 }
2687 }
2688 }
2689}
2690
2692
2693/* -------------------------------------------------------------------- */
2696
2697/* Assumes UV Map exists, doesn't run update functions. */
2698static void uvedit_unwrap(const Scene *scene,
2699 Object *obedit,
2700 const UnwrapOptions *options,
2701 int *r_count_changed,
2702 int *r_count_failed)
2703{
2706 return;
2707 }
2708
2709 bool use_subsurf;
2710 modifier_unwrap_state(obedit, options, &use_subsurf);
2711
2712 ParamHandle *handle;
2713 if (use_subsurf) {
2714 handle = construct_param_handle_subsurfed(scene, obedit, em, options, r_count_failed);
2715 }
2716 else {
2717 handle = construct_param_handle(scene, obedit, em->bm, options, r_count_failed);
2718 }
2719
2720 if (options->use_slim) {
2721 uv_parametrizer_slim_solve(handle, &options->slim, r_count_changed, r_count_failed);
2722 }
2723 else {
2725 blender::geometry::uv_parametrizer_lscm_solve(handle, r_count_changed, r_count_failed);
2727 }
2728
2729 blender::geometry::uv_parametrizer_average(handle, true, false, false);
2730
2732
2733 delete (handle);
2734}
2735
2736static void uvedit_unwrap_multi(const Scene *scene,
2737 const Span<Object *> objects,
2738 const UnwrapOptions *options,
2739 int *r_count_changed = nullptr,
2740 int *r_count_failed = nullptr)
2741{
2742 for (Object *obedit : objects) {
2743 uvedit_unwrap(scene, obedit, options, r_count_changed, r_count_failed);
2744 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
2745 WM_main_add_notifier(NC_GEOM | ND_DATA, obedit->data);
2746 }
2747}
2748
2749void ED_uvedit_live_unwrap(const Scene *scene, const Span<Object *> objects)
2750{
2751 if (scene->toolsettings->edge_mode_live_unwrap) {
2752 UnwrapOptions options = unwrap_options_get(nullptr, nullptr, scene->toolsettings);
2753 options.topology_from_uvs = false;
2754 options.only_selected_faces = false;
2755 options.only_selected_uvs = false;
2756
2757 uvedit_unwrap_multi(scene, objects, &options, nullptr);
2758
2759 blender::geometry::UVPackIsland_Params pack_island_params;
2760 pack_island_params.setFromUnwrapOptions(options);
2761 pack_island_params.rotate_method = ED_UVPACK_ROTATION_ANY;
2762 pack_island_params.pin_method = ED_UVPACK_PIN_IGNORE;
2763 pack_island_params.margin_method = ED_UVPACK_MARGIN_SCALED;
2764 pack_island_params.margin = scene->toolsettings->uvcalc_margin;
2765
2766 uvedit_pack_islands_multi(scene, objects, nullptr, nullptr, false, true, &pack_island_params);
2767 }
2768}
2769
2770enum {
2773};
2774
2776{
2777 ViewLayer *view_layer = CTX_data_view_layer(C);
2778 const Scene *scene = CTX_data_scene(C);
2779 int reported_errors = 0;
2780
2782 scene, view_layer, CTX_wm_view3d(C));
2783
2785
2786 UnwrapOptions options = unwrap_options_get(op, nullptr, nullptr);
2787 options.topology_from_uvs = false;
2788 options.only_selected_faces = true;
2789 options.only_selected_uvs = false;
2790
2791 /* We will report an error unless at least one object
2792 * has the subsurf modifier in the right place. */
2793 bool subsurf_error = options.use_subsurf;
2794
2795 if (CTX_wm_space_image(C)) {
2796 /* Inside the UV Editor, only unwrap selected UVs. */
2797 options.only_selected_uvs = true;
2798 options.pin_unselected = true;
2799 }
2800
2801 if (!uvedit_have_selection_multi(scene, objects, &options)) {
2802 return OPERATOR_CANCELLED;
2803 }
2804
2805 /* add uvs if they don't exist yet */
2806 for (Object *obedit : objects) {
2807 float obsize[3];
2808 bool use_subsurf_final;
2809
2810 if (!uvedit_ensure_uvs(obedit)) {
2811 continue;
2812 }
2813
2814 if (subsurf_error) {
2815 /* Double up the check here but better keep uvedit_unwrap interface simple and not
2816 * pass operator for warning append. */
2817 modifier_unwrap_state(obedit, &options, &use_subsurf_final);
2818 if (use_subsurf_final) {
2819 subsurf_error = false;
2820 }
2821 }
2822
2823 if (reported_errors & (UNWRAP_ERROR_NONUNIFORM | UNWRAP_ERROR_NEGATIVE)) {
2824 continue;
2825 }
2826
2827 mat4_to_size(obsize, obedit->object_to_world().ptr());
2828 if (!(fabsf(obsize[0] - obsize[1]) < 1e-4f && fabsf(obsize[1] - obsize[2]) < 1e-4f)) {
2829 if ((reported_errors & UNWRAP_ERROR_NONUNIFORM) == 0) {
2830 BKE_report(op->reports,
2831 RPT_INFO,
2832 "Object has non-uniform scale, unwrap will operate on a non-scaled version of "
2833 "the mesh");
2834 reported_errors |= UNWRAP_ERROR_NONUNIFORM;
2835 }
2836 }
2837 else if (is_negative_m4(obedit->object_to_world().ptr())) {
2838 if ((reported_errors & UNWRAP_ERROR_NEGATIVE) == 0) {
2839 BKE_report(
2840 op->reports,
2841 RPT_INFO,
2842 "Object has negative scale, unwrap will operate on a non-flipped version of the mesh");
2843 reported_errors |= UNWRAP_ERROR_NEGATIVE;
2844 }
2845 }
2846 }
2847
2848 if (subsurf_error) {
2849 BKE_report(op->reports,
2850 RPT_INFO,
2851 "Subdivision Surface modifier needs to be first to work with unwrap");
2852 }
2853
2854 /* execute unwrap */
2855 int count_changed = 0;
2856 int count_failed = 0;
2857 uvedit_unwrap_multi(scene, objects, &options, &count_changed, &count_failed);
2858
2859 blender::geometry::UVPackIsland_Params pack_island_params;
2860 pack_island_params.setFromUnwrapOptions(options);
2861 pack_island_params.rotate_method = ED_UVPACK_ROTATION_ANY;
2862 pack_island_params.pin_method = ED_UVPACK_PIN_IGNORE;
2863 pack_island_params.margin_method = eUVPackIsland_MarginMethod(
2864 RNA_enum_get(op->ptr, "margin_method"));
2865 pack_island_params.margin = RNA_float_get(op->ptr, "margin");
2866
2867 uvedit_pack_islands_multi(scene, objects, nullptr, nullptr, false, true, &pack_island_params);
2868
2869 if (count_failed == 0 && count_changed == 0) {
2870 BKE_report(op->reports,
2872 "Unwrap could not solve any island(s), edge seams may need to be added");
2873 }
2874 else if (count_failed) {
2875 BKE_reportf(op->reports,
2877 "Unwrap failed to solve %d of %d island(s), edge seams may need to be added",
2878 count_failed,
2879 count_changed + count_failed);
2880 }
2881
2882 return OPERATOR_FINISHED;
2883}
2884
2885static void unwrap_draw(bContext * /*C*/, wmOperator *op)
2886{
2887 uiLayout *layout = op->layout;
2888
2889 uiLayoutSetPropSep(layout, true);
2890 uiLayoutSetPropDecorate(layout, false);
2891
2892 /* Main draw call */
2894
2895 uiLayout *col;
2896
2897 col = &layout->column(true);
2898 col->prop(&ptr, "method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2899 bool is_slim = RNA_enum_get(op->ptr, "method") == UVCALC_UNWRAP_METHOD_MINIMUM_STRETCH;
2900
2901 if (is_slim) {
2902 col->prop(&ptr, "iterations", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2903 col->prop(&ptr, "no_flip", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2904
2905 col->separator();
2906 col->prop(&ptr, "use_weights", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2907
2908 if (RNA_boolean_get(op->ptr, "use_weights")) {
2909 col = &layout->column(true);
2910 col->prop(&ptr, "weight_group", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2911 col->prop(&ptr, "weight_factor", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2912 }
2913 }
2914 else {
2915 col->prop(&ptr, "fill_holes", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2916 }
2917
2918 col->separator();
2919 col->prop(&ptr, "use_subsurf_data", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2920
2921 col->separator();
2922 col->prop(&ptr, "correct_aspect", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2923 col->prop(&ptr, "margin_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2924 col->prop(&ptr, "margin", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2925}
2926
2928{
2929 const ToolSettings *tool_settings_default = DNA_struct_default_get(ToolSettings);
2930
2931 static const EnumPropertyItem method_items[] = {
2932 {UVCALC_UNWRAP_METHOD_ANGLE, "ANGLE_BASED", 0, "Angle Based", ""},
2933 {UVCALC_UNWRAP_METHOD_CONFORMAL, "CONFORMAL", 0, "Conformal", ""},
2934 {UVCALC_UNWRAP_METHOD_MINIMUM_STRETCH, "MINIMUM_STRETCH", 0, "Minimum Stretch", ""},
2935 {0, nullptr, 0, nullptr, nullptr},
2936 };
2937
2938 /* identifiers */
2939 ot->name = "Unwrap";
2940 ot->description = "Unwrap the mesh of the object being edited";
2941 ot->idname = "UV_OT_unwrap";
2942 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2943
2944 /* API callbacks. */
2945 ot->exec = unwrap_exec;
2946 ot->poll = ED_operator_uvmap;
2947
2948 /* Only draw relevant ui elements */
2949 ot->ui = unwrap_draw;
2950
2951 /* properties */
2952 ot->prop = RNA_def_enum(
2953 ot->srna,
2954 "method",
2955 method_items,
2956 tool_settings_default->unwrapper,
2957 "Method",
2958 "Unwrapping method (Angle Based usually gives better results than Conformal, while "
2959 "being somewhat slower)");
2960 RNA_def_boolean(ot->srna,
2961 "fill_holes",
2962 tool_settings_default->uvcalc_flag & UVCALC_FILLHOLES,
2963 "Fill Holes",
2964 "Virtually fill holes in mesh before unwrapping, to better avoid overlaps and "
2965 "preserve symmetry");
2966
2968
2970 ot->srna,
2971 "use_subsurf_data",
2972 false,
2973 "Use Subdivision Surface",
2974 "Map UVs taking vertex position after Subdivision Surface modifier has been applied");
2975 RNA_def_enum(ot->srna,
2976 "margin_method",
2979 "Margin Method",
2980 "");
2982 ot->srna, "margin", 0.001f, 0.0f, 1.0f, "Margin", "Space between islands", 0.0f, 1.0f);
2983
2984 /* SLIM only */
2985 RNA_def_boolean(ot->srna,
2986 "no_flip",
2987 tool_settings_default->uvcalc_flag & UVCALC_UNWRAP_NO_FLIP,
2988 "No Flip",
2989 "Prevent flipping UV's, "
2990 "flipping may lower distortion depending on the position of pins");
2991
2992 RNA_def_int(ot->srna,
2993 "iterations",
2994 tool_settings_default->uvcalc_iterations,
2995 0,
2996 10000,
2997 "Iterations",
2998 "Number of iterations when \"Minimum Stretch\" method is used",
2999 1,
3000 30);
3001
3002 RNA_def_boolean(ot->srna,
3003 "use_weights",
3004 tool_settings_default->uvcalc_flag & UVCALC_UNWRAP_USE_WEIGHTS,
3005 "Importance Weights",
3006 "Whether to take into account per-vertex importance weights");
3007 RNA_def_string(ot->srna,
3008 "weight_group",
3009 tool_settings_default->uvcalc_weight_group,
3011 "Weight Group",
3012 "Vertex group name for importance weights (modulating the deform)");
3014 ot->srna,
3015 "weight_factor",
3016 tool_settings_default->uvcalc_weight_factor,
3017 -10000.0,
3018 10000.0,
3019 "Weight Factor",
3020 "How much influence the weightmap has for weighted parameterization, 0 being no influence",
3021 -10.0,
3022 10.0);
3023}
3024
3026
3027/* -------------------------------------------------------------------- */
3030
3031/* Ignore all areas below this, as the UVs get zeroed. */
3032static const float smart_uv_project_area_ignore = 1e-12f;
3033
3035 float area;
3037};
3038
3039static int smart_uv_project_thickface_area_cmp_fn(const void *tf_a_p, const void *tf_b_p)
3040{
3041 const ThickFace *tf_a = (ThickFace *)tf_a_p;
3042 const ThickFace *tf_b = (ThickFace *)tf_b_p;
3043
3044 /* Ignore the area of small faces.
3045 * Also, order checks so `!isfinite(...)` values are counted as zero area. */
3046 if (!((tf_a->area > smart_uv_project_area_ignore) ||
3048 {
3049 return 0;
3050 }
3051
3052 if (tf_a->area < tf_b->area) {
3053 return 1;
3054 }
3055 if (tf_a->area > tf_b->area) {
3056 return -1;
3057 }
3058 return 0;
3059}
3060
3062 const ThickFace *thick_faces,
3063 const uint thick_faces_len,
3064 BMesh *bm,
3065 const float project_angle_limit_half_cos,
3066 const float project_angle_limit_cos,
3067 const float area_weight)
3068{
3069 if (UNLIKELY(thick_faces_len == 0)) {
3070 return {};
3071 }
3072
3073 const float *project_normal = thick_faces[0].efa->no;
3074
3075 blender::Vector<const ThickFace *> project_thick_faces;
3076 blender::Vector<blender::float3> project_normal_array;
3077
3079
3080 while (true) {
3081 for (int f_index = thick_faces_len - 1; f_index >= 0; f_index--) {
3082 if (BM_elem_flag_test(thick_faces[f_index].efa, BM_ELEM_TAG)) {
3083 continue;
3084 }
3085
3086 if (dot_v3v3(thick_faces[f_index].efa->no, project_normal) > project_angle_limit_half_cos) {
3087 project_thick_faces.append(&thick_faces[f_index]);
3088 BM_elem_flag_set(thick_faces[f_index].efa, BM_ELEM_TAG, true);
3089 }
3090 }
3091
3092 float average_normal[3] = {0.0f, 0.0f, 0.0f};
3093
3094 if (area_weight <= 0.0f) {
3095 for (int f_proj_index = 0; f_proj_index < project_thick_faces.size(); f_proj_index++) {
3096 const ThickFace *tf = project_thick_faces[f_proj_index];
3097 add_v3_v3(average_normal, tf->efa->no);
3098 }
3099 }
3100 else if (area_weight >= 1.0f) {
3101 for (int f_proj_index = 0; f_proj_index < project_thick_faces.size(); f_proj_index++) {
3102 const ThickFace *tf = project_thick_faces[f_proj_index];
3103 madd_v3_v3fl(average_normal, tf->efa->no, tf->area);
3104 }
3105 }
3106 else {
3107 for (int f_proj_index = 0; f_proj_index < project_thick_faces.size(); f_proj_index++) {
3108 const ThickFace *tf = project_thick_faces[f_proj_index];
3109 const float area_blend = (tf->area * area_weight) + (1.0f - area_weight);
3110 madd_v3_v3fl(average_normal, tf->efa->no, area_blend);
3111 }
3112 }
3113
3114 /* Avoid NAN. */
3115 if (normalize_v3(average_normal) != 0.0f) {
3116 project_normal_array.append(average_normal);
3117 }
3118
3119 /* Find the most unique angle that points away from other normals. */
3120 float anble_best = 1.0f;
3121 uint angle_best_index = 0;
3122
3123 for (int f_index = thick_faces_len - 1; f_index >= 0; f_index--) {
3124 if (BM_elem_flag_test(thick_faces[f_index].efa, BM_ELEM_TAG)) {
3125 continue;
3126 }
3127
3128 float angle_test = -1.0f;
3129 for (int p_index = 0; p_index < project_normal_array.size(); p_index++) {
3130 angle_test = max_ff(angle_test,
3131 dot_v3v3(project_normal_array[p_index], thick_faces[f_index].efa->no));
3132 }
3133
3134 if (angle_test < anble_best) {
3135 anble_best = angle_test;
3136 angle_best_index = f_index;
3137 }
3138 }
3139
3140 if (anble_best < project_angle_limit_cos) {
3141 project_normal = thick_faces[angle_best_index].efa->no;
3142 project_thick_faces.clear();
3143 project_thick_faces.append(&thick_faces[angle_best_index]);
3144 BM_elem_flag_enable(thick_faces[angle_best_index].efa, BM_ELEM_TAG);
3145 }
3146 else {
3147 if (project_normal_array.size() >= 1) {
3148 break;
3149 }
3150 }
3151 }
3152
3154
3155 return project_normal_array;
3156}
3157
3159{
3160 Scene *scene = CTX_data_scene(C);
3161 ViewLayer *view_layer = CTX_data_view_layer(C);
3162
3163 /* May be nullptr. */
3164 View3D *v3d = CTX_wm_view3d(C);
3165
3166 bool only_selected_uvs = false;
3167 if (CTX_wm_space_image(C)) {
3168 /* Inside the UV Editor, only project selected UVs. */
3169 only_selected_uvs = true;
3170 }
3171
3172 const float project_angle_limit = RNA_float_get(op->ptr, "angle_limit");
3173 const float island_margin = RNA_float_get(op->ptr, "island_margin");
3174 const float area_weight = RNA_float_get(op->ptr, "area_weight");
3175
3176 const float project_angle_limit_cos = cosf(project_angle_limit);
3177 const float project_angle_limit_half_cos = cosf(project_angle_limit / 2);
3178
3179 /* Memory arena for list links (cleared for each object). */
3181
3183 scene, view_layer, v3d);
3184
3185 Vector<Object *> objects_changed;
3186
3187 BMFace *efa;
3188 BMIter iter;
3189
3190 for (const int ob_index : objects.index_range()) {
3191 Object *obedit = objects[ob_index];
3193 bool changed = false;
3194
3195 if (!uvedit_ensure_uvs(obedit)) {
3196 continue;
3197 }
3198
3199 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
3200 BLI_assert(offsets.uv >= 0);
3201 ThickFace *thick_faces = MEM_malloc_arrayN<ThickFace>(em->bm->totface, __func__);
3202
3203 uint thick_faces_len = 0;
3204 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
3205 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
3206 continue;
3207 }
3208
3209 if (only_selected_uvs) {
3210 if (!uvedit_face_select_test(scene, efa, offsets)) {
3211 uvedit_face_select_disable(scene, em->bm, efa, offsets);
3212 continue;
3213 }
3214 }
3215
3216 thick_faces[thick_faces_len].area = BM_face_calc_area(efa);
3217 thick_faces[thick_faces_len].efa = efa;
3218 thick_faces_len++;
3219 }
3220
3221 qsort(thick_faces, thick_faces_len, sizeof(ThickFace), smart_uv_project_thickface_area_cmp_fn);
3222
3223 /* Remove all zero area faces. */
3224 while ((thick_faces_len > 0) &&
3225 !(thick_faces[thick_faces_len - 1].area > smart_uv_project_area_ignore))
3226 {
3227
3228 /* Zero UVs so they don't overlap with other faces being unwrapped. */
3229 BMIter liter;
3230 BMLoop *l;
3231 BM_ITER_ELEM (l, &liter, thick_faces[thick_faces_len - 1].efa, BM_LOOPS_OF_FACE) {
3232 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
3233 zero_v2(luv);
3234 changed = true;
3235 }
3236
3237 thick_faces_len -= 1;
3238 }
3239
3240 blender::Vector<blender::float3> project_normal_array =
3242 thick_faces_len,
3243 em->bm,
3244 project_angle_limit_half_cos,
3245 project_angle_limit_cos,
3246 area_weight);
3247
3248 if (project_normal_array.is_empty()) {
3249 MEM_freeN(thick_faces);
3250 continue;
3251 }
3252
3253 /* After finding projection vectors, we find the uv positions. */
3254 LinkNode **thickface_project_groups = static_cast<LinkNode **>(
3255 MEM_callocN(sizeof(*thickface_project_groups) * project_normal_array.size(), __func__));
3256
3257 BLI_memarena_clear(arena);
3258
3259 for (int f_index = thick_faces_len - 1; f_index >= 0; f_index--) {
3260 const float *f_normal = thick_faces[f_index].efa->no;
3261
3262 float angle_best = dot_v3v3(f_normal, project_normal_array[0]);
3263 uint angle_best_index = 0;
3264
3265 for (int p_index = 1; p_index < project_normal_array.size(); p_index++) {
3266 const float angle_test = dot_v3v3(f_normal, project_normal_array[p_index]);
3267 if (angle_test > angle_best) {
3268 angle_best = angle_test;
3269 angle_best_index = p_index;
3270 }
3271 }
3272
3274 &thickface_project_groups[angle_best_index], &thick_faces[f_index], arena);
3275 }
3276
3277 for (int p_index = 0; p_index < project_normal_array.size(); p_index++) {
3278 if (thickface_project_groups[p_index] == nullptr) {
3279 continue;
3280 }
3281
3282 float axis_mat[3][3];
3283 axis_dominant_v3_to_m3(axis_mat, project_normal_array[p_index]);
3284
3285 for (LinkNode *list = thickface_project_groups[p_index]; list; list = list->next) {
3286 ThickFace *tf = static_cast<ThickFace *>(list->link);
3287 BMIter liter;
3288 BMLoop *l;
3289 BM_ITER_ELEM (l, &liter, tf->efa, BM_LOOPS_OF_FACE) {
3290 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
3291 mul_v2_m3v3(luv, axis_mat, l->v->co);
3292 }
3293 changed = true;
3294 }
3295 }
3296
3297 MEM_freeN(thick_faces);
3298
3299 /* No need to free the lists in 'thickface_project_groups' values as the 'arena' is used. */
3300 MEM_freeN(thickface_project_groups);
3301
3302 if (changed) {
3303 objects_changed.append(obedit);
3304 }
3305 }
3306
3307 BLI_memarena_free(arena);
3308
3309 /* Pack islands & Stretch to UV bounds */
3310 if (!objects_changed.is_empty()) {
3311
3312 scene->toolsettings->uvcalc_margin = island_margin;
3313
3314 /* Depsgraph refresh functions are called here. */
3315 const bool correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect");
3316
3318 params.rotate_method = eUVPackIsland_RotationMethod(RNA_enum_get(op->ptr, "rotate_method"));
3319 params.only_selected_uvs = only_selected_uvs;
3320 params.only_selected_faces = true;
3321 params.correct_aspect = correct_aspect;
3322 params.use_seams = true;
3323 params.margin_method = eUVPackIsland_MarginMethod(RNA_enum_get(op->ptr, "margin_method"));
3324 params.margin = RNA_float_get(op->ptr, "island_margin");
3325
3326 uvedit_pack_islands_multi(scene, objects_changed, nullptr, nullptr, false, true, &params);
3327
3328 /* #uvedit_pack_islands_multi only supports `per_face_aspect = false`. */
3329 const bool per_face_aspect = false;
3330 uv_map_clip_correct(scene, objects_changed, op, per_face_aspect, only_selected_uvs);
3331 }
3332
3333 return OPERATOR_FINISHED;
3334}
3335
3337{
3339 C, op, event, IFACE_("Smart UV Project"), IFACE_("Unwrap"));
3340}
3341
3343{
3344 PropertyRNA *prop;
3345
3346 /* identifiers */
3347 ot->name = "Smart UV Project";
3348 ot->idname = "UV_OT_smart_project";
3349 ot->description = "Projection unwraps the selected faces of mesh objects";
3350
3351 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3352
3353 /* API callbacks. */
3354 ot->exec = smart_project_exec;
3355 ot->poll = ED_operator_uvmap;
3356 ot->invoke = smart_project_invoke;
3357
3358 /* properties */
3359 prop = RNA_def_float_rotation(ot->srna,
3360 "angle_limit",
3361 0,
3362 nullptr,
3363 DEG2RADF(0.0f),
3364 DEG2RADF(90.0f),
3365 "Angle Limit",
3366 "Lower for more projection groups, higher for less distortion",
3367 DEG2RADF(0.0f),
3368 DEG2RADF(89.0f));
3370
3371 RNA_def_enum(ot->srna,
3372 "margin_method",
3375 "Margin Method",
3376 "");
3377 RNA_def_enum(ot->srna,
3378 "rotate_method",
3379 /* Only show aligned options as the rotation from a projection
3380 * generated from a direction vector isn't meaningful. */
3383 "Rotation Method",
3384 "");
3385 RNA_def_float(ot->srna,
3386 "island_margin",
3387 0.0f,
3388 0.0f,
3389 1.0f,
3390 "Island Margin",
3391 "Margin to reduce bleed from adjacent islands",
3392 0.0f,
3393 1.0f);
3394 RNA_def_float(ot->srna,
3395 "area_weight",
3396 0.0f,
3397 0.0f,
3398 1.0f,
3399 "Area Weight",
3400 "Weight projection's vector by faces with larger areas",
3401 0.0f,
3402 1.0f);
3403
3405}
3406
3408
3409/* -------------------------------------------------------------------- */
3412
3413static wmOperatorStatus uv_from_view_exec(bContext *C, wmOperator *op);
3414
3416{
3417 View3D *v3d = CTX_wm_view3d(C);
3419 const Camera *camera = ED_view3d_camera_data_get(v3d, rv3d);
3420 PropertyRNA *prop;
3421
3422 prop = RNA_struct_find_property(op->ptr, "camera_bounds");
3423 if (!RNA_property_is_set(op->ptr, prop)) {
3424 RNA_property_boolean_set(op->ptr, prop, (camera != nullptr));
3425 }
3426 prop = RNA_struct_find_property(op->ptr, "correct_aspect");
3427 if (!RNA_property_is_set(op->ptr, prop)) {
3428 RNA_property_boolean_set(op->ptr, prop, (camera == nullptr));
3429 }
3430
3431 return uv_from_view_exec(C, op);
3432}
3433
3435{
3436 ViewLayer *view_layer = CTX_data_view_layer(C);
3437 const Scene *scene = CTX_data_scene(C);
3438 ARegion *region = CTX_wm_region(C);
3439 View3D *v3d = CTX_wm_view3d(C);
3441 const Camera *camera = ED_view3d_camera_data_get(v3d, rv3d);
3442 BMFace *efa;
3443 BMLoop *l;
3444 BMIter iter, liter;
3445 float rotmat[4][4];
3446 float objects_pos_offset[4];
3447
3448 const bool use_orthographic = RNA_boolean_get(op->ptr, "orthographic");
3449
3450 /* NOTE: objects that aren't touched are set to nullptr (to skip clipping). */
3452 scene, view_layer, v3d);
3453
3454 if (use_orthographic) {
3455 /* Calculate average object position. */
3456 float objects_pos_avg[4] = {0};
3457
3458 for (Object *object : objects) {
3459 add_v4_v4(objects_pos_avg, object->object_to_world().location());
3460 }
3461
3462 mul_v4_fl(objects_pos_avg, 1.0f / objects.size());
3463 negate_v4_v4(objects_pos_offset, objects_pos_avg);
3464 }
3465
3466 Vector<Object *> changed_objects;
3467
3468 for (Object *obedit : objects) {
3470 bool changed = false;
3471
3472 /* add uvs if they don't exist yet */
3473 if (!uvedit_ensure_uvs(obedit)) {
3474 continue;
3475 }
3476
3477 const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2);
3478
3479 if (use_orthographic) {
3480 uv_map_rotation_matrix_ex(rotmat, rv3d, obedit, 90.0f, 0.0f, 1.0f, objects_pos_offset);
3481
3482 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
3483 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
3484 continue;
3485 }
3486
3487 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
3488 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
3489 BKE_uvproject_from_view_ortho(luv, l->v->co, rotmat);
3490 }
3491 changed = true;
3492 }
3493 }
3494 else if (camera) {
3495 const bool camera_bounds = RNA_boolean_get(op->ptr, "camera_bounds");
3497 v3d->camera,
3498 obedit->object_to_world().ptr(),
3499 camera_bounds ? (scene->r.xsch * scene->r.xasp) : 1.0f,
3500 camera_bounds ? (scene->r.ysch * scene->r.yasp) : 1.0f);
3501
3502 if (uci) {
3503 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
3504 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
3505 continue;
3506 }
3507
3508 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
3509 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
3510 BKE_uvproject_from_camera(luv, l->v->co, uci);
3511 }
3512 changed = true;
3513 }
3514
3516 }
3517 }
3518 else {
3519 copy_m4_m4(rotmat, obedit->object_to_world().ptr());
3520
3521 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
3522 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
3523 continue;
3524 }
3525
3526 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
3527 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
3529 luv, l->v->co, rv3d->persmat, rotmat, region->winx, region->winy);
3530 }
3531 changed = true;
3532 }
3533 }
3534
3535 if (changed) {
3536 changed_objects.append(obedit);
3537 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
3538 WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
3539 }
3540 }
3541
3542 if (changed_objects.is_empty()) {
3543 return OPERATOR_CANCELLED;
3544 }
3545
3546 const bool per_face_aspect = true;
3547 const bool only_selected_uvs = false;
3548 uv_map_clip_correct(scene, objects, op, per_face_aspect, only_selected_uvs);
3549 return OPERATOR_FINISHED;
3550}
3551
3553{
3555
3556 if (!ED_operator_uvmap(C)) {
3557 return false;
3558 }
3559
3560 return (rv3d != nullptr);
3561}
3562
3564{
3565 /* identifiers */
3566 ot->name = "Project from View";
3567 ot->idname = "UV_OT_project_from_view";
3568 ot->description = "Project the UV vertices of the mesh as seen in current 3D view";
3569
3570 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3571
3572 /* API callbacks. */
3573 ot->invoke = uv_from_view_invoke;
3574 ot->exec = uv_from_view_exec;
3575 ot->poll = uv_from_view_poll;
3576
3577 /* properties */
3578 RNA_def_boolean(ot->srna, "orthographic", false, "Orthographic", "Use orthographic projection");
3579 RNA_def_boolean(ot->srna,
3580 "camera_bounds",
3581 true,
3582 "Camera Bounds",
3583 "Map UVs to the camera region taking resolution and aspect into account");
3585}
3586
3588
3589/* -------------------------------------------------------------------- */
3592
3594{
3595 const Scene *scene = CTX_data_scene(C);
3596 ViewLayer *view_layer = CTX_data_view_layer(C);
3597 View3D *v3d = CTX_wm_view3d(C);
3598
3600 scene, view_layer, v3d);
3601 for (Object *obedit : objects) {
3602 Mesh *mesh = (Mesh *)obedit->data;
3604
3605 if (em->bm->totfacesel == 0) {
3606 continue;
3607 }
3608
3609 /* add uvs if they don't exist yet */
3610 if (!uvedit_ensure_uvs(obedit)) {
3611 continue;
3612 }
3613
3614 ED_mesh_uv_loop_reset(C, mesh);
3615
3616 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
3617 WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
3618 }
3619
3620 return OPERATOR_FINISHED;
3621}
3622
3624{
3625 /* identifiers */
3626 ot->name = "Reset";
3627 ot->idname = "UV_OT_reset";
3628 ot->description = "Reset UV projection";
3629
3630 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3631
3632 /* API callbacks. */
3633 ot->exec = reset_exec;
3634 ot->poll = ED_operator_uvmap;
3635}
3636
3638
3639/* -------------------------------------------------------------------- */
3642
3643static void uv_map_mirror(BMFace *efa,
3644 const bool *regular,
3645 const bool fan,
3646 const int cd_loop_uv_offset)
3647{
3648 /* A heuristic to improve alignment of faces near the seam.
3649 * In simple terms, we're looking for faces which span more
3650 * than 0.5 units in the *u* coordinate.
3651 * If we find such a face, we try and improve the unwrapping
3652 * by adding (1.0, 0.0) onto some of the face's UVs.
3653 *
3654 * Note that this is only a heuristic. The property we're
3655 * attempting to maintain is that the winding of the face
3656 * in UV space corresponds with the handedness of the face
3657 * in 3D space w.r.t to the unwrapping. Even for triangles,
3658 * that property is somewhat complicated to evaluate. */
3659
3660 float right_u = -1.0e30f;
3661 BMLoop *l;
3662 BMIter liter;
3664 int j;
3665 BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, j) {
3666 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
3667 uvs[j] = luv;
3668 if (luv[0] >= 1.0f) {
3669 luv[0] -= 1.0f;
3670 }
3671 right_u = max_ff(right_u, luv[0]);
3672 }
3673
3674 float left_u = 1.0e30f;
3675 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
3676 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
3677 if (right_u <= luv[0] + 0.5f) {
3678 left_u = min_ff(left_u, luv[0]);
3679 }
3680 }
3681
3682 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
3683 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
3684 if (luv[0] + 0.5f < right_u) {
3685 if (2 * luv[0] + 1.0f < left_u + right_u) {
3686 luv[0] += 1.0f;
3687 }
3688 }
3689 }
3690 if (!fan) {
3691 return;
3692 }
3693
3694 /* Another heuristic, this time, we attempt to "fan"
3695 * the UVs of faces which pass through one of the poles
3696 * of the unwrapping. */
3697
3698 /* Need to recompute min and max. */
3699 float minmax_u[2] = {1.0e30f, -1.0e30f};
3700 int pole_count = 0;
3701 for (int i = 0; i < efa->len; i++) {
3702 if (regular[i]) {
3703 minmax_u[0] = min_ff(minmax_u[0], uvs[i][0]);
3704 minmax_u[1] = max_ff(minmax_u[1], uvs[i][0]);
3705 }
3706 else {
3707 pole_count++;
3708 }
3709 }
3710 if (ELEM(pole_count, 0, efa->len)) {
3711 return;
3712 }
3713 for (int i = 0; i < efa->len; i++) {
3714 if (regular[i]) {
3715 continue;
3716 }
3717 float u = 0.0f;
3718 float sum = 0.0f;
3719 const int i_plus = (i + 1) % efa->len;
3720 const int i_minus = (i + efa->len - 1) % efa->len;
3721 if (regular[i_plus]) {
3722 u += uvs[i_plus][0];
3723 sum += 1.0f;
3724 }
3725 if (regular[i_minus]) {
3726 u += uvs[i_minus][0];
3727 sum += 1.0f;
3728 }
3729 if (sum == 0) {
3730 u += minmax_u[0] + minmax_u[1];
3731 sum += 2.0f;
3732 }
3733 uvs[i][0] = u / sum;
3734 }
3735}
3736
3752 float branch;
3753};
3754
3765static float uv_sphere_project(const Scene *scene,
3766 BMesh *bm,
3767 BMFace *efa_init,
3768 const float center[3],
3769 const float rotmat[3][3],
3770 const bool fan,
3771 const BMUVOffsets &offsets,
3772 const bool only_selected_uvs,
3773 const bool use_seams,
3774 const float branch_init)
3775{
3776 float max_u = 0.0f;
3777 if (use_seams && BM_elem_flag_test(efa_init, BM_ELEM_TAG)) {
3778 return max_u;
3779 }
3780
3781 /* Similar to #BM_mesh_calc_face_groups with added connectivity information. */
3783 stack.append({efa_init, branch_init});
3784
3785 while (stack.size()) {
3786 UV_FaceBranch face_branch = stack.pop_last();
3787 BMFace *efa = face_branch.efa;
3788
3789 if (use_seams) {
3790 if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
3791 continue; /* Faces might be in the stack more than once. */
3792 }
3793
3794 BM_elem_flag_set(efa, BM_ELEM_TAG, true); /* Visited, don't consider again. */
3795 }
3796
3797 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
3798 continue; /* Unselected face, ignore. */
3799 }
3800
3801 if (only_selected_uvs) {
3802 if (!uvedit_face_select_test(scene, efa, offsets)) {
3803 uvedit_face_select_disable(scene, bm, efa, offsets);
3804 continue; /* Unselected UV, ignore. */
3805 }
3806 }
3807
3808 /* Remember which UVs are at the pole. */
3810
3811 int i;
3812 BMLoop *l;
3813 BMIter iter;
3814 BM_ITER_ELEM_INDEX (l, &iter, efa, BM_LOOPS_OF_FACE, i) {
3815 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
3816 float pv[3];
3817 sub_v3_v3v3(pv, l->v->co, center);
3818 mul_m3_v3(rotmat, pv);
3819 regular[i] = map_to_sphere(&luv[0], &luv[1], pv[0], pv[1], pv[2]);
3820 if (!use_seams) {
3821 continue; /* Nothing more to do. */
3822 }
3823
3824 /* Move UV to correct branch. */
3825 luv[0] = luv[0] + ceilf(face_branch.branch - 0.5f - luv[0]);
3826 max_u = max_ff(max_u, luv[0]);
3827
3828 BMEdge *edge = l->e;
3829 if (BM_elem_flag_test(edge, BM_ELEM_SEAM)) {
3830 continue; /* Stop flood fill at seams. */
3831 }
3832
3833 /* Extend flood fill by pushing to stack. */
3834 BMFace *efa2;
3835 BMIter iter2;
3836 BM_ITER_ELEM (efa2, &iter2, edge, BM_FACES_OF_EDGE) {
3837 if (!BM_elem_flag_test(efa2, BM_ELEM_TAG)) {
3838 stack.append({efa2, luv[0]});
3839 }
3840 }
3841 }
3842 uv_map_mirror(efa, regular.data(), fan, offsets.uv);
3843 }
3844
3845 return max_u;
3846}
3847
3849{
3850 const Scene *scene = CTX_data_scene(C);
3851 View3D *v3d = CTX_wm_view3d(C);
3852
3853 bool only_selected_uvs = false;
3854 if (CTX_wm_space_image(C)) {
3855 /* Inside the UV Editor, only project selected UVs. */
3856 only_selected_uvs = true;
3857 }
3858
3859 ViewLayer *view_layer = CTX_data_view_layer(C);
3861 scene, view_layer, v3d);
3862 for (Object *obedit : objects) {
3864 BMFace *efa;
3865 BMIter iter;
3866
3867 if (em->bm->totfacesel == 0) {
3868 continue;
3869 }
3870
3871 /* add uvs if they don't exist yet */
3872 if (!uvedit_ensure_uvs(obedit)) {
3873 continue;
3874 }
3875
3876 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
3877 float center[3], rotmat[3][3];
3878
3879 uv_map_transform(C, op, rotmat);
3880 uv_map_transform_center(scene, v3d, obedit, em, center, nullptr);
3881
3882 const bool fan = RNA_enum_get(op->ptr, "pole");
3883 const bool use_seams = RNA_boolean_get(op->ptr, "seam");
3884
3885 if (use_seams) {
3887 }
3888
3889 float island_offset = 0.0f;
3890 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
3891 const float max_u = uv_sphere_project(scene,
3892 em->bm,
3893 efa,
3894 center,
3895 rotmat,
3896 fan,
3897 offsets,
3898 only_selected_uvs,
3899 use_seams,
3900 island_offset + 0.5f);
3901 island_offset = ceilf(max_ff(max_u, island_offset));
3902 }
3903
3904 const bool per_face_aspect = true;
3905 uv_map_clip_correct(scene, {obedit}, op, per_face_aspect, only_selected_uvs);
3906
3907 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
3908 WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
3909 }
3910
3911 return OPERATOR_FINISHED;
3912}
3913
3915{
3916 /* identifiers */
3917 ot->name = "Sphere Projection";
3918 ot->idname = "UV_OT_sphere_project";
3919 ot->description = "Project the UV vertices of the mesh over the curved surface of a sphere";
3920
3921 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3922
3923 /* API callbacks. */
3924 ot->exec = sphere_project_exec;
3925 ot->poll = ED_operator_uvmap;
3926
3927 /* properties */
3930}
3931
3933
3934/* -------------------------------------------------------------------- */
3937
3938/* See #uv_sphere_project for description of parameters. */
3939static float uv_cylinder_project(const Scene *scene,
3940 BMesh *bm,
3941 BMFace *efa_init,
3942 const float center[3],
3943 const float rotmat[3][3],
3944 const bool fan,
3945 const BMUVOffsets &offsets,
3946 const bool only_selected_uvs,
3947 const bool use_seams,
3948 const float branch_init)
3949{
3950 float max_u = 0.0f;
3951 if (use_seams && BM_elem_flag_test(efa_init, BM_ELEM_TAG)) {
3952 return max_u;
3953 }
3954
3955 /* Similar to BM_mesh_calc_face_groups with added connectivity information. */
3956
3958
3959 stack.append({efa_init, branch_init});
3960
3961 while (stack.size()) {
3962 UV_FaceBranch face_branch = stack.pop_last();
3963 BMFace *efa = face_branch.efa;
3964
3965 if (use_seams) {
3966 if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
3967 continue; /* Faces might be in the stack more than once. */
3968 }
3969
3970 BM_elem_flag_set(efa, BM_ELEM_TAG, true); /* Visited, don't consider again. */
3971 }
3972
3973 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
3974 continue; /* Unselected face, ignore. */
3975 }
3976
3977 if (only_selected_uvs) {
3978 if (!uvedit_face_select_test(scene, efa, offsets)) {
3979 uvedit_face_select_disable(scene, bm, efa, offsets);
3980 continue; /* Unselected UV, ignore. */
3981 }
3982 }
3983
3984 /* Remember which UVs are at the pole. */
3986
3987 int i;
3988 BMLoop *l;
3989 BMIter iter;
3990 BM_ITER_ELEM_INDEX (l, &iter, efa, BM_LOOPS_OF_FACE, i) {
3991 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
3992 float pv[3];
3993 sub_v3_v3v3(pv, l->v->co, center);
3994 mul_m3_v3(rotmat, pv);
3995 regular[i] = map_to_tube(&luv[0], &luv[1], pv[0], pv[1], pv[2]);
3996
3997 if (!use_seams) {
3998 continue; /* Nothing more to do. */
3999 }
4000
4001 /* Move UV to correct branch. */
4002 luv[0] = luv[0] + ceilf(face_branch.branch - 0.5f - luv[0]);
4003 max_u = max_ff(max_u, luv[0]);
4004
4005 BMEdge *edge = l->e;
4006 if (BM_elem_flag_test(edge, BM_ELEM_SEAM)) {
4007 continue; /* Stop flood fill at seams. */
4008 }
4009
4010 /* Extend flood fill by pushing to stack. */
4011 BMFace *efa2;
4012 BMIter iter2;
4013 BM_ITER_ELEM (efa2, &iter2, edge, BM_FACES_OF_EDGE) {
4014 if (!BM_elem_flag_test(efa2, BM_ELEM_TAG)) {
4015 stack.append({efa2, luv[0]});
4016 }
4017 }
4018 }
4019
4020 uv_map_mirror(efa, regular.data(), fan, offsets.uv);
4021 }
4022
4023 return max_u;
4024}
4025
4027{
4028 const Scene *scene = CTX_data_scene(C);
4029 View3D *v3d = CTX_wm_view3d(C);
4030
4031 bool only_selected_uvs = false;
4032 if (CTX_wm_space_image(C)) {
4033 /* Inside the UV Editor, only project selected UVs. */
4034 only_selected_uvs = true;
4035 }
4036
4037 ViewLayer *view_layer = CTX_data_view_layer(C);
4039 scene, view_layer, v3d);
4040 for (Object *obedit : objects) {
4042 BMFace *efa;
4043 BMIter iter;
4044
4045 if (em->bm->totfacesel == 0) {
4046 continue;
4047 }
4048
4049 /* add uvs if they don't exist yet */
4050 if (!uvedit_ensure_uvs(obedit)) {
4051 continue;
4052 }
4053
4054 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
4055 float center[3], rotmat[3][3];
4056
4057 uv_map_transform(C, op, rotmat);
4058 uv_map_transform_center(scene, v3d, obedit, em, center, nullptr);
4059
4060 const bool fan = RNA_enum_get(op->ptr, "pole");
4061 const bool use_seams = RNA_boolean_get(op->ptr, "seam");
4062
4063 if (use_seams) {
4065 }
4066
4067 float island_offset = 0.0f;
4068
4069 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
4070 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
4071 continue;
4072 }
4073
4074 if (only_selected_uvs && !uvedit_face_select_test(scene, efa, offsets)) {
4075 uvedit_face_select_disable(scene, em->bm, efa, offsets);
4076 continue;
4077 }
4078
4079 const float max_u = uv_cylinder_project(scene,
4080 em->bm,
4081 efa,
4082 center,
4083 rotmat,
4084 fan,
4085 offsets,
4086 only_selected_uvs,
4087 use_seams,
4088 island_offset + 0.5f);
4089 island_offset = ceilf(max_ff(max_u, island_offset));
4090 }
4091
4092 const bool per_face_aspect = true;
4093 uv_map_clip_correct(scene, {obedit}, op, per_face_aspect, only_selected_uvs);
4094
4095 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
4096 WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
4097 }
4098
4099 return OPERATOR_FINISHED;
4100}
4101
4103{
4104 /* identifiers */
4105 ot->name = "Cylinder Projection";
4106 ot->idname = "UV_OT_cylinder_project";
4107 ot->description = "Project the UV vertices of the mesh over the curved wall of a cylinder";
4108
4109 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4110
4111 /* API callbacks. */
4112 ot->exec = cylinder_project_exec;
4113 ot->poll = ED_operator_uvmap;
4114
4115 /* properties */
4118}
4119
4121
4122/* -------------------------------------------------------------------- */
4125
4126static void uvedit_unwrap_cube_project(const Scene *scene,
4127 BMesh *bm,
4128 float cube_size,
4129 const bool use_select,
4130 const bool only_selected_uvs,
4131 const float center[3])
4132{
4133 BMFace *efa;
4134 BMLoop *l;
4135 BMIter iter, liter;
4136 float loc[3];
4137 int cox, coy;
4138
4139 const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
4140
4141 if (center) {
4142 copy_v3_v3(loc, center);
4143 }
4144 else {
4145 zero_v3(loc);
4146 }
4147
4148 if (UNLIKELY(cube_size == 0.0f)) {
4149 cube_size = 1.0f;
4150 }
4151
4152 /* choose x,y,z axis for projection depending on the largest normal
4153 * component, but clusters all together around the center of map. */
4154
4155 BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
4156 if (use_select && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
4157 continue;
4158 }
4159 if (only_selected_uvs && !uvedit_face_select_test(scene, efa, offsets)) {
4160 uvedit_face_select_disable(scene, bm, efa, offsets);
4161 continue;
4162 }
4163
4164 axis_dominant_v3(&cox, &coy, efa->no);
4165
4166 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
4167 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
4168 luv[0] = 0.5f + ((l->v->co[cox] - loc[cox]) / cube_size);
4169 luv[1] = 0.5f + ((l->v->co[coy] - loc[coy]) / cube_size);
4170 }
4171 }
4172}
4173
4175{
4176 const Scene *scene = CTX_data_scene(C);
4177 View3D *v3d = CTX_wm_view3d(C);
4178
4179 bool only_selected_uvs = false;
4180 if (CTX_wm_space_image(C)) {
4181 /* Inside the UV Editor, only cube project selected UVs. */
4182 only_selected_uvs = true;
4183 }
4184
4185 PropertyRNA *prop_cube_size = RNA_struct_find_property(op->ptr, "cube_size");
4186 const float cube_size_init = RNA_property_float_get(op->ptr, prop_cube_size);
4187
4188 ViewLayer *view_layer = CTX_data_view_layer(C);
4190 scene, view_layer, v3d);
4191 for (const int ob_index : objects.index_range()) {
4192 Object *obedit = objects[ob_index];
4194
4195 if (em->bm->totfacesel == 0) {
4196 continue;
4197 }
4198
4199 /* add uvs if they don't exist yet */
4200 if (!uvedit_ensure_uvs(obedit)) {
4201 continue;
4202 }
4203
4204 float bounds[2][3];
4205 float(*bounds_buf)[3] = nullptr;
4206
4207 if (!RNA_property_is_set(op->ptr, prop_cube_size)) {
4208 bounds_buf = bounds;
4209 }
4210
4211 float center[3];
4212 uv_map_transform_center(scene, v3d, obedit, em, center, bounds_buf);
4213
4214 /* calculate based on bounds */
4215 float cube_size = cube_size_init;
4216 if (bounds_buf) {
4217 float dims[3];
4218 sub_v3_v3v3(dims, bounds[1], bounds[0]);
4219 cube_size = max_fff(UNPACK3(dims));
4220 if (ob_index == 0) {
4221 /* This doesn't fit well with, multiple objects. */
4222 RNA_property_float_set(op->ptr, prop_cube_size, cube_size);
4223 }
4224 }
4225
4226 uvedit_unwrap_cube_project(scene, em->bm, cube_size, true, only_selected_uvs, center);
4227
4228 const bool per_face_aspect = true;
4229 uv_map_clip_correct(scene, {obedit}, op, per_face_aspect, only_selected_uvs);
4230
4231 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
4233 }
4234
4235 return OPERATOR_FINISHED;
4236}
4237
4239{
4240 /* identifiers */
4241 ot->name = "Cube Projection";
4242 ot->idname = "UV_OT_cube_project";
4243 ot->description = "Project the UV vertices of the mesh over the six faces of a cube";
4244
4245 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4246
4247 /* API callbacks. */
4248 ot->exec = cube_project_exec;
4249 ot->poll = ED_operator_uvmap;
4250
4251 /* properties */
4252 RNA_def_float(ot->srna,
4253 "cube_size",
4254 1.0f,
4255 0.0f,
4256 FLT_MAX,
4257 "Cube Size",
4258 "Size of the cube to project on",
4259 0.001f,
4260 100.0f);
4262}
4263
4265
4266/* -------------------------------------------------------------------- */
4269
4270void ED_uvedit_add_simple_uvs(Main *bmain, const Scene *scene, Object *ob)
4271{
4272 Mesh *mesh = static_cast<Mesh *>(ob->data);
4273 bool sync_selection = (scene->toolsettings->uv_flag & UV_SYNC_SELECTION) != 0;
4274
4275 BMeshCreateParams create_params{};
4276 create_params.use_toolflags = false;
4277 BMesh *bm = BM_mesh_create(&bm_mesh_allocsize_default, &create_params);
4278
4279 /* turn sync selection off,
4280 * since we are not in edit mode we need to ensure only the uv flags are tested */
4282
4283 ED_mesh_uv_ensure(mesh, nullptr);
4284
4285 BMeshFromMeshParams bm_from_me_params{};
4286 bm_from_me_params.calc_face_normal = true;
4287 bm_from_me_params.calc_vert_normal = true;
4288 BM_mesh_bm_from_me(bm, mesh, &bm_from_me_params);
4289
4290 /* Select all UVs for cube_project. */
4292 /* A cube size of 2.0 maps [-1..1] vertex coords to [0.0..1.0] in UV coords. */
4293 uvedit_unwrap_cube_project(scene, bm, 2.0, false, false, nullptr);
4294
4295 /* Pack UVs. */
4297 params.rotate_method = ED_UVPACK_ROTATION_ANY;
4298 params.only_selected_uvs = false;
4299 params.only_selected_faces = false;
4300 params.correct_aspect = false;
4301 params.use_seams = true;
4302 params.margin_method = ED_UVPACK_MARGIN_SCALED;
4303 params.margin = 0.001f;
4304
4305 uvedit_pack_islands_multi(scene, {ob}, &bm, nullptr, false, true, &params);
4306
4307 /* Write back from BMesh to Mesh. */
4308 BMeshToMeshParams bm_to_me_params{};
4309 BM_mesh_bm_to_me(bmain, bm, mesh, &bm_to_me_params);
4311
4312 if (sync_selection) {
4314 }
4315}
4316
SpaceImage * CTX_wm_space_image(const bContext *C)
ScrArea * CTX_wm_area(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Object * CTX_data_edit_object(const bContext *C)
RegionView3D * CTX_wm_region_view3d(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
View3D * CTX_wm_view3d(const bContext *C)
ViewLayer * CTX_data_view_layer(const bContext *C)
CustomData interface, see also DNA_customdata_types.h.
int CustomData_get_offset(const CustomData *data, eCustomDataType type)
const void * CustomData_get_layer(const CustomData *data, eCustomDataType type)
#define ORIGINDEX_NONE
bool CustomData_has_layer(const CustomData *data, eCustomDataType type)
const char * CustomData_get_active_layer_name(const CustomData *data, eCustomDataType type)
support for deformation groups and hooks.
float BKE_defvert_find_weight(const MDeformVert *dvert, int defgroup)
Definition deform.cc:763
int BKE_object_defgroup_name_index(const Object *ob, blender::StringRef name)
Definition deform.cc:580
BMEditMesh * BKE_editmesh_from_object(Object *ob)
Return the BMEditMesh for a given object.
Definition editmesh.cc:61
#define G_MAIN
int BKE_image_find_nearest_tile_with_offset(const Image *image, const float co[2], float r_uv_offset[2]) ATTR_NONNULL(2
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)
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_id_free(Main *bmain, void *idv)
Mesh * BKE_mesh_from_bmesh_for_eval_nomain(BMesh *bm, const CustomData_MeshMasks *cd_mask_extra, const Mesh *me_settings)
void BKE_mesh_ensure_default_orig_index_customdata(Mesh *mesh)
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
@ REGION_DRAW_LOCK_RENDER
blender::bke::subdiv::Settings BKE_subsurf_modifier_settings_init(const SubsurfModifierData *smd, bool use_render_params)
void BKE_uvproject_from_view(float target[2], float source[3], float persmat[4][4], float rotmat[4][4], float winx, float winy)
Definition uvproject.cc:84
void BKE_uvproject_from_view_ortho(float target[2], float source[3], const float rotmat[4][4])
Definition uvproject.cc:182
void BKE_uvproject_from_camera(float target[2], float source[3], struct ProjCameraInfo *uci)
Definition uvproject.cc:32
void BKE_uvproject_camera_info_free(ProjCameraInfo *uci)
Definition uvproject.cc:177
struct ProjCameraInfo * BKE_uvproject_camera_info(const struct Object *ob, const float rotmat[4][4], float winx, float winy)
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define ATTR_FALLTHROUGH
void BLI_heap_free(Heap *heap, HeapFreeFP ptrfreefp) ATTR_NONNULL(1)
Definition BLI_heap.cc:191
Heap * BLI_heap_new(void) ATTR_WARN_UNUSED_RESULT
Definition BLI_heap.cc:186
int BLI_findindex(const ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:586
void * BLI_findlink(const ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:534
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
void BLI_remlink(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:131
MINLINE float max_fff(float a, float b, float c)
MINLINE float max_ff(float a, float b)
MINLINE float min_ff(float a, float b)
#define DEG2RADF(_deg)
#define M_PI
void axis_dominant_v3_to_m3(float r_mat[3][3], const float normal[3])
Normal to x,y matrix.
bool map_to_sphere(float *r_u, float *r_v, float x, float y, float z)
bool map_to_tube(float *r_u, float *r_v, float x, float y, float z)
MINLINE void axis_dominant_v3(int *r_axis_a, int *r_axis_b, const float axis[3])
bool invert_m2_m2(float inverse[2][2], const float mat[2][2])
void mul_m3_v3(const float M[3][3], float r[3])
void zero_m4(float m[4][4])
void unit_m3(float m[3][3])
void copy_m3_m4(float m1[3][3], const float m2[4][4])
void mul_v2_m3v3(float r[2], const float M[3][3], const float a[3])
#define mul_m4_series(...)
void mul_v2_m2v2(float r[2], const float mat[2][2], const float vec[2])
void copy_m4_m4(float m1[4][4], const float m2[4][4])
void mul_v3_m4v3(float r[3], const float mat[4][4], const float vec[3])
bool is_negative_m4(const float mat[4][4])
bool invert_m4_m4(float inverse[4][4], const float mat[4][4])
void mat4_to_size(float size[3], const float M[4][4])
void unit_m4(float m[4][4])
MINLINE void mul_v4_fl(float r[4], float f)
MINLINE void add_v4_v4(float r[4], const float a[4])
MINLINE void madd_v3_v3fl(float r[3], const float a[3], float f)
MINLINE float len_squared_v2v2(const float a[2], const float b[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE void mul_v3_fl(float r[3], float f)
MINLINE void copy_v3_v3(float r[3], const float a[3])
void minmax_v2v2_v2(float min[2], float max[2], const float vec[2])
MINLINE float dot_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
MINLINE void add_v2_fl(float r[2], float f)
MINLINE void negate_v4_v4(float r[4], const float a[4])
MINLINE void clamp_v2(float vec[2], float min, float max)
void mid_v2_v2v2(float r[2], const float a[2], const float b[2])
MINLINE void zero_v2(float r[2])
void mid_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void zero_v3(float r[3])
MINLINE void add_v3_v3(float r[3], const float a[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_clear(MemArena *ma) ATTR_NONNULL(1)
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:599
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
unsigned int uint
Platform independent time functions.
double BLI_time_now_seconds(void)
Definition time.cc:65
#define INIT_MINMAX2(min, max)
#define INIT_MINMAX(min, max)
#define UNPACK3(a)
#define UNLIKELY(x)
#define ELEM(...)
#define IFACE_(msgid)
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:982
@ CD_MDEFORMVERT
@ CD_PROP_FLOAT2
#define DNA_struct_default_get(struct_name)
@ IMA_SRC_TILED
@ eModifierType_Subsurf
@ eSubsurfModifierFlag_ControlEdges
Object is a sort of wrapper for general info.
#define MAX_VGROUP_NAME
@ UVCALC_UNWRAP_NO_FLIP
@ UVCALC_UNWRAP_USE_WEIGHTS
@ UVCALC_USESUBSURF
@ UVCALC_NO_ASPECT_CORRECT
@ UVCALC_FILLHOLES
@ UV_SYNC_SELECTION
@ UVCALC_UNWRAP_METHOD_MINIMUM_STRETCH
@ UVCALC_UNWRAP_METHOD_CONFORMAL
@ UVCALC_UNWRAP_METHOD_ANGLE
@ V3D_AROUND_ACTIVE
@ V3D_AROUND_CENTER_BOUNDS
@ V3D_AROUND_CURSOR
@ V3D_AROUND_CENTER_MEDIAN
@ V3D_AROUND_LOCAL_ORIGINS
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
struct wmOperator wmOperator
void ED_image_get_uv_aspect(Image *ima, ImageUser *iuser, float *r_aspx, float *r_aspy)
void ED_mesh_uv_ensure(Mesh *mesh, const char *name)
Definition mesh_data.cc:364
int ED_mesh_uv_add(Mesh *mesh, const char *name, bool active_set, bool do_init, ReportList *reports)
Definition mesh_data.cc:221
void ED_mesh_uv_loop_reset(bContext *C, Mesh *mesh)
Definition mesh_data.cc:211
void EDBM_mesh_elem_index_ensure_multi(blender::Span< Object * > objects, char htype)
bool ED_operator_uvmap(bContext *C)
void ED_area_status_text(ScrArea *area, const char *str)
Definition area.cc:872
void ED_workspace_status_text(bContext *C, const char *str)
Definition area.cc:1040
bool ED_operator_uvedit(bContext *C)
void ED_undo_push(bContext *C, const char *str)
Definition ed_undo.cc:99
bool ED_object_get_active_image(Object *ob, int mat_nr, Image **r_ima, ImageUser **r_iuser, const bNode **r_node, const bNodeTree **r_ntree)
bool uvedit_face_select_test(const Scene *scene, const BMFace *efa, const BMUVOffsets &offsets)
void ED_uvedit_add_simple_uvs(Main *bmain, const Scene *scene, Object *ob)
void ED_uvedit_live_unwrap_begin(Scene *scene, Object *obedit, struct wmWindow *win_modal)
float ED_uvedit_get_aspect_y(Object *obedit)
void uvedit_face_select_disable(const Scene *scene, BMesh *bm, BMFace *efa, const BMUVOffsets &offsets)
void ED_uvedit_get_aspect_from_material(Object *ob, const int material_index, float *r_aspx, float *r_aspy)
bool ED_uvedit_live_unwrap_timer_check(const wmTimer *timer)
bool ED_uvedit_test(Object *obedit)
Definition uvedit_ops.cc:71
void ED_uvedit_select_all(BMesh *bm)
void ED_uvedit_live_unwrap_re_solve()
bool uvedit_uv_select_test(const Scene *scene, const BMLoop *l, const BMUVOffsets &offsets)
void ED_uvedit_get_aspect(Object *obedit, float *r_aspx, float *r_aspy)
int bm_mesh_calc_uv_islands(const Scene *scene, BMesh *bm, ListBase *island_list, const bool only_selected_faces, const bool only_selected_uvs, const bool use_seams, const float aspect_y, const BMUVOffsets &offsets)
void ED_uvedit_live_unwrap_end(bool cancel)
bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], const float coords[2])
void ED_uvedit_live_unwrap(const Scene *scene, blender::Span< Object * > objects)
Camera * ED_view3d_camera_data_get(View3D *v3d, RegionView3D *rv3d)
eUVPackIsland_MarginMethod
@ ED_UVPACK_MARGIN_FRACTION
@ ED_UVPACK_MARGIN_SCALED
@ ED_UVPACK_MARGIN_ADD
eUVPackIsland_ShapeMethod
@ ED_UVPACK_SHAPE_AABB
@ ED_UVPACK_SHAPE_CONCAVE
@ ED_UVPACK_SHAPE_CONVEX
eUVPackIsland_PinMethod
@ ED_UVPACK_PIN_NONE
@ ED_UVPACK_PIN_LOCK_ROTATION_SCALE
@ ED_UVPACK_PIN_LOCK_SCALE
@ ED_UVPACK_PIN_LOCK_ROTATION
@ ED_UVPACK_PIN_IGNORE
@ ED_UVPACK_PIN_LOCK_ALL
eUVPackIsland_RotationMethod
@ ED_UVPACK_ROTATION_ANY
@ ED_UVPACK_ROTATION_AXIS_ALIGNED_X
@ ED_UVPACK_ROTATION_AXIS_ALIGNED
@ ED_UVPACK_ROTATION_CARDINAL
@ ED_UVPACK_ROTATION_NONE
@ ED_UVPACK_ROTATION_AXIS_ALIGNED_Y
Read Guarded memory(de)allocation.
#define RNA_ENUM_ITEM_SEPR
Definition RNA_types.hh:645
#define C
Definition RandGen.cpp:29
void uiLayoutSetActive(uiLayout *layout, bool active)
void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
#define UI_ITEM_NONE
void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
#define UI_MAX_DRAW_STR
@ WM_JOB_TYPE_UV_PACK
Definition WM_api.hh:1761
@ WM_JOB_PROGRESS
Definition WM_api.hh:1716
#define NC_GEOM
Definition WM_types.hh:390
#define ND_DATA
Definition WM_types.hh:506
@ KM_PRESS
Definition WM_types.hh:308
#define ND_SPACE_IMAGE
Definition WM_types.hh:519
@ OPTYPE_BLOCKING
Definition WM_types.hh:184
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_GRAB_CURSOR_XY
Definition WM_types.hh:188
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#define NC_SPACE
Definition WM_types.hh:389
#define BM_ELEM_CD_GET_BOOL(ele, offset)
@ BM_ELEM_HIDDEN
@ BM_ELEM_SEAM
@ BM_ELEM_SELECT
@ BM_ELEM_TAG
#define BM_ELEM_CD_GET_FLOAT_P(ele, offset)
#define BM_ELEM_CD_SET_BOOL(ele, offset, f)
#define BM_ELEM_CD_GET_VOID_P(ele, offset)
#define BM_elem_index_get(ele)
#define BM_elem_flag_set(ele, hflag, val)
#define BM_elem_flag_test(ele, hflag)
#define BM_elem_flag_enable(ele, hflag)
void BM_uv_map_attr_edge_select_ensure(BMesh *bm, const StringRef uv_map_name)
void BM_uv_map_attr_vert_select_ensure(BMesh *bm, const StringRef uv_map_name)
#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_FACES_OF_EDGE
@ BM_EDGES_OF_MESH
@ BM_FACES_OF_MESH
@ BM_LOOPS_OF_EDGE
@ BM_LOOPS_OF_FACE
#define BM_ITER_ELEM_INDEX(ele, iter, data, itype, indexvar)
BMesh * bm
BMFace * BM_mesh_active_face_get(BMesh *bm, const bool is_sloppy, const bool is_selected)
void BM_editselection_center(BMEditSelection *ese, float r_center[3])
void BM_mesh_elem_hflag_disable_all(BMesh *bm, const char htype, const char hflag, const bool respecthide)
bool BM_select_history_active_get(BMesh *bm, BMEditSelection *ese)
const BMAllocTemplate bm_mesh_allocsize_default
Definition bmesh_mesh.cc:30
void BM_mesh_free(BMesh *bm)
BMesh Free Mesh.
void BM_mesh_elem_table_ensure(BMesh *bm, const char htype)
void BM_mesh_elem_index_ensure(BMesh *bm, const char htype)
BMesh * BM_mesh_create(const BMAllocTemplate *allocsize, const BMeshCreateParams *params)
BMesh Make Mesh.
BLI_INLINE BMEdge * BM_edge_at_index(BMesh *bm, const int index)
BLI_INLINE BMFace * BM_face_at_index(BMesh *bm, const int index)
void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams *params)
void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *mesh, const BMeshToMeshParams *params)
#define BM_FACE
#define BM_EDGE
#define BM_VERT
void BM_face_calc_bounds_expand(const BMFace *f, float min[3], float max[3])
float BM_face_calc_area(const BMFace *f)
void BM_face_calc_center_median(const BMFace *f, float r_cent[3])
ATTR_WARN_UNUSED_RESULT const BMLoop * l
BMUVOffsets BM_uv_map_offsets_get(const BMesh *bm)
void BM_face_uv_minmax(const BMFace *f, float min[2], float max[2], const int cd_loop_uv_offset)
long long int int64_t
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
static T sum(const btAlignedObjectArray< T > &items)
const T * data() const
Definition BLI_array.hh:301
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
int64_t size() const
void append(const T &value)
bool is_empty() const
IndexRange index_range() const
bool can_scale_(const UVPackIsland_Params &params) const
Definition uv_pack.cc:2416
void add_polygon(Span< float2 > uvs, MemArena *arena, Heap *heap)
Definition uv_pack.cc:155
void build_transformation(float scale, double angle, float r_matrix[2][2]) const
Definition uv_pack.cc:2339
eUVPackIsland_RotationMethod rotate_method
eUVPackIsland_MarginMethod margin_method
eUVPackIsland_ShapeMethod shape_method
void setUDIMOffsetFromSpaceImage(const SpaceImage *sima)
eUVPackIsland_PinMethod pin_method
void setFromUnwrapOptions(const UnwrapOptions &options)
CCL_NAMESPACE_BEGIN struct Options options
#define sinf(x)
#define cosf(x)
#define ceilf(x)
#define floorf(x)
#define fabsf(x)
#define str(s)
uint col
#define select(A, B, C)
#define MAX_ID_NAME
#define MEM_reallocN(vmemh, len)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
#define G(x, y, z)
void free(Subdiv *subdiv)
Definition subdiv.cc:190
Subdiv * new_from_mesh(const Settings *settings, const Mesh *mesh)
Definition subdiv.cc:131
Mesh * subdiv_to_mesh(Subdiv *subdiv, const ToMeshSettings *settings, const Mesh *coarse_mesh)
bool uv_parametrizer_is_slim(const ParamHandle *phandle)
void uv_parametrizer_construct_end(ParamHandle *phandle, bool fill_holes, bool topology_from_uvs, int *r_count_failed=nullptr)
void uv_parametrizer_edge_set_seam(ParamHandle *phandle, const ParamKey *vkeys)
void uv_parametrizer_average(ParamHandle *handle, bool ignore_pinned, bool scale_uv, bool shear)
void uv_parametrizer_flush(ParamHandle *handle)
void uv_parametrizer_slim_live_begin(ParamHandle *phandle, const ParamSlimOptions *slim_options)
void uv_parametrizer_slim_live_solve_iteration(ParamHandle *phandle)
ParamKey uv_find_pin_index(ParamHandle *handle, const int bmvertindex, const float uv[2])
void uv_prepare_pin_index(ParamHandle *handle, const int bmvertindex, const float uv[2])
void uv_parametrizer_slim_live_end(ParamHandle *phandle)
void uv_parametrizer_stretch_blend(ParamHandle *handle, float blend)
void uv_parametrizer_slim_solve(ParamHandle *phandle, const ParamSlimOptions *slim_options, int *count_changed, int *count_failed)
void mul_v2_m2_add_v2v2(float r[2], const float mat[2][2], const float a[2], const float b[2])
Definition uv_pack.cc:46
void uv_parametrizer_lscm_end(ParamHandle *handle)
void uv_parametrizer_aspect_ratio(ParamHandle *handle, float aspect_y)
void uv_parametrizer_stretch_begin(ParamHandle *handle)
void uv_parametrizer_flush_restore(ParamHandle *handle)
void uv_parametrizer_lscm_begin(ParamHandle *handle, bool live, bool abf)
float pack_islands(Span< PackIsland * > islands, const UVPackIsland_Params &params)
void uv_parametrizer_lscm_solve(ParamHandle *handle, int *count_changed, int *count_failed)
void uv_parametrizer_stretch_iter(ParamHandle *handle)
void uv_parametrizer_stretch_end(ParamHandle *handle)
void uv_parametrizer_face_add(ParamHandle *handle, const ParamKey key, const int nverts, const ParamKey *vkeys, const float **co, float **uv, const float *weight, const bool *pin, const bool *select)
VecBase< int32_t, 2 > int2
const int faceMap[6][4]
Definition octree.cpp:2830
float RNA_property_float_get(PointerRNA *ptr, PropertyRNA *prop)
void RNA_property_int_set(PointerRNA *ptr, PropertyRNA *prop, int value)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
bool RNA_property_is_set(PointerRNA *ptr, PropertyRNA *prop)
void RNA_int_set(PointerRNA *ptr, const char *name, int value)
void RNA_property_enum_set(PointerRNA *ptr, PropertyRNA *prop, int value)
bool RNA_property_boolean_get(PointerRNA *ptr, PropertyRNA *prop)
int RNA_property_int_get(PointerRNA *ptr, PropertyRNA *prop)
void RNA_string_get(PointerRNA *ptr, const char *name, char *value)
int RNA_int_get(PointerRNA *ptr, const char *name)
void RNA_property_boolean_set(PointerRNA *ptr, PropertyRNA *prop, bool value)
float RNA_float_get(PointerRNA *ptr, const char *name)
void RNA_float_set(PointerRNA *ptr, const char *name, float value)
int RNA_property_enum_get(PointerRNA *ptr, PropertyRNA *prop)
void RNA_property_float_set(PointerRNA *ptr, PropertyRNA *prop, float value)
std::string RNA_property_string_get(PointerRNA *ptr, PropertyRNA *prop)
bool RNA_struct_property_is_set(PointerRNA *ptr, const char *identifier)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
int RNA_enum_get(PointerRNA *ptr, const char *name)
void RNA_property_string_set(PointerRNA *ptr, PropertyRNA *prop, const char *value)
PropertyRNA * RNA_def_float_factor(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_string(StructOrFunctionRNA *cont_, const char *identifier, const char *default_value, const int maxlen, const char *ui_name, const char *ui_description)
void RNA_def_property_float_default(PropertyRNA *prop, float value)
PropertyRNA * RNA_def_float_rotation(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)
PropertyRNA * RNA_def_float(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)
PropertyRNA * RNA_def_int(StructOrFunctionRNA *cont_, const char *identifier, const int default_value, const int hardmin, const int hardmax, const char *ui_name, const char *ui_description, const int softmin, const int softmax)
#define min(a, b)
Definition sort.cc:36
#define FLT_MAX
Definition stdcycles.h:14
short mat_nr
float no[3]
int totfacesel
int totvertsel
CustomData ldata
int totface
float aspect_y
Definition ED_uvedit.hh:304
BMFace ** faces
Definition ED_uvedit.hh:297
BMUVOffsets offsets
Definition ED_uvedit.hh:303
Definition DNA_ID.h:404
ListBase tiles
short source
int active_tile_index
struct LinkNode * next
void * first
CustomData edge_data
int edges_num
CustomData face_data
CustomData vert_data
int faces_num
const Scene * scene
Vector< Object * > objects_edit
ParamHandle * handle
ObjectRuntimeHandle * runtime
ListBase modifiers
float persmat[4][4]
float viewmat[4][4]
struct ToolSettings * toolsettings
struct RenderData r
View3DCursor cursor
float cursor[2]
int tile_grid_shape[2]
struct Image * image
char uvcalc_weight_group[64]
wmWindowManager * wm
Vector< Object * > objects
const SpaceImage * sima
blender::geometry::UVPackIsland_Params pack_island_params
char weight_group[MAX_VGROUP_NAME]
struct Object * camera
uiLayout & column(bool align)
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)
wmEventType type
Definition WM_types.hh:754
short val
Definition WM_types.hh:756
void * customdata
Definition WM_types.hh:804
const char * name
Definition WM_types.hh:1030
StructRNA * srna
Definition WM_types.hh:1124
struct ReportList * reports
IDProperty * properties
struct uiLayout * layout
struct wmOperatorType * type
struct PointerRNA * ptr
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
void UV_OT_cylinder_project(wmOperatorType *ot)
void UV_OT_project_from_view(wmOperatorType *ot)
void UV_OT_smart_project(wmOperatorType *ot)
void UV_OT_unwrap(wmOperatorType *ot)
void UV_OT_sphere_project(wmOperatorType *ot)
void UV_OT_cube_project(wmOperatorType *ot)
void UV_OT_average_islands_scale(wmOperatorType *ot)
void UV_OT_reset(wmOperatorType *ot)
void UV_OT_minimize_stretch(wmOperatorType *ot)
void UV_OT_pack_islands(wmOperatorType *ot)
static void uv_map_transform_center(const Scene *scene, View3D *v3d, Object *ob, BMEditMesh *em, float r_center[3], float r_bounds[2][3])
static wmOperatorStatus uv_pack_islands_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static bool uv_from_view_poll(bContext *C)
static void uv_map_clip_correct(const Scene *scene, const Span< Object * > objects, wmOperator *op, bool per_face_aspect, bool only_selected_uvs)
static void minimize_stretch_exit(bContext *C, wmOperator *op, bool cancel)
static void pack_islands_freejob(void *pidv)
static void construct_param_handle_face_add(ParamHandle *handle, const Scene *scene, BMFace *efa, blender::geometry::ParamKey face_index, const UnwrapOptions *options, const BMUVOffsets &offsets, const int cd_weight_offset, const int cd_weight_index)
static bool island_has_pins(const Scene *scene, FaceIsland *island, const blender::geometry::UVPackIsland_Params *params)
static void construct_param_edge_set_seams(ParamHandle *handle, BMesh *bm, const UnwrapOptions *options)
static void uv_map_mirror(BMFace *efa, const bool *regular, const bool fan, const int cd_loop_uv_offset)
static void uvedit_unwrap_cube_project(const Scene *scene, BMesh *bm, float cube_size, const bool use_select, const bool only_selected_uvs, const float center[3])
static const EnumPropertyItem pack_margin_method_items[]
static wmOperatorStatus cylinder_project_exec(bContext *C, wmOperator *op)
static Mesh * subdivide_edit_mesh(const Object *object, const BMEditMesh *em, const SubsurfModifierData *smd)
static float uv_cylinder_project(const Scene *scene, BMesh *bm, BMFace *efa_init, const float center[3], const float rotmat[3][3], const bool fan, const BMUVOffsets &offsets, const bool only_selected_uvs, const bool use_seams, const float branch_init)
#define POLAR_ZX
static void uv_map_operator_property_correct_aspect(wmOperatorType *ot)
static void minimize_stretch_cancel(bContext *C, wmOperator *op)
@ PACK_UDIM_SRC_CLOSEST
@ PACK_ORIGINAL_AABB
@ PACK_UDIM_SRC_ACTIVE
static float uv_nearest_grid_tile_distance(const int udim_grid[2], const float coords[2], float nearest_tile_co[2])
static void uv_transform_properties(wmOperatorType *ot, int radius)
static ParamHandle * construct_param_handle_subsurfed(const Scene *scene, Object *ob, BMEditMesh *em, const UnwrapOptions *options, int *r_count_failed=nullptr)
static wmOperatorStatus pack_islands_exec(bContext *C, wmOperator *op)
static void uvedit_unwrap(const Scene *scene, Object *obedit, const UnwrapOptions *options, int *r_count_changed, int *r_count_failed)
@ UNWRAP_ERROR_NEGATIVE
@ UNWRAP_ERROR_NONUNIFORM
static void pack_islands_startjob(void *pidv, wmJobWorkerStatus *worker_status)
static void uv_pack_islands_ui(bContext *, wmOperator *op)
static const EnumPropertyItem pack_shape_method_items[]
static bool rna_property_sync_string(PointerRNA *ptr, const char *prop_name, char value_p[])
static const float smart_uv_project_area_ignore
static float uv_sphere_project(const Scene *scene, BMesh *bm, BMFace *efa_init, const float center[3], const float rotmat[3][3], const bool fan, const BMUVOffsets &offsets, const bool only_selected_uvs, const bool use_seams, const float branch_init)
static void texface_from_original_index(const Scene *scene, const BMUVOffsets &offsets, BMFace *efa, int index, float **r_uv, bool *r_pin, bool *r_select)
static int smart_uv_project_thickface_area_cmp_fn(const void *tf_a_p, const void *tf_b_p)
static UnwrapOptions unwrap_options_get(wmOperator *op, Object *ob, const ToolSettings *ts)
static void correct_uv_aspect(Object *ob, BMEditMesh *em)
static void pack_islands_endjob(void *pidv)
static bool uvedit_live_unwrap_timer_validate(const wmWindowManager *wm)
wmTimer * timer
static void uv_map_transform(bContext *C, wmOperator *op, float rotmat[3][3])
static void uv_map_transform_calc_center_median(BMEditMesh *em, float r_center[3])
static void uv_map_clip_correct_properties(wmOperatorType *ot)
static wmOperatorStatus sphere_project_exec(bContext *C, wmOperator *op)
static void uv_map_clip_correct_properties_ex(wmOperatorType *ot, bool clip_to_bounds)
uint len_alloc
static ParamHandle * construct_param_handle_multi(const Scene *scene, const Span< Object * > objects, const UnwrapOptions *options)
static wmOperatorStatus smart_project_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static const EnumPropertyItem pack_rotate_method_items[]
static void uv_map_rotation_matrix_ex(float result[4][4], RegionView3D *rv3d, Object *ob, float upangledeg, float sideangledeg, float radius, const float offset[4])
static void unwrap_options_sync_toolsettings(wmOperator *op, ToolSettings *ts)
static bool rna_property_sync_enum(PointerRNA *ptr, const char *prop_name, int *value_p)
#define VIEW_ON_POLES
static bool rna_property_sync_enum_char(PointerRNA *ptr, const char *prop_name, char *value_p)
static wmOperatorStatus uv_from_view_exec(bContext *C, wmOperator *op)
static float uv_nearest_image_tile_distance(const Image *image, const float coords[2], float nearest_tile_co[2])
static wmOperatorStatus unwrap_exec(bContext *C, wmOperator *op)
static void uvedit_prepare_pinned_indices(ParamHandle *handle, const Scene *scene, BMFace *efa, const UnwrapOptions *options, const BMUVOffsets &offsets)
static bool rna_property_sync_float(PointerRNA *ptr, const char *prop_name, float *value_p)
static bool uvedit_ensure_uvs(Object *obedit)
static bool rna_property_sync_int(PointerRNA *ptr, const char *prop_name, int *value_p)
static void uvedit_pack_islands_multi(const Scene *scene, const Span< Object * > objects, BMesh **bmesh_override, const SpaceImage *udim_source_closest, const bool original_selection, const bool notify_wm, blender::geometry::UVPackIsland_Params *params)
static wmOperatorStatus average_islands_scale_exec(bContext *C, wmOperator *op)
static wmOperatorStatus cube_project_exec(bContext *C, wmOperator *op)
static void minimize_stretch_iteration(bContext *C, wmOperator *op, bool interactive)
static bool uvedit_have_selection(const Scene *scene, BMEditMesh *em, const UnwrapOptions *options)
static wmOperatorStatus uv_from_view_invoke(bContext *C, wmOperator *op, const wmEvent *)
static wmOperatorStatus minimize_stretch_invoke(bContext *C, wmOperator *op, const wmEvent *)
#define PACK_ROTATE_METHOD_AXIS_ALIGNED_OFFSET
static bool minimize_stretch_init(bContext *C, wmOperator *op)
static wmOperatorStatus smart_project_exec(bContext *C, wmOperator *op)
static void uv_map_transform_calc_bounds(BMEditMesh *em, float r_min[3], float r_max[3])
static void unwrap_draw(bContext *, wmOperator *op)
static bool rna_property_sync_flag(PointerRNA *ptr, const char *prop_name, char flag, bool flipped, char *value_p)
static void uvedit_unwrap_multi(const Scene *scene, const Span< Object * > objects, const UnwrapOptions *options, int *r_count_changed=nullptr, int *r_count_failed=nullptr)
static wmOperatorStatus minimize_stretch_exec(bContext *C, wmOperator *op)
static void shrink_loop_uv_by_aspect_ratio(BMFace *efa, const int cd_loop_uv_offset, const float aspect_y)
static void correct_uv_aspect_per_face(Object *ob, BMEditMesh *em)
uint len
#define ALIGN_TO_OBJECT
#define POLAR_ZY
static ParamHandle * construct_param_handle(const Scene *scene, Object *ob, BMesh *bm, const UnwrapOptions *options, int *r_count_failed=nullptr)
ParamHandle ** handles
static void island_uv_transform(FaceIsland *island, const float matrix[2][2], const float pre_translate[2])
#define VIEW_ON_EQUATOR
static const EnumPropertyItem pinned_islands_method_items[]
static void modifier_unwrap_state(Object *obedit, const UnwrapOptions *options, bool *r_use_subsurf)
static struct @156300107036162110351213232142201364151003230060 g_live_unwrap
static blender::Vector< blender::float3 > smart_uv_project_calculate_project_normals(const ThickFace *thick_faces, const uint thick_faces_len, BMesh *bm, const float project_angle_limit_half_cos, const float project_angle_limit_cos, const float area_weight)
static bool uvedit_is_face_affected(const Scene *scene, BMFace *efa, const UnwrapOptions *options, const BMUVOffsets &offsets)
static bool uvedit_have_selection_multi(const Scene *scene, const Span< Object * > objects, const UnwrapOptions *options)
static wmOperatorStatus minimize_stretch_modal(bContext *C, wmOperator *op, const wmEvent *event)
static wmOperatorStatus reset_exec(bContext *C, wmOperator *)
void WM_cursor_wait(bool val)
void WM_main_add_notifier(uint type, void *reference)
wmEventHandler_Op * WM_event_add_modal_handler(bContext *C, wmOperator *op)
void WM_set_locked_interface_with_flags(wmWindowManager *wm, short lock_flags)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
void WM_set_locked_interface(wmWindowManager *wm, bool lock)
@ RIGHTMOUSE
@ TIMER
@ WHEELUPMOUSE
@ EVT_PADENTER
@ WHEELDOWNMOUSE
@ EVT_PADMINUS
@ LEFTMOUSE
@ EVT_ESCKEY
@ EVT_PADPLUSKEY
@ EVT_RETKEY
PointerRNA * ptr
Definition wm_files.cc:4227
wmOperatorType * ot
Definition wm_files.cc:4226
void WM_jobs_timer(wmJob *wm_job, double time_step, uint note, uint endnote)
Definition wm_jobs.cc:353
void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job)
Definition wm_jobs.cc:456
wmJob * WM_jobs_get(wmWindowManager *wm, wmWindow *win, const void *owner, const char *name, const eWM_JobFlag flag, const eWM_JobType job_type)
Definition wm_jobs.cc:190
void WM_jobs_callbacks(wmJob *wm_job, wm_jobs_start_callback startjob, void(*initjob)(void *), void(*update)(void *), void(*endjob)(void *))
Definition wm_jobs.cc:365
void WM_jobs_customdata_set(wmJob *wm_job, void *customdata, void(*free)(void *customdata))
Definition wm_jobs.cc:337
wmOperatorStatus WM_operator_props_popup_confirm_ex(bContext *C, wmOperator *op, const wmEvent *, std::optional< std::string > title, std::optional< std::string > confirm_text, const bool cancel_default)
wmOperatorStatus WM_operator_props_popup_call(bContext *C, wmOperator *op, const wmEvent *)
wmTimer * WM_event_timer_add(wmWindowManager *wm, wmWindow *win, const wmEventType event_type, const double time_step)
void WM_event_timer_remove(wmWindowManager *wm, wmWindow *, wmTimer *timer)
uint8_t flag
Definition wm_window.cc:139