Asset System: Prepare File Browser backend for the Asset Browser

The Asset Browser will be a sub-editor of the File Browser. This prepares the
File Browser code for that.

**File-Lists**
* Support loading assets with metadata read from external files into the
  file-list.
* New main based file-list type, for the "Current File" asset library.
* Refresh file-list when switching between browse modes or asset libraries.
* Support empty file-lists (asset library with no assets).
* Store file previews as icons, so scripts can reference them via icon-id. See
  previous commit.

**Space Data**
* Introduce "browse mode" to differeniate between file and asset browsing.
* Add `FileAssetSelectParams` to `SpaceFile`, with `FileSelectParams` as base.
  Makes sure data is separated between asset and file browsing when switching
  between them. The active params can be obtained through
  `ED_fileselect_get_active_params()`.
* `FileAssetSelectParams` stores the currently visible asset library ID.
* Introduce file history abstraction so file and asset browsing can keep a
  separate history (previous and next directories).

**General**
* Option to only show asset data-blocks while file browsing (not exposed here).
* Add "active_file" context member, so scripts can get and display info about
  the active file.
* Add "active_id" context member, so `ED_OT_lib_id_load_custom_preview` can set
  a custom ID preview. (Only for "Current File" asset library)
* Expose some of `FileDirEntry` in RNA as (non-editable). That way scripts can
  obtain name, preview icon and asset-data.

Part of the first Asset Browser milestone. Check the #asset_browser_milestone_1
project milestone on developer.blender.org.

Differential Revision: https://developer.blender.org/D9724

Reviewed by: Bastien Montagne
This commit is contained in:
Julian Eisel 2020-12-14 13:50:36 +01:00
parent e413c80371
commit 70474e1a7c
14 changed files with 1170 additions and 253 deletions

@ -1266,6 +1266,9 @@ static void write_area_regions(BlendWriter *writer, ScrArea *area)
if (sfile->params) {
BLO_write_struct(writer, FileSelectParams, sfile->params);
}
if (sfile->asset_params) {
BLO_write_struct(writer, FileAssetSelectParams, sfile->asset_params);
}
}
else if (sl->spacetype == SPACE_SEQ) {
BLO_write_struct(writer, SpaceSeq, sl);
@ -1663,11 +1666,14 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area)
* plus, it isn't saved to files yet!
*/
sfile->folders_prev = sfile->folders_next = NULL;
BLI_listbase_clear(&sfile->folder_histories);
sfile->files = NULL;
sfile->layout = NULL;
sfile->op = NULL;
sfile->previews_timer = NULL;
sfile->tags = 0;
BLO_read_data_address(reader, &sfile->params);
BLO_read_data_address(reader, &sfile->asset_params);
}
else if (sl->spacetype == SPACE_CLIP) {
SpaceClip *sclip = (SpaceClip *)sl;
@ -1751,8 +1757,11 @@ void BKE_screen_area_blend_read_lib(BlendLibReader *reader, ID *parent_id, ScrAr
}
break;
}
case SPACE_FILE:
case SPACE_FILE: {
SpaceFile *sfile = (SpaceFile *)sl;
sfile->tags |= FILE_TAG_REBUILD_MAIN_FILES;
break;
}
case SPACE_ACTION: {
SpaceAction *saction = (SpaceAction *)sl;
bDopeSheet *ads = &saction->ads;

@ -2751,6 +2751,7 @@ static void lib_link_workspace_layout_restore(struct IDNameLib_Map *id_map,
SpaceFile *sfile = (SpaceFile *)sl;
sfile->op = NULL;
sfile->previews_timer = NULL;
sfile->tags = FILE_TAG_REBUILD_MAIN_FILES;
}
else if (sl->spacetype == SPACE_ACTION) {
SpaceAction *saction = (SpaceAction *)sl;

@ -29,6 +29,7 @@ extern "C" {
struct ARegion;
struct FileSelectParams;
struct FileAssetSelectParams;
struct Scene;
struct ScrArea;
struct SpaceFile;
@ -103,14 +104,14 @@ struct rcti;
struct FileSelectParams *ED_fileselect_ensure_active_params(struct SpaceFile *sfile);
struct FileSelectParams *ED_fileselect_get_active_params(const struct SpaceFile *sfile);
struct FileSelectParams *ED_fileselect_get_file_params(const struct SpaceFile *sfile);
struct FileAssetSelectParams *ED_fileselect_get_asset_params(const struct SpaceFile *sfile);
void ED_fileselect_set_params_from_userdef(struct SpaceFile *sfile);
void ED_fileselect_params_to_userdef(struct SpaceFile *sfile,
const int temp_win_size[],
const bool is_maximized);
void ED_fileselect_reset_params(struct SpaceFile *sfile);
void ED_fileselect_init_layout(struct SpaceFile *sfile, struct ARegion *region);
FileLayout *ED_fileselect_get_layout(struct SpaceFile *sfile, struct ARegion *region);
@ -142,6 +143,8 @@ void ED_fileselect_exit(struct wmWindowManager *wm,
struct Scene *owner_scene,
struct SpaceFile *sfile);
bool ED_fileselect_is_asset_browser(const struct SpaceFile *sfile);
void ED_fileselect_window_params_get(const struct wmWindow *win,
int win_size[2],
bool *is_maximized);

@ -90,6 +90,7 @@ void file_sfile_to_operator(struct Main *bmain, struct wmOperator *op, struct Sp
void file_operator_to_sfile(struct Main *bmain, struct SpaceFile *sfile, struct wmOperator *op);
/* filesel.c */
void fileselect_refresh_params(struct SpaceFile *sfile);
void fileselect_file_set(SpaceFile *sfile, const int index);
bool file_attribute_column_type_enabled(const FileSelectParams *params,
FileAttributeColumnType column);

@ -930,6 +930,7 @@ void FILE_OT_select_all(wmOperatorType *ot)
/* Note we could get rid of this one, but it's used by some addon so...
* Does not hurt keeping it around for now. */
/* TODO disallow bookmark editing in assets mode? */
static int bookmark_select_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
@ -1836,10 +1837,6 @@ static int file_previous_exec(bContext *C, wmOperator *UNUSED(op))
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params) {
if (!sfile->folders_next) {
sfile->folders_next = folderlist_new();
}
folderlist_pushdir(sfile->folders_next, params->dir);
folderlist_popdir(sfile->folders_prev, params->dir);
folderlist_pushdir(sfile->folders_next, params->dir);
@ -1874,10 +1871,6 @@ static int file_next_exec(bContext *C, wmOperator *UNUSED(unused))
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params) {
if (!sfile->folders_next) {
sfile->folders_next = folderlist_new();
}
folderlist_pushdir(sfile->folders_prev, params->dir);
folderlist_popdir(sfile->folders_next, params->dir);

File diff suppressed because it is too large Load Diff

@ -30,6 +30,7 @@ extern "C" {
struct BlendHandle;
struct FileList;
struct FileSelection;
struct FileSelectAssetLibraryUID;
struct wmWindowManager;
struct FileDirEntry;
@ -46,14 +47,16 @@ typedef enum FileCheckType {
CHECK_ALL = 3,
} FileCheckType;
struct ListBase *folderlist_new(void);
void folderlist_free(struct ListBase *folderlist);
struct ListBase *folderlist_duplicate(ListBase *folderlist);
void folderlist_popdir(struct ListBase *folderlist, char *dir);
void folderlist_pushdir(struct ListBase *folderlist, const char *dir);
const char *folderlist_peeklastdir(struct ListBase *folderlist);
int folderlist_clear_next(struct SpaceFile *sfile);
void folder_history_list_ensure_for_active_browse_mode(struct SpaceFile *sfile);
void folder_history_list_free(struct SpaceFile *sfile);
struct ListBase folder_history_list_duplicate(struct ListBase *listbase);
void filelist_setsorting(struct FileList *filelist, const short sort, bool invert_sort);
void filelist_sort(struct FileList *filelist);
@ -63,17 +66,22 @@ void filelist_setfilter_options(struct FileList *filelist,
const bool hide_parent,
const uint64_t filter,
const uint64_t filter_id,
const bool filter_assets_only,
const char *filter_glob,
const char *filter_search);
void filelist_filter(struct FileList *filelist);
void filelist_setlibrary(struct FileList *filelist,
const struct FileSelectAssetLibraryUID *asset_library);
void filelist_init_icons(void);
void filelist_free_icons(void);
struct ImBuf *filelist_getimage(struct FileList *filelist, const int index);
struct ImBuf *filelist_file_getimage(const FileDirEntry *file);
struct ImBuf *filelist_geticon_image(struct FileList *filelist, const int index);
int filelist_geticon(struct FileList *filelist, const int index, const bool is_main);
struct FileList *filelist_new(short type);
void filelist_settype(struct FileList *filelist, short type);
void filelist_clear(struct FileList *filelist);
void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection);
void filelist_free(struct FileList *filelist);
@ -83,15 +91,18 @@ bool filelist_is_dir(struct FileList *filelist, const char *path);
void filelist_setdir(struct FileList *filelist, char *r_dir);
int filelist_files_ensure(struct FileList *filelist);
int filelist_empty(struct FileList *filelist);
int filelist_needs_reading(struct FileList *filelist);
FileDirEntry *filelist_file(struct FileList *filelist, int index);
int filelist_file_findpath(struct FileList *filelist, const char *file);
struct ID *filelist_file_get_id(const struct FileDirEntry *file);
FileDirEntry *filelist_entry_find_uuid(struct FileList *filelist, const int uuid[4]);
void filelist_file_cache_slidingwindow_set(struct FileList *filelist, size_t window_size);
bool filelist_file_cache_block(struct FileList *filelist, const int index);
bool filelist_force_reset(struct FileList *filelist);
bool filelist_needs_force_reset(struct FileList *filelist);
void filelist_tag_force_reset(struct FileList *filelist);
bool filelist_pending(struct FileList *filelist);
bool filelist_needs_reset_on_main_changes(const struct FileList *filelist);
bool filelist_is_ready(struct FileList *filelist);
unsigned int filelist_entry_select_set(const struct FileList *filelist,

@ -56,7 +56,9 @@
#include "BKE_appdir.h"
#include "BKE_context.h"
#include "BKE_idtype.h"
#include "BKE_main.h"
#include "BKE_preferences.h"
#include "BLF_api.h"
@ -77,14 +79,66 @@
#define VERTLIST_MAJORCOLUMN_WIDTH (25 * UI_UNIT_X)
FileSelectParams *ED_fileselect_get_active_params(const SpaceFile *sfile)
static void fileselect_initialize_params_common(SpaceFile *sfile, FileSelectParams *params)
{
if (!sfile) {
/* Sometimes called in poll before space type was checked. */
return NULL;
const char *blendfile_path = BKE_main_blendfile_path_from_global();
/* operator has no setting for this */
params->active_file = -1;
if (!params->dir[0]) {
if (blendfile_path[0] != '\0') {
BLI_split_dir_part(blendfile_path, params->dir, sizeof(params->dir));
}
else {
const char *doc_path = BKE_appdir_folder_default();
if (doc_path) {
BLI_strncpy(params->dir, doc_path, sizeof(params->dir));
}
}
}
return sfile->params;
folder_history_list_ensure_for_active_browse_mode(sfile);
folderlist_pushdir(sfile->folders_prev, params->dir);
/* Switching thumbnails needs to recalc layout T28809. */
if (sfile->layout) {
sfile->layout->dirty = true;
}
}
static void fileselect_ensure_updated_asset_params(SpaceFile *sfile)
{
BLI_assert(sfile->browse_mode == FILE_BROWSE_MODE_ASSETS);
BLI_assert(sfile->op == NULL);
FileAssetSelectParams *asset_params = sfile->asset_params;
if (!asset_params) {
asset_params = sfile->asset_params = MEM_callocN(sizeof(*asset_params),
"FileAssetSelectParams");
asset_params->base_params.details_flags = U_default.file_space_data.details_flags;
asset_params->asset_library.type = FILE_ASSET_LIBRARY_LOCAL;
}
FileSelectParams *base_params = &asset_params->base_params;
base_params->file[0] = '\0';
base_params->filter_glob[0] = '\0';
/* TODO this way of using filters to form categories is notably slower than specifying a
* "group" to read. That's because all types are read and filtering is applied afterwards. Would
* be nice if we could lazy-read individual groups. */
base_params->flag |= U_default.file_space_data.flag | FILE_ASSETS_ONLY | FILE_FILTER;
base_params->flag &= ~FILE_DIRSEL_ONLY;
base_params->filter |= FILE_TYPE_BLENDERLIB;
base_params->filter_id = FILTER_ID_OB | FILTER_ID_GR;
base_params->display = FILE_IMGDISPLAY;
base_params->sort = FILE_SORT_ALPHA;
base_params->recursion_level = 1;
/* 'SMALL' size by default. More reasonable since this is typically used as regular editor,
* space is more of an issue here. */
base_params->thumbnail_size = 96;
fileselect_initialize_params_common(sfile, base_params);
}
/**
@ -92,6 +146,8 @@ FileSelectParams *ED_fileselect_get_active_params(const SpaceFile *sfile)
* the previously used settings to be used here rather than overriding them */
static FileSelectParams *fileselect_ensure_updated_file_params(SpaceFile *sfile)
{
BLI_assert(sfile->browse_mode == FILE_BROWSE_MODE_FILES);
FileSelectParams *params;
wmOperator *op = sfile->op;
@ -297,42 +353,102 @@ static FileSelectParams *fileselect_ensure_updated_file_params(SpaceFile *sfile)
params->filter_glob[0] = '\0';
}
/* operator has no setting for this */
params->active_file = -1;
/* initialize the list with previous folders */
if (!sfile->folders_prev) {
sfile->folders_prev = folderlist_new();
}
if (!params->dir[0]) {
if (blendfile_path[0] != '\0') {
BLI_split_dir_part(blendfile_path, params->dir, sizeof(params->dir));
}
else {
const char *doc_path = BKE_appdir_folder_default();
if (doc_path) {
BLI_strncpy(params->dir, doc_path, sizeof(params->dir));
}
}
}
folderlist_pushdir(sfile->folders_prev, params->dir);
/* Switching thumbnails needs to recalc layout T28809. */
if (sfile->layout) {
sfile->layout->dirty = true;
}
fileselect_initialize_params_common(sfile, params);
return params;
}
/**
* If needed, create and return the file select parameters for the active browse mode.
*/
FileSelectParams *ED_fileselect_ensure_active_params(SpaceFile *sfile)
{
if (!sfile->params) {
fileselect_ensure_updated_file_params(sfile);
switch ((eFileBrowse_Mode)sfile->browse_mode) {
case FILE_BROWSE_MODE_FILES:
if (!sfile->params) {
fileselect_ensure_updated_file_params(sfile);
}
return sfile->params;
case FILE_BROWSE_MODE_ASSETS:
if (!sfile->asset_params) {
fileselect_ensure_updated_asset_params(sfile);
}
return &sfile->asset_params->base_params;
}
return sfile->params;
BLI_assert(!"Invalid browse mode set in file space.");
return NULL;
}
/**
* Get the file select parameters for the active browse mode.
*/
FileSelectParams *ED_fileselect_get_active_params(const SpaceFile *sfile)
{
if (!sfile) {
/* Sometimes called in poll before space type was checked. */
return NULL;
}
switch ((eFileBrowse_Mode)sfile->browse_mode) {
case FILE_BROWSE_MODE_FILES:
return sfile->params;
case FILE_BROWSE_MODE_ASSETS:
return (FileSelectParams *)sfile->asset_params;
}
BLI_assert(!"Invalid browse mode set in file space.");
return NULL;
}
FileSelectParams *ED_fileselect_get_file_params(const SpaceFile *sfile)
{
return (sfile->browse_mode == FILE_BROWSE_MODE_FILES) ? sfile->params : NULL;
}
FileAssetSelectParams *ED_fileselect_get_asset_params(const SpaceFile *sfile)
{
return (sfile->browse_mode == FILE_BROWSE_MODE_ASSETS) ? sfile->asset_params : NULL;
}
static void fileselect_refresh_asset_params(FileAssetSelectParams *asset_params)
{
FileSelectAssetLibraryUID *library = &asset_params->asset_library;
FileSelectParams *base_params = &asset_params->base_params;
bUserAssetLibrary *user_library = NULL;
/* Ensure valid repo, or fall-back to local one. */
if (library->type == FILE_ASSET_LIBRARY_CUSTOM) {
user_library = BKE_preferences_asset_library_find_from_name(
&U, library->custom_library_identifier);
if (!user_library) {
library->type = FILE_ASSET_LIBRARY_LOCAL;
}
}
switch (library->type) {
case FILE_ASSET_LIBRARY_LOCAL:
base_params->dir[0] = '\0';
break;
case FILE_ASSET_LIBRARY_CUSTOM:
BLI_assert(user_library);
BLI_strncpy(base_params->dir, user_library->path, sizeof(base_params->dir));
break;
}
base_params->type = (library->type == FILE_ASSET_LIBRARY_LOCAL) ? FILE_MAIN_ASSET : FILE_LOADLIB;
}
void fileselect_refresh_params(SpaceFile *sfile)
{
FileAssetSelectParams *asset_params = ED_fileselect_get_asset_params(sfile);
if (asset_params) {
fileselect_refresh_asset_params(asset_params);
}
}
bool ED_fileselect_is_asset_browser(const SpaceFile *sfile)
{
return (sfile->browse_mode == FILE_BROWSE_MODE_ASSETS);
}
/* The subset of FileSelectParams.flag items we store into preferences. Note that FILE_SORT_ALPHA
@ -371,6 +487,8 @@ void ED_fileselect_set_params_from_userdef(SpaceFile *sfile)
wmOperator *op = sfile->op;
UserDef_FileSpaceData *sfile_udata = &U.file_space_data;
BLI_assert(sfile->browse_mode == FILE_BROWSE_MODE_FILES);
FileSelectParams *params = fileselect_ensure_updated_file_params(sfile);
if (!op) {
return;
@ -438,15 +556,6 @@ void ED_fileselect_params_to_userdef(SpaceFile *sfile,
}
}
void ED_fileselect_reset_params(SpaceFile *sfile)
{
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
params->type = FILE_UNIX;
params->flag = 0;
params->title[0] = '\0';
params->active_file = -1;
}
/**
* Sets FileSelectParams->file (name of selected file)
*/
@ -1046,8 +1155,7 @@ void ED_fileselect_exit(wmWindowManager *wm, Scene *owner_scene, SpaceFile *sfil
sfile->op = NULL;
}
folderlist_free(sfile->folders_prev);
folderlist_free(sfile->folders_next);
folder_history_list_free(sfile);
if (sfile->files) {
ED_fileselect_clear(wm, owner_scene, sfile);

@ -35,6 +35,8 @@
#include "BKE_screen.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"
#include "WM_api.h"
#include "WM_message.h"
@ -149,22 +151,10 @@ static void file_free(SpaceLink *sl)
sfile->files = NULL;
}
if (sfile->folders_prev) {
folderlist_free(sfile->folders_prev);
MEM_freeN(sfile->folders_prev);
sfile->folders_prev = NULL;
}
folder_history_list_free(sfile);
if (sfile->folders_next) {
folderlist_free(sfile->folders_next);
MEM_freeN(sfile->folders_next);
sfile->folders_next = NULL;
}
if (sfile->params) {
MEM_freeN(sfile->params);
sfile->params = NULL;
}
MEM_SAFE_FREE(sfile->params);
MEM_SAFE_FREE(sfile->asset_params);
if (sfile->layout) {
MEM_freeN(sfile->layout);
@ -205,19 +195,20 @@ static SpaceLink *file_duplicate(SpaceLink *sl)
sfilen->previews_timer = NULL;
sfilen->smoothscroll_timer = NULL;
FileSelectParams *active_params_old = ED_fileselect_get_active_params(sfileo);
if (active_params_old) {
sfilen->files = filelist_new(active_params_old->type);
filelist_setdir(sfilen->files, active_params_old->dir);
}
if (sfileo->params) {
sfilen->files = filelist_new(sfileo->params->type);
sfilen->params = MEM_dupallocN(sfileo->params);
filelist_setdir(sfilen->files, sfilen->params->dir);
}
if (sfileo->asset_params) {
sfilen->asset_params = MEM_dupallocN(sfileo->asset_params);
}
if (sfileo->folders_prev) {
sfilen->folders_prev = folderlist_duplicate(sfileo->folders_prev);
}
if (sfileo->folders_next) {
sfilen->folders_next = folderlist_duplicate(sfileo->folders_next);
}
sfilen->folder_histories = folder_history_list_duplicate(&sfileo->folder_histories);
if (sfileo->layout) {
sfilen->layout = MEM_dupallocN(sfileo->layout);
@ -265,24 +256,42 @@ static void file_ensure_valid_region_state(bContext *C,
}
}
/**
* Tag the space to recreate the file-list.
*/
static void file_tag_reset_list(ScrArea *area, SpaceFile *sfile)
{
filelist_tag_force_reset(sfile->files);
ED_area_tag_refresh(area);
}
static void file_refresh(const bContext *C, ScrArea *area)
{
wmWindowManager *wm = CTX_wm_manager(C);
wmWindow *win = CTX_wm_window(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_ensure_active_params(sfile);
FileAssetSelectParams *asset_params = ED_fileselect_get_asset_params(sfile);
struct FSMenu *fsmenu = ED_fsmenu_get();
if (!sfile->folders_prev) {
sfile->folders_prev = folderlist_new();
fileselect_refresh_params(sfile);
folder_history_list_ensure_for_active_browse_mode(sfile);
if (sfile->files && (sfile->tags & FILE_TAG_REBUILD_MAIN_FILES) &&
filelist_needs_reset_on_main_changes(sfile->files)) {
filelist_tag_force_reset(sfile->files);
}
sfile->tags &= ~FILE_TAG_REBUILD_MAIN_FILES;
if (!sfile->files) {
sfile->files = filelist_new(params->type);
params->highlight_file = -1; /* added this so it opens nicer (ton) */
}
filelist_settype(sfile->files, params->type);
filelist_setdir(sfile->files, params->dir);
filelist_setrecursion(sfile->files, params->recursion_level);
filelist_setsorting(sfile->files, params->sort, params->flag & FILE_SORT_INVERT);
filelist_setlibrary(sfile->files, asset_params ? &asset_params->asset_library : NULL);
filelist_setfilter_options(
sfile->files,
(params->flag & FILE_FILTER) != 0,
@ -290,6 +299,7 @@ static void file_refresh(const bContext *C, ScrArea *area)
true, /* Just always hide parent, prefer to not add an extra user option for this. */
params->filter,
params->filter_id,
(params->flag & FILE_ASSETS_ONLY) != 0,
params->filter_glob,
params->filter_search);
@ -300,12 +310,12 @@ static void file_refresh(const bContext *C, ScrArea *area)
sfile->bookmarknr = fsmenu_get_active_indices(fsmenu, FS_CATEGORY_BOOKMARKS, params->dir);
sfile->recentnr = fsmenu_get_active_indices(fsmenu, FS_CATEGORY_RECENT, params->dir);
if (filelist_force_reset(sfile->files)) {
if (filelist_needs_force_reset(sfile->files)) {
filelist_readjob_stop(wm, CTX_data_scene(C));
filelist_clear(sfile->files);
}
if (filelist_empty(sfile->files)) {
if (filelist_needs_reading(sfile->files)) {
if (!filelist_pending(sfile->files)) {
filelist_readjob_start(sfile->files, C);
}
@ -365,6 +375,14 @@ static void file_listener(wmWindow *UNUSED(win),
break;
}
break;
case NC_ASSET: {
if (sfile->files && filelist_needs_reset_on_main_changes(sfile->files)) {
/* Full refresh of the file list if local asset data was changed. Refreshing this view is
* cheap and users expect this to be updated immediately. */
file_tag_reset_list(area, sfile);
}
break;
}
}
}
@ -442,6 +460,22 @@ static void file_main_region_message_subscribe(const struct bContext *UNUSED(C),
}
}
static bool file_main_region_needs_refresh_before_draw(SpaceFile *sfile)
{
/* Needed, because filelist is not initialized on loading */
if (!sfile->files || filelist_needs_reading(sfile->files)) {
return true;
}
/* File reading tagged the space because main data changed that may require a filelist reset. */
if (filelist_needs_reset_on_main_changes(sfile->files) &&
(sfile->tags & FILE_TAG_REBUILD_MAIN_FILES)) {
return true;
}
return false;
}
static void file_main_region_draw(const bContext *C, ARegion *region)
{
/* draw entirely, view changes should be handled here */
@ -450,8 +484,7 @@ static void file_main_region_draw(const bContext *C, ARegion *region)
View2D *v2d = &region->v2d;
/* Needed, because filelist is not initialized on loading */
if (!sfile->files || filelist_empty(sfile->files)) {
if (file_main_region_needs_refresh_before_draw(sfile)) {
file_refresh(C, NULL);
}
@ -681,6 +714,52 @@ static void file_dropboxes(void)
WM_dropbox_add(lb, "FILE_OT_filepath_drop", filepath_drop_poll, filepath_drop_copy);
}
const char *file_context_dir[] = {"active_file", "active_id", NULL};
static int /*eContextResult*/ file_context(const bContext *C,
const char *member,
bContextDataResult *result)
{
bScreen *screen = CTX_wm_screen(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
BLI_assert(!ED_area_is_global(CTX_wm_area(C)));
if (CTX_data_dir(member)) {
CTX_data_dir_set(result, file_context_dir);
return CTX_RESULT_OK;
}
else if (CTX_data_equals(member, "active_file")) {
FileDirEntry *file = filelist_file(sfile->files, params->active_file);
CTX_data_pointer_set(result, &screen->id, &RNA_FileSelectEntry, file);
return CTX_RESULT_OK;
}
else if (CTX_data_equals(member, "active_id")) {
const FileDirEntry *file = filelist_file(sfile->files, params->active_file);
ID *id = filelist_file_get_id(file);
if (id) {
CTX_data_id_pointer_set(result, id);
}
return CTX_RESULT_OK;
}
return CTX_RESULT_MEMBER_NOT_FOUND;
}
static void file_id_remap(ScrArea *area, SpaceLink *sl, ID *UNUSED(old_id), ID *UNUSED(new_id))
{
SpaceFile *sfile = (SpaceFile *)sl;
/* If the file shows main data (IDs), tag it for reset. */
if (sfile->files && filelist_needs_reset_on_main_changes(sfile->files)) {
/* Full refresh of the file list if main data was changed, don't even attempt remap pointers.
* We could give file list types a id-remap callback, but it's probably not worth it.
* Refreshing local file lists is relatively cheap. */
file_tag_reset_list(area, sfile);
}
}
/* only called once, from space/spacetypes.c */
void ED_spacetype_file(void)
{
@ -700,6 +779,8 @@ void ED_spacetype_file(void)
st->operatortypes = file_operatortypes;
st->keymap = file_keymap;
st->dropboxes = file_dropboxes;
st->context = file_context;
st->id_remap = file_id_remap;
/* regions: main window */
art = MEM_callocN(sizeof(ARegionType), "spacetype file region");

@ -664,6 +664,23 @@ typedef enum eSpaceSeq_OverlayType {
/** \name File Selector
* \{ */
/**
* Information to identify a asset library. May be either one of the predefined types (current
* 'Main', builtin library, project library), or a custom type as defined in the Preferences.
*
* If the type is set to #FILE_ASSET_LIBRARY_CUSTOM, idname must have the name to identify the
* custom library. Otherwise idname is not used.
*/
typedef struct FileSelectAssetLibraryUID {
short type;
char _pad[6];
/**
* If showing a custom asset library (#FILE_ASSET_LIBRARY_CUSTOM), this name has to be set to
* define which. Can be empty otherwise.
*/
char custom_library_identifier[64]; /* MAX_NAME */
} FileSelectAssetLibraryUID;
/* Config and Input for File Selector */
typedef struct FileSelectParams {
/** Title, also used for the text of the execute button. */
@ -708,6 +725,7 @@ typedef struct FileSelectParams {
/** Details toggles (file size, creation date, etc.) */
char details_flags;
char _pad2[3];
/** Filter when (flags & FILE_FILTER) is true. */
int filter;
@ -723,6 +741,32 @@ typedef struct FileSelectParams {
/* XXX --- end unused -- */
} FileSelectParams;
/**
* File selection parameters for asset browsing mode, with #FileSelectParams as base.
*/
typedef struct FileAssetSelectParams {
FileSelectParams base_params;
FileSelectAssetLibraryUID asset_library;
} FileAssetSelectParams;
/**
* A wrapper to store previous and next folder lists (#FolderList) for a specific browse mode
* (#eFileBrowse_Mode).
*/
typedef struct FileFolderHistory {
struct FileFolderLists *next, *prev;
/** The browse mode this prev/next folder-lists are created for. */
char browse_mode; /* eFileBrowse_Mode */
char _pad[7];
/** Holds the list of previous directories to show. */
ListBase folders_prev;
/** Holds the list of next directories (pushed from previous) to show. */
ListBase folders_next;
} FileFolderHistory;
/* File Browser */
typedef struct SpaceFile {
SpaceLink *next, *prev;
@ -733,20 +777,42 @@ typedef struct SpaceFile {
char _pad0[6];
/* End 'SpaceLink' header. */
char _pad1[4];
/** Is this a File Browser or an Asset Browser? */
char browse_mode; /* eFileBrowse_Mode */
char _pad1[1];
short tags;
int scroll_offset;
/** Config and input for file select. */
struct FileSelectParams *params;
/** Config and input for file select. One for each browse-mode, to keep them independent. */
FileSelectParams *params;
FileAssetSelectParams *asset_params;
/** Holds the list of files to show. */
void *_pad2;
/**
* Holds the list of files to show.
* Currently recreated when browse-mode changes. Could be per browse-mode to avoid refreshes.
*/
struct FileList *files;
/** Holds the list of previous directories to show. */
/**
* Holds the list of previous directories to show. Owned by `folder_histories` below.
*/
ListBase *folders_prev;
/** Holds the list of next directories (pushed from previous) to show. */
/**
* Holds the list of next directories (pushed from previous) to show. Owned by
* `folder_histories` below.
*/
ListBase *folders_next;
/**
* This actually owns the prev/next folder-lists above. On browse-mode change, the lists of the
* new mode get assigned to the above.
*/
ListBase folder_histories; /* FileFolderHistory */
/* operator that is invoking fileselect
* op->exec() will be called on the 'Load' button.
* if operator provides op->cancel(), then this will be invoked
@ -763,6 +829,30 @@ typedef struct SpaceFile {
short systemnr, system_bookmarknr;
} SpaceFile;
/* SpaceFile.browse_mode (File Space Browsing Mode) */
typedef enum eFileBrowse_Mode {
/* Regular Blender File Browser */
FILE_BROWSE_MODE_FILES = 0,
/* Asset Browser */
FILE_BROWSE_MODE_ASSETS = 1,
} eFileBrowse_Mode;
typedef enum eFileAssetLibrary_Type {
/* For the future. Display assets bundled with Blender by default. */
// FILE_ASSET_LIBRARY_BUNDLED = 0,
/** Display assets from the current session (current "Main"). */
FILE_ASSET_LIBRARY_LOCAL = 1,
/* For the future. Display assets for the current project. */
// FILE_ASSET_LIBRARY_PROJECT = 2,
/** Display assets from custom asset libraries, as defined in the preferences
* (#bUserAssetLibrary). The name will be taken from #FileSelectParams.asset_library.idname
* then.
* In RNA, we add the index of the custom library to this to identify it by index. So keep
* this last! */
FILE_ASSET_LIBRARY_CUSTOM = 100,
} eFileAssetLibrary_Type;
/* FileSelectParams.display */
enum eFileDisplayType {
/** Internal (not exposed to users): Keep whatever display type was used during the last File
@ -792,6 +882,13 @@ enum eFileSortType {
FILE_SORT_SIZE = 4,
};
/* SpaceFile.tags */
enum eFileTags {
/** Tag the space as having to update files representing or containing main data. Must be set
* after file read and undo/redo. */
FILE_TAG_REBUILD_MAIN_FILES = (1 << 0),
};
/* FileSelectParams.details_flags */
enum eFileDetails {
FILE_DETAILS_SIZE = (1 << 0),
@ -810,6 +907,7 @@ enum eFileDetails {
typedef enum eFileSelectType {
FILE_LOADLIB = 1,
FILE_MAIN = 2,
FILE_MAIN_ASSET = 3,
FILE_UNIX = 8,
FILE_BLENDER = 8, /* don't display relative paths */
@ -842,6 +940,7 @@ typedef enum eFileSel_Params_Flag {
FILE_SORT_INVERT = (1 << 11),
FILE_HIDE_TOOL_PROPS = (1 << 12),
FILE_CHECK_EXISTING = (1 << 13),
FILE_ASSETS_ONLY = (1 << 14),
} eFileSel_Params_Flag;
/* sfile->params->rename_flag */
@ -885,6 +984,7 @@ typedef enum eFileSel_File_Types {
FILE_TYPE_USD = (1 << 18),
FILE_TYPE_VOLUME = (1 << 19),
FILE_TYPE_ASSET = (1 << 28),
/** An FS directory (i.e. S_ISDIR on its path is true). */
FILE_TYPE_DIR = (1 << 30),
FILE_TYPE_BLENDERLIB = (1u << 31),
@ -985,9 +1085,16 @@ typedef struct FileDirEntry {
/** Optional argument for shortcuts, aliases etc. */
char *redirection_path;
/** TODO: make this a real ID pointer? */
void *poin;
struct ImBuf *image;
/** When showing local IDs (FILE_MAIN, FILE_MAIN_ASSET), ID this file represents. Note comment
* for FileListInternEntry.local_data, the same applies here! */
ID *id;
/** If this file represents an asset, its asset data is here. Note that we may show assets of
* external files in which case this is set but not the id above.
* Note comment for FileListInternEntry.local_data, the same applies here! */
struct AssetMetaData *asset_data;
/* The icon_id for the preview image. */
int preview_icon_id;
/* Tags are for info only, most of filtering is done in asset engine. */
char **tags;

@ -253,7 +253,9 @@ extern StructRNA RNA_FModifierPython;
extern StructRNA RNA_FModifierStepped;
extern StructRNA RNA_FaceMap;
extern StructRNA RNA_FieldSettings;
extern StructRNA RNA_FileAssetSelectParams;
extern StructRNA RNA_FileBrowserFSMenuEntry;
extern StructRNA RNA_FileSelectEntry;
extern StructRNA RNA_FileSelectParams;
extern StructRNA RNA_FloatAttribute;
extern StructRNA RNA_FloatAttributeValue;

@ -56,6 +56,7 @@ extern const EnumPropertyItem rna_enum_mesh_select_mode_items[];
extern const EnumPropertyItem rna_enum_mesh_select_mode_uv_items[];
extern const EnumPropertyItem rna_enum_mesh_delimit_mode_items[];
extern const EnumPropertyItem rna_enum_space_graph_mode_items[];
extern const EnumPropertyItem rna_enum_space_file_browse_mode_items[];
extern const EnumPropertyItem rna_enum_space_sequencer_view_type_items[];
extern const EnumPropertyItem rna_enum_space_type_items[];
extern const EnumPropertyItem rna_enum_space_image_mode_items[];

@ -170,6 +170,12 @@ const EnumPropertyItem rna_enum_space_sequencer_view_type_items[] = {
{0, NULL, 0, NULL, NULL},
};
const EnumPropertyItem rna_enum_space_file_browse_mode_items[] = {
{FILE_BROWSE_MODE_FILES, "FILES", ICON_FILEBROWSER, "File Browser", ""},
{FILE_BROWSE_MODE_ASSETS, "ASSETS", ICON_ASSET_MANAGER, "Asset Browser", ""},
{0, NULL, 0, NULL, NULL},
};
#define SACT_ITEM_DOPESHEET \
{ \
SACTCONT_DOPESHEET, "DOPESHEET", ICON_ACTION, "Dope Sheet", "Edit all keyframes in scene" \
@ -504,6 +510,7 @@ static const EnumPropertyItem rna_enum_curve_display_handle_items[] = {
# include "BKE_layer.h"
# include "BKE_nla.h"
# include "BKE_paint.h"
# include "BKE_preferences.h"
# include "BKE_scene.h"
# include "BKE_screen.h"
# include "BKE_workspace.h"
@ -2462,13 +2469,159 @@ static PointerRNA rna_FileSelectParams_filter_id_get(PointerRNA *ptr)
return rna_pointer_inherit_refine(ptr, &RNA_FileSelectIDFilter, ptr->data);
}
static PointerRNA rna_FileBrowser_params_get(PointerRNA *ptr)
static int rna_FileAssetSelectParams_asset_library_get(PointerRNA *ptr)
{
FileAssetSelectParams *params = ptr->data;
/* Just an extra sanity check to ensure this isn't somehow called for RNA_FileSelectParams. */
BLI_assert(ptr->type == &RNA_FileAssetSelectParams);
/* Simple case: Predefined repo, just set the value. */
if (params->asset_library.type < FILE_ASSET_LIBRARY_CUSTOM) {
return params->asset_library.type;
}
/* Note that the path isn't checked for validity here. If an invalid library path is used, the
* Asset Browser can give a nice hint on what's wrong. */
const bUserAssetLibrary *user_library = BKE_preferences_asset_library_find_from_name(
&U, params->asset_library.custom_library_identifier);
const int index = BKE_preferences_asset_library_get_index(&U, user_library);
if (index > -1) {
return FILE_ASSET_LIBRARY_CUSTOM + index;
}
BLI_assert(0);
return FILE_ASSET_LIBRARY_LOCAL;
}
static void rna_FileAssetSelectParams_asset_library_set(PointerRNA *ptr, int value)
{
FileAssetSelectParams *params = ptr->data;
/* Simple case: Predefined repo, just set the value. */
if (value < FILE_ASSET_LIBRARY_CUSTOM) {
params->asset_library.type = value;
params->asset_library.custom_library_identifier[0] = '\0';
BLI_assert(ELEM(value, FILE_ASSET_LIBRARY_LOCAL));
return;
}
const bUserAssetLibrary *user_library = BKE_preferences_asset_library_find_from_index(
&U, value - FILE_ASSET_LIBRARY_CUSTOM);
/* Note that the path isn't checked for validity here. If an invalid library path is used, the
* Asset Browser can give a nice hint on what's wrong. */
const bool is_valid = (user_library->name[0] && user_library->path[0]);
if (user_library && is_valid) {
BLI_strncpy(params->asset_library.custom_library_identifier,
user_library->name,
sizeof(params->asset_library.custom_library_identifier));
params->asset_library.type = FILE_ASSET_LIBRARY_CUSTOM;
}
}
static const EnumPropertyItem *rna_FileAssetSelectParams_asset_library_itemf(
bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free)
{
const EnumPropertyItem predefined_items[] = {
/* For the future. */
// {FILE_ASSET_REPO_BUNDLED, "BUNDLED", 0, "Bundled", "Show the default user assets"},
{FILE_ASSET_LIBRARY_LOCAL,
"LOCAL",
ICON_BLENDER,
"Current File",
"Show the assets currently available in this Blender session"},
{0, NULL, 0, NULL, NULL},
};
EnumPropertyItem *item = NULL;
int totitem = 0;
/* Add separator if needed. */
if (!BLI_listbase_is_empty(&U.asset_libraries)) {
const EnumPropertyItem sepr = {0, "", 0, "Custom", NULL};
RNA_enum_item_add(&item, &totitem, &sepr);
}
int i = 0;
for (bUserAssetLibrary *user_library = U.asset_libraries.first; user_library;
user_library = user_library->next, i++) {
/* Note that the path itself isn't checked for validity here. If an invalid library path is
* used, the Asset Browser can give a nice hint on what's wrong. */
const bool is_valid = (user_library->name[0] && user_library->path[0]);
if (!is_valid) {
continue;
}
/* Use library path as description, it's a nice hint for users. */
EnumPropertyItem tmp = {FILE_ASSET_LIBRARY_CUSTOM + i,
user_library->name,
ICON_NONE,
user_library->name,
user_library->path};
RNA_enum_item_add(&item, &totitem, &tmp);
}
if (totitem) {
const EnumPropertyItem sepr = {0, "", 0, "Built-in", NULL};
RNA_enum_item_add(&item, &totitem, &sepr);
}
/* Add predefined items. */
RNA_enum_items_add(&item, &totitem, predefined_items);
RNA_enum_item_end(&item, &totitem);
*r_free = true;
return item;
}
static void rna_FileBrowser_FileSelectEntry_name_get(PointerRNA *ptr, char *value)
{
const FileDirEntry *entry = ptr->data;
strcpy(value, entry->name);
}
static int rna_FileBrowser_FileSelectEntry_name_length(PointerRNA *ptr)
{
const FileDirEntry *entry = ptr->data;
return (int)strlen(entry->name);
}
static int rna_FileBrowser_FileSelectEntry_preview_icon_id_get(PointerRNA *ptr)
{
const FileDirEntry *entry = ptr->data;
return entry->preview_icon_id;
}
static PointerRNA rna_FileBrowser_FileSelectEntry_asset_data_get(PointerRNA *ptr)
{
const FileDirEntry *entry = ptr->data;
return rna_pointer_inherit_refine(ptr, &RNA_AssetMetaData, entry->asset_data);
}
static StructRNA *rna_FileBrowser_params_typef(PointerRNA *ptr)
{
SpaceFile *sfile = ptr->data;
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params) {
return rna_pointer_inherit_refine(ptr, &RNA_FileSelectParams, params);
if (params == ED_fileselect_get_file_params(sfile)) {
return &RNA_FileSelectParams;
}
if (params == (void *)ED_fileselect_get_asset_params(sfile)) {
return &RNA_FileAssetSelectParams;
}
BLI_assert(!"Could not identify file select parameters");
return NULL;
}
static PointerRNA rna_FileBrowser_params_get(PointerRNA *ptr)
{
SpaceFile *sfile = ptr->data;
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
StructRNA *params_struct = rna_FileBrowser_params_typef(ptr);
if (params && params_struct) {
return rna_pointer_inherit_refine(ptr, params_struct, params);
}
return rna_pointer_inherit_refine(ptr, NULL, NULL);
@ -2784,6 +2937,14 @@ static void rna_FileBrowser_FSMenuRecent_active_range(
rna_FileBrowser_FSMenu_active_range(ptr, min, max, softmin, softmax, FS_CATEGORY_RECENT);
}
static void rna_SpaceFileBrowser_browse_mode_update(Main *UNUSED(bmain),
Scene *UNUSED(scene),
PointerRNA *ptr)
{
ScrArea *area = rna_area_from_space(ptr);
ED_area_tag_refresh(area);
}
#else
static const EnumPropertyItem dt_uv_items[] = {
@ -5794,6 +5955,44 @@ static void rna_def_fileselect_idfilter(BlenderRNA *brna)
}
}
static void rna_def_fileselect_entry(BlenderRNA *brna)
{
PropertyRNA *prop;
StructRNA *srna = RNA_def_struct(brna, "FileSelectEntry", NULL);
RNA_def_struct_sdna(srna, "FileDirEntry");
RNA_def_struct_ui_text(srna, "File Select Entry", "A file viewable in the File Browser");
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
RNA_def_property_string_funcs(prop,
"rna_FileBrowser_FileSelectEntry_name_get",
"rna_FileBrowser_FileSelectEntry_name_length",
NULL);
RNA_def_property_ui_text(prop, "Name", "");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_struct_name_property(srna, prop);
prop = RNA_def_int(
srna,
"preview_icon_id",
0,
INT_MIN,
INT_MAX,
"Icon ID",
"Unique integer identifying the preview of this file as an icon (zero means invalid)",
INT_MIN,
INT_MAX);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_int_funcs(
prop, "rna_FileBrowser_FileSelectEntry_preview_icon_id_get", NULL, NULL);
prop = RNA_def_property(srna, "asset_data", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "AssetMetaData");
RNA_def_property_pointer_funcs(
prop, "rna_FileBrowser_FileSelectEntry_asset_data_get", NULL, NULL, NULL);
RNA_def_property_ui_text(
prop, "Asset Data", "Asset data, valid if the file represents an asset");
}
static void rna_def_fileselect_params(BlenderRNA *brna)
{
StructRNA *srna;
@ -5966,6 +6165,12 @@ static void rna_def_fileselect_params(BlenderRNA *brna)
RNA_def_property_ui_icon(prop, ICON_BLENDER, 0);
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
prop = RNA_def_property(srna, "use_filter_asset_only", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", FILE_ASSETS_ONLY);
RNA_def_property_ui_text(
prop, "Only Assets", "Hide .blend files items that are not data-blocks with asset metadata");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
prop = RNA_def_property(srna, "filter_id", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_NEVER_NULL);
RNA_def_property_struct_type(prop, "FileSelectIDFilter");
@ -5997,6 +6202,25 @@ static void rna_def_fileselect_params(BlenderRNA *brna)
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
}
static void rna_def_fileselect_asset_params(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "FileAssetSelectParams", "FileSelectParams");
RNA_def_struct_ui_text(
srna, "Asset Select Parameters", "Settings for the file selection in Asset Browser mode");
prop = RNA_def_property(srna, "asset_library", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, DummyRNA_NULL_items);
RNA_def_property_enum_funcs(prop,
"rna_FileAssetSelectParams_asset_library_get",
"rna_FileAssetSelectParams_asset_library_set",
"rna_FileAssetSelectParams_asset_library_itemf");
RNA_def_property_ui_text(prop, "Asset Library", "");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
}
static void rna_def_filemenu_entry(BlenderRNA *brna)
{
StructRNA *srna;
@ -6050,9 +6274,18 @@ static void rna_def_space_filebrowser(BlenderRNA *brna)
rna_def_space_generic_show_region_toggles(srna, (1 << RGN_TYPE_TOOLS) | (1 << RGN_TYPE_UI));
prop = RNA_def_property(srna, "browse_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_space_file_browse_mode_items);
RNA_def_property_ui_text(
prop,
"Browsing Mode",
"Type of the File Editor view (regular file browsing or asset browsing)");
RNA_def_property_update(prop, 0, "rna_SpaceFileBrowser_browse_mode_update");
prop = RNA_def_property(srna, "params", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "FileSelectParams");
RNA_def_property_pointer_funcs(prop, "rna_FileBrowser_params_get", NULL, NULL, NULL);
RNA_def_property_pointer_funcs(
prop, "rna_FileBrowser_params_get", NULL, "rna_FileBrowser_params_typef", NULL);
RNA_def_property_ui_text(
prop, "Filebrowser Parameter", "Parameters and Settings for the Filebrowser");
@ -6800,7 +7033,9 @@ void RNA_def_space(BlenderRNA *brna)
rna_def_space_image(brna);
rna_def_space_sequencer(brna);
rna_def_space_text(brna);
rna_def_fileselect_entry(brna);
rna_def_fileselect_params(brna);
rna_def_fileselect_asset_params(brna);
rna_def_fileselect_idfilter(brna);
rna_def_filemenu_entry(brna);
rna_def_space_filebrowser(brna);

@ -198,6 +198,8 @@ void WM_operator_properties_filesel(wmOperatorType *ot,
ot->srna, "filter_blenlib", (filter & FILE_TYPE_BLENDERLIB) != 0, "Filter Blender IDs", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
/* TODO asset only filter? */
prop = RNA_def_int(
ot->srna,
"filemode",