diff options
Diffstat (limited to 'source/blender')
-rw-r--r-- | source/blender/blenkernel/BKE_bpath.h | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_image.h | 51 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/bpath.c | 6 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/image.c | 321 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/image_save.c | 53 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_path_util.h | 6 | ||||
-rw-r--r-- | source/blender/blenlib/intern/path_util.c | 22 | ||||
-rw-r--r-- | source/blender/blenloader/intern/versioning_300.c | 10 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_ops.c | 3 | ||||
-rw-r--r-- | source/blender/editors/space_file/filesel.c | 4 | ||||
-rw-r--r-- | source/blender/editors/space_image/image_ops.c | 27 | ||||
-rw-r--r-- | source/blender/editors/space_image/image_sequence.c | 68 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_space_types.h | 2 |
13 files changed, 457 insertions, 118 deletions
diff --git a/source/blender/blenkernel/BKE_bpath.h b/source/blender/blenkernel/BKE_bpath.h index bae151f6a72..bc9e3f01e96 100644 --- a/source/blender/blenkernel/BKE_bpath.h +++ b/source/blender/blenkernel/BKE_bpath.h @@ -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. * diff --git a/source/blender/blenkernel/BKE_image.h b/source/blender/blenkernel/BKE_image.h index 4db6da4b3ea..0709d05c0d1 100644 --- a/source/blender/blenkernel/BKE_image.h +++ b/source/blender/blenkernel/BKE_image.h @@ -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); diff --git a/source/blender/blenkernel/intern/bpath.c b/source/blender/blenkernel/intern/bpath.c index 85e49774dfd..44ef3ec96ff 100644 --- a/source/blender/blenkernel/intern/bpath.c +++ b/source/blender/blenkernel/intern/bpath.c @@ -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; diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index 3fec5db422d..2d4366846aa 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -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) @@ -5513,6 +5811,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); if (iv->filepath[0]) { @@ -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)); diff --git a/source/blender/blenkernel/intern/image_save.c b/source/blender/blenkernel/intern/image_save.c index f93ede517a9..329bc7b498b 100644 --- a/source/blender/blenkernel/intern/image_save.c +++ b/source/blender/blenkernel/intern/image_save.c @@ -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; - } + /* 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 = image_save_single(reports, ima, iuser, opts, &colorspace_changed); if (!ok) { - continue; + break; } - - /* Build filepath of the tile. */ - BLI_path_sequence_encode(opts->filepath, head, tail, numlen, tile->tile_number); - - iuser->tile = tile->tile_number; - ok = ok && image_save_single(reports, ima, iuser, opts, &colorspace_changed); } + BLI_strncpy(ima->filepath, filepath, sizeof(ima->filepath)); BLI_strncpy(opts->filepath, filepath, sizeof(opts->filepath)); + MEM_freeN(udim_pattern); } if (colorspace_changed) { diff --git a/source/blender/blenlib/BLI_path_util.h b/source/blender/blenlib/BLI_path_util.h index 8b54ee52322..70eb80e7763 100644 --- a/source/blender/blenlib/BLI_path_util.h +++ b/source/blender/blenlib/BLI_path_util.h @@ -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. * diff --git a/source/blender/blenlib/intern/path_util.c b/source/blender/blenlib/intern/path_util.c index c824def9104..958480cc616 100644 --- a/source/blender/blenlib/intern/path_util.c +++ b/source/blender/blenlib/intern/path_util.c @@ -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. diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 5eb9b0a4c6a..803e33aa8ad 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -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. */ diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index 8178e54e023..a1472a2c1b3 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -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, diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c index e3ac5840da3..03261d6f267 100644 --- a/source/blender/editors/space_file/filesel.c +++ b/source/blender/editors/space_file/filesel.c @@ -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); } diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index f9160774c41..b72fe40d510 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -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, diff --git a/source/blender/editors/space_image/image_sequence.c b/source/blender/editors/space_image/image_sequence.c index 87bff913ff2..84b85b396fe 100644 --- a/source/blender/editors/space_image/image_sequence.c +++ b/source/blender/editors/space_image/image_sequence.c @@ -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; diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 7837409cece..5a98277531c 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -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), |