UV: Pack to closest/active UDIM

Implements T78397
Extends the functionality of pack islands operator to allow packing UVs
to either the closest or active UDIM tile.
This provides 2 new options for packing UVs :

* Closest UDIM: Selected UVs will be packed to the UDIM tile they were
  placed on. If not present on a valid UDIM tile, the UVs will be packed
  to the closest UDIM in UV space
* Active UDIM: Selected UVs will be packed to the active UDIM image tile
  In case, no image is present in the UV editor, then UVs will be packed
  to the tile on the UDIM grid where the 2D cursor is located.

Reviewed By: campbellbarton

Maniphest Tasks: T78397

Ref D12680
This commit is contained in:
Siddhartha Jejurkar 2021-09-29 17:51:41 +10:00 committed by Campbell Barton
parent bf06f76be6
commit a285299ebb
Notes: blender-bot 2023-02-14 02:30:11 +01:00
Referenced by issue #78397, Pack to the same UDIM
3 changed files with 247 additions and 6 deletions

View File

@ -246,9 +246,14 @@ struct UVPackIsland_Params {
uint use_seams : 1;
uint correct_aspect : 1;
};
bool uv_coords_isect_udim(const struct Image *image, const int udim_grid[2], float coords[2]);
void ED_uvedit_pack_islands_multi(const struct Scene *scene,
const struct SpaceImage *sima,
Object **objects,
const uint objects_len,
const bool use_target_udim,
int target_udim,
const struct UVPackIsland_Params *params);
#ifdef __cplusplus

View File

@ -29,6 +29,7 @@
#include "DNA_meshdata_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "BLI_boxpack_2d.h"
#include "BLI_convexhull_2d.h"
@ -38,6 +39,7 @@
#include "BKE_customdata.h"
#include "BKE_editmesh.h"
#include "BKE_image.h"
#include "DEG_depsgraph.h"
@ -231,6 +233,99 @@ static void bm_face_array_uv_scale_y(BMFace **faces,
/** \} */
/* -------------------------------------------------------------------- */
/** \name UDIM packing helper functions
* \{ */
/* Returns true if UV coordinates lie on a valid tile in UDIM grid or tiled image. */
bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], float coords[2])
{
const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])};
const bool is_tiled_image = image && (image->source == IMA_SRC_TILED);
if (coords[0] < udim_grid[0] && coords[0] > 0 && coords[1] < udim_grid[1] && coords[1] > 0) {
return true;
}
/* Check if selection lies on a valid UDIM image tile. */
else if (is_tiled_image) {
LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) {
const int tile_index = tile->tile_number - 1001;
const int target_x = (tile_index % 10);
const int target_y = (tile_index / 10);
if (coords_floor[0] == target_x && coords_floor[1] == target_y) {
return true;
}
}
}
/* Probably not required since UDIM grid checks for 1001. */
else if (image && !is_tiled_image) {
if (is_zero_v2(coords_floor)) {
return true;
}
}
return false;
}
/**
* Calculates distance to nearest UDIM image tile in UV space and its UDIM tile number.
*/
static float uv_nearest_image_tile_distance(const Image *image,
float coords[2],
float nearest_tile_co[2])
{
int nearest_image_tile_index = BKE_image_find_nearest_tile(image, coords);
if (nearest_image_tile_index == -1) {
nearest_image_tile_index = 1001;
}
nearest_tile_co[0] = (nearest_image_tile_index - 1001) % 10;
nearest_tile_co[1] = (nearest_image_tile_index - 1001) / 10;
/* Add 0.5 to get tile center coordinates. */
float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]};
add_v2_fl(nearest_tile_center_co, 0.5f);
return len_squared_v2v2(coords, nearest_tile_center_co);
}
/**
* Calculates distance to nearest UDIM grid tile in UV space and its UDIM tile number.
*/
static float uv_nearest_grid_tile_distance(const int udim_grid[2],
float coords[2],
float nearest_tile_co[2])
{
const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])};
if (coords[0] > udim_grid[0]) {
nearest_tile_co[0] = udim_grid[0] - 1;
}
else if (coords[0] < 0) {
nearest_tile_co[0] = 0;
}
else {
nearest_tile_co[0] = coords_floor[0];
}
if (coords[1] > udim_grid[1]) {
nearest_tile_co[1] = udim_grid[1] - 1;
}
else if (coords[1] < 0) {
nearest_tile_co[1] = 0;
}
else {
nearest_tile_co[1] = coords_floor[1];
}
/* Add 0.5 to get tile center coordinates. */
float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]};
add_v2_fl(nearest_tile_center_co, 0.5f);
return len_squared_v2v2(coords, nearest_tile_center_co);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Calculate UV Islands
*
@ -357,8 +452,11 @@ static int bm_mesh_calc_uv_islands(const Scene *scene,
* \{ */
void ED_uvedit_pack_islands_multi(const Scene *scene,
const SpaceImage *sima,
Object **objects,
const uint objects_len,
const bool use_target_udim,
int target_udim,
const struct UVPackIsland_Params *params)
{
/* Align to the Y axis, could make this configurable. */
@ -407,8 +505,26 @@ void ED_uvedit_pack_islands_multi(const Scene *scene,
BoxPack *boxarray = MEM_mallocN(sizeof(*boxarray) * island_list_len, __func__);
int index;
/* Coordinates of bounding box containing all selected UVs. */
float selection_min_co[2], selection_max_co[2];
INIT_MINMAX2(selection_min_co, selection_max_co);
LISTBASE_FOREACH_INDEX (struct FaceIsland *, island, &island_list, index) {
/* Skip calculation if using specified UDIM option. */
if (!use_target_udim) {
float bounds_min[2], bounds_max[2];
INIT_MINMAX2(bounds_min, bounds_max);
for (int i = 0; i < island->faces_len; i++) {
BMFace *f = island->faces[i];
BM_face_uv_minmax(f, bounds_min, bounds_max, island->cd_loop_uv_offset);
}
selection_min_co[0] = MIN2(bounds_min[0], selection_min_co[0]);
selection_min_co[1] = MIN2(bounds_min[1], selection_min_co[1]);
selection_max_co[0] = MAX2(bounds_max[0], selection_max_co[0]);
selection_max_co[1] = MAX2(bounds_max[1], selection_max_co[1]);
}
if (params->rotate) {
if (island->aspect_y != 1.0f) {
bm_face_array_uv_scale_y(
@ -441,6 +557,13 @@ void ED_uvedit_pack_islands_multi(const Scene *scene,
}
}
/* Center of bounding box containing all selected UVs. */
float selection_center[2];
if (!use_target_udim) {
selection_center[0] = (selection_min_co[0] + selection_max_co[0]) / 2.0f;
selection_center[1] = (selection_min_co[1] + selection_max_co[1]) / 2.0f;
}
if (margin > 0.0f) {
/* Logic matches behavior from #param_pack,
* use area so multiply the margin by the area to give
@ -464,6 +587,55 @@ void ED_uvedit_pack_islands_multi(const Scene *scene,
const float scale[2] = {1.0f / boxarray_size[0], 1.0f / boxarray_size[1]};
/* Tile offset. */
float base_offset[2] = {0.0f, 0.0f};
/* CASE: Active/specified(smart uv project) UDIM. */
if (use_target_udim) {
/* Calculate offset based on specified_tile_index. */
base_offset[0] = (target_udim - 1001) % 10;
base_offset[1] = (target_udim - 1001) / 10;
}
/* CASE: Closest UDIM. */
else {
const Image *image;
int udim_grid[2] = {1, 1};
/* To handle cases where `sima == NULL` - Smart UV project in 3D viewport. */
if (sima != NULL) {
image = sima->image;
udim_grid[0] = sima->tile_grid_shape[0];
udim_grid[1] = sima->tile_grid_shape[1];
}
/* Check if selection lies on a valid UDIM grid tile. */
bool is_valid_udim = uv_coords_isect_udim(image, udim_grid, selection_center);
if (is_valid_udim) {
base_offset[0] = floorf(selection_center[0]);
base_offset[1] = floorf(selection_center[1]);
}
/* If selection doesn't lie on any UDIM then find the closest UDIM grid or image tile. */
else {
float nearest_image_tile_co[2] = {FLT_MAX, FLT_MAX};
float nearest_image_tile_dist = FLT_MAX, nearest_grid_tile_dist = FLT_MAX;
if (image) {
nearest_image_tile_dist = uv_nearest_image_tile_distance(
image, selection_center, nearest_image_tile_co);
}
float nearest_grid_tile_co[2] = {0.0f, 0.0f};
nearest_grid_tile_dist = uv_nearest_grid_tile_distance(
udim_grid, selection_center, nearest_grid_tile_co);
base_offset[0] = (nearest_image_tile_dist < nearest_grid_tile_dist) ?
nearest_image_tile_co[0] :
nearest_grid_tile_co[0];
base_offset[1] = (nearest_image_tile_dist < nearest_grid_tile_dist) ?
nearest_image_tile_co[1] :
nearest_grid_tile_co[1];
}
}
for (int i = 0; i < island_list_len; i++) {
struct FaceIsland *island = island_array[boxarray[i].index];
const float pivot[2] = {
@ -471,8 +643,8 @@ void ED_uvedit_pack_islands_multi(const Scene *scene,
island->bounds_rect.ymin,
};
const float offset[2] = {
(boxarray[i].x * scale[0]) - island->bounds_rect.xmin,
(boxarray[i].y * scale[1]) - island->bounds_rect.ymin,
(boxarray[i].x * scale[0]) - island->bounds_rect.xmin + base_offset[0],
(boxarray[i].y * scale[1]) - island->bounds_rect.ymin + base_offset[1],
};
for (int j = 0; j < island->faces_len; j++) {
BMFace *efa = island->faces[j];

View File

@ -37,6 +37,7 @@
#include "BLI_alloca.h"
#include "BLI_array.h"
#include "BLI_linklist.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_memarena.h"
#include "BLI_string.h"
@ -64,6 +65,7 @@
#include "PIL_time.h"
#include "UI_interface.h"
#include "UI_view2d.h"
#include "ED_image.h"
#include "ED_mesh.h"
@ -1005,10 +1007,17 @@ static void uvedit_pack_islands_multi(const Scene *scene,
}
}
/* Packing targets. */
enum {
PACK_UDIM_SRC_CLOSEST = 0,
PACK_UDIM_SRC_ACTIVE = 1,
};
static int pack_islands_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
const Scene *scene = CTX_data_scene(C);
const SpaceImage *sima = CTX_wm_space_image(C);
const UnwrapOptions options = {
.topology_from_uvs = true,
@ -1018,17 +1027,20 @@ static int pack_islands_exec(bContext *C, wmOperator *op)
.correct_aspect = true,
};
bool rotate = RNA_boolean_get(op->ptr, "rotate");
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
view_layer, CTX_wm_view3d(C), &objects_len);
/* Early exit in case no UVs are selected. */
if (!uvedit_have_selection_multi(scene, objects, objects_len, &options)) {
MEM_freeN(objects);
return OPERATOR_CANCELLED;
}
/* RNA props */
const bool rotate = RNA_boolean_get(op->ptr, "rotate");
const int udim_source = RNA_enum_get(op->ptr, "udim_source");
bool use_target_udim = false;
if (RNA_struct_property_is_set(op->ptr, "margin")) {
scene->toolsettings->uvcalc_margin = RNA_float_get(op->ptr, "margin");
}
@ -1036,9 +1048,46 @@ static int pack_islands_exec(bContext *C, wmOperator *op)
RNA_float_set(op->ptr, "margin", scene->toolsettings->uvcalc_margin);
}
int target_udim = 1001;
if (udim_source == PACK_UDIM_SRC_CLOSEST) {
/* pass */
}
else if (udim_source == PACK_UDIM_SRC_ACTIVE) {
int active_udim = 1001;
/* NOTE: Presently, when UDIM grid and tiled image are present together, only active tile for
* the tiled imgae is considered. */
if (sima && sima->image) {
Image *image = sima->image;
ImageTile *active_tile = BLI_findlink(&image->tiles, image->active_tile_index);
if (active_tile) {
active_udim = active_tile->tile_number;
}
}
else if (sima && !sima->image) {
/* Use 2D cursor to find the active tile index for the UDIM grid. */
float cursor_loc[2] = {sima->cursor[0], sima->cursor[1]};
if (uv_coords_isect_udim(sima->image, sima->tile_grid_shape, cursor_loc)) {
int tile_number = 1001;
tile_number += floorf(cursor_loc[1]) * 10;
tile_number += floorf(cursor_loc[0]);
active_udim = tile_number;
}
/* TODO: Support storing an active UDIM when there are no tiles present. */
}
target_udim = active_udim;
use_target_udim = true;
}
else {
BLI_assert_unreachable();
}
ED_uvedit_pack_islands_multi(scene,
sima,
objects,
objects_len,
use_target_udim,
target_udim,
&(struct UVPackIsland_Params){
.rotate = rotate,
.rotate_align_axis = -1,
@ -1048,16 +1097,25 @@ static int pack_islands_exec(bContext *C, wmOperator *op)
});
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void UV_OT_pack_islands(wmOperatorType *ot)
{
static const EnumPropertyItem pack_target[] = {
{PACK_UDIM_SRC_CLOSEST, "CLOSEST_UDIM", 0, "Closest UDIM", "Pack islands to closest UDIM"},
{PACK_UDIM_SRC_ACTIVE,
"ACTIVE_UDIM",
0,
"Active UDIM",
"Pack islands to active UDIM image tile or UDIM grid tile where 2D cursor is located"},
{0, NULL, 0, NULL, NULL},
};
/* identifiers */
ot->name = "Pack Islands";
ot->idname = "UV_OT_pack_islands";
ot->description = "Transform all islands so that they fill up the UV space as much as possible";
ot->description =
"Transform all islands so that they fill up the UV/UDIM space as much as possible";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@ -1066,6 +1124,7 @@ void UV_OT_pack_islands(wmOperatorType *ot)
ot->poll = ED_operator_uvedit;
/* properties */
RNA_def_enum(ot->srna, "udim_source", pack_target, PACK_UDIM_SRC_CLOSEST, "Pack to", "");
RNA_def_boolean(ot->srna, "rotate", true, "Rotate", "Rotate islands for best fit");
RNA_def_float_factor(
ot->srna, "margin", 0.001f, 0.0f, 1.0f, "Margin", "Space between islands", 0.0f, 1.0f);
@ -2055,6 +2114,7 @@ static int smart_project_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
const SpaceImage *sima = CTX_wm_space_image(C);
/* May be NULL. */
View3D *v3d = CTX_wm_view3d(C);
@ -2065,6 +2125,7 @@ static int smart_project_exec(bContext *C, wmOperator *op)
const float project_angle_limit_cos = cosf(project_angle_limit);
const float project_angle_limit_half_cos = cosf(project_angle_limit / 2);
const int target_udim = 1001; /* 0-1 UV space. */
/* Memory arena for list links (cleared for each object). */
MemArena *arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
@ -2204,8 +2265,11 @@ static int smart_project_exec(bContext *C, wmOperator *op)
/* Depsgraph refresh functions are called here. */
const bool correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect");
ED_uvedit_pack_islands_multi(scene,
sima,
objects_changed,
object_changed_len,
true,
target_udim,
&(struct UVPackIsland_Params){
.rotate = true,
/* We could make this optional. */