Assets: Special .asset.blend files, for storing externally editable assets

Introduces a new kind of blend file that store assets that can savely be edited
from the Blender UI, without having to open the blend-file storing the asset
itself (asset "pushing" workflow).

Only brush assets will use this for now.

Technical Documentation:
https://developer.blender.org/docs/features/asset_system/asset_editing/

User Documentation:
https://docs.blender.org/manual/en/latest/files/asset_libraries/introduction.html#asset-system-files-asset-blend-extention

The API to manage assets by generating these files is added in the following
commit.

Main authors: Bastien Montagne, Brecht Van Lommel, Julian Eisel

Pull Request for the latest design iteration:
https://projects.blender.org/blender/blender/pulls/124246

Part of the brush assets project, see:
- https://projects.blender.org/blender/blender/issues/116337
- https://projects.blender.org/blender/blender/pulls/106303
This commit is contained in:
Julian Eisel
2024-07-06 21:42:49 +02:00
parent 5f6586526c
commit c5180e0988
10 changed files with 245 additions and 86 deletions

View File

@@ -29,6 +29,11 @@ struct ReportList;
struct UserDef;
struct WorkspaceConfigFileData;
/**
* The suffix used for blendfiles managed by the asset system.
*/
#define BLENDER_ASSET_FILE_SUFFIX ".asset.blend"
/**
* Check whether given path ends with a blend file compatible extension
* (`.blend`, `.ble` or `.blend.gz`).

View File

@@ -294,6 +294,12 @@ enum {
/** BMesh option to save as older mesh format */
/* #define G_FILE_MESH_COMPAT (1 << 26) */
/* #define G_FILE_GLSL_NO_ENV_LIGHTING (1 << 28) */ /* deprecated */
/**
* This file contains a single asset and its dependencies. Users may edit the asset through the
* UI, at which point the file will be regenerated by the asset system (API in
* #BKE_asset_edit.hh). Stored with a .asset.blend prefix.
*/
G_FILE_ASSET_EDIT_FILE = (1 << 29),
};
/**

View File

@@ -143,6 +143,13 @@ struct Main {
* could try to use more refined detection on load. */
bool has_forward_compatibility_issues;
/**
* This file was written by the asset system with the #G_FILE_ASSET_EDIT_FILE flag (now cleared).
* It must not be overwritten, except by the asset system itself. Otherwise the file could end up
* with user created data that would be lost when the asset system regenerates the file.
*/
bool is_asset_edit_file;
/** Commit timestamp from `buildinfo`. */
uint64_t build_commit_timestamp;
/** Commit Hash from `buildinfo`. */
@@ -344,6 +351,17 @@ void BKE_main_merge(Main *bmain_dst, Main **r_bmain_src, MainMergeReport &report
*/
bool BKE_main_is_empty(Main *bmain);
/**
* Check whether the bmain has issues, e.g. for reporting in the status bar.
*/
bool BKE_main_has_issues(const Main *bmain);
/**
* Check whether user confirmation should be required when overwriting this `bmain` into its source
* blendfile.
*/
bool BKE_main_needs_overwrite_confirm(const Main *bmain);
void BKE_main_lock(Main *bmain);
void BKE_main_unlock(Main *bmain);

View File

@@ -874,6 +874,18 @@ static void setup_app_data(bContext *C,
BLI_assert(bfd->curscene != nullptr);
mode = LOAD_UNDO;
}
else if (bfd->fileflags & G_FILE_ASSET_EDIT_FILE) {
BKE_report(reports->reports,
RPT_WARNING,
"This file is managed by the asset system, you cannot overwrite it (using \"Save "
"As\" is possible)");
/* From now on the file in memory is a normal file, further saving it will contain a
* window-manager, scene, ... and potentially user created data. Use #Main.is_asset_edit_file
* to detect if saving this file needs extra protections. */
bfd->fileflags &= ~G_FILE_ASSET_EDIT_FILE;
BLI_assert(bfd->main->is_asset_edit_file);
mode = LOAD_UI_OFF;
}
/* May happen with library files, loading undo-data should never have a null `curscene`
* (but may have a null `curscreen`). */
else if (ELEM(nullptr, bfd->curscreen, bfd->curscene)) {

View File

@@ -466,6 +466,16 @@ bool BKE_main_is_empty(Main *bmain)
return result;
}
bool BKE_main_has_issues(const Main *bmain)
{
return bmain->has_forward_compatibility_issues || bmain->is_asset_edit_file;
}
bool BKE_main_needs_overwrite_confirm(const Main *bmain)
{
return bmain->has_forward_compatibility_issues || bmain->is_asset_edit_file;
}
void BKE_main_lock(Main *bmain)
{
BLI_spin_lock((SpinLock *)bmain->lock);

View File

@@ -356,6 +356,8 @@ void blo_join_main(ListBase *mainlist)
BKE_main_namemap_clear(mainl);
while ((tojoin = mainl->next)) {
BLI_assert(((tojoin->curlib->runtime.tag & LIBRARY_IS_ASSET_EDIT_FILE) != 0) ==
tojoin->is_asset_edit_file);
add_main_to_main(mainl, tojoin);
BLI_remlink(mainlist, tojoin);
tojoin->next = tojoin->prev = nullptr;
@@ -418,6 +420,7 @@ void blo_split_main(ListBase *mainlist, Main *main)
libmain->subversionfile = lib->runtime.subversionfile;
libmain->has_forward_compatibility_issues = !MAIN_VERSION_FILE_OLDER_OR_EQUAL(
libmain, BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION);
libmain->is_asset_edit_file = (lib->runtime.tag & LIBRARY_IS_ASSET_EDIT_FILE) != 0;
BLI_addtail(mainlist, libmain);
lib->runtime.temp_index = i;
lib_main_array[i] = libmain;
@@ -448,6 +451,7 @@ static void read_file_version(FileData *fd, Main *main)
main->subversionfile = fg->subversion;
main->minversionfile = fg->minversion;
main->minsubversionfile = fg->minsubversion;
main->is_asset_edit_file = (fg->fileflags & G_FILE_ASSET_EDIT_FILE) != 0;
MEM_freeN(fg);
}
else if (bhead->code == BLO_CODE_ENDB) {
@@ -458,6 +462,8 @@ static void read_file_version(FileData *fd, Main *main)
if (main->curlib) {
main->curlib->runtime.versionfile = main->versionfile;
main->curlib->runtime.subversionfile = main->subversionfile;
SET_FLAG_FROM_TEST(
main->curlib->runtime.tag, main->is_asset_edit_file, LIBRARY_IS_ASSET_EDIT_FILE);
}
}
@@ -2957,6 +2963,7 @@ static BHead *read_global(BlendFileData *bfd, FileData *fd, BHead *bhead)
bfd->main->build_commit_timestamp = fg->build_commit_timestamp;
STRNCPY(bfd->main->build_hash, fg->build_hash);
bfd->main->is_asset_edit_file = (fg->fileflags & G_FILE_ASSET_EDIT_FILE) != 0;
bfd->fileflags = fg->fileflags;
bfd->globalf = fg->globalf;

View File

@@ -1528,6 +1528,14 @@ static bool BLO_write_file_impl(Main *mainvar,
const BlendThumbnail *thumb = params->thumb;
const bool relbase_valid = (mainvar->filepath[0] != '\0');
/* Extra protection: Never save a non asset file as asset file. Otherwise a normal file is turned
* into an asset file, which can result in data loss because the asset system will allow editing
* this file from the UI, regenerating its content with just the asset and it dependencies. */
if ((write_flags & G_FILE_ASSET_EDIT_FILE) && !mainvar->is_asset_edit_file) {
BKE_reportf(reports, RPT_ERROR, "Cannot save normal file (%s) as asset system file", tempname);
return false;
}
/* Path backup/restore. */
void *path_list_backup = nullptr;
const eBPathForeachFlag path_list_flag = (BKE_BPATH_FOREACH_PATH_SKIP_LINKED |

View File

@@ -6416,6 +6416,29 @@ void uiTemplateInputStatus(uiLayout *layout, bContext *C)
}
}
static std::string ui_template_status_tooltip(bContext *C, void * /*argN*/, const char * /*tip*/)
{
Main *bmain = CTX_data_main(C);
std::string tooltip_message = "";
if (bmain->has_forward_compatibility_issues) {
char writer_ver_str[12];
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), bmain->versionfile, -1);
tooltip_message += fmt::format(RPT_("File saved by newer Blender\n({}), expect loss of data"),
writer_ver_str);
}
if (bmain->is_asset_edit_file) {
if (!tooltip_message.empty()) {
tooltip_message += "\n\n";
}
tooltip_message += RPT_(
"This file is managed by the Blender asset system and cannot be overridden");
}
return tooltip_message;
}
void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
{
Main *bmain = CTX_data_main(C);
@@ -6488,7 +6511,7 @@ void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
}
}
if (!bmain->has_forward_compatibility_issues) {
if (!BKE_main_has_issues(bmain)) {
if (U.statusbar_flag & STATUSBAR_SHOW_VERSION) {
if (has_status_info) {
uiItemS_ex(row, -0.5f);
@@ -6502,12 +6525,21 @@ void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
return;
}
blender::StringRefNull version_string = ED_info_statusbar_string_ex(
bmain, scene, view_layer, STATUSBAR_SHOW_VERSION);
blender::StringRefNull warning_message;
/* Blender version part is shown as warning area when there are forward compatibility issues with
* currently loaded .blend file. */
status_info_txt = ED_info_statusbar_string_ex(bmain, scene, view_layer, STATUSBAR_SHOW_VERSION);
uiBut *but;
if (bmain->has_forward_compatibility_issues) {
warning_message = version_string;
}
else {
/* For other issues, still show the version if enabled. */
if (U.statusbar_flag & STATUSBAR_SHOW_VERSION) {
uiItemL(layout, version_string.c_str(), ICON_NONE);
}
}
const uiStyle *style = UI_style_get();
uiLayout *ui_abs = uiLayoutAbsolute(layout, false);
@@ -6515,57 +6547,53 @@ void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
eUIEmbossType previous_emboss = UI_block_emboss_get(block);
UI_fontstyle_set(&style->widgetlabel);
int width = int(
BLF_width(style->widgetlabel.uifont_id, status_info_txt, strlen(status_info_txt)));
width = max_ii(width, int(10 * UI_SCALE_FAC));
const int width = max_ii(int(BLF_width(style->widgetlabel.uifont_id,
warning_message.c_str(),
warning_message.size())),
int(10 * UI_SCALE_FAC));
UI_block_align_begin(block);
/* Background for icon. */
but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
0,
0,
UI_UNIT_X + (6 * UI_SCALE_FAC),
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
"");
uiBut *but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
0,
0,
UI_UNIT_X + (6 * UI_SCALE_FAC),
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
"");
/* UI_BTYPE_ROUNDBOX's bg color is set in but->col. */
UI_GetThemeColorType4ubv(TH_INFO_WARNING, SPACE_INFO, but->col);
/* Background for the rest of the message. */
but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
UI_UNIT_X + (6 * UI_SCALE_FAC),
0,
UI_UNIT_X + width,
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
"");
if (!warning_message.is_empty()) {
/* Background for the rest of the message. */
but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
UI_UNIT_X + (6 * UI_SCALE_FAC),
0,
UI_UNIT_X + width,
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
"");
/* Use icon background at low opacity to highlight, but still contrasting with area TH_TEXT. */
UI_GetThemeColorType4ubv(TH_INFO_WARNING, SPACE_INFO, but->col);
but->col[3] = 64;
/* Use icon background at low opacity to highlight, but still contrasting with area TH_TEXT. */
UI_GetThemeColorType4ubv(TH_INFO_WARNING, SPACE_INFO, but->col);
but->col[3] = 64;
}
UI_block_align_end(block);
UI_block_emboss_set(block, UI_EMBOSS_NONE);
/* The report icon itself. */
static char compat_error_msg[256];
char writer_ver_str[12];
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), bmain->versionfile, -1);
SNPRINTF(compat_error_msg,
RPT_("File saved by newer Blender\n(%s), expect loss of data"),
writer_ver_str);
/* The warning icon itself. */
but = uiDefIconBut(block,
UI_BTYPE_BUT,
0,
@@ -6577,23 +6605,27 @@ void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
nullptr,
0.0f,
0.0f,
compat_error_msg);
nullptr);
UI_but_func_tooltip_set(but, ui_template_status_tooltip, nullptr, nullptr);
UI_GetThemeColorType4ubv(TH_INFO_WARNING_TEXT, SPACE_INFO, but->col);
but->col[3] = 255; /* This theme color is RBG only, so have to set alpha here. */
/* The report message. */
but = uiDefBut(block,
UI_BTYPE_BUT,
0,
status_info_txt,
UI_UNIT_X,
0,
short(width + UI_UNIT_X),
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
compat_error_msg);
/* The warning message, if any. */
if (!warning_message.is_empty()) {
but = uiDefBut(block,
UI_BTYPE_BUT,
0,
warning_message.c_str(),
UI_UNIT_X,
0,
short(width + UI_UNIT_X),
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
nullptr);
UI_but_func_tooltip_set(but, ui_template_status_tooltip, nullptr, nullptr);
}
UI_block_emboss_set(block, previous_emboss);
}

View File

@@ -550,6 +550,11 @@ enum eLibrary_Tag {
LIBRARY_ASSET_EDITABLE = 1 << 1,
/** The blend file of this library is writable for asset editing. */
LIBRARY_ASSET_FILE_WRITABLE = 1 << 2,
/**
* The blend file of this library has the #G_FILE_ASSET_EDIT_FILE flag set (refer to it for more
* info).
*/
LIBRARY_IS_ASSET_EDIT_FILE = 1 << 3,
};
/**

View File

@@ -1923,6 +1923,13 @@ static bool wm_file_write_check_with_report_on_failure(Main *bmain,
return false;
}
if (bmain->is_asset_edit_file &&
blender::StringRef(filepath).endswith(BLENDER_ASSET_FILE_SUFFIX))
{
BKE_report(reports, RPT_ERROR, "Cannot overwrite files that are managed by the asset system");
return false;
}
LISTBASE_FOREACH (Library *, li, &bmain->libraries) {
if (BLI_path_cmp(li->runtime.filepath_abs, filepath) == 0) {
BKE_reportf(reports, RPT_ERROR, "Cannot overwrite used library '%.240s'", filepath);
@@ -3424,6 +3431,16 @@ static void save_set_filepath(bContext *C, wmOperator *op)
STRNCPY(filepath, blendfile_path);
}
/* For convencience when using "Save As" on asset system files: Replace .asset.blend extension
* with just .blend. Asset system files must not be overridden (except by the asset system),
* there are further checks to prevent this entirely. */
if (bmain->is_asset_edit_file &&
blender::StringRef(filepath).endswith(BLENDER_ASSET_FILE_SUFFIX))
{
filepath[strlen(filepath) - strlen(BLENDER_ASSET_FILE_SUFFIX)] = '\0';
BLI_path_extension_ensure(filepath, FILE_MAX, ".blend");
}
wm_filepath_default(bmain, filepath);
RNA_property_string_set(op->ptr, prop, filepath);
}
@@ -3639,7 +3656,7 @@ static int wm_save_mainfile_invoke(bContext *C, wmOperator *op, const wmEvent *
}
if (blendfile_path[0] != '\0') {
if (CTX_data_main(C)->has_forward_compatibility_issues) {
if (BKE_main_needs_overwrite_confirm(CTX_data_main(C))) {
wm_save_file_overwrite_dialog(C, op);
ret = OPERATOR_INTERFACE;
}
@@ -4001,31 +4018,44 @@ static void file_overwrite_detailed_info_show(uiLayout *parent_layout, Main *bma
* block. */
uiLayoutSetScaleY(layout, 0.70f);
char writer_ver_str[16];
char current_ver_str[16];
if (bmain->versionfile == BLENDER_VERSION) {
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), bmain->versionfile, bmain->subversionfile);
BKE_blender_version_blendfile_string_from_values(
current_ver_str, sizeof(current_ver_str), BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION);
}
else {
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), bmain->versionfile, -1);
BKE_blender_version_blendfile_string_from_values(
current_ver_str, sizeof(current_ver_str), BLENDER_VERSION, -1);
if (bmain->has_forward_compatibility_issues) {
char writer_ver_str[16];
char current_ver_str[16];
if (bmain->versionfile == BLENDER_VERSION) {
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), bmain->versionfile, bmain->subversionfile);
BKE_blender_version_blendfile_string_from_values(
current_ver_str, sizeof(current_ver_str), BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION);
}
else {
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), bmain->versionfile, -1);
BKE_blender_version_blendfile_string_from_values(
current_ver_str, sizeof(current_ver_str), BLENDER_VERSION, -1);
}
char message_line1[256];
char message_line2[256];
SNPRINTF(message_line1,
RPT_("This file was saved by a newer version of Blender (%s)"),
writer_ver_str);
SNPRINTF(message_line2,
RPT_("Saving it with this Blender (%s) may cause loss of data"),
current_ver_str);
uiItemL(layout, message_line1, ICON_NONE);
uiItemL(layout, message_line2, ICON_NONE);
}
char message_line1[256];
char message_line2[256];
SNPRINTF(message_line1,
RPT_("This file was saved by a newer version of Blender (%s)"),
writer_ver_str);
SNPRINTF(message_line2,
RPT_("Saving it with this Blender (%s) may cause loss of data"),
current_ver_str);
uiItemL(layout, message_line1, ICON_NONE);
uiItemL(layout, message_line2, ICON_NONE);
if (bmain->is_asset_edit_file) {
if (bmain->has_forward_compatibility_issues) {
uiItemS_ex(layout, 1.4f);
}
uiItemL(layout,
RPT_("This file is managed by the Blender asset system. It can only be"),
ICON_NONE);
uiItemL(layout, RPT_("saved as a new, regular file."), ICON_NONE);
}
}
static void save_file_overwrite_cancel(bContext *C, void *arg_block, void * /*arg_data*/)
@@ -4126,8 +4156,30 @@ static uiBlock *block_create_save_file_overwrite_dialog(bContext *C, ARegion *re
uiLayout *layout = uiItemsAlertBox(block, 34, ALERT_ICON_WARNING);
/* Title. */
uiItemL_ex(
layout, RPT_("Overwrite file with an older Blender version?"), ICON_NONE, true, false);
if (bmain->has_forward_compatibility_issues) {
if (bmain->is_asset_edit_file) {
uiItemL_ex(layout,
RPT_("Cannot overwrite asset system files. Save as new file"),
ICON_NONE,
true,
false);
uiItemL_ex(layout, RPT_("with an older Blender version?"), ICON_NONE, true, false);
}
else {
uiItemL_ex(
layout, RPT_("Overwrite file with an older Blender version?"), ICON_NONE, true, false);
}
}
else if (bmain->is_asset_edit_file) {
uiItemL_ex(layout,
RPT_("Cannot overwrite asset system files. Save as new file?"),
ICON_NONE,
true,
false);
}
else {
BLI_assert_unreachable();
}
/* Filename. */
const char *blendfile_path = BKE_main_blendfile_path(CTX_data_main(C));
@@ -4154,7 +4206,11 @@ static uiBlock *block_create_save_file_overwrite_dialog(bContext *C, ARegion *re
uiLayoutSetScaleY(split, 1.2f);
uiLayoutColumn(split, false);
save_file_overwrite_confirm_button(block, post_action);
/* Asset files don't actually allow overriding. */
const bool allow_overwrite = !bmain->is_asset_edit_file;
if (allow_overwrite) {
save_file_overwrite_confirm_button(block, post_action);
}
uiLayout *split_right = uiLayoutSplit(split, 0.1f, true);
@@ -4339,7 +4395,7 @@ static uiBlock *block_create__close_file_dialog(bContext *C, ARegion *region, vo
uiLayout *layout = uiItemsAlertBox(block, 34, ALERT_ICON_QUESTION);
const bool needs_overwrite_confirm = bmain->has_forward_compatibility_issues;
const bool needs_overwrite_confirm = BKE_main_needs_overwrite_confirm(bmain);
/* Title. */
uiItemL_ex(layout, RPT_("Save changes before closing?"), ICON_NONE, true, false);