Blender V5.0
asset_edit.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_fileops.h"
10#include "BLI_path_utils.hh"
11#include "BLI_string.h"
12
13#include "DNA_ID.h"
14#include "DNA_asset_types.h"
15#include "DNA_space_enums.h"
16#include "DNA_userdef_types.h"
17
18#include "AS_asset_library.hh"
19
20#include "BKE_asset_edit.hh"
21#include "BKE_blendfile.hh"
23#include "BKE_global.hh"
24#include "BKE_idtype.hh"
25#include "BKE_lib_id.hh"
26#include "BKE_lib_remap.hh"
27#include "BKE_library.hh"
28#include "BKE_main.hh"
29#include "BKE_packedFile.hh"
30#include "BKE_preferences.h"
31#include "BKE_report.hh"
32
33#include "BLO_read_write.hh"
34#include "BLO_readfile.hh"
35#include "BLO_writefile.hh"
36
37#include "DEG_depsgraph.hh"
39
40namespace blender::bke {
41
42static ID *asset_link_id(Main &global_main,
43 const ID_Type id_type,
44 const char *filepath,
45 const char *asset_name,
46 ReportList *reports = nullptr)
47{
48 /* Load asset from asset library. */
49 LibraryLink_Params lapp_params{};
50 lapp_params.bmain = &global_main;
53
54 BKE_blendfile_link_append_context_library_add(lapp_context, filepath, nullptr);
55
57 lapp_context, asset_name, id_type, nullptr);
59
61
62 BKE_blendfile_link(lapp_context, reports);
63
65
66 ID *local_asset = BKE_blendfile_link_append_context_item_newid_get(lapp_context, lapp_item);
67
69
70 /* Verify that the name matches. It must for referencing the same asset again to work. */
71 BLI_assert(local_asset == nullptr || STREQ(local_asset->name + 2, asset_name));
72
73 /* Tag library as being editable. */
74 if (local_asset && local_asset->lib) {
75 local_asset->lib->runtime->tag |= LIBRARY_ASSET_EDITABLE;
76
77 if ((local_asset->lib->runtime->tag & LIBRARY_IS_ASSET_EDIT_FILE) &&
78 StringRef(filepath).endswith(BLENDER_ASSET_FILE_SUFFIX) &&
80 BLI_file_is_writable(filepath))
81 {
82 local_asset->lib->runtime->tag |= LIBRARY_ASSET_FILE_WRITABLE;
83 }
84 }
85
86 return local_asset;
87}
88
89static std::string asset_root_path_for_save(const bUserAssetLibrary &user_library,
90 const ID_Type id_type)
91{
92 BLI_assert(user_library.dirpath[0] != '\0');
93
94 char libpath[FILE_MAX];
95 STRNCPY(libpath, user_library.dirpath);
96 BLI_path_slash_native(libpath);
97 BLI_path_normalize(libpath);
98
99 /* Capitalize folder name. Ideally this would already available in
100 * the type info to work correctly with multiple words. */
101 const IDTypeInfo *id_type_info = BKE_idtype_get_info_from_idcode(id_type);
102 std::string name = id_type_info->name_plural;
103 name[0] = BLI_toupper_ascii(name[0]);
104
105 return std::string(libpath) + SEP + "Saved" + SEP + name;
106}
107
108static std::string asset_blendfile_path_for_save(const bUserAssetLibrary &user_library,
109 const StringRef base_name,
110 const ID_Type id_type,
111 ReportList &reports)
112{
113 std::string root_path = asset_root_path_for_save(user_library, id_type);
114 BLI_assert(!root_path.empty());
115
116 if (!BLI_dir_create_recursive(root_path.c_str())) {
117 BKE_report(&reports, RPT_ERROR, "Failed to create asset library directory to save asset");
118 return "";
119 }
120
121 /* Make sure filename only contains valid characters for file-system. */
122 char base_name_filesafe[FILE_MAXFILE];
123 BLI_strncpy(base_name_filesafe,
124 base_name.data(),
125 std::min(sizeof(base_name_filesafe), size_t(base_name.size() + 1)));
126 BLI_path_make_safe_filename(base_name_filesafe);
127
128 /* FIXME: MAX_ID_NAME & FILE_MAXFILE
129 *
130 * This already does not respect the FILE_MAXFILE max length of filenames for the final filepath
131 * it seems?
132 */
133 {
134 const std::string filepath = root_path + SEP + base_name_filesafe + BLENDER_ASSET_FILE_SUFFIX;
135 if (!BLI_is_file(filepath.c_str())) {
136 return filepath;
137 }
138 }
139
140 /* Avoid overwriting existing file by adding number suffix. */
141 for (int i = 1;; i++) {
142 const std::string filepath = root_path + SEP + base_name_filesafe + "_" + std::to_string(i++) +
144 if (!BLI_is_file(filepath.c_str())) {
145 return filepath;
146 }
147 }
148
149 return "";
150}
151
152static bool asset_write_in_library(Main &bmain,
153 const ID &id_const,
154 const StringRef name,
155 const StringRefNull filepath,
156 std::string &final_full_file_path,
157 ReportList &reports)
158{
159 using namespace blender::bke::blendfile;
160
161 ID &id = const_cast<ID &>(id_const);
162
163 /* This is not expected to ever happen currently from this codepath. */
165
166 PartialWriteContext lib_write_ctx{bmain};
167 ID *new_id = lib_write_ctx.id_add(&id,
171 if (!new_id) {
172 BKE_reportf(&reports,
173 RPT_ERROR,
174 "Could not create a copy of ID '%s' to write it in the library",
175 id.name);
176 return false;
177 }
178
179 std::string new_name = name;
180 BKE_libblock_rename(lib_write_ctx.bmain, *new_id, new_name);
181
182 BKE_packedfile_pack_all(&lib_write_ctx.bmain, nullptr, false);
183 lib_write_ctx.bmain.is_asset_edit_file = true;
184
185 const int write_flags = G_FILE_COMPRESS | G_FILE_ASSET_EDIT_FILE;
186 const int remap_mode = BLO_WRITE_PATH_REMAP_RELATIVE;
187 const bool success = lib_write_ctx.write(filepath.c_str(), write_flags, remap_mode, reports);
188
189 if (success) {
190 const IDTypeInfo *idtype = BKE_idtype_get_info_from_id(&id);
191 final_full_file_path = std::string(filepath) + SEP + std::string(idtype->name) + SEP + name;
192 }
193
194 return success;
195}
196
197static ID *asset_reload(Main &global_main, ID &id, ReportList *reports)
198{
200
201 const std::string name = BKE_id_name(id);
202 const std::string filepath = id.lib->runtime->filepath_abs;
203 const ID_Type id_type = GS(id.name);
204
205 /* TODO: There's no API to reload a single data block (and its dependencies) yet. For now
206 * deleting the brush and re-linking it is the best way to get reloading to work. */
207 BKE_id_delete(&global_main, &id);
208 ID *new_id = asset_link_id(global_main, id_type, filepath.c_str(), name.c_str(), reports);
209
210 /* Recreate dependency graph to include new IDs. */
211 DEG_relations_tag_update(&global_main);
212
213 return new_id;
214}
215
217 const bUserAssetLibrary &user_library,
218 const short idcode,
219 const char *idname,
220 const char *filepath)
221{
222 AssetWeakReference weak_ref;
224 weak_ref.asset_library_identifier = BLI_strdup(user_library.name);
225
226 /* BLI_path_rel requires a trailing slash. */
227 char user_library_dirpath[FILE_MAX];
228 STRNCPY(user_library_dirpath, user_library.dirpath);
229 BLI_path_slash_ensure(user_library_dirpath, sizeof(user_library_dirpath));
230
231 char relative_filepath[FILE_MAX];
232 STRNCPY(relative_filepath, filepath);
233 BLI_path_rel(relative_filepath, user_library_dirpath);
234 const char *asset_blend_path = relative_filepath + 2; /* Strip out // prefix. */
235
237 "%s/%s/%s", asset_blend_path, BKE_idtype_idcode_to_name(idcode), idname);
238
239 return weak_ref;
240}
241
243 const char *idname,
244 const char *filepath)
245{
246 AssetWeakReference weak_ref;
248 weak_ref.relative_asset_identifier = BLI_sprintfN("%s/%s/%s/%s",
250 BLI_path_basename(filepath),
252 idname);
253
254 return weak_ref;
255}
256
257std::optional<std::string> asset_edit_id_save_as(Main &global_main,
258 const ID &id,
259 const StringRefNull name,
260 const bUserAssetLibrary &user_library,
261 AssetWeakReference &r_weak_ref,
262 ReportList &reports)
263{
264 const std::string filepath = asset_blendfile_path_for_save(
265 user_library, name, GS(id.name), reports);
266
267 std::string final_full_asset_filepath;
268 const bool success = asset_write_in_library(
269 global_main, id, name, filepath, final_full_asset_filepath, reports);
270 if (!success) {
271 BKE_report(&reports, RPT_ERROR, "Failed to write to asset library");
272 return std::nullopt;
273 }
274
276 user_library, GS(id.name), name.c_str(), filepath.c_str());
277
278 BKE_reportf(&reports, RPT_INFO, "Saved \"%s\"", filepath.c_str());
279
280 return final_full_asset_filepath;
281}
282
283bool asset_edit_id_save(Main &global_main, const ID &id, ReportList &reports)
284{
285 if (!asset_edit_id_is_writable(id)) {
286 return false;
287 }
288
289 std::string final_full_asset_filepath;
290 const bool success = asset_write_in_library(global_main,
291 id,
292 id.name + 2,
293 id.lib->runtime->filepath_abs,
294 final_full_asset_filepath,
295 reports);
296
297 if (!success) {
298 BKE_report(&reports, RPT_ERROR, "Failed to write to asset library");
299 return false;
300 }
301
302 return true;
303}
304
305ID *asset_edit_id_revert(Main &global_main, ID &id, ReportList &reports)
306{
307 if (!asset_edit_id_is_editable(id)) {
308 return nullptr;
309 }
310
311 return asset_reload(global_main, id, &reports);
312}
313
314bool asset_edit_id_delete(Main &global_main, ID &id, ReportList &reports)
315{
317 if (BLI_delete(id.lib->runtime->filepath_abs, false, false) != 0) {
318 BKE_report(&reports, RPT_ERROR, "Failed to delete asset library file");
319 return false;
320 }
321 }
322
323 BKE_id_delete(&global_main, &id);
324
325 return true;
326}
327
329 const ID_Type id_type,
330 const AssetWeakReference &weak_ref)
331{
332 /* Don't do this in file load. */
333 BLI_assert(!global_main.is_locked_for_linking);
334
335 char asset_full_path_buffer[FILE_MAX_LIBEXTRA];
336 char *asset_lib_path, *asset_group, *asset_name;
337
339 &weak_ref, asset_full_path_buffer, &asset_lib_path, &asset_group, &asset_name);
340 if (asset_lib_path == nullptr && asset_group == nullptr && asset_name == nullptr) {
341 return nullptr;
342 }
343
344 /* If this is the same file as we have open, use local datablock. */
345 if (asset_lib_path && STREQ(asset_lib_path, global_main.filepath)) {
346 asset_lib_path = nullptr;
347 }
348
349 BLI_assert(asset_name != nullptr);
350
351 /* Test if asset has been loaded already. */
353 &global_main, id_type, asset_name, asset_lib_path);
354 if (local_asset) {
355 return local_asset;
356 }
357
358 /* Try linking in the required file. */
359 if (asset_lib_path == nullptr) {
360 return nullptr;
361 }
362
363 return asset_link_id(global_main, id_type, asset_lib_path, asset_name);
364}
365
366std::optional<AssetWeakReference> asset_edit_weak_reference_from_id(const ID &id)
367{
368 /* Brush is local to the file. */
369 if (!id.lib) {
370 AssetWeakReference weak_ref;
371
372 weak_ref.asset_library_type = eAssetLibraryType::ASSET_LIBRARY_LOCAL;
374 "%s/%s", BKE_idtype_idcode_to_name(GS(id.name)), id.name + 2);
375
376 return weak_ref;
377 }
378
379 if (!asset_edit_id_is_editable(id)) {
380 return std::nullopt;
381 }
382
384 &U, id.lib->runtime->filepath_abs);
385
386 const short idcode = GS(id.name);
387
388 if (user_library && user_library->dirpath[0]) {
390 *user_library, idcode, id.name + 2, id.lib->runtime->filepath_abs);
391 }
392
393 return asset_weak_reference_for_essentials(idcode, id.name + 2, id.lib->runtime->filepath_abs);
394}
395
397{
398 return (id.lib && (id.lib->runtime->tag & LIBRARY_ASSET_EDITABLE));
399}
400
402{
403 return asset_edit_id_is_editable(id) && (id.lib->runtime->tag & LIBRARY_ASSET_FILE_WRITABLE);
404}
405
407{
408 if (!asset_edit_id_is_editable(id)) {
409 return &id;
410 }
411
412 return BKE_main_library_weak_reference_find(&global_main, id.lib->filepath, id.name);
413}
414
416{
417 ID *local_id = asset_edit_id_find_local(global_main, id);
418 if (local_id) {
419 return local_id;
420 }
421
422 /* Make local and create weak library reference for reuse. */
423 BKE_lib_id_make_local(&global_main,
424 &id,
427 BLI_assert(id.newid != nullptr);
428 BKE_main_library_weak_reference_add(id.newid, id.lib->filepath, id.name);
429
430 return id.newid;
431}
432
433} // namespace blender::bke
void AS_asset_full_path_explode_from_weak_ref(const AssetWeakReference *asset_reference, char r_path_buffer[1282], char **r_dir, char **r_group, char **r_name)
#define BLENDER_ASSET_FILE_SUFFIX
@ G_FILE_ASSET_EDIT_FILE
@ G_FILE_COMPRESS
const IDTypeInfo * BKE_idtype_get_info_from_id(const ID *id)
Definition idtype.cc:146
const IDTypeInfo * BKE_idtype_get_info_from_idcode(short id_code)
Definition idtype.cc:141
const char * BKE_idtype_idcode_to_name(short idcode)
Definition idtype.cc:164
const char * BKE_idtype_idcode_to_name_plural(short idcode)
Definition idtype.cc:171
void BKE_id_delete(Main *bmain, void *idv) ATTR_NONNULL()
@ LIB_ID_MAKELOCAL_INDIRECT
@ LIB_ID_MAKELOCAL_ASSET_DATA_CLEAR
@ LIB_ID_MAKELOCAL_FORCE_COPY
ID * BKE_libblock_find_name_and_library_filepath(Main *bmain, short type, const char *name, const char *lib_filepath_abs)
Definition lib_id.cc:1767
IDNewNameResult BKE_libblock_rename(Main &bmain, ID &id, blender::StringRefNull name, const IDNewNameMode mode=IDNewNameMode::RenameExistingNever)
Definition lib_id.cc:2370
bool BKE_lib_id_make_local(Main *bmain, ID *id, int flags)
Definition lib_id.cc:598
const char * BKE_id_name(const ID &id)
@ LIBRARY_IS_ASSET_EDIT_FILE
@ LIBRARY_ASSET_EDITABLE
@ LIBRARY_ASSET_FILE_WRITABLE
void BKE_main_library_weak_reference_add(ID *local_id, const char *library_filepath, const char *library_id_name)
Definition main.cc:803
ID * BKE_main_library_weak_reference_find(Main *bmain, const char *library_filepath, const char *library_id_name)
Definition main.cc:772
void BKE_packedfile_pack_all(Main *bmain, ReportList *reports, bool verbose)
struct bUserAssetLibrary * BKE_preferences_asset_library_containing_path(const struct UserDef *userdef, const char *path) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
@ RPT_INFO
Definition BKE_report.hh:35
@ RPT_ERROR
Definition BKE_report.hh:39
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
#define BLI_assert(a)
Definition BLI_assert.h:46
File and directory operations.
bool BLI_file_is_writable(const char *filepath) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition fileops_c.cc:291
bool BLI_dir_create_recursive(const char *dirname) ATTR_NONNULL()
Definition fileops_c.cc:414
int BLI_delete(const char *path, bool dir, bool recursive) ATTR_NONNULL()
bool BLI_is_file(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:448
void void void const char * BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
#define FILE_MAXFILE
#define FILE_MAX
void BLI_path_slash_native(char *path) ATTR_NONNULL(1)
int BLI_path_normalize(char *path) ATTR_NONNULL(1)
#define SEP
bool void BLI_path_rel(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1)
int BLI_path_slash_ensure(char *path, size_t path_maxncpy) ATTR_NONNULL(1)
bool BLI_path_make_safe_filename(char *filename) ATTR_NONNULL(1)
char * BLI_sprintfN(const char *__restrict format,...) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC ATTR_PRINTF_FORMAT(1
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
char BLI_toupper_ascii(const char c) ATTR_WARN_UNUSED_RESULT
Definition string.cc:951
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define STREQ(a, b)
external readfile function prototypes.
@ BLO_LIBLINK_FORCE_INDIRECT
external writefile.cc function prototypes.
@ BLO_WRITE_PATH_REMAP_RELATIVE
void DEG_relations_tag_update(Main *bmain)
ID and Library types, which are fundamental for SDNA.
#define ID_IS_PACKED(_id)
Definition DNA_ID.h:700
#define ID_IS_LINKED(_id)
Definition DNA_ID.h:694
ID_Type
@ ASSET_LIBRARY_CUSTOM
@ ASSET_LIBRARY_ESSENTIALS
#define FILE_MAX_LIBEXTRA
#define U
constexpr int64_t size() const
constexpr const char * data() const
constexpr const char * c_str() const
ID * id_add(const ID *id, IDAddOptions options, blender::FunctionRef< IDAddOperations(LibraryIDLinkCallbackData *cb_data, IDAddOptions options)> dependencies_filter_cb=nullptr)
bool write(const char *write_filepath, int write_flags, int remap_mode, ReportList &reports)
#define GS(x)
ID * asset_edit_id_from_weak_reference(Main &global_main, ID_Type id_type, const AssetWeakReference &weak_ref)
static std::string asset_root_path_for_save(const bUserAssetLibrary &user_library, const ID_Type id_type)
Definition asset_edit.cc:89
static AssetWeakReference asset_weak_reference_for_user_library(const bUserAssetLibrary &user_library, const short idcode, const char *idname, const char *filepath)
std::optional< AssetWeakReference > asset_edit_weak_reference_from_id(const ID &id)
static AssetWeakReference asset_weak_reference_for_essentials(const short idcode, const char *idname, const char *filepath)
std::optional< std::string > asset_edit_id_save_as(Main &global_main, const ID &id, StringRefNull name, const bUserAssetLibrary &user_library, AssetWeakReference &r_weak_ref, ReportList &reports)
static bool asset_write_in_library(Main &bmain, const ID &id_const, const StringRef name, const StringRefNull filepath, std::string &final_full_file_path, ReportList &reports)
static std::string asset_blendfile_path_for_save(const bUserAssetLibrary &user_library, const StringRef base_name, const ID_Type id_type, ReportList &reports)
ID * asset_edit_id_revert(Main &global_main, ID &id, ReportList &reports)
bool asset_edit_id_delete(Main &global_main, ID &id, ReportList &reports)
ID * asset_edit_id_find_local(Main &global_main, ID &id)
bool asset_edit_id_is_writable(const ID &id)
ID * asset_edit_id_ensure_local(Main &global_main, ID &id)
static ID * asset_reload(Main &global_main, ID &id, ReportList *reports)
static ID * asset_link_id(Main &global_main, const ID_Type id_type, const char *filepath, const char *asset_name, ReportList *reports=nullptr)
Definition asset_edit.cc:42
bool asset_edit_id_is_editable(const ID &id)
bool asset_edit_id_save(Main &global_main, const ID &id, ReportList &reports)
const char * name
const char * relative_asset_identifier
const char * asset_library_identifier
const char * name
const char * name_plural
Definition DNA_ID.h:414
struct Library * lib
Definition DNA_ID.h:420
char name[258]
Definition DNA_ID.h:432
LibraryRuntimeHandle * runtime
Definition DNA_ID.h:579
bool is_locked_for_linking
Definition BKE_main.hh:224
bool is_asset_edit_file
Definition BKE_main.hh:199
char filepath[1024]
Definition BKE_main.hh:179
i
Definition text_draw.cc:230
static DynamicLibrary lib