Blender V5.0
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_utf8.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
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 /* select new UVs (ignore UV_FLAG_SELECT_SYNC in this case) */
108 em->bm->uv_select_sync_valid = false;
109 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
110 BMIter liter;
111 BMLoop *l;
112
114 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
117 }
118 }
119
120 return true;
121}
122
124
125/* -------------------------------------------------------------------- */
128
130{
132 ot->srna,
133 "correct_aspect",
134 true,
135 "Correct Aspect",
136 "Map UVs taking aspect ratio of the image associated with the material into account");
137}
138
140
141/* -------------------------------------------------------------------- */
144
146{
147 if (!sima) {
148 return; /* Nothing to do. */
149 }
150
151 /* NOTE: Presently, when UDIM grid and tiled image are present together, only active tile for
152 * the tiled image is considered. */
153 const Image *image = sima->image;
154 if (image && image->source == IMA_SRC_TILED) {
155 ImageTile *active_tile = static_cast<ImageTile *>(
156 BLI_findlink(&image->tiles, image->active_tile_index));
157 if (active_tile) {
158 udim_base_offset[0] = (active_tile->tile_number - 1001) % 10;
159 udim_base_offset[1] = (active_tile->tile_number - 1001) / 10;
160 }
161 return;
162 }
163
164 /* TODO: Support storing an active UDIM when there are no tiles present.
165 * Until then, use 2D cursor to find the active tile index for the UDIM grid. */
166 if (uv_coords_isect_udim(sima->image, sima->tile_grid_shape, sima->cursor)) {
167 udim_base_offset[0] = floorf(sima->cursor[0]);
168 udim_base_offset[1] = floorf(sima->cursor[1]);
169 }
170}
171
172
174{
175 if (stop) {
176 return *stop;
177 }
178 return false;
179}
180
181/* -------------------------------------------------------------------- */
184
214
216{
217 only_selected_uvs = options.only_selected_uvs;
218 only_selected_faces = options.only_selected_faces;
219 use_seams = !options.topology_from_uvs || options.topology_from_uvs_use_seams;
220 correct_aspect = options.correct_aspect;
221 pin_unselected = options.pin_unselected;
222}
223
224static void modifier_unwrap_state(Object *obedit,
225 const UnwrapOptions *options,
226 bool *r_use_subsurf)
227{
228 ModifierData *md;
229 bool subsurf = options->use_subsurf;
230
231 md = static_cast<ModifierData *>(obedit->modifiers.first);
232
233 /* Subdivision-surface will take the modifier settings
234 * only if modifier is first or right after mirror. */
235 if (subsurf) {
236 if (md && md->type == eModifierType_Subsurf) {
237 const SubsurfModifierData &smd = *reinterpret_cast<const SubsurfModifierData *>(md);
238 if (smd.levels > 0) {
239 /* Skip all calculation for zero subdivision levels, similar to the way the modifier is
240 * disabled in that case. */
241 subsurf = true;
242 }
243 else {
244 subsurf = false;
245 }
246 }
247 else {
248 subsurf = false;
249 }
250 }
251
252 *r_use_subsurf = subsurf;
253}
254
256{
258
259 /* To be set by the upper layer */
260 options.topology_from_uvs = false;
261 options.topology_from_uvs_use_seams = false;
262 options.only_selected_faces = false;
263 options.only_selected_uvs = false;
264 options.pin_unselected = false;
265
266 options.slim.skip_init = false;
267
268 if (ts) {
269 options.method = ts->unwrapper;
270 options.correct_aspect = (ts->uvcalc_flag & UVCALC_NO_ASPECT_CORRECT) == 0;
271 options.fill_holes = (ts->uvcalc_flag & UVCALC_FILLHOLES) != 0;
272 options.use_subsurf = (ts->uvcalc_flag & UVCALC_USESUBSURF) != 0;
273
275 STRNCPY_UTF8(options.weight_group, ts->uvcalc_weight_group);
276 options.slim.weight_influence = ts->uvcalc_weight_factor;
277
278 options.slim.iterations = ts->uvcalc_iterations;
279 options.slim.no_flip = ts->uvcalc_flag & UVCALC_UNWRAP_NO_FLIP;
280 }
281 else {
282 options.method = RNA_enum_get(op->ptr, "method");
283 options.correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect");
284 options.fill_holes = RNA_boolean_get(op->ptr, "fill_holes");
285 options.use_subsurf = RNA_boolean_get(op->ptr, "use_subsurf_data");
286
287 options.use_weights = RNA_boolean_get(op->ptr, "use_weights");
288 RNA_string_get(op->ptr, "weight_group", options.weight_group);
289 options.slim.weight_influence = RNA_float_get(op->ptr, "weight_factor");
290
291 options.slim.iterations = RNA_int_get(op->ptr, "iterations");
292 options.slim.no_flip = RNA_boolean_get(op->ptr, "no_flip");
293 }
294
295#ifndef WITH_UV_SLIM
298 if (op) {
299 BKE_report(op->reports, RPT_WARNING, "Built without SLIM, falling back to conformal method");
300 }
301 }
302#endif /* !WITH_UV_SLIM */
303
304 if (options.weight_group[0] == '\0' || options.use_weights == false) {
305 options.slim.weight_influence = 0.0f;
306 }
307
308 options.use_abf = options.method == UVCALC_UNWRAP_METHOD_ANGLE;
310
311 /* SLIM requires hole filling */
312 if (options.use_slim) {
313 options.fill_holes = true;
314 }
315
316 if (ob) {
317 bool use_subsurf_final;
318 modifier_unwrap_state(ob, &options, &use_subsurf_final);
319 options.use_subsurf = use_subsurf_final;
320 }
321
322 return options;
323}
324
325/* Generic sync functions
326 *
327 * NOTE: these could be moved to a generic API.
328 */
329
331 PointerRNA *ptr, const char *prop_name, char flag, bool flipped, char *value_p)
332{
333 if (PropertyRNA *prop = RNA_struct_find_property(ptr, prop_name)) {
334 if (RNA_property_is_set(ptr, prop)) {
335 if (RNA_property_boolean_get(ptr, prop) ^ flipped) {
336 *value_p |= flag;
337 }
338 else {
339 *value_p &= ~flag;
340 }
341 return true;
342 }
343 RNA_property_boolean_set(ptr, prop, ((*value_p & flag) > 0) ^ flipped);
344 return false;
345 }
347 return false;
348}
349
350static bool rna_property_sync_enum(PointerRNA *ptr, const char *prop_name, int *value_p)
351{
352 if (PropertyRNA *prop = RNA_struct_find_property(ptr, prop_name)) {
353 if (RNA_property_is_set(ptr, prop)) {
354 *value_p = RNA_property_enum_get(ptr, prop);
355 return true;
356 }
357 RNA_property_enum_set(ptr, prop, *value_p);
358 return false;
359 }
361 return false;
362}
363
364static bool rna_property_sync_enum_char(PointerRNA *ptr, const char *prop_name, char *value_p)
365{
366 int value_i = *value_p;
367 if (rna_property_sync_enum(ptr, prop_name, &value_i)) {
368 *value_p = value_i;
369 return true;
370 }
371 return false;
372}
373
374static bool rna_property_sync_int(PointerRNA *ptr, const char *prop_name, int *value_p)
375{
376 if (PropertyRNA *prop = RNA_struct_find_property(ptr, prop_name)) {
377 if (RNA_property_is_set(ptr, prop)) {
378 *value_p = RNA_property_int_get(ptr, prop);
379 return true;
380 }
381 RNA_property_int_set(ptr, prop, *value_p);
382 return false;
383 }
385 return false;
386}
387
388static bool rna_property_sync_float(PointerRNA *ptr, const char *prop_name, float *value_p)
389{
390 if (PropertyRNA *prop = RNA_struct_find_property(ptr, prop_name)) {
391 if (RNA_property_is_set(ptr, prop)) {
392 *value_p = RNA_property_float_get(ptr, prop);
393 return true;
394 }
395 RNA_property_float_set(ptr, prop, *value_p);
396 return false;
397 }
399 return false;
400}
401
402static bool rna_property_sync_string(PointerRNA *ptr, const char *prop_name, char value_p[])
403{
404 if (PropertyRNA *prop = RNA_struct_find_property(ptr, prop_name)) {
405 if (RNA_property_is_set(ptr, prop)) {
406 RNA_property_string_get(ptr, prop, value_p);
407 return true;
408 }
409 RNA_property_string_set(ptr, prop, value_p);
410 return false;
411 }
413 return false;
414}
415
417{
418 /* Remember last method for live unwrap. */
419 rna_property_sync_enum_char(op->ptr, "method", &ts->unwrapper);
420
421 /* Remember packing margin. */
422 rna_property_sync_float(op->ptr, "margin", &ts->uvcalc_margin);
423
424 rna_property_sync_int(op->ptr, "iterations", &ts->uvcalc_iterations);
425
426 rna_property_sync_float(op->ptr, "weight_factor", &ts->uvcalc_weight_factor);
427
428 rna_property_sync_string(op->ptr, "weight_group", ts->uvcalc_weight_group);
429
430 rna_property_sync_flag(op->ptr, "fill_holes", UVCALC_FILLHOLES, false, &ts->uvcalc_flag);
432 op->ptr, "correct_aspect", UVCALC_NO_ASPECT_CORRECT, true, &ts->uvcalc_flag);
433 rna_property_sync_flag(op->ptr, "use_subsurf_data", UVCALC_USESUBSURF, false, &ts->uvcalc_flag);
434 rna_property_sync_flag(op->ptr, "no_flip", UVCALC_UNWRAP_NO_FLIP, false, &ts->uvcalc_flag);
435
437 op->ptr, "use_weights", UVCALC_UNWRAP_USE_WEIGHTS, false, &ts->uvcalc_flag);
438}
439
440static bool uvedit_have_selection(const Scene *scene, BMEditMesh *em, const UnwrapOptions *options)
441{
442 BMFace *efa;
443 BMLoop *l;
444 BMIter iter, liter;
445 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
446
447 if (offsets.uv == -1) {
448 return (em->bm->totfacesel != 0);
449 }
450
451 /* verify if we have any selected uv's before unwrapping,
452 * so we can cancel the operator early */
453 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
456 continue;
457 }
458 }
459 else if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
460 continue;
461 }
462
463 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
464 if (uvedit_uv_select_test(scene, em->bm, l, offsets)) {
465 break;
466 }
467 }
468
469 if (options->only_selected_uvs && !l) {
470 continue;
471 }
472
473 return true;
474 }
475
476 return false;
477}
478
479static bool uvedit_have_selection_multi(const Scene *scene,
480 const Span<Object *> objects,
481 const UnwrapOptions *options)
482{
483 bool have_select = false;
484 for (Object *obedit : objects) {
486 if (uvedit_have_selection(scene, em, options)) {
487 have_select = true;
488 break;
489 }
490 }
491 return have_select;
492}
493
495 const int material_index,
496 float *r_aspx,
497 float *r_aspy)
498{
499 if (UNLIKELY(material_index < 0 || material_index >= ob->totcol)) {
500 *r_aspx = 1.0f;
501 *r_aspy = 1.0f;
502 return;
503 }
504 Image *ima;
505 ED_object_get_active_image(ob, material_index + 1, &ima, nullptr, nullptr, nullptr);
506 ED_image_get_uv_aspect(ima, nullptr, r_aspx, r_aspy);
507}
508
509void ED_uvedit_get_aspect(Object *ob, float *r_aspx, float *r_aspy)
510{
512 BLI_assert(em != nullptr);
513 bool sloppy = true;
514 bool selected = false;
515 BMFace *efa = BM_mesh_active_face_get(em->bm, sloppy, selected);
516 if (!efa) {
517 *r_aspx = 1.0f;
518 *r_aspy = 1.0f;
519 return;
520 }
521
522 ED_uvedit_get_aspect_from_material(ob, efa->mat_nr, r_aspx, r_aspy);
523}
524
526{
527 float aspect[2];
528 ED_uvedit_get_aspect(ob, &aspect[0], &aspect[1]);
529 return aspect[0] / aspect[1];
530}
531
532static bool uvedit_is_face_affected(const Scene *scene,
533 const BMesh *bm,
534 BMFace *efa,
535 const UnwrapOptions *options,
536 const BMUVOffsets &offsets)
537{
539 return false;
540 }
541
542 if (options->only_selected_faces && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
543 return false;
544 }
545
546 if (options->only_selected_uvs) {
547 BMLoop *l;
548 BMIter iter;
549 BM_ITER_ELEM (l, &iter, efa, BM_LOOPS_OF_FACE) {
550 if (uvedit_uv_select_test(scene, bm, l, offsets)) {
551 return true;
552 }
553 }
554 return false;
555 }
556
557 return true;
558}
559
560/* Prepare unique indices for each unique pinned UV, even if it shares a BMVert.
561 */
563 const Scene *scene,
564 const BMesh *bm,
565 BMFace *efa,
566 const UnwrapOptions *options,
567 const BMUVOffsets &offsets)
568{
569 BMIter liter;
570 BMLoop *l;
571 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
572 bool pin = BM_ELEM_CD_GET_BOOL(l, offsets.pin);
573 if (options->pin_unselected && !pin) {
574 pin = !uvedit_uv_select_test(scene, bm, l, offsets);
575 }
576 if (pin) {
577 int bmvertindex = BM_elem_index_get(l->v);
578 const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
579 blender::geometry::uv_prepare_pin_index(handle, bmvertindex, luv);
580 }
581 }
582}
583
585 const Scene *scene,
586 const BMesh *bm,
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, bm, 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, bm, efa, options, offsets)) {
709 uvedit_prepare_pinned_indices(handle, scene, bm, 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, bm, efa, options, offsets)) {
716 handle, scene, bm, 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, bm, efa, options, offsets)) {
766 uvedit_prepare_pinned_indices(handle, scene, bm, 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, bm, efa, options, offsets)) {
773 scene,
774 bm,
775 efa,
776 i + offset,
777 options,
778 offsets,
779 cd_weight_offset,
780 cd_weight_index);
781 }
782 }
783
785
786 offset += bm->totface;
787 }
788
790 handle, options->fill_holes, options->topology_from_uvs, nullptr);
791
792 return handle;
793}
794
795static void texface_from_original_index(const Scene *scene,
796 const BMesh *bm,
797 const BMUVOffsets &offsets,
798 BMFace *efa,
799 int index,
800 float **r_uv,
801 bool *r_pin,
802 bool *r_select)
803{
804 BMLoop *l;
805 BMIter liter;
806
807 *r_uv = nullptr;
808 *r_pin = false;
809 *r_select = true;
810
811 if (index == ORIGINDEX_NONE) {
812 return;
813 }
814
815 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
816 if (BM_elem_index_get(l->v) == index) {
817 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
818 *r_uv = luv;
819 *r_pin = BM_ELEM_CD_GET_BOOL(l, offsets.pin);
820 *r_select = uvedit_uv_select_test(scene, bm, l, offsets);
821 break;
822 }
823 }
824}
825
826static Mesh *subdivide_edit_mesh(const Object *object,
827 const BMEditMesh *em,
828 const SubsurfModifierData *smd)
829{
830 using namespace blender;
832 em->bm, nullptr, static_cast<const Mesh *>(object->data));
834
836 /* A zero level must be prevented by #modifier_unwrap_state
837 * since necessary data won't be available, see: #128958. */
838 BLI_assert(settings.level > 0);
839
840 /* Level 1 causes disconnected triangles, force level 2 to prevent this, see: #129503. */
841 if (settings.level == 1) {
842 settings.level = 2;
843 }
844
845 bke::subdiv::ToMeshSettings mesh_settings;
846 mesh_settings.resolution = (1 << smd->levels) + 1;
848
849 bke::subdiv::Subdiv *subdiv = bke::subdiv::new_from_mesh(&settings, me_from_em);
850 if (!subdiv) {
851 return nullptr;
852 }
853 Mesh *result = bke::subdiv::subdiv_to_mesh(subdiv, &mesh_settings, me_from_em);
854 BKE_id_free(nullptr, me_from_em);
855 bke::subdiv::free(subdiv);
856 return result;
857}
858
865 Object *ob,
866 BMEditMesh *em,
867 const UnwrapOptions *options,
868 int *r_count_failed = nullptr)
869{
870 /* pointers to modifier data for unwrap control */
871 SubsurfModifierData *smd_real;
872 /* Modifier initialization data, will control what type of subdivision will happen. */
873 SubsurfModifierData smd = {{nullptr}};
874
875 /* Holds a map to edit-faces for every subdivision-surface face.
876 * These will be used to get hidden/ selected flags etc. */
877 BMFace **faceMap;
878 /* Similar to the above, we need a way to map edges to their original ones. */
879 BMEdge **edgeMap;
880
881 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
882 const int cd_weight_index = BKE_object_defgroup_name_index(ob, options->weight_group);
883
885
886 if (options->correct_aspect) {
888 }
889
890 /* number of subdivisions to perform */
891 ModifierData *md = static_cast<ModifierData *>(ob->modifiers.first);
892 smd_real = (SubsurfModifierData *)md;
893
894 smd.levels = smd_real->levels;
895 smd.subdivType = smd_real->subdivType;
896 smd.flags = smd_real->flags;
897 smd.quality = smd_real->quality;
898
899 Mesh *subdiv_mesh = subdivide_edit_mesh(ob, em, &smd);
900
901 const blender::Span<blender::float3> subsurf_positions = subdiv_mesh->vert_positions();
902 const blender::Span<blender::int2> subsurf_edges = subdiv_mesh->edges();
903 const blender::OffsetIndices subsurf_facess = subdiv_mesh->faces();
904 const blender::Span<int> subsurf_corner_verts = subdiv_mesh->corner_verts();
905 const blender::Span<MDeformVert> subsurf_deform_verts = subdiv_mesh->deform_verts();
906
907 const int *origVertIndices = static_cast<const int *>(
909 const int *origEdgeIndices = static_cast<const int *>(
911 const int *origPolyIndices = static_cast<const int *>(
913
914 faceMap = MEM_malloc_arrayN<BMFace *>(subdiv_mesh->faces_num, "unwrap_edit_face_map");
915
918
919 /* map subsurfed faces to original editFaces */
920 for (int i = 0; i < subdiv_mesh->faces_num; i++) {
921 faceMap[i] = BM_face_at_index(em->bm, origPolyIndices[i]);
922 }
923
924 edgeMap = MEM_malloc_arrayN<BMEdge *>(subdiv_mesh->edges_num, "unwrap_edit_edge_map");
925
926 /* map subsurfed edges to original editEdges */
927 for (int i = 0; i < subdiv_mesh->edges_num; i++) {
928 /* not all edges correspond to an old edge */
929 edgeMap[i] = (origEdgeIndices[i] != ORIGINDEX_NONE) ?
930 BM_edge_at_index(em->bm, origEdgeIndices[i]) :
931 nullptr;
932 }
933
934 /* Prepare and feed faces to the solver. */
935 for (const int i : subsurf_facess.index_range()) {
936 ParamKey key, vkeys[4];
937 bool pin[4], select[4];
938 const float *co[4];
939 float *uv[4];
940 float weight[4];
941 BMFace *origFace = faceMap[i];
942
944 if (BM_elem_flag_test(origFace, BM_ELEM_HIDDEN)) {
945 continue;
946 }
947 }
948 else {
949 if (BM_elem_flag_test(origFace, BM_ELEM_HIDDEN) ||
950 (options->only_selected_faces && !BM_elem_flag_test(origFace, BM_ELEM_SELECT)))
951 {
952 continue;
953 }
954 }
955
956 const blender::Span<int> poly_corner_verts = subsurf_corner_verts.slice(subsurf_facess[i]);
957
958 /* We will not check for v4 here. Sub-surface faces always have 4 vertices. */
959 BLI_assert(poly_corner_verts.size() == 4);
960 key = (ParamKey)i;
961 vkeys[0] = (ParamKey)poly_corner_verts[0];
962 vkeys[1] = (ParamKey)poly_corner_verts[1];
963 vkeys[2] = (ParamKey)poly_corner_verts[2];
964 vkeys[3] = (ParamKey)poly_corner_verts[3];
965
966 co[0] = subsurf_positions[poly_corner_verts[0]];
967 co[1] = subsurf_positions[poly_corner_verts[1]];
968 co[2] = subsurf_positions[poly_corner_verts[2]];
969 co[3] = subsurf_positions[poly_corner_verts[3]];
970
971 /* Optional vertex group weights. */
972 if (cd_weight_index >= 0) {
973 weight[0] = BKE_defvert_find_weight(&subsurf_deform_verts[poly_corner_verts[0]],
974 cd_weight_index);
975 weight[1] = BKE_defvert_find_weight(&subsurf_deform_verts[poly_corner_verts[1]],
976 cd_weight_index);
977 weight[2] = BKE_defvert_find_weight(&subsurf_deform_verts[poly_corner_verts[2]],
978 cd_weight_index);
979 weight[3] = BKE_defvert_find_weight(&subsurf_deform_verts[poly_corner_verts[3]],
980 cd_weight_index);
981 }
982 else {
983 weight[0] = 1.0f;
984 weight[1] = 1.0f;
985 weight[2] = 1.0f;
986 weight[3] = 1.0f;
987 }
988
989 /* This is where all the magic is done.
990 * If the vertex exists in the, we pass the original uv pointer to the solver, thus
991 * flushing the solution to the edit mesh. */
993 em->bm,
994 offsets,
995 origFace,
996 origVertIndices[poly_corner_verts[0]],
997 &uv[0],
998 &pin[0],
999 &select[0]);
1001 em->bm,
1002 offsets,
1003 origFace,
1004 origVertIndices[poly_corner_verts[1]],
1005 &uv[1],
1006 &pin[1],
1007 &select[1]);
1009 em->bm,
1010 offsets,
1011 origFace,
1012 origVertIndices[poly_corner_verts[2]],
1013 &uv[2],
1014 &pin[2],
1015 &select[2]);
1017 em->bm,
1018 offsets,
1019 origFace,
1020 origVertIndices[poly_corner_verts[3]],
1021 &uv[3],
1022 &pin[3],
1023 &select[3]);
1024
1026 handle, key, 4, vkeys, co, uv, weight, pin, select);
1027 }
1028
1029 /* These are calculated from original mesh too. */
1030 for (const int64_t i : subsurf_edges.index_range()) {
1031 if ((edgeMap[i] != nullptr) && BM_elem_flag_test(edgeMap[i], BM_ELEM_SEAM)) {
1032 const blender::int2 &edge = subsurf_edges[i];
1033 ParamKey vkeys[2];
1034 vkeys[0] = (ParamKey)edge[0];
1035 vkeys[1] = (ParamKey)edge[1];
1037 }
1038 }
1039
1041 handle, options->fill_holes, options->topology_from_uvs, r_count_failed);
1042
1043 /* cleanup */
1045 MEM_freeN(edgeMap);
1046 BKE_id_free(nullptr, subdiv_mesh);
1047
1048 return handle;
1049}
1050
1052
1053/* -------------------------------------------------------------------- */
1056
1066
1068{
1069 const Scene *scene = CTX_data_scene(C);
1070 ViewLayer *view_layer = CTX_data_view_layer(C);
1071
1073 options.topology_from_uvs = true;
1074 options.fill_holes = RNA_boolean_get(op->ptr, "fill_holes");
1075 options.only_selected_faces = true;
1076 options.only_selected_uvs = true;
1077 options.correct_aspect = true;
1078
1080 scene, view_layer, CTX_wm_view3d(C));
1081
1082 if (!uvedit_have_selection_multi(scene, objects, &options)) {
1083 return false;
1084 }
1085
1086 MinStretch *ms = MEM_new<MinStretch>(__func__);
1087 ms->scene = scene;
1088 ms->objects_edit = objects;
1089 ms->blend = RNA_float_get(op->ptr, "blend");
1090 ms->iterations = RNA_int_get(op->ptr, "iterations");
1091 ms->i = 0;
1092 ms->handle = construct_param_handle_multi(scene, objects, &options);
1094
1096 if (ms->blend != 0.0f) {
1098 }
1099
1100 op->customdata = ms;
1101
1102 return true;
1103}
1104
1105static void minimize_stretch_iteration(bContext *C, wmOperator *op, bool interactive)
1106{
1107 MinStretch *ms = static_cast<MinStretch *>(op->customdata);
1108 ScrArea *area = CTX_wm_area(C);
1109 const Scene *scene = CTX_data_scene(C);
1110 ToolSettings *ts = scene->toolsettings;
1111 const bool synced_selection = (ts->uv_flag & UV_FLAG_SELECT_SYNC) != 0;
1112
1115
1116 ms->i++;
1117 RNA_int_set(op->ptr, "iterations", ms->i);
1118
1119 if (interactive && (BLI_time_now_seconds() - ms->lasttime > 0.5)) {
1120 char str[UI_MAX_DRAW_STR];
1121
1123
1124 if (area) {
1125 SNPRINTF_UTF8(str, IFACE_("Minimize Stretch. Blend %.2f"), ms->blend);
1126 ED_area_status_text(area, str);
1127 ED_workspace_status_text(C, IFACE_("Press + and -, or scroll wheel to set blending"));
1128 }
1129
1131
1132 for (Object *obedit : ms->objects_edit) {
1134
1135 if (synced_selection && (em->bm->totfacesel == 0)) {
1136 continue;
1137 }
1138
1139 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
1141 }
1142 }
1143}
1144
1145static void minimize_stretch_exit(bContext *C, wmOperator *op, bool cancel)
1146{
1147 MinStretch *ms = static_cast<MinStretch *>(op->customdata);
1148 ScrArea *area = CTX_wm_area(C);
1149 const Scene *scene = CTX_data_scene(C);
1150 ToolSettings *ts = scene->toolsettings;
1151 const bool synced_selection = (ts->uv_flag & UV_FLAG_SELECT_SYNC) != 0;
1152
1153 ED_area_status_text(area, nullptr);
1154 ED_workspace_status_text(C, nullptr);
1155
1156 if (ms->timer) {
1158 }
1159
1160 if (cancel) {
1162 }
1163 else {
1165 }
1166
1168 delete (ms->handle);
1169
1170 for (Object *obedit : ms->objects_edit) {
1172
1173 if (synced_selection && (em->bm->totfacesel == 0)) {
1174 continue;
1175 }
1176
1177 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
1179 }
1180
1181 MEM_delete(ms);
1182 op->customdata = nullptr;
1183}
1184
1186{
1187 int i, iterations;
1188
1189 if (!minimize_stretch_init(C, op)) {
1190 return OPERATOR_CANCELLED;
1191 }
1192
1193 iterations = RNA_int_get(op->ptr, "iterations");
1194 for (i = 0; i < iterations; i++) {
1195 minimize_stretch_iteration(C, op, false);
1196 }
1197 minimize_stretch_exit(C, op, false);
1198
1199 return OPERATOR_FINISHED;
1200}
1201
1203 wmOperator *op,
1204 const wmEvent * /*event*/)
1205{
1206 if (!minimize_stretch_init(C, op)) {
1207 return OPERATOR_CANCELLED;
1208 }
1209
1210 minimize_stretch_iteration(C, op, true);
1211
1212 MinStretch *ms = static_cast<MinStretch *>(op->customdata);
1215
1217}
1218
1220{
1221 MinStretch *ms = static_cast<MinStretch *>(op->customdata);
1222
1223 switch (event->type) {
1224 case EVT_ESCKEY:
1225 case RIGHTMOUSE:
1226 minimize_stretch_exit(C, op, true);
1227 return OPERATOR_CANCELLED;
1228 case EVT_RETKEY:
1229 case EVT_PADENTER:
1230 case LEFTMOUSE:
1231 minimize_stretch_exit(C, op, false);
1232 return OPERATOR_FINISHED;
1233 case EVT_PADPLUSKEY:
1234 case WHEELUPMOUSE:
1235 if (event->val == KM_PRESS) {
1236 if (ms->blend < 0.95f) {
1237 ms->blend += 0.1f;
1238 ms->lasttime = 0.0f;
1239 RNA_float_set(op->ptr, "blend", ms->blend);
1240 minimize_stretch_iteration(C, op, true);
1241 }
1242 }
1243 break;
1244 case EVT_PADMINUS:
1245 case WHEELDOWNMOUSE:
1246 if (event->val == KM_PRESS) {
1247 if (ms->blend > 0.05f) {
1248 ms->blend -= 0.1f;
1249 ms->lasttime = 0.0f;
1250 RNA_float_set(op->ptr, "blend", ms->blend);
1251 minimize_stretch_iteration(C, op, true);
1252 }
1253 }
1254 break;
1255 case TIMER:
1256 if (ms->timer == event->customdata) {
1257 double start = BLI_time_now_seconds();
1258
1259 do {
1260 minimize_stretch_iteration(C, op, true);
1261 } while (BLI_time_now_seconds() - start < 0.01);
1262 }
1263 break;
1264 default: {
1265 break;
1266 }
1267 }
1268
1269 if (ms->iterations && ms->i >= ms->iterations) {
1270 minimize_stretch_exit(C, op, false);
1271 return OPERATOR_FINISHED;
1272 }
1273
1275}
1276
1278{
1279 minimize_stretch_exit(C, op, true);
1280}
1281
1283{
1284 /* identifiers */
1285 ot->name = "Minimize Stretch";
1286 ot->idname = "UV_OT_minimize_stretch";
1288 ot->description = "Reduce UV stretching by relaxing angles";
1289
1290 /* API callbacks. */
1291 ot->exec = minimize_stretch_exec;
1292 ot->invoke = minimize_stretch_invoke;
1293 ot->modal = minimize_stretch_modal;
1294 ot->cancel = minimize_stretch_cancel;
1295 ot->poll = ED_operator_uvedit;
1296
1297 /* properties */
1298 RNA_def_boolean(ot->srna,
1299 "fill_holes",
1300 true,
1301 "Fill Holes",
1302 "Virtually fill holes in mesh before unwrapping, to better avoid overlaps and "
1303 "preserve symmetry");
1305 "blend",
1306 0.0f,
1307 0.0f,
1308 1.0f,
1309 "Blend",
1310 "Blend factor between stretch minimized and original",
1311 0.0f,
1312 1.0f);
1313 RNA_def_int(ot->srna,
1314 "iterations",
1315 0,
1316 0,
1317 INT_MAX,
1318 "Iterations",
1319 "Number of iterations to run, 0 is unlimited when run interactively",
1320 0,
1321 100);
1322}
1323
1325
1327 const float matrix[2][2], /* Scale and rotation. */
1328 const float pre_translate[2] /* (pre) Translation. */
1329)
1330{
1331 /* Use a pre-transform to compute `A * (x+b)`
1332 *
1333 * \note Ordinarily, we'd use a post_transform like `A * x + b`
1334 * In general, post-transforms are easier to work with when using homogeneous coordinates.
1335 *
1336 * When UV mapping into the unit square, post-transforms can lose precision on small islands.
1337 * Instead we're using a pre-transform to maintain precision.
1338 *
1339 * To convert post-transform to pre-transform, use `A * x + b == A * (x + c), c = A^-1 * b`
1340 */
1341
1342 const int cd_loop_uv_offset = island->offsets.uv;
1343 const int faces_len = island->faces_len;
1344 for (int i = 0; i < faces_len; i++) {
1345 BMFace *f = island->faces[i];
1346 BMLoop *l;
1347 BMIter iter;
1348 BM_ITER_ELEM (l, &iter, f, BM_LOOPS_OF_FACE) {
1349 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
1350 blender::geometry::mul_v2_m2_add_v2v2(luv, matrix, luv, pre_translate);
1351 }
1352 }
1353}
1354
1358static float uv_nearest_image_tile_distance(const Image *image,
1359 const float coords[2],
1360 float nearest_tile_co[2])
1361{
1362 BKE_image_find_nearest_tile_with_offset(image, coords, nearest_tile_co);
1363
1364 /* Add 0.5 to get tile center coordinates. */
1365 float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]};
1366 add_v2_fl(nearest_tile_center_co, 0.5f);
1367
1368 return len_squared_v2v2(coords, nearest_tile_center_co);
1369}
1370
1374static float uv_nearest_grid_tile_distance(const int udim_grid[2],
1375 const float coords[2],
1376 float nearest_tile_co[2])
1377{
1378 const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])};
1379
1380 if (coords[0] > udim_grid[0]) {
1381 nearest_tile_co[0] = udim_grid[0] - 1;
1382 }
1383 else if (coords[0] < 0) {
1384 nearest_tile_co[0] = 0;
1385 }
1386 else {
1387 nearest_tile_co[0] = coords_floor[0];
1388 }
1389
1390 if (coords[1] > udim_grid[1]) {
1391 nearest_tile_co[1] = udim_grid[1] - 1;
1392 }
1393 else if (coords[1] < 0) {
1394 nearest_tile_co[1] = 0;
1395 }
1396 else {
1397 nearest_tile_co[1] = coords_floor[1];
1398 }
1399
1400 /* Add 0.5 to get tile center coordinates. */
1401 float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]};
1402 add_v2_fl(nearest_tile_center_co, 0.5f);
1403
1404 return len_squared_v2v2(coords, nearest_tile_center_co);
1405}
1406
1407static bool island_has_pins(const Scene *scene,
1408 const BMesh *bm,
1409 FaceIsland *island,
1411{
1412 const bool pin_unselected = params->pin_unselected;
1413 const bool only_selected_faces = params->only_selected_faces;
1414 BMLoop *l;
1415 BMIter iter;
1416 const int pin_offset = island->offsets.pin;
1417 for (int i = 0; i < island->faces_len; i++) {
1418 BMFace *efa = island->faces[i];
1419 if (pin_unselected && only_selected_faces && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
1420 return true;
1421 }
1422 BM_ITER_ELEM (l, &iter, island->faces[i], BM_LOOPS_OF_FACE) {
1423 if (BM_ELEM_CD_GET_BOOL(l, pin_offset)) {
1424 return true;
1425 }
1426 if (pin_unselected && !uvedit_uv_select_test(scene, bm, l, island->offsets)) {
1427 return true;
1428 }
1429 }
1430 }
1431 return false;
1432}
1433
1448static void uvedit_pack_islands_multi(const Scene *scene,
1449 const Span<Object *> objects,
1450 BMesh **bmesh_override,
1451 const SpaceImage *udim_source_closest,
1452 const bool original_selection,
1453 const bool notify_wm,
1454 const rctf *custom_region,
1456{
1457 blender::Vector<FaceIsland *> island_vector;
1458 blender::Vector<bool> pinned_vector;
1459
1460 for (const int ob_index : objects.index_range()) {
1461 Object *obedit = objects[ob_index];
1462 BMesh *bm = nullptr;
1463 if (bmesh_override) {
1464 /* NOTE: obedit is still required for aspect ratio and ID_RECALC_GEOMETRY. */
1465 bm = bmesh_override[ob_index];
1466 }
1467 else {
1469 bm = em->bm;
1470 }
1471 BLI_assert(bm);
1472
1473 const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
1474 if (offsets.uv == -1) {
1475 continue;
1476 }
1477
1478 const float aspect_y = params->correct_aspect ? ED_uvedit_get_aspect_y(obedit) : 1.0f;
1479
1480 bool only_selected_faces = params->only_selected_faces;
1481 bool only_selected_uvs = params->only_selected_uvs;
1482 const bool ignore_pinned = params->pin_method == ED_UVPACK_PIN_IGNORE;
1483 if (ignore_pinned && params->pin_unselected) {
1484 only_selected_faces = false;
1485 only_selected_uvs = false;
1486 }
1487 ListBase island_list = {nullptr};
1489 bm,
1490 &island_list,
1491 only_selected_faces,
1492 only_selected_uvs,
1493 params->use_seams,
1494 aspect_y,
1495 offsets);
1496
1497 /* Remove from linked list and append to blender::Vector. */
1498 LISTBASE_FOREACH_MUTABLE (FaceIsland *, island, &island_list) {
1499 BLI_remlink(&island_list, island);
1500 const bool pinned = island_has_pins(scene, bm, island, params);
1501 if (ignore_pinned && pinned) {
1502 MEM_freeN(island->faces);
1503 MEM_freeN(island);
1504 continue;
1505 }
1506 island_vector.append(island);
1507 pinned_vector.append(pinned);
1508 }
1509 }
1510
1511 if (island_vector.is_empty()) {
1512 return;
1513 }
1514
1515 /* Coordinates of bounding box containing all selected UVs. */
1516 float selection_min_co[2], selection_max_co[2];
1517 INIT_MINMAX2(selection_min_co, selection_max_co);
1518
1519 for (int index = 0; index < island_vector.size(); index++) {
1520 FaceIsland *island = island_vector[index];
1521
1522 for (int i = 0; i < island->faces_len; i++) {
1523 BMFace *f = island->faces[i];
1524 BM_face_uv_minmax(f, selection_min_co, selection_max_co, island->offsets.uv);
1525 }
1526 }
1527
1528 /* Center of bounding box containing all selected UVs. */
1529 float selection_center[2];
1530 mid_v2_v2v2(selection_center, selection_min_co, selection_max_co);
1531
1532 if (original_selection) {
1533 /* Protect against degenerate source AABB. */
1534 if ((selection_max_co[0] - selection_min_co[0]) * (selection_max_co[1] - selection_min_co[1]) >
1535 1e-40f)
1536 {
1537 copy_v2_v2(params->udim_base_offset, selection_min_co);
1538 params->target_extent = selection_max_co[1] - selection_min_co[1];
1539 params->target_aspect_y = (selection_max_co[0] - selection_min_co[0]) /
1540 (selection_max_co[1] - selection_min_co[1]);
1541 }
1542 }
1543 else if (custom_region) {
1544 if (!BLI_rctf_is_empty(custom_region)) {
1545 const blender::float2 custom_region_size = {
1546 BLI_rctf_size_x(custom_region),
1547 BLI_rctf_size_y(custom_region),
1548 };
1549 ARRAY_SET_ITEMS(params->udim_base_offset, custom_region->xmin, custom_region->ymin);
1550 params->target_extent = custom_region_size.y;
1551 params->target_aspect_y = custom_region_size.x / custom_region_size.y;
1552 }
1553 }
1554
1556 Heap *heap = BLI_heap_new();
1557
1559 for (int i = 0; i < island_vector.size(); i++) {
1560 FaceIsland *face_island = island_vector[i];
1562 pack_island->caller_index = i;
1563 pack_island->aspect_y = face_island->aspect_y;
1564 pack_island->pinned = pinned_vector[i];
1565 pack_island_vector.append(pack_island);
1566
1567 for (int i = 0; i < face_island->faces_len; i++) {
1568 BMFace *f = face_island->faces[i];
1569
1570 /* Storage. */
1572
1573 /* Obtain UVs of face. */
1574 BMLoop *l;
1575 BMIter iter;
1576 int j;
1577 BM_ITER_ELEM_INDEX (l, &iter, f, BM_LOOPS_OF_FACE, j) {
1578 copy_v2_v2(uvs[j], BM_ELEM_CD_GET_FLOAT_P(l, face_island->offsets.uv));
1579 }
1580
1581 pack_island->add_polygon(uvs, arena, heap);
1582
1583 BLI_memarena_clear(arena);
1584 }
1585 }
1586 BLI_heap_free(heap, nullptr);
1587 BLI_memarena_free(arena);
1588
1589 const float scale = pack_islands(pack_island_vector, *params);
1590 const bool is_cancelled = params->isCancelled();
1591
1592 float base_offset[2] = {0.0f, 0.0f};
1593 copy_v2_v2(base_offset, params->udim_base_offset);
1594
1595 if (udim_source_closest) {
1596 const Image *image = udim_source_closest->image;
1597 const int *udim_grid = udim_source_closest->tile_grid_shape;
1598 /* Check if selection lies on a valid UDIM grid tile. */
1599 bool is_valid_udim = uv_coords_isect_udim(image, udim_grid, selection_center);
1600 if (is_valid_udim) {
1601 base_offset[0] = floorf(selection_center[0]);
1602 base_offset[1] = floorf(selection_center[1]);
1603 }
1604 /* If selection doesn't lie on any UDIM then find the closest UDIM grid or image tile. */
1605 else {
1606 float nearest_image_tile_co[2] = {FLT_MAX, FLT_MAX};
1607 float nearest_image_tile_dist = FLT_MAX, nearest_grid_tile_dist = FLT_MAX;
1608 if (image) {
1609 nearest_image_tile_dist = uv_nearest_image_tile_distance(
1610 image, selection_center, nearest_image_tile_co);
1611 }
1612
1613 float nearest_grid_tile_co[2] = {0.0f, 0.0f};
1614 nearest_grid_tile_dist = uv_nearest_grid_tile_distance(
1615 udim_grid, selection_center, nearest_grid_tile_co);
1616
1617 base_offset[0] = (nearest_image_tile_dist < nearest_grid_tile_dist) ?
1618 nearest_image_tile_co[0] :
1619 nearest_grid_tile_co[0];
1620 base_offset[1] = (nearest_image_tile_dist < nearest_grid_tile_dist) ?
1621 nearest_image_tile_co[1] :
1622 nearest_grid_tile_co[1];
1623 }
1624 }
1625
1626 float matrix[2][2];
1627 float matrix_inverse[2][2];
1628 float pre_translate[2];
1629 for (const int64_t i : pack_island_vector.index_range()) {
1630 if (is_cancelled) {
1631 continue;
1632 }
1633 blender::geometry::PackIsland *pack_island = pack_island_vector[i];
1634 FaceIsland *island = island_vector[pack_island->caller_index];
1635 const float island_scale = pack_island->can_scale_(*params) ? scale : 1.0f;
1636 pack_island->build_transformation(island_scale, pack_island->angle, matrix);
1637 invert_m2_m2(matrix_inverse, matrix);
1638
1639 /* Add base_offset, post transform. */
1640 if (!pinned_vector[i] || params->pin_method != ED_UVPACK_PIN_LOCK_ALL) {
1641 mul_v2_m2v2(pre_translate, matrix_inverse, base_offset);
1642
1643 /* Add pre-translation from #pack_islands. */
1644 pre_translate[0] += pack_island->pre_translate.x;
1645 pre_translate[1] += pack_island->pre_translate.y;
1646
1647 /* Perform the transformation. */
1648 island_uv_transform(island, matrix, pre_translate);
1649 }
1650 }
1651
1652 for (const int64_t i : pack_island_vector.index_range()) {
1653 blender::geometry::PackIsland *pack_island = pack_island_vector[i];
1654 /* Cleanup memory. */
1655 pack_island_vector[i] = nullptr;
1656 delete pack_island;
1657 }
1658
1659 if (notify_wm && !is_cancelled) {
1660 for (Object *obedit : objects) {
1661 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
1662 WM_main_add_notifier(NC_GEOM | ND_DATA, obedit->data);
1663 }
1664 }
1665
1666 for (FaceIsland *island : island_vector) {
1667 MEM_freeN(island->faces);
1668 MEM_freeN(island);
1669 }
1670}
1671
1672/* -------------------------------------------------------------------- */
1675
1676/* TODO: support this, interaction with the job-system needs to be handled carefully. */
1677// #define USE_INTERACTIVE_PACK
1678
1679/* Packing targets. */
1680enum {
1685};
1686
1703
1704static void pack_islands_startjob(void *pidv, wmJobWorkerStatus *worker_status)
1705{
1706 worker_status->progress = 0.02f;
1707
1708 UVPackIslandsData *pid = static_cast<UVPackIslandsData *>(pidv);
1709
1710 pid->pack_island_params.stop = &worker_status->stop;
1711 pid->pack_island_params.do_update = &worker_status->do_update;
1712 pid->pack_island_params.progress = &worker_status->progress;
1713
1715 pid->objects,
1716 nullptr,
1717 (pid->udim_source == PACK_UDIM_SRC_CLOSEST) ? pid->sima : nullptr,
1719 !pid->use_job,
1720 (pid->udim_source == PACK_CUSTOM_REGION) ? &pid->custom_region :
1721 nullptr,
1722 &pid->pack_island_params);
1723
1724 worker_status->progress = 0.99f;
1725 worker_status->do_update = true;
1726}
1727
1728static void pack_islands_endjob(void *pidv)
1729{
1730 UVPackIslandsData *pid = static_cast<UVPackIslandsData *>(pidv);
1731 for (Object *obedit : pid->objects) {
1732 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
1734 }
1736
1737 if (pid->undo_str) {
1739 }
1740}
1741
1742static void pack_islands_freejob(void *pidv)
1743{
1744 WM_cursor_wait(false);
1745 UVPackIslandsData *pid = static_cast<UVPackIslandsData *>(pidv);
1746 WM_locked_interface_set(pid->wm, false);
1747 MEM_delete(pid);
1748}
1749
1751{
1752 ViewLayer *view_layer = CTX_data_view_layer(C);
1753 const Scene *scene = CTX_data_scene(C);
1754 const SpaceImage *sima = CTX_wm_space_image(C);
1755 const ToolSettings *ts = scene->toolsettings;
1756
1758 options.topology_from_uvs = true;
1759 options.only_selected_faces = true;
1760 options.only_selected_uvs = true;
1761 options.fill_holes = false;
1762 options.correct_aspect = true;
1763
1765 scene, view_layer, CTX_wm_view3d(C));
1766
1767 /* Early exit in case no UVs are selected. */
1768 if (!uvedit_have_selection_multi(scene, objects, &options)) {
1769 return OPERATOR_CANCELLED;
1770 }
1771
1772 /* RNA props */
1773 const int udim_source = RNA_enum_get(op->ptr, "udim_source");
1774 if (RNA_struct_property_is_set(op->ptr, "margin")) {
1775 scene->toolsettings->uvcalc_margin = RNA_float_get(op->ptr, "margin");
1776 }
1777 else {
1778 RNA_float_set(op->ptr, "margin", scene->toolsettings->uvcalc_margin);
1779 }
1780
1781 UVPackIslandsData *pid = MEM_new<UVPackIslandsData>(__func__);
1782 pid->use_job = op->flag & OP_IS_INVOKE;
1783 pid->scene = scene;
1784 pid->objects = std::move(objects);
1785 pid->sima = sima;
1786 pid->udim_source = udim_source;
1787 pid->wm = CTX_wm_manager(C);
1788
1789 if (udim_source == PACK_CUSTOM_REGION) {
1790 if (ts->uv_flag & UV_FLAG_CUSTOM_REGION) {
1792 }
1793 else {
1794 pid->custom_region.xmin = pid->custom_region.ymin = 0.0f;
1795 pid->custom_region.xmax = pid->custom_region.ymax = 1.0f;
1796 }
1797 }
1798 else {
1799 pid->custom_region = {0.0f};
1800 }
1801
1803 {
1804 /* Call default constructor and copy the defaults. */
1806 pack_island_params = default_params;
1807 }
1808
1809 pack_island_params.setFromUnwrapOptions(options);
1810 if (RNA_boolean_get(op->ptr, "rotate")) {
1811 pack_island_params.rotate_method = eUVPackIsland_RotationMethod(
1812 RNA_enum_get(op->ptr, "rotate_method"));
1813 }
1814 else {
1815 pack_island_params.rotate_method = ED_UVPACK_ROTATION_NONE;
1816 }
1817 pack_island_params.scale_to_fit = RNA_boolean_get(op->ptr, "scale");
1818 pack_island_params.merge_overlap = RNA_boolean_get(op->ptr, "merge_overlap");
1819
1820 if (RNA_boolean_get(op->ptr, "pin")) {
1821 pack_island_params.pin_method = eUVPackIsland_PinMethod(RNA_enum_get(op->ptr, "pin_method"));
1822 }
1823 else {
1824 pack_island_params.pin_method = ED_UVPACK_PIN_NONE;
1825 }
1826
1827 pack_island_params.margin_method = eUVPackIsland_MarginMethod(
1828 RNA_enum_get(op->ptr, "margin_method"));
1829 pack_island_params.margin = RNA_float_get(op->ptr, "margin");
1830 pack_island_params.shape_method = eUVPackIsland_ShapeMethod(
1831 RNA_enum_get(op->ptr, "shape_method"));
1832
1833 if (udim_source == PACK_UDIM_SRC_ACTIVE) {
1834 pack_island_params.setUDIMOffsetFromSpaceImage(sima);
1835 }
1836
1837 /* Needed even when jobs aren't used, see: #146195. */
1838 G.is_break = false;
1839
1840 if (pid->use_job) {
1841 /* Setup job. */
1842 if (pid->wm->op_undo_depth == 0) {
1843 /* The job must do its own undo push. */
1844 pid->undo_context = C;
1845 pid->undo_str = op->type->name;
1846 }
1847
1848 wmJob *wm_job = WM_jobs_get(
1849 pid->wm, CTX_wm_window(C), scene, "Packing UVs...", WM_JOB_PROGRESS, WM_JOB_TYPE_UV_PACK);
1851 WM_jobs_timer(wm_job, 0.1, 0, 0);
1854
1855 WM_cursor_wait(true);
1856 WM_jobs_start(CTX_wm_manager(C), wm_job);
1857 return OPERATOR_FINISHED;
1858 }
1859
1860 wmJobWorkerStatus worker_status = {};
1861 pack_islands_startjob(pid, &worker_status);
1864
1865 return OPERATOR_FINISHED;
1866}
1867
1870 "SCALED",
1871 0,
1872 "Scaled",
1873 "Use scale of existing UVs to multiply margin"},
1874 {ED_UVPACK_MARGIN_ADD, "ADD", 0, "Add", "Just add the margin, ignoring any UV scale"},
1876 "FRACTION",
1877 0,
1878 "Fraction",
1879 "Specify a precise fraction of final UV output"},
1880 {0, nullptr, 0, nullptr, nullptr},
1881};
1882
1884 {ED_UVPACK_ROTATION_ANY, "ANY", 0, "Any", "Any angle is allowed for rotation"},
1886 "CARDINAL",
1887 0,
1888 "Cardinal",
1889 "Only 90 degree rotations are allowed"},
1891
1892#define PACK_ROTATE_METHOD_AXIS_ALIGNED_OFFSET 3
1894 "AXIS_ALIGNED",
1895 0,
1896 "Axis-aligned",
1897 "Rotated to a minimal rectangle, either vertical or horizontal"},
1899 "AXIS_ALIGNED_X",
1900 0,
1901 "Axis-aligned (Horizontal)",
1902 "Rotate islands to be aligned horizontally"},
1904 "AXIS_ALIGNED_Y",
1905 0,
1906 "Axis-aligned (Vertical)",
1907 "Rotate islands to be aligned vertically"},
1908 {0, nullptr, 0, nullptr, nullptr},
1909};
1910
1912 {ED_UVPACK_SHAPE_CONCAVE, "CONCAVE", 0, "Exact Shape (Concave)", "Uses exact geometry"},
1913 {ED_UVPACK_SHAPE_CONVEX, "CONVEX", 0, "Boundary Shape (Convex)", "Uses convex hull"},
1915 {ED_UVPACK_SHAPE_AABB, "AABB", 0, "Bounding Box", "Uses bounding boxes"},
1916 {0, nullptr, 0, nullptr, nullptr},
1917};
1918
1926 {ED_UVPACK_PIN_LOCK_SCALE, "SCALE", 0, "Scale", "Pinned islands won't rescale"},
1927 {ED_UVPACK_PIN_LOCK_ROTATION, "ROTATION", 0, "Rotation", "Pinned islands won't rotate"},
1929 "ROTATION_SCALE",
1930 0,
1931 "Rotation and Scale",
1932 "Pinned islands will translate only"},
1933 {ED_UVPACK_PIN_LOCK_ALL, "LOCKED", 0, "All", "Pinned islands are locked in place"},
1934 {0, nullptr, 0, nullptr, nullptr},
1935};
1936
1937static void uv_pack_islands_ui(bContext * /*C*/, wmOperator *op)
1938{
1939 uiLayout *layout = op->layout;
1940 layout->use_property_split_set(true);
1941 layout->use_property_decorate_set(false);
1942 layout->prop(op->ptr, "shape_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1943 layout->prop(op->ptr, "scale", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1944 {
1945 layout->prop(op->ptr, "rotate", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1946 uiLayout *sub = &layout->row(true);
1947 sub->active_set(RNA_boolean_get(op->ptr, "rotate"));
1948 sub->prop(op->ptr, "rotate_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1949 layout->separator();
1950 }
1951 layout->prop(op->ptr, "margin_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1952 layout->prop(op->ptr, "margin", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1953 layout->separator();
1954 {
1955 layout->prop(op->ptr, "pin", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1956 uiLayout *sub = &layout->row(true);
1957 sub->active_set(RNA_boolean_get(op->ptr, "pin"));
1958 sub->prop(op->ptr, "pin_method", UI_ITEM_NONE, IFACE_("Lock Method"), ICON_NONE);
1959 layout->separator();
1960 }
1961 layout->prop(op->ptr, "merge_overlap", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1962 layout->prop(op->ptr, "udim_source", UI_ITEM_NONE, std::nullopt, ICON_NONE);
1963 layout->separator();
1964}
1965
1967{
1968 return WM_operator_props_popup_confirm_ex(C, op, event, IFACE_("Pack Islands"), IFACE_("Pack"));
1969}
1970
1972{
1973 static const EnumPropertyItem pack_target[] = {
1974 {PACK_UDIM_SRC_CLOSEST, "CLOSEST_UDIM", 0, "Closest UDIM", "Pack islands to closest UDIM"},
1976 "ACTIVE_UDIM",
1977 0,
1978 "Active UDIM",
1979 "Pack islands to active UDIM image tile or UDIM grid tile where 2D cursor is located"},
1981 "ORIGINAL_AABB",
1982 0,
1983 "Original bounding box",
1984 "Pack to starting bounding box of islands"},
1985 {PACK_CUSTOM_REGION, "CUSTOM_REGION", 0, "Custom Region", "Pack islands to custom region"},
1986 {0, nullptr, 0, nullptr, nullptr},
1987 };
1988 /* identifiers */
1989 ot->name = "Pack Islands";
1990 ot->idname = "UV_OT_pack_islands";
1991 ot->description =
1992 "Transform all islands so that they fill up the UV/UDIM space as much as possible";
1993
1994#ifdef USE_INTERACTIVE_PACK
1995 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1996#else
1997 /* The operator will handle undo, so the job system can push() it after the job completes. */
1998 ot->flag = OPTYPE_REGISTER;
1999#endif
2000
2001 /* API callbacks. */
2002 ot->exec = pack_islands_exec;
2003
2004#ifdef USE_INTERACTIVE_PACK
2006#else
2007 ot->invoke = uv_pack_islands_invoke;
2008#endif
2009 ot->ui = uv_pack_islands_ui;
2010 ot->poll = ED_operator_uvedit;
2011
2012 /* properties */
2013 RNA_def_enum(ot->srna, "udim_source", pack_target, PACK_UDIM_SRC_CLOSEST, "Pack to", "");
2014 RNA_def_boolean(ot->srna, "rotate", true, "Rotate", "Rotate islands to improve layout");
2015 RNA_def_enum(ot->srna,
2016 "rotate_method",
2019 "Rotation Method",
2020 "");
2021 RNA_def_boolean(ot->srna, "scale", true, "Scale", "Scale islands to fill unit square");
2023 ot->srna, "merge_overlap", false, "Merge Overlapping", "Overlapping islands stick together");
2024 RNA_def_enum(ot->srna,
2025 "margin_method",
2028 "Margin Method",
2029 "");
2031 ot->srna, "margin", 0.001f, 0.0f, 1.0f, "Margin", "Space between islands", 0.0f, 1.0f);
2032 RNA_def_boolean(ot->srna,
2033 "pin",
2034 false,
2035 "Lock Pinned Islands",
2036 "Constrain islands containing any pinned UV's");
2037 RNA_def_enum(ot->srna,
2038 "pin_method",
2041 "Pin Method",
2042 "");
2043 RNA_def_enum(ot->srna,
2044 "shape_method",
2047 "Shape Method",
2048 "");
2049}
2050
2052
2053/* -------------------------------------------------------------------- */
2056
2058{
2059 const Scene *scene = CTX_data_scene(C);
2060 ViewLayer *view_layer = CTX_data_view_layer(C);
2061 ToolSettings *ts = scene->toolsettings;
2062 const bool synced_selection = (ts->uv_flag & UV_FLAG_SELECT_SYNC) != 0;
2063
2064 UnwrapOptions options = unwrap_options_get(nullptr, nullptr, ts);
2065 options.topology_from_uvs = true;
2066 options.only_selected_faces = true;
2067 options.only_selected_uvs = true;
2068 options.fill_holes = false;
2069 options.correct_aspect = true;
2070
2072 scene, view_layer, CTX_wm_view3d(C));
2073
2074 if (!uvedit_have_selection_multi(scene, objects, &options)) {
2075 return OPERATOR_CANCELLED;
2076 }
2077
2078 /* RNA props */
2079 const bool scale_uv = RNA_boolean_get(op->ptr, "scale_uv");
2080 const bool shear = RNA_boolean_get(op->ptr, "shear");
2081
2082 ParamHandle *handle = construct_param_handle_multi(scene, objects, &options);
2083 blender::geometry::uv_parametrizer_average(handle, false, scale_uv, shear);
2085 delete (handle);
2086
2087 for (Object *obedit : objects) {
2089
2090 if (synced_selection && (em->bm->totvertsel == 0)) {
2091 continue;
2092 }
2093
2094 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
2095 WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
2096 }
2097 return OPERATOR_FINISHED;
2098}
2099
2101{
2102 /* identifiers */
2103 ot->name = "Average Islands Scale";
2104 ot->idname = "UV_OT_average_islands_scale";
2105 ot->description = "Average the size of separate UV islands, based on their area in 3D space";
2106
2107 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2108
2109 /* API callbacks. */
2111 ot->poll = ED_operator_uvedit;
2112
2113 /* properties */
2114 RNA_def_boolean(ot->srna, "scale_uv", false, "Non-Uniform", "Scale U and V independently");
2115 RNA_def_boolean(ot->srna, "shear", false, "Shear", "Reduce shear within islands");
2116}
2117
2119
2120/* -------------------------------------------------------------------- */
2123
2124static struct {
2128} g_live_unwrap = {nullptr};
2129
2131{
2132 /* NOTE: don't validate the timer, assume the timer passed in is valid. */
2133 return g_live_unwrap.timer == timer;
2134}
2135
2143{
2144 if (g_live_unwrap.timer == nullptr) {
2145 return false;
2146 }
2147 if (BLI_findindex(&wm->runtime->timers, g_live_unwrap.timer) != -1) {
2148 return false;
2149 }
2150 g_live_unwrap.timer = nullptr;
2151 return true;
2152}
2153
2154void ED_uvedit_live_unwrap_begin(Scene *scene, Object *obedit, wmWindow *win_modal)
2155{
2156 ParamHandle *handle = nullptr;
2158
2159 if (!ED_uvedit_test(obedit)) {
2160 return;
2161 }
2162
2163 UnwrapOptions options = unwrap_options_get(nullptr, obedit, scene->toolsettings);
2164 options.topology_from_uvs = false;
2165 options.only_selected_faces = false;
2166 options.only_selected_uvs = false;
2167
2168 if (options.use_subsurf) {
2169 handle = construct_param_handle_subsurfed(scene, obedit, em, &options, nullptr);
2170 }
2171 else {
2172 handle = construct_param_handle(scene, obedit, em->bm, &options, nullptr);
2173 }
2174
2175 if (options.use_slim) {
2176 options.slim.no_flip = false;
2177 options.slim.skip_init = true;
2179
2180 if (win_modal) {
2181 wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
2182 /* Clear in the unlikely event this is still set. */
2184 BLI_assert(!g_live_unwrap.timer);
2185 g_live_unwrap.timer = WM_event_timer_add(wm, win_modal, TIMER, 0.01f);
2186 }
2187 }
2188 else {
2190 }
2191
2192 /* Create or increase size of g_live_unwrap.handles array */
2193 if (g_live_unwrap.handles == nullptr) {
2194 g_live_unwrap.len_alloc = 32;
2196 "uvedit_live_unwrap_liveHandles");
2197 g_live_unwrap.len = 0;
2198 }
2199 if (g_live_unwrap.len >= g_live_unwrap.len_alloc) {
2200 g_live_unwrap.len_alloc *= 2;
2201 g_live_unwrap.handles = static_cast<ParamHandle **>(
2202 MEM_reallocN(g_live_unwrap.handles, sizeof(ParamHandle *) * g_live_unwrap.len_alloc));
2203 }
2204 g_live_unwrap.handles[g_live_unwrap.len] = handle;
2205 g_live_unwrap.len++;
2206}
2207
2209{
2210 if (g_live_unwrap.handles) {
2211 for (int i = 0; i < g_live_unwrap.len; i++) {
2212 if (uv_parametrizer_is_slim(g_live_unwrap.handles[i])) {
2214 }
2215 else {
2217 }
2218
2220 }
2221 }
2222}
2223
2224void ED_uvedit_live_unwrap_end(const bool cancel)
2225{
2226 if (g_live_unwrap.timer) {
2227 wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
2229 if (g_live_unwrap.timer) {
2230 wmWindow *win = g_live_unwrap.timer->win;
2232 g_live_unwrap.timer = nullptr;
2233 }
2234 }
2235
2236 if (g_live_unwrap.handles) {
2237 for (int i = 0; i < g_live_unwrap.len; i++) {
2238 if (uv_parametrizer_is_slim(g_live_unwrap.handles[i])) {
2240 }
2241 else {
2243 }
2244
2245 if (cancel) {
2247 }
2248 delete (g_live_unwrap.handles[i]);
2249 }
2250 MEM_freeN(g_live_unwrap.handles);
2251 g_live_unwrap.handles = nullptr;
2252 g_live_unwrap.len = 0;
2253 g_live_unwrap.len_alloc = 0;
2254 }
2255}
2256
2258
2259/* -------------------------------------------------------------------- */
2262
2263#define VIEW_ON_EQUATOR 0
2264#define VIEW_ON_POLES 1
2265#define ALIGN_TO_OBJECT 2
2266
2267#define POLAR_ZX 0
2268#define POLAR_ZY 1
2269
2270enum {
2272 FAN = 1,
2273};
2274
2275static void uv_map_transform_calc_bounds(BMEditMesh *em, float r_min[3], float r_max[3])
2276{
2277 BMFace *efa;
2278 BMIter iter;
2279 INIT_MINMAX(r_min, r_max);
2280 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2282 BM_face_calc_bounds_expand(efa, r_min, r_max);
2283 }
2284 }
2285}
2286
2287static void uv_map_transform_calc_center_median(BMEditMesh *em, float r_center[3])
2288{
2289 BMFace *efa;
2290 BMIter iter;
2291 uint center_accum_num = 0;
2292 zero_v3(r_center);
2293 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2295 float center[3];
2296 BM_face_calc_center_median(efa, center);
2297 add_v3_v3(r_center, center);
2298 center_accum_num += 1;
2299 }
2300 }
2301 mul_v3_fl(r_center, 1.0f / float(center_accum_num));
2302}
2303
2304static void uv_map_transform_center(const Scene *scene,
2305 View3D *v3d,
2306 Object *ob,
2307 BMEditMesh *em,
2308 float r_center[3],
2309 float r_bounds[2][3])
2310{
2311 /* only operates on the edit object - this is all that's needed now */
2312 const int around = (v3d) ? scene->toolsettings->transform_pivot_point :
2314
2315 float bounds[2][3];
2316 INIT_MINMAX(bounds[0], bounds[1]);
2317 bool is_minmax_set = false;
2318
2319 switch (around) {
2320 case V3D_AROUND_CENTER_BOUNDS: /* bounding box center */
2321 {
2323 is_minmax_set = true;
2324 mid_v3_v3v3(r_center, bounds[0], bounds[1]);
2325 break;
2326 }
2329 break;
2330 }
2331 case V3D_AROUND_CURSOR: /* cursor center */
2332 {
2333 invert_m4_m4(ob->runtime->world_to_object.ptr(), ob->object_to_world().ptr());
2334 mul_v3_m4v3(r_center, ob->world_to_object().ptr(), scene->cursor.location);
2335 break;
2336 }
2337 case V3D_AROUND_ACTIVE: {
2338 BMEditSelection ese;
2339 if (BM_select_history_active_get(em->bm, &ese)) {
2340 BM_editselection_center(&ese, r_center);
2341 break;
2342 }
2344 }
2345 case V3D_AROUND_LOCAL_ORIGINS: /* object center */
2346 default:
2347 zero_v3(r_center);
2348 break;
2349 }
2350
2351 /* if this is passed, always set! */
2352 if (r_bounds) {
2353 if (!is_minmax_set) {
2355 }
2356 copy_v3_v3(r_bounds[0], bounds[0]);
2357 copy_v3_v3(r_bounds[1], bounds[1]);
2358 }
2359}
2360
2361static void uv_map_rotation_matrix_ex(float result[4][4],
2362 RegionView3D *rv3d,
2363 Object *ob,
2364 float upangledeg,
2365 float sideangledeg,
2366 float radius,
2367 const float offset[4])
2368{
2369 float rotup[4][4], rotside[4][4], viewmatrix[4][4], rotobj[4][4];
2370 float sideangle = 0.0f, upangle = 0.0f;
2371
2372 /* get rotation of the current view matrix */
2373 if (rv3d) {
2374 copy_m4_m4(viewmatrix, rv3d->viewmat);
2375 }
2376 else {
2377 unit_m4(viewmatrix);
2378 }
2379
2380 /* but shifting */
2381 zero_v3(viewmatrix[3]);
2382
2383 /* get rotation of the current object matrix */
2384 copy_m4_m4(rotobj, ob->object_to_world().ptr());
2385 zero_v3(rotobj[3]);
2386
2387 /* but shifting */
2388 add_v4_v4(rotobj[3], offset);
2389 rotobj[3][3] = 0.0f;
2390
2391 zero_m4(rotup);
2392 zero_m4(rotside);
2393
2394 /* Compensate front/side.. against opengl x,y,z world definition.
2395 * This is "a sledgehammer to crack a nut" (overkill), a few plus minus 1 will do here.
2396 * I wanted to keep the reason here, so we're rotating. */
2397 sideangle = float(M_PI) * (sideangledeg + 180.0f) / 180.0f;
2398 rotside[0][0] = cosf(sideangle);
2399 rotside[0][1] = -sinf(sideangle);
2400 rotside[1][0] = sinf(sideangle);
2401 rotside[1][1] = cosf(sideangle);
2402 rotside[2][2] = 1.0f;
2403
2404 upangle = float(M_PI) * upangledeg / 180.0f;
2405 rotup[1][1] = cosf(upangle) / radius;
2406 rotup[1][2] = -sinf(upangle) / radius;
2407 rotup[2][1] = sinf(upangle) / radius;
2408 rotup[2][2] = cosf(upangle) / radius;
2409 rotup[0][0] = 1.0f / radius;
2410
2411 /* Calculate transforms. */
2412 mul_m4_series(result, rotup, rotside, viewmatrix, rotobj);
2413}
2414
2415static void uv_map_transform(bContext *C, wmOperator *op, float rotmat[3][3])
2416{
2417 Object *obedit = CTX_data_edit_object(C);
2419
2420 const int align = RNA_enum_get(op->ptr, "align");
2421 const int direction = RNA_enum_get(op->ptr, "direction");
2422 const float radius = RNA_struct_find_property(op->ptr, "radius") ?
2423 RNA_float_get(op->ptr, "radius") :
2424 1.0f;
2425
2426 /* Be compatible to the "old" sphere/cylinder mode. */
2427 if (direction == ALIGN_TO_OBJECT) {
2428 unit_m3(rotmat);
2429
2430 if (align == POLAR_ZY) {
2431 rotmat[0][0] = 0.0f;
2432 rotmat[0][1] = 1.0f;
2433 rotmat[1][0] = -1.0f;
2434 rotmat[1][1] = 0.0f;
2435 }
2436 return;
2437 }
2438
2439 const float up_angle_deg = (direction == VIEW_ON_EQUATOR) ? 90.0f : 0.0f;
2440 const float side_angle_deg = (align == POLAR_ZY) == (direction == VIEW_ON_EQUATOR) ? 90.0f :
2441 0.0f;
2442 const float offset[4] = {0};
2443 float rotmat4[4][4];
2444 uv_map_rotation_matrix_ex(rotmat4, rv3d, obedit, up_angle_deg, side_angle_deg, radius, offset);
2445 copy_m3_m4(rotmat, rotmat4);
2446}
2447
2449{
2450 static const EnumPropertyItem direction_items[] = {
2451 {VIEW_ON_EQUATOR, "VIEW_ON_EQUATOR", 0, "View on Equator", "3D view is on the equator"},
2452 {VIEW_ON_POLES, "VIEW_ON_POLES", 0, "View on Poles", "3D view is on the poles"},
2454 "ALIGN_TO_OBJECT",
2455 0,
2456 "Align to Object",
2457 "Align according to object transform"},
2458 {0, nullptr, 0, nullptr, nullptr},
2459 };
2460 static const EnumPropertyItem align_items[] = {
2461 {POLAR_ZX, "POLAR_ZX", 0, "Polar ZX", "Polar 0 is X"},
2462 {POLAR_ZY, "POLAR_ZY", 0, "Polar ZY", "Polar 0 is Y"},
2463 {0, nullptr, 0, nullptr, nullptr},
2464 };
2465
2466 static const EnumPropertyItem pole_items[] = {
2467 {PINCH, "PINCH", 0, "Pinch", "UVs are pinched at the poles"},
2468 {FAN, "FAN", 0, "Fan", "UVs are fanned at the poles"},
2469 {0, nullptr, 0, nullptr, nullptr},
2470 };
2471
2472 RNA_def_enum(ot->srna,
2473 "direction",
2474 direction_items,
2476 "Direction",
2477 "Direction of the sphere or cylinder");
2478 RNA_def_enum(ot->srna,
2479 "align",
2480 align_items,
2481 POLAR_ZX,
2482 "Align",
2483 "How to determine rotation around the pole");
2484 RNA_def_enum(ot->srna, "pole", pole_items, PINCH, "Pole", "How to handle faces at the poles");
2485 RNA_def_boolean(ot->srna,
2486 "seam",
2487 false,
2488 "Preserve Seams",
2489 "Separate projections by islands isolated by seams");
2490
2491 if (radius) {
2492 RNA_def_float(ot->srna,
2493 "radius",
2494 1.0f,
2495 0.0f,
2496 FLT_MAX,
2497 "Radius",
2498 "Radius of the sphere or cylinder",
2499 0.0001f,
2500 100.0f);
2501 }
2502}
2503
2505 const int cd_loop_uv_offset,
2506 const float aspect_y)
2507{
2508 BLI_assert(aspect_y != 1.0f); /* Nothing to do, should be handled by caller. */
2509 BLI_assert(aspect_y > 0.0f); /* Negative aspect ratios are not supported. */
2510
2511 BMLoop *l;
2512 BMIter iter;
2513 BM_ITER_ELEM (l, &iter, efa, BM_LOOPS_OF_FACE) {
2514 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
2515 if (aspect_y > 1.0f) {
2516 /* Reduce round-off error, i.e. `u = (u - 0.5) / aspect_y + 0.5`. */
2517 luv[0] = luv[0] / aspect_y + (0.5f - 0.5f / aspect_y);
2518 }
2519 else {
2520 /* Reduce round-off error, i.e. `v = (v - 0.5) * aspect_y + 0.5`. */
2521 luv[1] = luv[1] * aspect_y + (0.5f - 0.5f * aspect_y);
2522 }
2523 }
2524}
2525
2527{
2528 const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2);
2529 const float aspect_y = ED_uvedit_get_aspect_y(ob);
2530 if (aspect_y == 1.0f) {
2531 /* Scaling by 1.0 has no effect. */
2532 return;
2533 }
2534 BMFace *efa;
2535 BMIter iter;
2536 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2538 shrink_loop_uv_by_aspect_ratio(efa, cd_loop_uv_offset, aspect_y);
2539 }
2540 }
2541}
2542
2544{
2545 const int materials_num = ob->totcol;
2546 if (materials_num == 0) {
2547 /* Without any materials, there is no aspect_y information and nothing to do. */
2548 return;
2549 }
2550
2551 blender::Array<float, 16> material_aspect_y(materials_num, -1);
2552 /* Lazily initialize aspect ratio for materials. */
2553
2554 const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2);
2555
2556 BMFace *efa;
2557 BMIter iter;
2558 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2559 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
2560 continue;
2561 }
2562
2563 const int material_index = efa->mat_nr;
2564 if (UNLIKELY(material_index < 0 || material_index >= materials_num)) {
2565 /* The index might be for a material slot which is not currently setup. */
2566 continue;
2567 }
2568
2569 float aspect_y = material_aspect_y[material_index];
2570 if (aspect_y == -1.0f) {
2571 /* Lazily initialize aspect ratio for materials. */
2572 float aspx, aspy;
2573 ED_uvedit_get_aspect_from_material(ob, material_index, &aspx, &aspy);
2574 aspect_y = aspx / aspy;
2575 material_aspect_y[material_index] = aspect_y;
2576 }
2577
2578 if (aspect_y == 1.0f) {
2579 /* Scaling by 1.0 has no effect. */
2580 continue;
2581 }
2582 shrink_loop_uv_by_aspect_ratio(efa, cd_loop_uv_offset, aspect_y);
2583 }
2584}
2585
2586#undef VIEW_ON_EQUATOR
2587#undef VIEW_ON_POLES
2588#undef ALIGN_TO_OBJECT
2589
2590#undef POLAR_ZX
2591#undef POLAR_ZY
2592
2594
2595/* -------------------------------------------------------------------- */
2598
2599static void uv_map_clip_correct_properties_ex(wmOperatorType *ot, bool clip_to_bounds)
2600{
2602 /* Optional, since not all unwrapping types need to be clipped. */
2603 if (clip_to_bounds) {
2604 RNA_def_boolean(ot->srna,
2605 "clip_to_bounds",
2606 false,
2607 "Clip to Bounds",
2608 "Clip UV coordinates to bounds after unwrapping");
2609 }
2610 RNA_def_boolean(ot->srna,
2611 "scale_to_bounds",
2612 false,
2613 "Scale to Bounds",
2614 "Scale UV coordinates to bounds after unwrapping");
2615}
2616
2621
2629static void uv_map_clip_correct(const Scene *scene,
2630 const Span<Object *> objects,
2631 wmOperator *op,
2632 bool per_face_aspect,
2633 bool only_selected_uvs)
2634{
2635 BMFace *efa;
2636 BMLoop *l;
2637 BMIter iter, liter;
2638 float dx, dy, min[2], max[2];
2639 const bool correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect");
2640 const bool clip_to_bounds = (RNA_struct_find_property(op->ptr, "clip_to_bounds") &&
2641 RNA_boolean_get(op->ptr, "clip_to_bounds"));
2642 const bool scale_to_bounds = RNA_boolean_get(op->ptr, "scale_to_bounds");
2643
2645
2646 for (Object *ob : objects) {
2648 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
2649
2650 /* Correct for image aspect ratio. */
2651 if (correct_aspect) {
2652 if (per_face_aspect) {
2654 }
2655 else {
2656 correct_uv_aspect(ob, em);
2657 }
2658 }
2659
2660 if (scale_to_bounds) {
2661 /* find uv limits */
2662 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2663 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
2664 continue;
2665 }
2666
2667 if (only_selected_uvs && !uvedit_face_select_test(scene, em->bm, efa)) {
2668 continue;
2669 }
2670
2671 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
2672 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
2673 minmax_v2v2_v2(min, max, luv);
2674 }
2675 }
2676 }
2677 else if (clip_to_bounds) {
2678 /* clipping and wrapping */
2679 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2680 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
2681 continue;
2682 }
2683
2684 if (only_selected_uvs && !uvedit_face_select_test(scene, em->bm, efa)) {
2685 continue;
2686 }
2687
2688 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
2689 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
2690 clamp_v2(luv, 0.0f, 1.0f);
2691 }
2692 }
2693 }
2694 }
2695
2696 if (scale_to_bounds) {
2697 /* rescale UV to be in 1/1 */
2698 dx = (max[0] - min[0]);
2699 dy = (max[1] - min[1]);
2700
2701 if (dx > 0.0f) {
2702 dx = 1.0f / dx;
2703 }
2704 if (dy > 0.0f) {
2705 dy = 1.0f / dy;
2706 }
2707
2708 if (dx == 1.0f && dy == 1.0f && min[0] == 0.0f && min[1] == 0.0f) {
2709 /* Scaling by 1.0, without translating, has no effect. */
2710 return;
2711 }
2712
2713 for (Object *ob : objects) {
2715 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
2716
2717 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
2718 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
2719 continue;
2720 }
2721
2722 if (only_selected_uvs && !uvedit_face_select_test(scene, em->bm, efa)) {
2723 continue;
2724 }
2725
2726 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
2727 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
2728
2729 luv[0] = (luv[0] - min[0]) * dx;
2730 luv[1] = (luv[1] - min[1]) * dy;
2731 }
2732 }
2733 }
2734 }
2735}
2736
2738
2739/* -------------------------------------------------------------------- */
2742
2743/* Assumes UV Map exists, doesn't run update functions. */
2744static void uvedit_unwrap(const Scene *scene,
2745 Object *obedit,
2746 const UnwrapOptions *options,
2747 int *r_count_changed,
2748 int *r_count_failed)
2749{
2752 return;
2753 }
2754
2755 bool use_subsurf;
2756 modifier_unwrap_state(obedit, options, &use_subsurf);
2757
2758 ParamHandle *handle;
2759 if (use_subsurf) {
2760 handle = construct_param_handle_subsurfed(scene, obedit, em, options, r_count_failed);
2761 }
2762 else {
2763 handle = construct_param_handle(scene, obedit, em->bm, options, r_count_failed);
2764 }
2765
2766 if (options->use_slim) {
2767 uv_parametrizer_slim_solve(handle, &options->slim, r_count_changed, r_count_failed);
2768 }
2769 else {
2771 blender::geometry::uv_parametrizer_lscm_solve(handle, r_count_changed, r_count_failed);
2773 }
2774
2775 blender::geometry::uv_parametrizer_average(handle, true, false, false);
2776
2778
2779 delete (handle);
2780}
2781
2782static void uvedit_unwrap_multi(const Scene *scene,
2783 const Span<Object *> objects,
2784 const UnwrapOptions *options,
2785 int *r_count_changed = nullptr,
2786 int *r_count_failed = nullptr)
2787{
2788 for (Object *obedit : objects) {
2789 uvedit_unwrap(scene, obedit, options, r_count_changed, r_count_failed);
2790 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
2791 WM_main_add_notifier(NC_GEOM | ND_DATA, obedit->data);
2792 }
2793}
2794
2795void ED_uvedit_live_unwrap(const Scene *scene, const Span<Object *> objects)
2796{
2797 if (scene->toolsettings->edge_mode_live_unwrap) {
2798 UnwrapOptions options = unwrap_options_get(nullptr, nullptr, scene->toolsettings);
2799 options.topology_from_uvs = false;
2800 options.only_selected_faces = false;
2801 options.only_selected_uvs = false;
2802
2803 uvedit_unwrap_multi(scene, objects, &options, nullptr);
2804
2805 blender::geometry::UVPackIsland_Params pack_island_params;
2806 pack_island_params.setFromUnwrapOptions(options);
2807 pack_island_params.rotate_method = ED_UVPACK_ROTATION_ANY;
2808 pack_island_params.pin_method = ED_UVPACK_PIN_IGNORE;
2809 pack_island_params.margin_method = ED_UVPACK_MARGIN_SCALED;
2810 pack_island_params.margin = scene->toolsettings->uvcalc_margin;
2811
2813 scene, objects, nullptr, nullptr, false, true, nullptr, &pack_island_params);
2814 }
2815}
2816
2817enum {
2820};
2821
2823{
2824 ViewLayer *view_layer = CTX_data_view_layer(C);
2825 const Scene *scene = CTX_data_scene(C);
2826 const bool sync_selection = (scene->toolsettings->uv_flag & UV_FLAG_SELECT_SYNC) != 0;
2827
2828 int reported_errors = 0;
2829
2831 scene, view_layer, CTX_wm_view3d(C));
2832
2834
2835 UnwrapOptions options = unwrap_options_get(op, nullptr, nullptr);
2836 options.topology_from_uvs = false;
2837 options.only_selected_faces = true;
2838 options.only_selected_uvs = false;
2839
2840 /* We will report an error unless at least one object
2841 * has the subsurf modifier in the right place. */
2842 bool subsurf_error = options.use_subsurf;
2843
2844 if (CTX_wm_space_image(C)) {
2845 /* Inside the UV Editor, only unwrap selected UVs. */
2846 if (sync_selection) {
2847 /* It's important to include unselected faces so they are taken into account
2848 * when unwrapping adjacent UV's, so it's possible to select part of an island
2849 * without it becoming disconnected from the unselected region, see: #148536. */
2850 options.only_selected_faces = false;
2851 }
2852 options.only_selected_uvs = true;
2853 options.pin_unselected = true;
2854 }
2855
2856 if (!uvedit_have_selection_multi(scene, objects, &options)) {
2857 return OPERATOR_CANCELLED;
2858 }
2859
2860 /* add uvs if they don't exist yet */
2861 for (Object *obedit : objects) {
2862 float obsize[3];
2863 bool use_subsurf_final;
2864
2865 if (!uvedit_ensure_uvs(obedit)) {
2866 continue;
2867 }
2868
2869 if (subsurf_error) {
2870 /* Double up the check here but better keep uvedit_unwrap interface simple and not
2871 * pass operator for warning append. */
2872 modifier_unwrap_state(obedit, &options, &use_subsurf_final);
2873 if (use_subsurf_final) {
2874 subsurf_error = false;
2875 }
2876 }
2877
2878 if (reported_errors & (UNWRAP_ERROR_NONUNIFORM | UNWRAP_ERROR_NEGATIVE)) {
2879 continue;
2880 }
2881
2882 mat4_to_size(obsize, obedit->object_to_world().ptr());
2883 if (!(fabsf(obsize[0] - obsize[1]) < 1e-4f && fabsf(obsize[1] - obsize[2]) < 1e-4f)) {
2884 if ((reported_errors & UNWRAP_ERROR_NONUNIFORM) == 0) {
2885 BKE_report(op->reports,
2886 RPT_INFO,
2887 "Object has non-uniform scale, unwrap will operate on a non-scaled version of "
2888 "the mesh");
2889 reported_errors |= UNWRAP_ERROR_NONUNIFORM;
2890 }
2891 }
2892 else if (is_negative_m4(obedit->object_to_world().ptr())) {
2893 if ((reported_errors & UNWRAP_ERROR_NEGATIVE) == 0) {
2894 BKE_report(
2895 op->reports,
2896 RPT_INFO,
2897 "Object has negative scale, unwrap will operate on a non-flipped version of the mesh");
2898 reported_errors |= UNWRAP_ERROR_NEGATIVE;
2899 }
2900 }
2901 }
2902
2903 if (subsurf_error) {
2904 BKE_report(op->reports,
2905 RPT_INFO,
2906 "Subdivision Surface modifier needs to be first to work with unwrap");
2907 }
2908
2909 /* execute unwrap */
2910 int count_changed = 0;
2911 int count_failed = 0;
2912 uvedit_unwrap_multi(scene, objects, &options, &count_changed, &count_failed);
2913
2914 blender::geometry::UVPackIsland_Params pack_island_params;
2915 pack_island_params.setFromUnwrapOptions(options);
2916 pack_island_params.rotate_method = ED_UVPACK_ROTATION_ANY;
2917 pack_island_params.pin_method = ED_UVPACK_PIN_IGNORE;
2918 pack_island_params.margin_method = eUVPackIsland_MarginMethod(
2919 RNA_enum_get(op->ptr, "margin_method"));
2920 pack_island_params.margin = RNA_float_get(op->ptr, "margin");
2921
2923 scene, objects, nullptr, nullptr, false, true, nullptr, &pack_island_params);
2924
2925 if (count_failed == 0 && count_changed == 0) {
2926 BKE_report(op->reports,
2928 "Unwrap could not solve any island(s), edge seams may need to be added");
2929 }
2930 else if (count_failed) {
2931 BKE_reportf(op->reports,
2933 "Unwrap failed to solve %d of %d island(s), edge seams may need to be added",
2934 count_failed,
2935 count_changed + count_failed);
2936 }
2937
2938 return OPERATOR_FINISHED;
2939}
2940
2941static void unwrap_draw(bContext * /*C*/, wmOperator *op)
2942{
2943 uiLayout *layout = op->layout;
2944
2945 layout->use_property_split_set(true);
2946 layout->use_property_decorate_set(false);
2947
2948 /* Main draw call */
2950
2951 uiLayout *col;
2952
2953 col = &layout->column(true);
2954 col->prop(&ptr, "method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2955 bool is_slim = RNA_enum_get(op->ptr, "method") == UVCALC_UNWRAP_METHOD_MINIMUM_STRETCH;
2956
2957 if (is_slim) {
2958 col->prop(&ptr, "iterations", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2959 col->prop(&ptr, "no_flip", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2960
2961 col->separator();
2962 col->prop(&ptr, "use_weights", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2963
2964 if (RNA_boolean_get(op->ptr, "use_weights")) {
2965 col = &layout->column(true);
2966 col->prop(&ptr, "weight_group", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2967 col->prop(&ptr, "weight_factor", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2968 }
2969 }
2970 else {
2971 col->prop(&ptr, "fill_holes", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2972 }
2973
2974 col->separator();
2975 col->prop(&ptr, "use_subsurf_data", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2976
2977 col->separator();
2978 col->prop(&ptr, "correct_aspect", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2979 col->prop(&ptr, "margin_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2980 col->prop(&ptr, "margin", UI_ITEM_NONE, std::nullopt, ICON_NONE);
2981}
2982
2984{
2985 const ToolSettings *tool_settings_default = DNA_struct_default_get(ToolSettings);
2986
2987 static const EnumPropertyItem method_items[] = {
2988 {UVCALC_UNWRAP_METHOD_ANGLE, "ANGLE_BASED", 0, "Angle Based", ""},
2989 {UVCALC_UNWRAP_METHOD_CONFORMAL, "CONFORMAL", 0, "Conformal", ""},
2990 {UVCALC_UNWRAP_METHOD_MINIMUM_STRETCH, "MINIMUM_STRETCH", 0, "Minimum Stretch", ""},
2991 {0, nullptr, 0, nullptr, nullptr},
2992 };
2993
2994 /* identifiers */
2995 ot->name = "Unwrap";
2996 ot->description = "Unwrap the mesh of the object being edited";
2997 ot->idname = "UV_OT_unwrap";
2998 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2999
3000 /* API callbacks. */
3001 ot->exec = unwrap_exec;
3002 ot->poll = ED_operator_uvmap;
3003
3004 /* Only draw relevant ui elements */
3005 ot->ui = unwrap_draw;
3006
3007 /* properties */
3008 ot->prop = RNA_def_enum(
3009 ot->srna,
3010 "method",
3011 method_items,
3012 tool_settings_default->unwrapper,
3013 "Method",
3014 "Unwrapping method (Angle Based usually gives better results than Conformal, while "
3015 "being somewhat slower)");
3016 RNA_def_boolean(ot->srna,
3017 "fill_holes",
3018 tool_settings_default->uvcalc_flag & UVCALC_FILLHOLES,
3019 "Fill Holes",
3020 "Virtually fill holes in mesh before unwrapping, to better avoid overlaps and "
3021 "preserve symmetry");
3022
3024
3026 ot->srna,
3027 "use_subsurf_data",
3028 false,
3029 "Use Subdivision Surface",
3030 "Map UVs taking vertex position after Subdivision Surface modifier has been applied");
3031 RNA_def_enum(ot->srna,
3032 "margin_method",
3035 "Margin Method",
3036 "");
3038 ot->srna, "margin", 0.001f, 0.0f, 1.0f, "Margin", "Space between islands", 0.0f, 1.0f);
3039
3040 /* SLIM only */
3041 RNA_def_boolean(ot->srna,
3042 "no_flip",
3043 tool_settings_default->uvcalc_flag & UVCALC_UNWRAP_NO_FLIP,
3044 "No Flip",
3045 "Prevent flipping UV's, "
3046 "flipping may lower distortion depending on the position of pins");
3047
3048 RNA_def_int(ot->srna,
3049 "iterations",
3050 tool_settings_default->uvcalc_iterations,
3051 0,
3052 10000,
3053 "Iterations",
3054 "Number of iterations when \"Minimum Stretch\" method is used",
3055 1,
3056 30);
3057
3058 RNA_def_boolean(ot->srna,
3059 "use_weights",
3060 tool_settings_default->uvcalc_flag & UVCALC_UNWRAP_USE_WEIGHTS,
3061 "Importance Weights",
3062 "Whether to take into account per-vertex importance weights");
3063 RNA_def_string(ot->srna,
3064 "weight_group",
3065 tool_settings_default->uvcalc_weight_group,
3067 "Weight Group",
3068 "Vertex group name for importance weights (modulating the deform)");
3070 ot->srna,
3071 "weight_factor",
3072 tool_settings_default->uvcalc_weight_factor,
3073 -10000.0,
3074 10000.0,
3075 "Weight Factor",
3076 "How much influence the weightmap has for weighted parameterization, 0 being no influence",
3077 -10.0,
3078 10.0);
3079}
3080
3082
3083/* -------------------------------------------------------------------- */
3086
3087/* Ignore all areas below this, as the UVs get zeroed. */
3088static const float smart_uv_project_area_ignore = 1e-12f;
3089
3091 float area;
3093};
3094
3095static int smart_uv_project_thickface_area_cmp_fn(const void *tf_a_p, const void *tf_b_p)
3096{
3097 const ThickFace *tf_a = (ThickFace *)tf_a_p;
3098 const ThickFace *tf_b = (ThickFace *)tf_b_p;
3099
3100 /* Ignore the area of small faces.
3101 * Also, order checks so `!isfinite(...)` values are counted as zero area. */
3102 if (!((tf_a->area > smart_uv_project_area_ignore) ||
3104 {
3105 return 0;
3106 }
3107
3108 if (tf_a->area < tf_b->area) {
3109 return 1;
3110 }
3111 if (tf_a->area > tf_b->area) {
3112 return -1;
3113 }
3114 return 0;
3115}
3116
3118 const ThickFace *thick_faces,
3119 const uint thick_faces_len,
3120 BMesh *bm,
3121 const float project_angle_limit_half_cos,
3122 const float project_angle_limit_cos,
3123 const float area_weight)
3124{
3125 if (UNLIKELY(thick_faces_len == 0)) {
3126 return {};
3127 }
3128
3129 const float *project_normal = thick_faces[0].efa->no;
3130
3131 blender::Vector<const ThickFace *> project_thick_faces;
3132 blender::Vector<blender::float3> project_normal_array;
3133
3135
3136 while (true) {
3137 for (int f_index = thick_faces_len - 1; f_index >= 0; f_index--) {
3138 if (BM_elem_flag_test(thick_faces[f_index].efa, BM_ELEM_TAG)) {
3139 continue;
3140 }
3141
3142 if (dot_v3v3(thick_faces[f_index].efa->no, project_normal) > project_angle_limit_half_cos) {
3143 project_thick_faces.append(&thick_faces[f_index]);
3144 BM_elem_flag_set(thick_faces[f_index].efa, BM_ELEM_TAG, true);
3145 }
3146 }
3147
3148 float average_normal[3] = {0.0f, 0.0f, 0.0f};
3149
3150 if (area_weight <= 0.0f) {
3151 for (int f_proj_index = 0; f_proj_index < project_thick_faces.size(); f_proj_index++) {
3152 const ThickFace *tf = project_thick_faces[f_proj_index];
3153 add_v3_v3(average_normal, tf->efa->no);
3154 }
3155 }
3156 else if (area_weight >= 1.0f) {
3157 for (int f_proj_index = 0; f_proj_index < project_thick_faces.size(); f_proj_index++) {
3158 const ThickFace *tf = project_thick_faces[f_proj_index];
3159 madd_v3_v3fl(average_normal, tf->efa->no, tf->area);
3160 }
3161 }
3162 else {
3163 for (int f_proj_index = 0; f_proj_index < project_thick_faces.size(); f_proj_index++) {
3164 const ThickFace *tf = project_thick_faces[f_proj_index];
3165 const float area_blend = (tf->area * area_weight) + (1.0f - area_weight);
3166 madd_v3_v3fl(average_normal, tf->efa->no, area_blend);
3167 }
3168 }
3169
3170 /* Avoid NAN. */
3171 if (normalize_v3(average_normal) != 0.0f) {
3172 project_normal_array.append(average_normal);
3173 }
3174
3175 /* Find the most unique angle that points away from other normals. */
3176 float anble_best = 1.0f;
3177 uint angle_best_index = 0;
3178
3179 for (int f_index = thick_faces_len - 1; f_index >= 0; f_index--) {
3180 if (BM_elem_flag_test(thick_faces[f_index].efa, BM_ELEM_TAG)) {
3181 continue;
3182 }
3183
3184 float angle_test = -1.0f;
3185 for (int p_index = 0; p_index < project_normal_array.size(); p_index++) {
3186 angle_test = max_ff(angle_test,
3187 dot_v3v3(project_normal_array[p_index], thick_faces[f_index].efa->no));
3188 }
3189
3190 if (angle_test < anble_best) {
3191 anble_best = angle_test;
3192 angle_best_index = f_index;
3193 }
3194 }
3195
3196 if (anble_best < project_angle_limit_cos) {
3197 project_normal = thick_faces[angle_best_index].efa->no;
3198 project_thick_faces.clear();
3199 project_thick_faces.append(&thick_faces[angle_best_index]);
3200 BM_elem_flag_enable(thick_faces[angle_best_index].efa, BM_ELEM_TAG);
3201 }
3202 else {
3203 if (project_normal_array.size() >= 1) {
3204 break;
3205 }
3206 }
3207 }
3208
3210
3211 return project_normal_array;
3212}
3213
3215{
3216 Scene *scene = CTX_data_scene(C);
3217 ViewLayer *view_layer = CTX_data_view_layer(C);
3218
3219 /* May be nullptr. */
3220 View3D *v3d = CTX_wm_view3d(C);
3221
3222 bool only_selected_uvs = false;
3223 if (CTX_wm_space_image(C)) {
3224 /* Inside the UV Editor, only project selected UVs. */
3225 only_selected_uvs = true;
3226 }
3227
3228 const float project_angle_limit = RNA_float_get(op->ptr, "angle_limit");
3229 const float island_margin = RNA_float_get(op->ptr, "island_margin");
3230 const float area_weight = RNA_float_get(op->ptr, "area_weight");
3231
3232 const float project_angle_limit_cos = cosf(project_angle_limit);
3233 const float project_angle_limit_half_cos = cosf(project_angle_limit / 2);
3234
3235 /* Memory arena for list links (cleared for each object). */
3237
3239 scene, view_layer, v3d);
3240
3241 Vector<Object *> objects_changed;
3242
3243 BMFace *efa;
3244 BMIter iter;
3245
3246 for (const int ob_index : objects.index_range()) {
3247 Object *obedit = objects[ob_index];
3249 bool changed = false;
3250
3251 if (!uvedit_ensure_uvs(obedit)) {
3252 continue;
3253 }
3254
3255 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
3256 BLI_assert(offsets.uv >= 0);
3257 ThickFace *thick_faces = MEM_malloc_arrayN<ThickFace>(em->bm->totface, __func__);
3258
3259 uint thick_faces_len = 0;
3260 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
3261 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
3262 continue;
3263 }
3264
3265 if (only_selected_uvs) {
3266 if (!uvedit_face_select_test(scene, em->bm, efa)) {
3267 uvedit_face_select_disable(scene, em->bm, efa);
3268 continue;
3269 }
3270 }
3271
3272 thick_faces[thick_faces_len].area = BM_face_calc_area(efa);
3273 thick_faces[thick_faces_len].efa = efa;
3274 thick_faces_len++;
3275 }
3276
3277 qsort(thick_faces, thick_faces_len, sizeof(ThickFace), smart_uv_project_thickface_area_cmp_fn);
3278
3279 /* Remove all zero area faces. */
3280 while ((thick_faces_len > 0) &&
3281 !(thick_faces[thick_faces_len - 1].area > smart_uv_project_area_ignore))
3282 {
3283
3284 /* Zero UVs so they don't overlap with other faces being unwrapped. */
3285 BMIter liter;
3286 BMLoop *l;
3287 BM_ITER_ELEM (l, &liter, thick_faces[thick_faces_len - 1].efa, BM_LOOPS_OF_FACE) {
3288 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
3289 zero_v2(luv);
3290 changed = true;
3291 }
3292
3293 thick_faces_len -= 1;
3294 }
3295
3296 blender::Vector<blender::float3> project_normal_array =
3298 thick_faces_len,
3299 em->bm,
3300 project_angle_limit_half_cos,
3301 project_angle_limit_cos,
3302 area_weight);
3303
3304 if (project_normal_array.is_empty()) {
3305 MEM_freeN(thick_faces);
3306 continue;
3307 }
3308
3309 /* After finding projection vectors, we find the uv positions. */
3310 LinkNode **thickface_project_groups = static_cast<LinkNode **>(
3311 MEM_callocN(sizeof(*thickface_project_groups) * project_normal_array.size(), __func__));
3312
3313 BLI_memarena_clear(arena);
3314
3315 for (int f_index = thick_faces_len - 1; f_index >= 0; f_index--) {
3316 const float *f_normal = thick_faces[f_index].efa->no;
3317
3318 float angle_best = dot_v3v3(f_normal, project_normal_array[0]);
3319 uint angle_best_index = 0;
3320
3321 for (int p_index = 1; p_index < project_normal_array.size(); p_index++) {
3322 const float angle_test = dot_v3v3(f_normal, project_normal_array[p_index]);
3323 if (angle_test > angle_best) {
3324 angle_best = angle_test;
3325 angle_best_index = p_index;
3326 }
3327 }
3328
3330 &thickface_project_groups[angle_best_index], &thick_faces[f_index], arena);
3331 }
3332
3333 for (int p_index = 0; p_index < project_normal_array.size(); p_index++) {
3334 if (thickface_project_groups[p_index] == nullptr) {
3335 continue;
3336 }
3337
3338 float axis_mat[3][3];
3339 axis_dominant_v3_to_m3(axis_mat, project_normal_array[p_index]);
3340
3341 for (LinkNode *list = thickface_project_groups[p_index]; list; list = list->next) {
3342 ThickFace *tf = static_cast<ThickFace *>(list->link);
3343 BMIter liter;
3344 BMLoop *l;
3345 BM_ITER_ELEM (l, &liter, tf->efa, BM_LOOPS_OF_FACE) {
3346 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
3347 mul_v2_m3v3(luv, axis_mat, l->v->co);
3348 }
3349 changed = true;
3350 }
3351 }
3352
3353 MEM_freeN(thick_faces);
3354
3355 /* No need to free the lists in 'thickface_project_groups' values as the 'arena' is used. */
3356 MEM_freeN(thickface_project_groups);
3357
3358 if (changed) {
3359 objects_changed.append(obedit);
3360 }
3361 }
3362
3363 BLI_memarena_free(arena);
3364
3365 /* Pack islands & Stretch to UV bounds */
3366 if (!objects_changed.is_empty()) {
3367
3368 scene->toolsettings->uvcalc_margin = island_margin;
3369
3370 /* Depsgraph refresh functions are called here. */
3371 const bool correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect");
3372
3374 params.rotate_method = eUVPackIsland_RotationMethod(RNA_enum_get(op->ptr, "rotate_method"));
3375 params.only_selected_uvs = only_selected_uvs;
3376 params.only_selected_faces = true;
3377 params.correct_aspect = correct_aspect;
3378 params.use_seams = true;
3379 params.margin_method = eUVPackIsland_MarginMethod(RNA_enum_get(op->ptr, "margin_method"));
3380 params.margin = RNA_float_get(op->ptr, "island_margin");
3381
3383 scene, objects_changed, nullptr, nullptr, false, true, nullptr, &params);
3384
3385 /* #uvedit_pack_islands_multi only supports `per_face_aspect = false`. */
3386 const bool per_face_aspect = false;
3387 uv_map_clip_correct(scene, objects_changed, op, per_face_aspect, only_selected_uvs);
3388 }
3389
3390 return OPERATOR_FINISHED;
3391}
3392
3394{
3396 C, op, event, IFACE_("Smart UV Project"), IFACE_("Unwrap"));
3397}
3398
3400{
3401 PropertyRNA *prop;
3402
3403 /* identifiers */
3404 ot->name = "Smart UV Project";
3405 ot->idname = "UV_OT_smart_project";
3406 ot->description = "Projection unwraps the selected faces of mesh objects";
3407
3408 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3409
3410 /* API callbacks. */
3411 ot->exec = smart_project_exec;
3412 ot->poll = ED_operator_uvmap;
3413 ot->invoke = smart_project_invoke;
3414
3415 /* properties */
3416 prop = RNA_def_float_rotation(ot->srna,
3417 "angle_limit",
3418 0,
3419 nullptr,
3420 DEG2RADF(0.0f),
3421 DEG2RADF(90.0f),
3422 "Angle Limit",
3423 "Lower for more projection groups, higher for less distortion",
3424 DEG2RADF(0.0f),
3425 DEG2RADF(89.0f));
3427
3428 RNA_def_enum(ot->srna,
3429 "margin_method",
3432 "Margin Method",
3433 "");
3434 RNA_def_enum(ot->srna,
3435 "rotate_method",
3436 /* Only show aligned options as the rotation from a projection
3437 * generated from a direction vector isn't meaningful. */
3440 "Rotation Method",
3441 "");
3442 RNA_def_float(ot->srna,
3443 "island_margin",
3444 0.0f,
3445 0.0f,
3446 1.0f,
3447 "Island Margin",
3448 "Margin to reduce bleed from adjacent islands",
3449 0.0f,
3450 1.0f);
3451 RNA_def_float(ot->srna,
3452 "area_weight",
3453 0.0f,
3454 0.0f,
3455 1.0f,
3456 "Area Weight",
3457 "Weight projection's vector by faces with larger areas",
3458 0.0f,
3459 1.0f);
3460
3462}
3463
3465
3466/* -------------------------------------------------------------------- */
3469
3470static wmOperatorStatus uv_from_view_exec(bContext *C, wmOperator *op);
3471
3473{
3474 View3D *v3d = CTX_wm_view3d(C);
3476 const Camera *camera = ED_view3d_camera_data_get(v3d, rv3d);
3477 PropertyRNA *prop;
3478
3479 prop = RNA_struct_find_property(op->ptr, "camera_bounds");
3480 if (!RNA_property_is_set(op->ptr, prop)) {
3481 RNA_property_boolean_set(op->ptr, prop, (camera != nullptr));
3482 }
3483 prop = RNA_struct_find_property(op->ptr, "correct_aspect");
3484 if (!RNA_property_is_set(op->ptr, prop)) {
3485 RNA_property_boolean_set(op->ptr, prop, (camera == nullptr));
3486 }
3487
3488 return uv_from_view_exec(C, op);
3489}
3490
3492{
3493 ViewLayer *view_layer = CTX_data_view_layer(C);
3494 const Scene *scene = CTX_data_scene(C);
3495 ARegion *region = CTX_wm_region(C);
3496 View3D *v3d = CTX_wm_view3d(C);
3498 const Camera *camera = ED_view3d_camera_data_get(v3d, rv3d);
3499 BMFace *efa;
3500 BMLoop *l;
3501 BMIter iter, liter;
3502 float rotmat[4][4];
3503 float objects_pos_offset[4];
3504
3505 const bool use_orthographic = RNA_boolean_get(op->ptr, "orthographic");
3506
3507 /* NOTE: objects that aren't touched are set to nullptr (to skip clipping). */
3509 scene, view_layer, v3d);
3510
3511 if (use_orthographic) {
3512 /* Calculate average object position. */
3513 float objects_pos_avg[4] = {0};
3514
3515 for (Object *object : objects) {
3516 add_v4_v4(objects_pos_avg, object->object_to_world().location());
3517 }
3518
3519 mul_v4_fl(objects_pos_avg, 1.0f / objects.size());
3520 negate_v4_v4(objects_pos_offset, objects_pos_avg);
3521 }
3522
3523 Vector<Object *> changed_objects;
3524
3525 for (Object *obedit : objects) {
3527 bool changed = false;
3528
3529 /* add uvs if they don't exist yet */
3530 if (!uvedit_ensure_uvs(obedit)) {
3531 continue;
3532 }
3533
3534 const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2);
3535
3536 if (use_orthographic) {
3537 uv_map_rotation_matrix_ex(rotmat, rv3d, obedit, 90.0f, 0.0f, 1.0f, objects_pos_offset);
3538
3539 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
3540 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
3541 continue;
3542 }
3543
3544 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
3545 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
3546 BKE_uvproject_from_view_ortho(luv, l->v->co, rotmat);
3547 }
3548 changed = true;
3549 }
3550 }
3551 else if (camera) {
3552 const bool camera_bounds = RNA_boolean_get(op->ptr, "camera_bounds");
3554 v3d->camera,
3555 obedit->object_to_world().ptr(),
3556 camera_bounds ? (scene->r.xsch * scene->r.xasp) : 1.0f,
3557 camera_bounds ? (scene->r.ysch * scene->r.yasp) : 1.0f);
3558
3559 if (uci) {
3560 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
3561 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
3562 continue;
3563 }
3564
3565 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
3566 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
3567 BKE_uvproject_from_camera(luv, l->v->co, uci);
3568 }
3569 changed = true;
3570 }
3571
3573 }
3574 }
3575 else {
3576 copy_m4_m4(rotmat, obedit->object_to_world().ptr());
3577
3578 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
3579 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
3580 continue;
3581 }
3582
3583 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
3584 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
3586 luv, l->v->co, rv3d->persmat, rotmat, region->winx, region->winy);
3587 }
3588 changed = true;
3589 }
3590 }
3591
3592 if (changed) {
3593 changed_objects.append(obedit);
3594 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
3595 WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
3596 }
3597 }
3598
3599 if (changed_objects.is_empty()) {
3600 return OPERATOR_CANCELLED;
3601 }
3602
3603 const bool per_face_aspect = true;
3604 const bool only_selected_uvs = false;
3605 uv_map_clip_correct(scene, objects, op, per_face_aspect, only_selected_uvs);
3606 return OPERATOR_FINISHED;
3607}
3608
3610{
3612
3613 if (!ED_operator_uvmap(C)) {
3614 return false;
3615 }
3616
3617 return (rv3d != nullptr);
3618}
3619
3621{
3622 /* identifiers */
3623 ot->name = "Project from View";
3624 ot->idname = "UV_OT_project_from_view";
3625 ot->description = "Project the UV vertices of the mesh as seen in current 3D view";
3626
3627 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3628
3629 /* API callbacks. */
3630 ot->invoke = uv_from_view_invoke;
3631 ot->exec = uv_from_view_exec;
3632 ot->poll = uv_from_view_poll;
3633
3634 /* properties */
3635 RNA_def_boolean(ot->srna, "orthographic", false, "Orthographic", "Use orthographic projection");
3636 RNA_def_boolean(ot->srna,
3637 "camera_bounds",
3638 true,
3639 "Camera Bounds",
3640 "Map UVs to the camera region taking resolution and aspect into account");
3642}
3643
3645
3646/* -------------------------------------------------------------------- */
3649
3651{
3652 const Scene *scene = CTX_data_scene(C);
3653 ViewLayer *view_layer = CTX_data_view_layer(C);
3654 View3D *v3d = CTX_wm_view3d(C);
3655
3657 scene, view_layer, v3d);
3658 for (Object *obedit : objects) {
3659 Mesh *mesh = (Mesh *)obedit->data;
3661
3662 if (em->bm->totfacesel == 0) {
3663 continue;
3664 }
3665
3666 /* add uvs if they don't exist yet */
3667 if (!uvedit_ensure_uvs(obedit)) {
3668 continue;
3669 }
3670
3671 ED_mesh_uv_loop_reset(C, mesh);
3672
3673 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
3674 WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
3675 }
3676
3677 return OPERATOR_FINISHED;
3678}
3679
3681{
3682 /* identifiers */
3683 ot->name = "Reset";
3684 ot->idname = "UV_OT_reset";
3685 ot->description = "Reset UV projection";
3686
3687 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3688
3689 /* API callbacks. */
3690 ot->exec = reset_exec;
3691 ot->poll = ED_operator_uvmap;
3692}
3693
3695
3696/* -------------------------------------------------------------------- */
3699
3700static void uv_map_mirror(BMFace *efa,
3701 const bool *regular,
3702 const bool fan,
3703 const int cd_loop_uv_offset)
3704{
3705 /* A heuristic to improve alignment of faces near the seam.
3706 * In simple terms, we're looking for faces which span more
3707 * than 0.5 units in the *u* coordinate.
3708 * If we find such a face, we try and improve the unwrapping
3709 * by adding (1.0, 0.0) onto some of the face's UVs.
3710 *
3711 * Note that this is only a heuristic. The property we're
3712 * attempting to maintain is that the winding of the face
3713 * in UV space corresponds with the handedness of the face
3714 * in 3D space w.r.t to the unwrapping. Even for triangles,
3715 * that property is somewhat complicated to evaluate. */
3716
3717 float right_u = -1.0e30f;
3718 BMLoop *l;
3719 BMIter liter;
3721 int j;
3722 BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, j) {
3723 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
3724 uvs[j] = luv;
3725 if (luv[0] >= 1.0f) {
3726 luv[0] -= 1.0f;
3727 }
3728 right_u = max_ff(right_u, luv[0]);
3729 }
3730
3731 float left_u = 1.0e30f;
3732 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
3733 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
3734 if (right_u <= luv[0] + 0.5f) {
3735 left_u = min_ff(left_u, luv[0]);
3736 }
3737 }
3738
3739 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
3740 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
3741 if (luv[0] + 0.5f < right_u) {
3742 if (2 * luv[0] + 1.0f < left_u + right_u) {
3743 luv[0] += 1.0f;
3744 }
3745 }
3746 }
3747 if (!fan) {
3748 return;
3749 }
3750
3751 /* Another heuristic, this time, we attempt to "fan"
3752 * the UVs of faces which pass through one of the poles
3753 * of the unwrapping. */
3754
3755 /* Need to recompute min and max. */
3756 float minmax_u[2] = {1.0e30f, -1.0e30f};
3757 int pole_count = 0;
3758 for (int i = 0; i < efa->len; i++) {
3759 if (regular[i]) {
3760 minmax_u[0] = min_ff(minmax_u[0], uvs[i][0]);
3761 minmax_u[1] = max_ff(minmax_u[1], uvs[i][0]);
3762 }
3763 else {
3764 pole_count++;
3765 }
3766 }
3767 if (ELEM(pole_count, 0, efa->len)) {
3768 return;
3769 }
3770 for (int i = 0; i < efa->len; i++) {
3771 if (regular[i]) {
3772 continue;
3773 }
3774 float u = 0.0f;
3775 float sum = 0.0f;
3776 const int i_plus = (i + 1) % efa->len;
3777 const int i_minus = (i + efa->len - 1) % efa->len;
3778 if (regular[i_plus]) {
3779 u += uvs[i_plus][0];
3780 sum += 1.0f;
3781 }
3782 if (regular[i_minus]) {
3783 u += uvs[i_minus][0];
3784 sum += 1.0f;
3785 }
3786 if (sum == 0) {
3787 u += minmax_u[0] + minmax_u[1];
3788 sum += 2.0f;
3789 }
3790 uvs[i][0] = u / sum;
3791 }
3792}
3793
3809 float branch;
3810};
3811
3822static float uv_sphere_project(const Scene *scene,
3823 BMesh *bm,
3824 BMFace *efa_init,
3825 const float center[3],
3826 const float rotmat[3][3],
3827 const bool fan,
3828 const BMUVOffsets &offsets,
3829 const bool only_selected_uvs,
3830 const bool use_seams,
3831 const float branch_init)
3832{
3833 float max_u = 0.0f;
3834 if (use_seams && BM_elem_flag_test(efa_init, BM_ELEM_TAG)) {
3835 return max_u;
3836 }
3837
3838 /* Similar to #BM_mesh_calc_face_groups with added connectivity information. */
3840 stack.append({efa_init, branch_init});
3841
3842 while (stack.size()) {
3843 UV_FaceBranch face_branch = stack.pop_last();
3844 BMFace *efa = face_branch.efa;
3845
3846 if (use_seams) {
3847 if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
3848 continue; /* Faces might be in the stack more than once. */
3849 }
3850
3851 BM_elem_flag_set(efa, BM_ELEM_TAG, true); /* Visited, don't consider again. */
3852 }
3853
3854 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
3855 continue; /* Unselected face, ignore. */
3856 }
3857
3858 if (only_selected_uvs) {
3859 if (!uvedit_face_select_test(scene, bm, efa)) {
3860 uvedit_face_select_disable(scene, bm, efa);
3861 continue; /* Unselected UV, ignore. */
3862 }
3863 }
3864
3865 /* Remember which UVs are at the pole. */
3867
3868 int i;
3869 BMLoop *l;
3870 BMIter iter;
3871 BM_ITER_ELEM_INDEX (l, &iter, efa, BM_LOOPS_OF_FACE, i) {
3872 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
3873 float pv[3];
3874 sub_v3_v3v3(pv, l->v->co, center);
3875 mul_m3_v3(rotmat, pv);
3876 regular[i] = map_to_sphere(&luv[0], &luv[1], pv[0], pv[1], pv[2]);
3877 if (!use_seams) {
3878 continue; /* Nothing more to do. */
3879 }
3880
3881 /* Move UV to correct branch. */
3882 luv[0] = luv[0] + ceilf(face_branch.branch - 0.5f - luv[0]);
3883 max_u = max_ff(max_u, luv[0]);
3884
3885 BMEdge *edge = l->e;
3886 if (BM_elem_flag_test(edge, BM_ELEM_SEAM)) {
3887 continue; /* Stop flood fill at seams. */
3888 }
3889
3890 /* Extend flood fill by pushing to stack. */
3891 BMFace *efa2;
3892 BMIter iter2;
3893 BM_ITER_ELEM (efa2, &iter2, edge, BM_FACES_OF_EDGE) {
3894 if (!BM_elem_flag_test(efa2, BM_ELEM_TAG)) {
3895 stack.append({efa2, luv[0]});
3896 }
3897 }
3898 }
3899 uv_map_mirror(efa, regular.data(), fan, offsets.uv);
3900 }
3901
3902 return max_u;
3903}
3904
3906{
3907 const Scene *scene = CTX_data_scene(C);
3908 View3D *v3d = CTX_wm_view3d(C);
3909
3910 bool only_selected_uvs = false;
3911 if (CTX_wm_space_image(C)) {
3912 /* Inside the UV Editor, only project selected UVs. */
3913 only_selected_uvs = true;
3914 }
3915
3916 ViewLayer *view_layer = CTX_data_view_layer(C);
3918 scene, view_layer, v3d);
3919 for (Object *obedit : objects) {
3921 BMFace *efa;
3922 BMIter iter;
3923
3924 if (em->bm->totfacesel == 0) {
3925 continue;
3926 }
3927
3928 /* add uvs if they don't exist yet */
3929 if (!uvedit_ensure_uvs(obedit)) {
3930 continue;
3931 }
3932
3933 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
3934 float center[3], rotmat[3][3];
3935
3936 uv_map_transform(C, op, rotmat);
3937 uv_map_transform_center(scene, v3d, obedit, em, center, nullptr);
3938
3939 const bool fan = RNA_enum_get(op->ptr, "pole");
3940 const bool use_seams = RNA_boolean_get(op->ptr, "seam");
3941
3942 if (use_seams) {
3944 }
3945
3946 float island_offset = 0.0f;
3947 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
3948 const float max_u = uv_sphere_project(scene,
3949 em->bm,
3950 efa,
3951 center,
3952 rotmat,
3953 fan,
3954 offsets,
3955 only_selected_uvs,
3956 use_seams,
3957 island_offset + 0.5f);
3958 island_offset = ceilf(max_ff(max_u, island_offset));
3959 }
3960
3961 const bool per_face_aspect = true;
3962 uv_map_clip_correct(scene, {obedit}, op, per_face_aspect, only_selected_uvs);
3963
3964 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
3965 WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
3966 }
3967
3968 return OPERATOR_FINISHED;
3969}
3970
3972{
3973 /* identifiers */
3974 ot->name = "Sphere Projection";
3975 ot->idname = "UV_OT_sphere_project";
3976 ot->description = "Project the UV vertices of the mesh over the curved surface of a sphere";
3977
3978 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3979
3980 /* API callbacks. */
3981 ot->exec = sphere_project_exec;
3982 ot->poll = ED_operator_uvmap;
3983
3984 /* properties */
3987}
3988
3990
3991/* -------------------------------------------------------------------- */
3994
3995/* See #uv_sphere_project for description of parameters. */
3996static float uv_cylinder_project(const Scene *scene,
3997 BMesh *bm,
3998 BMFace *efa_init,
3999 const float center[3],
4000 const float rotmat[3][3],
4001 const bool fan,
4002 const BMUVOffsets &offsets,
4003 const bool only_selected_uvs,
4004 const bool use_seams,
4005 const float branch_init)
4006{
4007 float max_u = 0.0f;
4008 if (use_seams && BM_elem_flag_test(efa_init, BM_ELEM_TAG)) {
4009 return max_u;
4010 }
4011
4012 /* Similar to BM_mesh_calc_face_groups with added connectivity information. */
4013
4015
4016 stack.append({efa_init, branch_init});
4017
4018 while (stack.size()) {
4019 UV_FaceBranch face_branch = stack.pop_last();
4020 BMFace *efa = face_branch.efa;
4021
4022 if (use_seams) {
4023 if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
4024 continue; /* Faces might be in the stack more than once. */
4025 }
4026
4027 BM_elem_flag_set(efa, BM_ELEM_TAG, true); /* Visited, don't consider again. */
4028 }
4029
4030 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
4031 continue; /* Unselected face, ignore. */
4032 }
4033
4034 if (only_selected_uvs) {
4035 if (!uvedit_face_select_test(scene, bm, efa)) {
4036 uvedit_face_select_disable(scene, bm, efa);
4037 continue; /* Unselected UV, ignore. */
4038 }
4039 }
4040
4041 /* Remember which UVs are at the pole. */
4043
4044 int i;
4045 BMLoop *l;
4046 BMIter iter;
4047 BM_ITER_ELEM_INDEX (l, &iter, efa, BM_LOOPS_OF_FACE, i) {
4048 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
4049 float pv[3];
4050 sub_v3_v3v3(pv, l->v->co, center);
4051 mul_m3_v3(rotmat, pv);
4052 regular[i] = map_to_tube(&luv[0], &luv[1], pv[0], pv[1], pv[2]);
4053
4054 if (!use_seams) {
4055 continue; /* Nothing more to do. */
4056 }
4057
4058 /* Move UV to correct branch. */
4059 luv[0] = luv[0] + ceilf(face_branch.branch - 0.5f - luv[0]);
4060 max_u = max_ff(max_u, luv[0]);
4061
4062 BMEdge *edge = l->e;
4063 if (BM_elem_flag_test(edge, BM_ELEM_SEAM)) {
4064 continue; /* Stop flood fill at seams. */
4065 }
4066
4067 /* Extend flood fill by pushing to stack. */
4068 BMFace *efa2;
4069 BMIter iter2;
4070 BM_ITER_ELEM (efa2, &iter2, edge, BM_FACES_OF_EDGE) {
4071 if (!BM_elem_flag_test(efa2, BM_ELEM_TAG)) {
4072 stack.append({efa2, luv[0]});
4073 }
4074 }
4075 }
4076
4077 uv_map_mirror(efa, regular.data(), fan, offsets.uv);
4078 }
4079
4080 return max_u;
4081}
4082
4084{
4085 const Scene *scene = CTX_data_scene(C);
4086 View3D *v3d = CTX_wm_view3d(C);
4087
4088 bool only_selected_uvs = false;
4089 if (CTX_wm_space_image(C)) {
4090 /* Inside the UV Editor, only project selected UVs. */
4091 only_selected_uvs = true;
4092 }
4093
4094 ViewLayer *view_layer = CTX_data_view_layer(C);
4096 scene, view_layer, v3d);
4097 for (Object *obedit : objects) {
4099 BMFace *efa;
4100 BMIter iter;
4101
4102 if (em->bm->totfacesel == 0) {
4103 continue;
4104 }
4105
4106 /* add uvs if they don't exist yet */
4107 if (!uvedit_ensure_uvs(obedit)) {
4108 continue;
4109 }
4110
4111 const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
4112 float center[3], rotmat[3][3];
4113
4114 uv_map_transform(C, op, rotmat);
4115 uv_map_transform_center(scene, v3d, obedit, em, center, nullptr);
4116
4117 const bool fan = RNA_enum_get(op->ptr, "pole");
4118 const bool use_seams = RNA_boolean_get(op->ptr, "seam");
4119
4120 if (use_seams) {
4122 }
4123
4124 float island_offset = 0.0f;
4125
4126 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
4127 if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
4128 continue;
4129 }
4130
4131 if (only_selected_uvs && !uvedit_face_select_test(scene, em->bm, efa)) {
4132 uvedit_face_select_disable(scene, em->bm, efa);
4133 continue;
4134 }
4135
4136 const float max_u = uv_cylinder_project(scene,
4137 em->bm,
4138 efa,
4139 center,
4140 rotmat,
4141 fan,
4142 offsets,
4143 only_selected_uvs,
4144 use_seams,
4145 island_offset + 0.5f);
4146 island_offset = ceilf(max_ff(max_u, island_offset));
4147 }
4148
4149 const bool per_face_aspect = true;
4150 uv_map_clip_correct(scene, {obedit}, op, per_face_aspect, only_selected_uvs);
4151
4152 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
4153 WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
4154 }
4155
4156 return OPERATOR_FINISHED;
4157}
4158
4160{
4161 /* identifiers */
4162 ot->name = "Cylinder Projection";
4163 ot->idname = "UV_OT_cylinder_project";
4164 ot->description = "Project the UV vertices of the mesh over the curved wall of a cylinder";
4165
4166 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4167
4168 /* API callbacks. */
4169 ot->exec = cylinder_project_exec;
4170 ot->poll = ED_operator_uvmap;
4171
4172 /* properties */
4175}
4176
4178
4179/* -------------------------------------------------------------------- */
4182
4183static void uvedit_unwrap_cube_project(const Scene *scene,
4184 BMesh *bm,
4185 float cube_size,
4186 const bool use_select,
4187 const bool only_selected_uvs,
4188 const float center[3])
4189{
4190 BMFace *efa;
4191 BMLoop *l;
4192 BMIter iter, liter;
4193 float loc[3];
4194 int cox, coy;
4195
4196 const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
4197
4198 if (center) {
4199 copy_v3_v3(loc, center);
4200 }
4201 else {
4202 zero_v3(loc);
4203 }
4204
4205 if (UNLIKELY(cube_size == 0.0f)) {
4206 cube_size = 1.0f;
4207 }
4208
4209 /* choose x,y,z axis for projection depending on the largest normal
4210 * component, but clusters all together around the center of map. */
4211
4212 BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
4213 if (use_select && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
4214 continue;
4215 }
4216 if (only_selected_uvs && !uvedit_face_select_test(scene, bm, efa)) {
4217 uvedit_face_select_disable(scene, bm, efa);
4218 continue;
4219 }
4220
4221 axis_dominant_v3(&cox, &coy, efa->no);
4222
4223 BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
4224 float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
4225 luv[0] = 0.5f + ((l->v->co[cox] - loc[cox]) / cube_size);
4226 luv[1] = 0.5f + ((l->v->co[coy] - loc[coy]) / cube_size);
4227 }
4228 }
4229}
4230
4232{
4233 const Scene *scene = CTX_data_scene(C);
4234 View3D *v3d = CTX_wm_view3d(C);
4235
4236 bool only_selected_uvs = false;
4237 if (CTX_wm_space_image(C)) {
4238 /* Inside the UV Editor, only cube project selected UVs. */
4239 only_selected_uvs = true;
4240 }
4241
4242 PropertyRNA *prop_cube_size = RNA_struct_find_property(op->ptr, "cube_size");
4243 const float cube_size_init = RNA_property_float_get(op->ptr, prop_cube_size);
4244
4245 ViewLayer *view_layer = CTX_data_view_layer(C);
4247 scene, view_layer, v3d);
4248 for (const int ob_index : objects.index_range()) {
4249 Object *obedit = objects[ob_index];
4251
4252 if (em->bm->totfacesel == 0) {
4253 continue;
4254 }
4255
4256 /* add uvs if they don't exist yet */
4257 if (!uvedit_ensure_uvs(obedit)) {
4258 continue;
4259 }
4260
4261 float bounds[2][3];
4262 float (*bounds_buf)[3] = nullptr;
4263
4264 if (!RNA_property_is_set(op->ptr, prop_cube_size)) {
4265 bounds_buf = bounds;
4266 }
4267
4268 float center[3];
4269 uv_map_transform_center(scene, v3d, obedit, em, center, bounds_buf);
4270
4271 /* calculate based on bounds */
4272 float cube_size = cube_size_init;
4273 if (bounds_buf) {
4274 float dims[3];
4275 sub_v3_v3v3(dims, bounds[1], bounds[0]);
4276 cube_size = max_fff(UNPACK3(dims));
4277 if (ob_index == 0) {
4278 /* This doesn't fit well with, multiple objects. */
4279 RNA_property_float_set(op->ptr, prop_cube_size, cube_size);
4280 }
4281 }
4282
4283 uvedit_unwrap_cube_project(scene, em->bm, cube_size, true, only_selected_uvs, center);
4284
4285 const bool per_face_aspect = true;
4286 uv_map_clip_correct(scene, {obedit}, op, per_face_aspect, only_selected_uvs);
4287
4288 DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
4290 }
4291
4292 return OPERATOR_FINISHED;
4293}
4294
4296{
4297 /* identifiers */
4298 ot->name = "Cube Projection";
4299 ot->idname = "UV_OT_cube_project";
4300 ot->description = "Project the UV vertices of the mesh over the six faces of a cube";
4301
4302 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
4303
4304 /* API callbacks. */
4305 ot->exec = cube_project_exec;
4306 ot->poll = ED_operator_uvmap;
4307
4308 /* properties */
4309 RNA_def_float(ot->srna,
4310 "cube_size",
4311 1.0f,
4312 0.0f,
4313 FLT_MAX,
4314 "Cube Size",
4315 "Size of the cube to project on",
4316 0.001f,
4317 100.0f);
4319}
4320
4322
4323/* -------------------------------------------------------------------- */
4326
4327void ED_uvedit_add_simple_uvs(Main *bmain, const Scene *scene, Object *ob)
4328{
4329 Mesh *mesh = static_cast<Mesh *>(ob->data);
4330 bool sync_selection = (scene->toolsettings->uv_flag & UV_FLAG_SELECT_SYNC) != 0;
4331
4332 BMeshCreateParams create_params{};
4333 create_params.use_toolflags = false;
4334 BMesh *bm = BM_mesh_create(&bm_mesh_allocsize_default, &create_params);
4335
4336 /* turn sync selection off,
4337 * since we are not in edit mode we need to ensure only the uv flags are tested */
4339
4340 ED_mesh_uv_ensure(mesh, nullptr);
4341
4342 BMeshFromMeshParams bm_from_me_params{};
4343 bm_from_me_params.calc_face_normal = true;
4344 bm_from_me_params.calc_vert_normal = true;
4345 BM_mesh_bm_from_me(bm, mesh, &bm_from_me_params);
4346
4347 /* Select all UVs for cube_project. */
4349 /* A cube size of 2.0 maps [-1..1] vertex coords to [0.0..1.0] in UV coords. */
4350 uvedit_unwrap_cube_project(scene, bm, 2.0, false, false, nullptr);
4351
4352 /* Pack UVs. */
4354 params.rotate_method = ED_UVPACK_ROTATION_ANY;
4355 params.only_selected_uvs = false;
4356 params.only_selected_faces = false;
4357 params.correct_aspect = false;
4358 params.use_seams = true;
4359 params.margin_method = ED_UVPACK_MARGIN_SCALED;
4360 params.margin = 0.001f;
4361
4362 uvedit_pack_islands_multi(scene, {ob}, &bm, nullptr, false, true, nullptr, &params);
4363
4364 /* Write back from BMesh to Mesh. */
4365 BMeshToMeshParams bm_to_me_params{};
4366 BM_mesh_bm_to_me(bmain, bm, mesh, &bm_to_me_params);
4368
4369 if (sync_selection) {
4371 }
4372}
4373
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)
support for deformation groups and hooks.
float BKE_defvert_find_weight(const MDeformVert *dvert, int defgroup)
Definition deform.cc:774
int BKE_object_defgroup_name_index(const Object *ob, blender::StringRef name)
Definition deform.cc:591
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
@ RPT_INFO
Definition BKE_report.hh:35
@ RPT_WARNING
Definition BKE_report.hh:38
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
@ 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)
bool BLI_rctf_is_empty(const struct rctf *rect)
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:202
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:206
#define SNPRINTF_UTF8(dst, format,...)
#define STRNCPY_UTF8(dst, src)
unsigned int uint
Platform independent time functions.
double BLI_time_now_seconds(void)
Definition time.cc:113
#define INIT_MINMAX2(min, max)
#define INIT_MINMAX(min, max)
#define ARRAY_SET_ITEMS(...)
#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:1074
#define MAX_ID_NAME
Definition DNA_ID.h:373
@ 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
@ UVCALC_UNWRAP_METHOD_MINIMUM_STRETCH
@ UVCALC_UNWRAP_METHOD_CONFORMAL
@ UVCALC_UNWRAP_METHOD_ANGLE
@ UV_FLAG_CUSTOM_REGION
@ UV_FLAG_SELECT_SYNC
@ 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:322
int ED_mesh_uv_add(Mesh *mesh, const char *name, bool active_set, bool do_init, ReportList *reports)
Definition mesh_data.cc:218
void ED_mesh_uv_loop_reset(bContext *C, Mesh *mesh)
Definition mesh_data.cc:208
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:851
void ED_workspace_status_text(bContext *C, const char *str)
Definition area.cc:1024
bool ED_operator_uvedit(bContext *C)
void ED_undo_push(bContext *C, const char *str)
Definition ed_undo.cc:98
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)
void ED_uvedit_select_all(const ToolSettings *ts, BMesh *bm)
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 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:73
void ED_uvedit_live_unwrap_re_solve()
bool uvedit_uv_select_test(const Scene *scene, const BMesh *bm, 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)
void uvedit_face_select_disable(const Scene *scene, BMesh *bm, BMFace *efa)
bool uvedit_face_select_test(const Scene *scene, const BMesh *bm, const BMFace *efa)
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 MEM_reallocN(vmemh, len)
#define RNA_ENUM_ITEM_SEPR
Definition RNA_types.hh:676
#define C
Definition RandGen.cpp:29
#define UI_ITEM_NONE
#define UI_MAX_DRAW_STR
@ WM_JOB_TYPE_UV_PACK
Definition WM_api.hh:1811
@ WM_JOB_PROGRESS
Definition WM_api.hh:1766
#define NC_GEOM
Definition WM_types.hh:393
#define ND_DATA
Definition WM_types.hh:509
@ KM_PRESS
Definition WM_types.hh:311
@ 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 ND_SPACE_IMAGE
Definition WM_types.hh:522
#define NC_SPACE
Definition WM_types.hh:392
#define BM_ELEM_CD_GET_BOOL(ele, offset)
#define BM_ELEM_SELECT_UV_EDGE
#define BM_ELEM_CD_GET_FLOAT_P(ele, offset)
@ BM_ELEM_HIDDEN
@ BM_ELEM_SEAM
@ BM_ELEM_SELECT
@ BM_ELEM_SELECT_UV
@ BM_ELEM_TAG
#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)
#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:312
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:2409
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:2332
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)
nullptr float
CCL_NAMESPACE_BEGIN struct Options options
const int faceMap[6][4]
#define str(s)
uint col
#define select(A, B, C)
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
VecBase< float, 2 > float2
#define floorf
#define fabsf
#define sinf
#define cosf
#define ceilf
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)
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)
std::string RNA_string_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]
bool uv_select_sync_valid
int totfacesel
int totvertsel
CustomData ldata
int totface
float aspect_y
Definition ED_uvedit.hh:374
BMFace ** faces
Definition ED_uvedit.hh:367
BMUVOffsets offsets
Definition ED_uvedit.hh:373
Definition DNA_ID.h:414
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
float xmax
float xmin
float ymax
float ymin
void use_property_decorate_set(bool is_sep)
uiLayout & column(bool align)
void active_set(bool active)
void separator(float factor=1.0f, LayoutSeparatorType type=LayoutSeparatorType::Auto)
uiLayout & row(bool align)
void use_property_split_set(bool value)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
wmEventType type
Definition WM_types.hh:757
short val
Definition WM_types.hh:759
void * customdata
Definition WM_types.hh:807
const char * name
Definition WM_types.hh:1033
StructRNA * srna
Definition WM_types.hh:1127
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_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)
@ PACK_UDIM_SRC_CLOSEST
@ PACK_CUSTOM_REGION
@ PACK_ORIGINAL_AABB
@ PACK_UDIM_SRC_ACTIVE
static void minimize_stretch_cancel(bContext *C, wmOperator *op)
static float uv_nearest_grid_tile_distance(const int udim_grid[2], const float coords[2], float nearest_tile_co[2])
static bool uvedit_is_face_affected(const Scene *scene, const BMesh *bm, BMFace *efa, const UnwrapOptions *options, const BMUVOffsets &offsets)
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 construct_param_handle_face_add(ParamHandle *handle, const Scene *scene, const BMesh *bm, BMFace *efa, blender::geometry::ParamKey face_index, const UnwrapOptions *options, const BMUVOffsets &offsets, const int cd_weight_offset, const int cd_weight_index)
static void uvedit_prepare_pinned_indices(ParamHandle *handle, const Scene *scene, const BMesh *bm, BMFace *efa, const UnwrapOptions *options, const BMUVOffsets &offsets)
static void uvedit_unwrap(const Scene *scene, Object *obedit, const UnwrapOptions *options, int *r_count_changed, int *r_count_failed)
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 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 struct @316145304201052102160124321267331173100057323212 g_live_unwrap
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)
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, const rctf *custom_region, blender::geometry::UVPackIsland_Params *params)
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 bool island_has_pins(const Scene *scene, const BMesh *bm, FaceIsland *island, const blender::geometry::UVPackIsland_Params *params)
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 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 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)
@ UNWRAP_ERROR_NEGATIVE
@ UNWRAP_ERROR_NONUNIFORM
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 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 void texface_from_original_index(const Scene *scene, const BMesh *bm, const BMUVOffsets &offsets, BMFace *efa, int index, float **r_uv, bool *r_pin, bool *r_select)
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_locked_interface_set(wmWindowManager *wm, bool lock)
void WM_locked_interface_set_with_flags(wmWindowManager *wm, short lock_flags)
void WM_main_add_notifier(uint type, void *reference)
wmEventHandler_Op * WM_event_add_modal_handler(bContext *C, wmOperator *op)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
@ RIGHTMOUSE
@ TIMER
@ WHEELUPMOUSE
@ EVT_PADENTER
@ WHEELDOWNMOUSE
@ EVT_PADMINUS
@ LEFTMOUSE
@ EVT_ESCKEY
@ EVT_PADPLUSKEY
@ EVT_RETKEY
PointerRNA * ptr
Definition wm_files.cc:4238
wmOperatorType * ot
Definition wm_files.cc:4237
void WM_jobs_timer(wmJob *wm_job, double time_step, uint note, uint endnote)
Definition wm_jobs.cc:376
void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job)
Definition wm_jobs.cc:479
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:211
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:388
void WM_jobs_customdata_set(wmJob *wm_job, void *customdata, void(*free)(void *customdata))
Definition wm_jobs.cc:360
wmOperatorStatus WM_operator_props_popup_call(bContext *C, wmOperator *op, const wmEvent *)
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, std::optional< std::string > message)
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:145