UDIM: Support virtual filenames

This implements the design detailed in T92696 to support virtual
filenames for UDIM textures. Currently, the following 2 substitution
tokens are supported:

| Token | Meaning |
| ----- | ---- |
| <UDIM>   | 1001 + u-tile + v-tile * 10 |
| <UVTILE> | Equivalent to u<u-tile + 1>_v<v-tile + 1> |

Example for u-tile of 3 and v-tile of 1:
filename.<UDIM>_ver0023.png   --> filename.1014_ver0023.png
filename.<UVTILE>_ver0023.png --> filename.u4_v2_ver0023.png

For image loading, the existing workflow is unchanged. A user can select
one or more image files, belonging to one or more UDIM tile sets, and
have Blender load them all as it does today. Now the <UVTILE> format is
"guessed" just as the <UDIM> format was guessed before.

If guessing fails, the user can simply go into the Image Editor and type
the proper substitution in the filename. Once typing is complete,
Blender will reload the files and correctly fill the tiles. This
workflow is new as attempting to fix the guessing in current versions
did not really work, and the user was often stuck with a confusing
situation.

For image saving, the existing workflow is changed slightly. Currently,
when saving, a user has to be sure to type the filename of the first
tile (e.g. filename.1001.png) to save the entire UDIM set. The number
could differ if they start at a different tile etc. This is confusing.
Now, the user should type a filename containing the appropriate
substitution token. By default Blender will fill in a default name using
the <UDIM> token but the user is free to save out images using <UVTILE>
if they wish.

Differential Revision: https://developer.blender.org/D13057
This commit is contained in:
Jesse Yurkovich 2021-12-30 22:06:23 -08:00
parent 367fc69dc1
commit 180b66ae8a
Notes: blender-bot 2023-02-14 04:56:36 +01:00
Referenced by commit 98eb111568, Fix T97366: Misdetection of numbers as UDIMs in certain filepaths
Referenced by issue #96984, Loading UDIM from file is broken if opening into an already loaded tiled image
Referenced by issue #77989, UDIM Textures: support for different file name schemes
Referenced by issue #75116, UDIM tiles not recognized when reloading images
Referenced by issue #74777, UDIM issue when switch from Tiled to Single and back to Tiled
16 changed files with 471 additions and 136 deletions

View File

@ -776,7 +776,7 @@ static ShaderNode *add_node(Scene *scene,
}
else {
ustring filename = ustring(
image_user_file_path(b_image_user, b_image, b_scene.frame_current(), true));
image_user_file_path(b_image_user, b_image, b_scene.frame_current()));
image->set_filename(filename);
}
}
@ -813,7 +813,7 @@ static ShaderNode *add_node(Scene *scene,
}
else {
env->set_filename(
ustring(image_user_file_path(b_image_user, b_image, b_scene.frame_current(), false)));
ustring(image_user_file_path(b_image_user, b_image, b_scene.frame_current())));
}
}
node = env;

View File

@ -33,7 +33,7 @@
extern "C" {
void BKE_image_user_frame_calc(void *ima, void *iuser, int cfra);
void BKE_image_user_file_path(void *iuser, void *ima, char *path);
void BKE_image_user_file_path_ex(void *iuser, void *ima, char *path, bool resolve_udim);
unsigned char *BKE_image_get_pixels_for_frame(void *image, int frame, int tile);
float *BKE_image_get_float_pixels_for_frame(void *image, int frame, int tile);
}
@ -290,25 +290,14 @@ static inline int render_resolution_y(BL::RenderSettings &b_render)
return b_render.resolution_y() * b_render.resolution_percentage() / 100;
}
static inline string image_user_file_path(BL::ImageUser &iuser,
BL::Image &ima,
int cfra,
bool load_tiled)
static inline string image_user_file_path(BL::ImageUser &iuser, BL::Image &ima, int cfra)
{
char filepath[1024];
iuser.tile(0);
BKE_image_user_frame_calc(ima.ptr.data, iuser.ptr.data, cfra);
BKE_image_user_file_path(iuser.ptr.data, ima.ptr.data, filepath);
BKE_image_user_file_path_ex(iuser.ptr.data, ima.ptr.data, filepath, false);
string filepath_str = string(filepath);
if (load_tiled && ima.source() == BL::Image::source_TILED) {
string udim;
if (!ima.tiles.empty()) {
udim = to_string(ima.tiles[0].number());
}
string_replace(filepath_str, udim, "<UDIM>");
}
return filepath_str;
return string(filepath);
}
static inline int image_user_frame_number(BL::ImageUser &iuser, BL::Image &ima, int cfra)

View File

@ -381,8 +381,15 @@ ImageHandle ImageManager::add_image(const string &filename,
foreach (int tile, tiles) {
string tile_filename = filename;
/* Since we don't have information about the exact tile format used in this code location,
* just attempt all replacement patterns that Blender supports. */
if (tile != 0) {
string_replace(tile_filename, "<UDIM>", string_printf("%04d", tile));
int u = ((tile - 1001) % 10);
int v = ((tile - 1001) / 10);
string_replace(tile_filename, "<UVTILE>", string_printf("u%d_v%d", u + 1, v + 1));
}
const int slot = add_image_slot(new OIIOImageLoader(tile_filename), params, false);
handle.tile_slots.push_back(slot);

View File

@ -49,6 +49,8 @@ typedef enum eBPathForeachFlag {
BKE_BPATH_FOREACH_PATH_SKIP_LINKED = (1 << 1),
/** Skip paths when their matching data is packed. */
BKE_BPATH_FOREACH_PATH_SKIP_PACKED = (1 << 2),
/** Resolve tokens within a virtual filepath to a single, concrete, filepath. */
BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN = (1 << 3),
/* Skip weak reference paths. Those paths are typically 'nice to have' extra information, but are
* not used as actual source of data by the current .blend file.
*

View File

@ -36,6 +36,7 @@ struct ImageFormatData;
struct ImagePool;
struct ImageTile;
struct ImbFormatOptions;
struct ListBase;
struct Main;
struct Object;
struct RenderResult;
@ -284,6 +285,10 @@ void BKE_image_ensure_viewer_views(const struct RenderData *rd,
void BKE_image_user_frame_calc(struct Image *ima, struct ImageUser *iuser, int cfra);
int BKE_image_user_frame_get(const struct ImageUser *iuser, int cfra, bool *r_is_in_range);
void BKE_image_user_file_path(struct ImageUser *iuser, struct Image *ima, char *path);
void BKE_image_user_file_path_ex(struct ImageUser *iuser,
struct Image *ima,
char *path,
bool resolve_udim);
void BKE_image_editors_update_frame(const struct Main *bmain, int cfra);
/**
@ -397,6 +402,18 @@ void BKE_image_get_tile_label(struct Image *ima,
char *label,
int len_label);
/**
* Checks whether the given filepath refers to a UDIM tiled texture.
* If yes, the range from the lowest to the highest tile is returned.
*
* `filepath` may be modified to ensure a UDIM token is present.
* `tiles` may be filled even if the result ultimately is false!
*/
bool BKE_image_get_tile_info(char *filepath,
struct ListBase *tiles,
int *tile_start,
int *tile_range);
struct ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *label);
bool BKE_image_remove_tile(struct Image *ima, struct ImageTile *tile);
void BKE_image_reassign_tile(struct Image *ima, struct ImageTile *tile, int new_tile_number);
@ -411,6 +428,40 @@ bool BKE_image_fill_tile(struct Image *ima,
int planes,
bool is_float);
typedef enum {
UDIM_TILE_FORMAT_NONE = 0,
UDIM_TILE_FORMAT_UDIM = 1,
UDIM_TILE_FORMAT_UVTILE = 2
} eUDIM_TILE_FORMAT;
/**
* Ensures that `filename` contains a UDIM token if we find a supported format pattern.
*/
void BKE_image_ensure_tile_token(char *filename);
/**
* When provided with an absolute virtual filepath, check to see if at least
* one concrete file exists.
* Note: This function requires directory traversal and may be inefficient in time-critical,
* or iterative, code paths.
*/
bool BKE_image_tile_filepath_exists(const char *filepath);
/**
* Retrieves the UDIM token format and returns the pattern from the provided `filepath`.
* The returned pattern is typically passed to either `BKE_image_get_tile_number_from_filepath` or
* `BKE_image_set_filepath_from_tile_number`.
*/
char *BKE_image_get_tile_strformat(const char *filepath, eUDIM_TILE_FORMAT *r_tile_format);
bool BKE_image_get_tile_number_from_filepath(const char *filepath,
const char *pattern,
eUDIM_TILE_FORMAT tile_format,
int *r_tile_number);
void BKE_image_set_filepath_from_tile_number(char *filepath,
const char *pattern,
eUDIM_TILE_FORMAT tile_format,
int tile_number);
struct ImageTile *BKE_image_get_tile(struct Image *ima, int tile_number);
struct ImageTile *BKE_image_get_tile_from_iuser(struct Image *ima, const struct ImageUser *iuser);

View File

@ -236,7 +236,8 @@ void BKE_bpath_missing_files_check(Main *bmain, ReportList *reports)
BKE_bpath_foreach_path_main(&(BPathForeachPathData){
.bmain = bmain,
.callback_function = check_missing_files_foreach_path_cb,
.flag = BKE_BPATH_FOREACH_PATH_ABSOLUTE | BKE_BPATH_FOREACH_PATH_SKIP_PACKED,
.flag = BKE_BPATH_FOREACH_PATH_ABSOLUTE | BKE_BPATH_FOREACH_PATH_SKIP_PACKED |
BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN,
.user_data = reports});
}
@ -384,7 +385,8 @@ void BKE_bpath_missing_files_find(Main *bmain,
const bool find_all)
{
struct BPathFind_Data data = {NULL};
const int flag = BKE_BPATH_FOREACH_PATH_ABSOLUTE | BKE_BPATH_FOREACH_PATH_RELOAD_EDITED;
const int flag = BKE_BPATH_FOREACH_PATH_ABSOLUTE | BKE_BPATH_FOREACH_PATH_RELOAD_EDITED |
BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN;
data.basedir = BKE_main_blendfile_path(bmain);
data.reports = reports;

View File

@ -21,6 +21,7 @@
* \ingroup bke
*/
#include <ctype.h>
#include <fcntl.h>
#include <math.h>
#include <stdio.h>
@ -272,7 +273,33 @@ static void image_foreach_path(ID *id, BPathForeachPathData *bpath_data)
return;
}
if (BKE_bpath_foreach_path_fixed_process(bpath_data, ima->filepath)) {
/* If this is a tiled image, and we're asked to resolve the tokens in the virtual
* filepath, use the first tile to generate a concrete path for use during processing. */
bool result = false;
if (ima->source == IMA_SRC_TILED && (flag & BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN) != 0) {
char temp_path[FILE_MAX], orig_file[FILE_MAXFILE];
BLI_strncpy(temp_path, ima->filepath, sizeof(temp_path));
BLI_split_file_part(temp_path, orig_file, sizeof(orig_file));
eUDIM_TILE_FORMAT tile_format;
char *udim_pattern = BKE_image_get_tile_strformat(temp_path, &tile_format);
BKE_image_set_filepath_from_tile_number(
temp_path, udim_pattern, tile_format, ((ImageTile *)ima->tiles.first)->tile_number);
MEM_SAFE_FREE(udim_pattern);
result = BKE_bpath_foreach_path_fixed_process(bpath_data, temp_path);
if (result) {
/* Put the filepath back together using the new directory and the original file name. */
char new_dir[FILE_MAXDIR];
BLI_split_dir_part(temp_path, new_dir, sizeof(new_dir));
BLI_join_dirfile(ima->filepath, sizeof(ima->filepath), new_dir, orig_file);
}
}
else {
result = BKE_bpath_foreach_path_fixed_process(bpath_data, ima->filepath);
}
if (result) {
if (flag & BKE_BPATH_FOREACH_PATH_RELOAD_EDITED) {
if (!BKE_image_has_packedfile(ima) &&
/* Image may have been painted onto (and not saved, T44543). */
@ -888,9 +915,13 @@ Image *BKE_image_load(Main *bmain, const char *filepath)
/* exists? */
file = BLI_open(str, O_BINARY | O_RDONLY, 0);
if (file == -1) {
return NULL;
if (!BKE_image_tile_filepath_exists(str)) {
return NULL;
}
}
else {
close(file);
}
close(file);
ima = image_alloc(bmain, BLI_path_basename(filepath), IMA_SRC_FILE, IMA_TYPE_IMAGE);
STRNCPY(ima->filepath, filepath);
@ -3699,6 +3730,43 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal)
BKE_image_free_buffers(ima);
}
if (ima->source == IMA_SRC_TILED) {
ListBase new_tiles = {NULL, NULL};
int new_start, new_range;
char filepath[FILE_MAX];
BLI_strncpy(filepath, ima->filepath, sizeof(filepath));
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&ima->id));
bool result = BKE_image_get_tile_info(filepath, &new_tiles, &new_start, &new_range);
if (result) {
/* Because the prior and new list of tiles are both sparse sequences, we need to be sure
* to account for how the two sets might or might not overlap. To be complete, we start
* the refresh process by clearing all existing tiles, stopping when there's only 1 tile
* left. */
while (BKE_image_remove_tile(ima, ima->tiles.last)) {
;
}
int remaining_tile_number = ((ImageTile *)ima->tiles.first)->tile_number;
bool needs_final_cleanup = true;
/* Add in all the new tiles. */
LISTBASE_FOREACH (LinkData *, new_tile, &new_tiles) {
int new_tile_number = POINTER_AS_INT(new_tile->data);
BKE_image_add_tile(ima, new_tile_number, NULL);
if (new_tile_number == remaining_tile_number) {
needs_final_cleanup = false;
}
}
/* Final cleanup if the prior remaining tile was never encountered in the new list. */
if (needs_final_cleanup) {
BKE_image_remove_tile(ima, BKE_image_get_tile(ima, remaining_tile_number));
}
}
BLI_freelistN(&new_tiles);
}
if (iuser) {
image_tag_reload(ima, NULL, iuser, ima);
}
@ -3782,6 +3850,57 @@ void BKE_image_get_tile_label(Image *ima, ImageTile *tile, char *label, int len_
}
}
bool BKE_image_get_tile_info(char *filepath,
ListBase *udim_tiles,
int *udim_start,
int *udim_range)
{
char filename[FILE_MAXFILE], dirname[FILE_MAXDIR];
BLI_split_dirfile(filepath, dirname, filename, sizeof(dirname), sizeof(filename));
BKE_image_ensure_tile_token(filename);
eUDIM_TILE_FORMAT tile_format;
char *udim_pattern = BKE_image_get_tile_strformat(filename, &tile_format);
bool is_udim = true;
int min_udim = IMA_UDIM_MAX + 1;
int max_udim = 0;
int id;
struct direntry *dir;
uint totfile = BLI_filelist_dir_contents(dirname, &dir);
for (int i = 0; i < totfile; i++) {
if (!(dir[i].type & S_IFREG)) {
continue;
}
if (!BKE_image_get_tile_number_from_filepath(dir[i].relname, udim_pattern, tile_format, &id)) {
continue;
}
if (id < 1001 || id > IMA_UDIM_MAX) {
is_udim = false;
break;
}
BLI_addtail(udim_tiles, BLI_genericNodeN(POINTER_FROM_INT(id)));
min_udim = min_ii(min_udim, id);
max_udim = max_ii(max_udim, id);
}
BLI_filelist_free(dir, totfile);
MEM_SAFE_FREE(udim_pattern);
if (is_udim && min_udim <= IMA_UDIM_MAX) {
BLI_join_dirfile(filepath, FILE_MAX, dirname, filename);
*udim_start = min_udim;
*udim_range = max_udim - min_udim + 1;
return true;
}
return false;
}
ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *label)
{
if (ima->source != IMA_SRC_TILED) {
@ -3941,6 +4060,185 @@ bool BKE_image_fill_tile(struct Image *ima,
return false;
}
void BKE_image_ensure_tile_token(char *filename)
{
if (filename == NULL) {
return;
}
/* Is there a '<' character in the filename? Assume tokens already present. */
if (strstr(filename, "<") != NULL) {
return;
}
/* Is there a sequence of digits in the filename? */
ushort digits;
char head[FILE_MAX], tail[FILE_MAX];
BLI_path_sequence_decode(filename, head, tail, &digits);
if (digits == 4) {
sprintf(filename, "%s<UDIM>%s", head, tail);
return;
}
/* Is there a sequence like u##_v#### in the filename? */
uint cur = 0;
uint name_end = strlen(filename);
uint u_digits = 0;
uint v_digits = 0;
uint u_start = (uint)-1;
bool u_found = false;
bool v_found = false;
bool sep_found = false;
while (cur < name_end) {
if (filename[cur] == 'u') {
u_found = true;
u_digits = 0;
u_start = cur;
}
else if (filename[cur] == 'v') {
v_found = true;
v_digits = 0;
}
else if (u_found && !v_found) {
if (isdigit(filename[cur]) && u_digits < 2) {
u_digits++;
}
else if (filename[cur] == '_') {
sep_found = true;
}
else {
u_found = false;
}
}
else if (u_found && u_digits > 0 && v_found) {
if (isdigit(filename[cur])) {
if (v_digits < 4) {
v_digits++;
}
else {
u_found = false;
v_found = false;
}
}
else if (v_digits > 0) {
break;
}
}
cur++;
}
if (u_found && sep_found && v_found && (u_digits + v_digits > 1)) {
const char *token = "<UVTILE>";
const size_t token_length = strlen(token);
memmove(filename + u_start + token_length, filename + cur, name_end - cur);
memcpy(filename + u_start, token, token_length);
filename[u_start + token_length + (name_end - cur)] = '\0';
}
}
bool BKE_image_tile_filepath_exists(const char *filepath)
{
BLI_assert(!BLI_path_is_rel(filepath));
char dirname[FILE_MAXDIR];
BLI_split_dir_part(filepath, dirname, sizeof(dirname));
eUDIM_TILE_FORMAT tile_format;
char *udim_pattern = BKE_image_get_tile_strformat(filepath, &tile_format);
bool found = false;
struct direntry *dir;
uint totfile = BLI_filelist_dir_contents(dirname, &dir);
for (int i = 0; i < totfile; i++) {
if (!(dir[i].type & S_IFREG)) {
continue;
}
int id;
if (!BKE_image_get_tile_number_from_filepath(dir[i].path, udim_pattern, tile_format, &id)) {
continue;
}
if (id < 1001 || id > IMA_UDIM_MAX) {
continue;
}
found = true;
break;
}
BLI_filelist_free(dir, totfile);
MEM_SAFE_FREE(udim_pattern);
return found;
}
char *BKE_image_get_tile_strformat(const char *filepath, eUDIM_TILE_FORMAT *r_tile_format)
{
if (filepath == NULL || r_tile_format == NULL) {
return NULL;
}
if (strstr(filepath, "<UDIM>") != NULL) {
*r_tile_format = UDIM_TILE_FORMAT_UDIM;
return BLI_str_replaceN(filepath, "<UDIM>", "%d");
}
if (strstr(filepath, "<UVTILE>") != NULL) {
*r_tile_format = UDIM_TILE_FORMAT_UVTILE;
return BLI_str_replaceN(filepath, "<UVTILE>", "u%d_v%d");
}
*r_tile_format = UDIM_TILE_FORMAT_NONE;
return NULL;
}
bool BKE_image_get_tile_number_from_filepath(const char *filepath,
const char *pattern,
eUDIM_TILE_FORMAT tile_format,
int *r_tile_number)
{
if (filepath == NULL || pattern == NULL || r_tile_number == NULL) {
return false;
}
int u, v;
bool result = false;
if (tile_format == UDIM_TILE_FORMAT_UDIM) {
if (sscanf(filepath, pattern, &u) == 1) {
*r_tile_number = u;
result = true;
}
}
else if (tile_format == UDIM_TILE_FORMAT_UVTILE) {
if (sscanf(filepath, pattern, &u, &v) == 2) {
*r_tile_number = 1001 + (u - 1) + ((v - 1) * 10);
result = true;
}
}
return result;
}
void BKE_image_set_filepath_from_tile_number(char *filepath,
const char *pattern,
eUDIM_TILE_FORMAT tile_format,
int tile_number)
{
if (filepath == NULL || pattern == NULL) {
return;
}
if (tile_format == UDIM_TILE_FORMAT_UDIM) {
sprintf(filepath, pattern, tile_number);
}
else if (tile_format == UDIM_TILE_FORMAT_UVTILE) {
int u = ((tile_number - 1001) % 10);
int v = ((tile_number - 1001) / 10);
sprintf(filepath, pattern, u + 1, v + 1);
}
}
/* if layer or pass changes, we need an index for the imbufs list */
/* note it is called for rendered results, but it doesn't use the index! */
RenderPass *BKE_image_multilayer_index(RenderResult *rr, ImageUser *iuser)
@ -5512,6 +5810,11 @@ void BKE_image_user_id_eval_animation(Depsgraph *depsgraph, ID *id)
}
void BKE_image_user_file_path(ImageUser *iuser, Image *ima, char *filepath)
{
BKE_image_user_file_path_ex(iuser, ima, filepath, true);
}
void BKE_image_user_file_path_ex(ImageUser *iuser, Image *ima, char *filepath, bool resolve_udim)
{
if (BKE_image_is_multiview(ima)) {
ImageView *iv = BLI_findlink(&ima->views, iuser->view);
@ -5533,13 +5836,17 @@ void BKE_image_user_file_path(ImageUser *iuser, Image *ima, char *filepath)
int index;
if (ima->source == IMA_SRC_SEQUENCE) {
index = iuser ? iuser->framenr : ima->lastframe;
BLI_path_sequence_decode(filepath, head, tail, &numlen);
BLI_path_sequence_encode(filepath, head, tail, numlen, index);
}
else {
else if (resolve_udim) {
index = image_get_tile_number_from_iuser(ima, iuser);
}
BLI_path_sequence_decode(filepath, head, tail, &numlen);
BLI_path_sequence_encode(filepath, head, tail, numlen, index);
eUDIM_TILE_FORMAT tile_format;
char *udim_pattern = BKE_image_get_tile_strformat(filepath, &tile_format);
BKE_image_set_filepath_from_tile_number(filepath, udim_pattern, tile_format, index);
MEM_SAFE_FREE(udim_pattern);
}
}
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&ima->id));

View File

@ -30,6 +30,8 @@
#include "DNA_image_types.h"
#include "MEM_guardedalloc.h"
#include "IMB_colormanagement.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
@ -402,15 +404,17 @@ bool BKE_image_save(
bool colorspace_changed = false;
eUDIM_TILE_FORMAT tile_format;
char *udim_pattern = NULL;
if (ima->source == IMA_SRC_TILED) {
/* Verify filepath for tiles images. */
ImageTile *first_tile = ima->tiles.first;
if (BLI_path_sequence_decode(opts->filepath, NULL, NULL, NULL) != first_tile->tile_number) {
/* Verify filepath for tiled images contains a valid UDIM marker. */
udim_pattern = BKE_image_get_tile_strformat(opts->filepath, &tile_format);
if (tile_format == UDIM_TILE_FORMAT_NONE) {
BKE_reportf(reports,
RPT_ERROR,
"When saving a tiled image, the path '%s' must contain the UDIM tile number %d",
opts->filepath,
first_tile->tile_number);
"When saving a tiled image, the path '%s' must contain a valid UDIM marker",
opts->filepath);
return false;
}
@ -420,36 +424,29 @@ bool BKE_image_save(
}
}
/* Save image - or, for tiled images, the first tile. */
bool ok = image_save_single(reports, ima, iuser, opts, &colorspace_changed);
if (ok && ima->source == IMA_SRC_TILED) {
/* Save images */
bool ok = false;
if (ima->source != IMA_SRC_TILED) {
ok = image_save_single(reports, ima, iuser, opts, &colorspace_changed);
}
else {
char filepath[FILE_MAX];
BLI_strncpy(filepath, opts->filepath, sizeof(filepath));
char head[FILE_MAX], tail[FILE_MAX];
unsigned short numlen;
BLI_path_sequence_decode(filepath, head, tail, &numlen);
/* Save all other tiles. */
int index;
LISTBASE_FOREACH_INDEX (ImageTile *, tile, &ima->tiles, index) {
/* First tile was already saved before the loop. */
if (index == 0) {
continue;
}
if (!ok) {
continue;
}
/* Build filepath of the tile. */
BLI_path_sequence_encode(opts->filepath, head, tail, numlen, tile->tile_number);
/* Save all the tiles. */
LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
BKE_image_set_filepath_from_tile_number(
opts->filepath, udim_pattern, tile_format, tile->tile_number);
iuser->tile = tile->tile_number;
ok = ok && image_save_single(reports, ima, iuser, opts, &colorspace_changed);
ok = image_save_single(reports, ima, iuser, opts, &colorspace_changed);
if (!ok) {
break;
}
}
BLI_strncpy(ima->filepath, filepath, sizeof(ima->filepath));
BLI_strncpy(opts->filepath, filepath, sizeof(opts->filepath));
MEM_freeN(udim_pattern);
}
if (colorspace_changed) {

View File

@ -254,6 +254,10 @@ void BLI_path_normalize_dir(const char *relabase, char *dir) ATTR_NONNULL(2);
/**
* Make given name safe to be used in paths.
*
* \param allow_tokens: Permit the usage of '<' and '>' characters. This can be
* leveraged by higher layers to support "virtual filenames" which contain
* substitution markers delineated between the two characters.
*
* \return true if \a fname was changed, false otherwise.
*
* For now, simply replaces reserved chars (as listed in
@ -273,7 +277,9 @@ void BLI_path_normalize_dir(const char *relabase, char *dir) ATTR_NONNULL(2);
* \note On Windows, it also checks for forbidden names
* (see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx ).
*/
bool BLI_filename_make_safe_ex(char *fname, bool allow_tokens) ATTR_NONNULL(1);
bool BLI_filename_make_safe(char *fname) ATTR_NONNULL(1);
/**
* Make given path OS-safe.
*

View File

@ -245,12 +245,19 @@ void BLI_path_normalize_dir(const char *relabase, char *dir)
BLI_path_slash_ensure(dir);
}
bool BLI_filename_make_safe(char *fname)
bool BLI_filename_make_safe_ex(char *fname, bool allow_tokens)
{
const char *invalid =
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
"/\\?*:|\"<>";
#define INVALID_CHARS \
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
"/\\?*:|\""
#define INVALID_TOKENS "<>"
const char *invalid = allow_tokens ? INVALID_CHARS : INVALID_CHARS INVALID_TOKENS;
#undef INVALID_CHARS
#undef INVALID_TOKENS
char *fn;
bool changed = false;
@ -315,6 +322,11 @@ bool BLI_filename_make_safe(char *fname)
return changed;
}
bool BLI_filename_make_safe(char *fname)
{
return BLI_filename_make_safe_ex(fname, false);
}
bool BLI_path_make_safe(char *path)
{
/* Simply apply BLI_filename_make_safe() over each component of the path.

View File

@ -58,6 +58,7 @@
#include "BKE_fcurve.h"
#include "BKE_fcurve_driver.h"
#include "BKE_idprop.h"
#include "BKE_image.h"
#include "BKE_lib_id.h"
#include "BKE_lib_override.h"
#include "BKE_main.h"
@ -817,6 +818,13 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports))
}
FOREACH_MAIN_ID_END;
}
/* Ensure tiled image sources contain a UDIM token. */
LISTBASE_FOREACH (Image *, ima, &bmain->images) {
if (ima->source == IMA_SRC_TILED) {
BKE_image_ensure_tile_token(ima->filepath);
}
}
}
}
@ -2062,7 +2070,7 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
SpaceFile *sfile = (SpaceFile *)sl;
if (sfile->params) {
sfile->params->flag &= ~(FILE_PARAMS_FLAG_UNUSED_1 | FILE_PARAMS_FLAG_UNUSED_2 |
FILE_PARAMS_FLAG_UNUSED_3 | FILE_PARAMS_FLAG_UNUSED_4);
FILE_PARAMS_FLAG_UNUSED_3 | FILE_PATH_TOKENS_ALLOW);
}
/* New default import type: Append with reuse. */

View File

@ -2624,7 +2624,8 @@ void file_filename_enter_handle(bContext *C, void *UNUSED(arg_unused), void *arg
matches = file_select_match(sfile, params->file, matched_file);
/* *After* file_select_match! */
BLI_filename_make_safe(params->file);
const bool allow_tokens = (params->flag & FILE_PATH_TOKENS_ALLOW) != 0;
BLI_filename_make_safe_ex(params->file, allow_tokens);
if (matches) {
/* replace the pattern (or filename that the user typed in,

View File

@ -318,6 +318,10 @@ static FileSelectParams *fileselect_ensure_updated_file_params(SpaceFile *sfile)
params->flag |= RNA_boolean_get(op->ptr, "active_collection") ? FILE_ACTIVE_COLLECTION : 0;
}
if ((prop = RNA_struct_find_property(op->ptr, "allow_path_tokens"))) {
params->flag |= RNA_property_boolean_get(op->ptr, prop) ? FILE_PATH_TOKENS_ALLOW : 0;
}
if ((prop = RNA_struct_find_property(op->ptr, "display_type"))) {
params->display = RNA_property_enum_get(op->ptr, prop);
}

View File

@ -1499,6 +1499,13 @@ static void image_open_draw(bContext *UNUSED(C), wmOperator *op)
}
}
static void image_operator_prop_allow_tokens(wmOperatorType *ot)
{
PropertyRNA *prop = RNA_def_boolean(
ot->srna, "allow_path_tokens", true, "", "Allow the path to contain substitution tokens");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
void IMAGE_OT_open(wmOperatorType *ot)
{
/* identifiers */
@ -1516,6 +1523,7 @@ void IMAGE_OT_open(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
image_operator_prop_allow_tokens(ot);
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_IMAGE | FILE_TYPE_MOVIE,
FILE_SPECIAL,
@ -1767,7 +1775,13 @@ static int image_save_options_init(Main *bmain,
opts->im_format.views_format = ima->views_format;
}
BLI_strncpy(opts->filepath, ibuf->name, sizeof(opts->filepath));
if (ima->source == IMA_SRC_TILED) {
BLI_strncpy(opts->filepath, ima->filepath, sizeof(opts->filepath));
BLI_path_abs(opts->filepath, ID_BLEND_PATH_FROM_GLOBAL(&ima->id));
}
else {
BLI_strncpy(opts->filepath, ibuf->name, sizeof(opts->filepath));
}
/* sanitize all settings */
@ -1804,14 +1818,10 @@ static int image_save_options_init(Main *bmain,
BLI_path_abs(opts->filepath, is_prev_save ? G.ima : BKE_main_blendfile_path(bmain));
}
/* append UDIM numbering if not present */
if (ima->source == IMA_SRC_TILED) {
char udim[6];
ImageTile *tile = ima->tiles.first;
BLI_snprintf(udim, sizeof(udim), ".%d", tile->tile_number);
/* append UDIM marker if not present */
if (ima->source == IMA_SRC_TILED && strstr(opts->filepath, "<UDIM>") == NULL) {
int len = strlen(opts->filepath);
STR_CONCAT(opts->filepath, len, udim);
STR_CONCAT(opts->filepath, len, ".<UDIM>");
}
}
@ -2070,6 +2080,7 @@ void IMAGE_OT_save_as(wmOperatorType *ot)
"Copy",
"Create a new image file without modifying the current image in blender");
image_operator_prop_allow_tokens(ot);
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_IMAGE | FILE_TYPE_MOVIE,
FILE_SPECIAL,

View File

@ -115,79 +115,17 @@ static int image_cmp_frame(const void *a, const void *b)
return 0;
}
/*
* Checks whether the given filepath refers to a UDIM texture.
* If yes, the range from 1001 to the highest tile is returned, otherwise 0.
*
* If the result is positive, the filepath will be overwritten with that of
* the 1001 tile.
*
* udim_tiles may get filled even if the result ultimately is false!
*/
static bool image_get_udim(char *filepath, ListBase *udim_tiles, int *udim_start, int *udim_range)
{
char filename[FILE_MAX], dirname[FILE_MAXDIR];
BLI_split_dirfile(filepath, dirname, filename, sizeof(dirname), sizeof(filename));
ushort digits;
char base_head[FILE_MAX], base_tail[FILE_MAX];
int id = BLI_path_sequence_decode(filename, base_head, base_tail, &digits);
if (id < 1001 || id > IMA_UDIM_MAX) {
return false;
}
bool is_udim = true;
int min_udim = IMA_UDIM_MAX + 1;
int max_udim = 0;
struct direntry *dir;
uint totfile = BLI_filelist_dir_contents(dirname, &dir);
for (int i = 0; i < totfile; i++) {
if (!(dir[i].type & S_IFREG)) {
continue;
}
char head[FILE_MAX], tail[FILE_MAX];
id = BLI_path_sequence_decode(dir[i].relname, head, tail, &digits);
if (digits > 4 || !(STREQLEN(base_head, head, FILE_MAX)) ||
!(STREQLEN(base_tail, tail, FILE_MAX))) {
continue;
}
if (id < 1001 || id > IMA_UDIM_MAX) {
is_udim = false;
break;
}
BLI_addtail(udim_tiles, BLI_genericNodeN(POINTER_FROM_INT(id)));
min_udim = min_ii(min_udim, id);
max_udim = max_ii(max_udim, id);
}
BLI_filelist_free(dir, totfile);
if (is_udim && min_udim <= IMA_UDIM_MAX) {
char primary_filename[FILE_MAX];
BLI_path_sequence_encode(primary_filename, base_head, base_tail, digits, min_udim);
BLI_join_dirfile(filepath, FILE_MAX, dirname, primary_filename);
*udim_start = min_udim;
*udim_range = max_udim - min_udim + 1;
return true;
}
return false;
}
/**
* From a list of frames, compute the start (offset) and length of the sequence
* of contiguous frames. If UDIM is detect, it will return UDIM tiles as well.
* of contiguous frames. If `detect_udim` is set, it will return UDIM tiles as well.
*/
static void image_detect_frame_range(ImageFrameRange *range, const bool detect_udim)
{
/* UDIM */
if (detect_udim) {
int udim_start, udim_range;
bool result = image_get_udim(range->filepath, &range->udim_tiles, &udim_start, &udim_range);
bool result = BKE_image_get_tile_info(
range->filepath, &range->udim_tiles, &udim_start, &udim_range);
if (result) {
range->offset = udim_start;

View File

@ -1022,7 +1022,7 @@ typedef enum eFileSel_Params_Flag {
FILE_DIRSEL_ONLY = (1 << 7),
FILE_FILTER = (1 << 8),
FILE_PARAMS_FLAG_UNUSED_3 = (1 << 9),
FILE_PARAMS_FLAG_UNUSED_4 = (1 << 10),
FILE_PATH_TOKENS_ALLOW = (1 << 10),
FILE_SORT_INVERT = (1 << 11),
FILE_HIDE_TOOL_PROPS = (1 << 12),
FILE_CHECK_EXISTING = (1 << 13),