Blender V4.3
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
9#include <memory>
10#include <utility>
11
12#include "BLI_fileops.h"
13#include "BLI_path_utils.hh"
14#include "BLI_string.h"
15#include "BLI_vector.hh"
16
17#include "DNA_asset_types.h"
18#include "DNA_space_types.h"
19
20#include "AS_asset_library.hh"
21
22#include "BKE_asset.hh"
23#include "BKE_asset_edit.hh"
24#include "BKE_blendfile.hh"
26#include "BKE_global.hh"
27#include "BKE_idtype.hh"
28#include "BKE_lib_id.hh"
29#include "BKE_lib_remap.hh"
30#include "BKE_library.hh"
31#include "BKE_main.hh"
32#include "BKE_packedFile.hh"
33#include "BKE_preferences.h"
34#include "BKE_report.hh"
35
36#include "BLO_read_write.hh"
37#include "BLO_readfile.hh"
38#include "BLO_writefile.hh"
39
40#include "DEG_depsgraph.hh"
42
43#include "MEM_guardedalloc.h"
44
45namespace blender::bke {
46
47static ID *asset_link_id(Main &global_main,
48 const ID_Type id_type,
49 const char *filepath,
50 const char *asset_name,
51 ReportList *reports = nullptr)
52{
53 /* Load asset from asset library. */
54 LibraryLink_Params lapp_params{};
55 lapp_params.bmain = &global_main;
58
59 BKE_blendfile_link_append_context_library_add(lapp_context, filepath, nullptr);
60
62 lapp_context, asset_name, id_type, nullptr);
64
66
67 BKE_blendfile_link(lapp_context, reports);
68
70
71 ID *local_asset = BKE_blendfile_link_append_context_item_newid_get(lapp_context, lapp_item);
72
74
75 /* Verify that the name matches. It must for referencing the same asset again to work. */
76 BLI_assert(local_asset == nullptr || STREQ(local_asset->name + 2, asset_name));
77
78 /* Tag library as being editable. */
79 if (local_asset && local_asset->lib) {
80 local_asset->lib->runtime.tag |= LIBRARY_ASSET_EDITABLE;
81
82 if ((local_asset->lib->runtime.tag & LIBRARY_IS_ASSET_EDIT_FILE) &&
83 StringRef(filepath).endswith(BLENDER_ASSET_FILE_SUFFIX) &&
85 BLI_file_is_writable(filepath))
86 {
88 }
89 }
90
91 return local_asset;
92}
93
94static std::string asset_root_path_for_save(const bUserAssetLibrary &user_library,
95 const ID_Type id_type)
96{
97 BLI_assert(user_library.dirpath[0] != '\0');
98
99 char libpath[FILE_MAX];
100 STRNCPY(libpath, user_library.dirpath);
101 BLI_path_slash_native(libpath);
102 BLI_path_normalize(libpath);
103
104 /* Capitalize folder name. Ideally this would already available in
105 * the type info to work correctly with multiple words. */
106 const IDTypeInfo *id_type_info = BKE_idtype_get_info_from_idcode(id_type);
107 std::string name = id_type_info->name_plural;
108 name[0] = BLI_toupper_ascii(name[0]);
109
110 return std::string(libpath) + SEP + "Saved" + SEP + name;
111}
112
113static std::string asset_blendfile_path_for_save(const bUserAssetLibrary &user_library,
114 const StringRef base_name,
115 const ID_Type id_type,
116 ReportList &reports)
117{
118 std::string root_path = asset_root_path_for_save(user_library, id_type);
119 BLI_assert(!root_path.empty());
120
121 if (!BLI_dir_create_recursive(root_path.c_str())) {
122 BKE_report(&reports, RPT_ERROR, "Failed to create asset library directory to save asset");
123 return "";
124 }
125
126 /* Make sure filename only contains valid characters for file-system. */
127 char base_name_filesafe[FILE_MAXFILE];
128 BLI_strncpy(base_name_filesafe,
129 base_name.data(),
130 std::min(sizeof(base_name_filesafe), size_t(base_name.size() + 1)));
131 BLI_path_make_safe_filename(base_name_filesafe);
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 void asset_main_create_expander(void * /*handle*/, Main * /*bmain*/, void *vid)
153{
154 ID *id = static_cast<ID *>(vid);
155
156 if (id && (id->tag & ID_TAG_DOIT) == 0) {
158 }
159}
160
161static Main *asset_main_create_from_ID(Main &bmain_src, ID &id_asset, ID **id_asset_new)
162{
163 /* Tag asset ID and its dependencies. */
164 ID *id_src;
165 FOREACH_MAIN_ID_BEGIN (&bmain_src, id_src) {
166 id_src->tag &= ~(ID_TAG_NEED_EXPAND | ID_TAG_DOIT);
167 }
169
170 id_asset.tag |= ID_TAG_NEED_EXPAND | ID_TAG_DOIT;
171
172 BLO_expand_main(nullptr, &bmain_src, asset_main_create_expander);
173
174 /* Create main and copy all tagged datablocks. */
175 Main *bmain_dst = BKE_main_new();
176 STRNCPY(bmain_dst->filepath, bmain_src.filepath);
177 bmain_dst->is_asset_edit_file = true;
178
180
181 FOREACH_MAIN_ID_BEGIN (&bmain_src, id_src) {
182 if (id_src->tag & ID_TAG_DOIT) {
183 /* Note that this will not copy Library datablocks, and all copied
184 * datablocks will become local as a result. */
185 ID *id_dst = BKE_id_copy_ex(bmain_dst,
186 id_src,
187 nullptr,
189 ((id_src == &id_asset) ? LIB_ID_COPY_ASSET_METADATA : 0));
190 id_remapper.add(id_src, id_dst);
191 if (id_src == &id_asset) {
192 *id_asset_new = id_dst;
193 }
194 }
195 else {
196 id_remapper.add(id_src, nullptr);
197 }
198
199 id_src->tag &= ~(ID_TAG_NEED_EXPAND | ID_TAG_DOIT);
200 }
202
203 /* Remap datablock pointers. */
205
206 /* Compute reference counts. */
207 ID *id_dst;
208 FOREACH_MAIN_ID_BEGIN (bmain_dst, id_dst) {
209 id_dst->tag &= ~ID_TAG_NO_USER_REFCOUNT;
210 }
212 BKE_main_id_refcount_recompute(bmain_dst, false);
213
214 return bmain_dst;
215}
216
217static bool asset_write_in_library(Main &bmain,
218 const ID &id_const,
219 const StringRef name,
220 const StringRefNull filepath,
221 std::string &final_full_file_path,
222 ReportList &reports)
223{
224 ID &id = const_cast<ID &>(id_const);
225
226 ID *new_id = nullptr;
227 Main *new_main = asset_main_create_from_ID(bmain, id, &new_id);
228
229 std::string new_name = name;
230 BKE_libblock_rename(*new_main, *new_id, new_name);
231 id_fake_user_set(new_id);
232
233 BlendFileWriteParams blend_file_write_params{};
234 blend_file_write_params.remap_mode = BLO_WRITE_PATH_REMAP_RELATIVE;
235
236 BKE_packedfile_pack_all(new_main, nullptr, false);
237
238 const int write_flags = G_FILE_COMPRESS | G_FILE_ASSET_EDIT_FILE;
239 const bool success = BLO_write_file(
240 new_main, filepath.c_str(), write_flags, &blend_file_write_params, &reports);
241
242 if (success) {
243 const IDTypeInfo *idtype = BKE_idtype_get_info_from_id(&id);
244 final_full_file_path = std::string(filepath) + SEP + std::string(idtype->name) + SEP + name;
245 }
246
247 BKE_main_free(new_main);
248
249 return success;
250}
251
252static ID *asset_reload(Main &global_main, ID &id, ReportList *reports)
253{
255
256 const std::string name = BKE_id_name(id);
257 const std::string filepath = id.lib->runtime.filepath_abs;
258 const ID_Type id_type = GS(id.name);
259
260 /* TODO: There's no API to reload a single data block (and its dependencies) yet. For now
261 * deleting the brush and re-linking it is the best way to get reloading to work. */
262 BKE_id_delete(&global_main, &id);
263 ID *new_id = asset_link_id(global_main, id_type, filepath.c_str(), name.c_str(), reports);
264
265 /* Recreate dependency graph to include new IDs. */
266 DEG_relations_tag_update(&global_main);
267
268 return new_id;
269}
270
272 const bUserAssetLibrary &user_library,
273 const short idcode,
274 const char *idname,
275 const char *filepath)
276{
277 AssetWeakReference weak_ref;
279 weak_ref.asset_library_identifier = BLI_strdup(user_library.name);
280
281 /* BLI_path_rel requires a trailing slash. */
282 char user_library_dirpath[FILE_MAX];
283 STRNCPY(user_library_dirpath, user_library.dirpath);
284 BLI_path_slash_ensure(user_library_dirpath, sizeof(user_library_dirpath));
285
286 char relative_filepath[FILE_MAX];
287 STRNCPY(relative_filepath, filepath);
288 BLI_path_rel(relative_filepath, user_library_dirpath);
289 const char *asset_blend_path = relative_filepath + 2; /* Strip out // prefix. */
290
292 "%s/%s/%s", asset_blend_path, BKE_idtype_idcode_to_name(idcode), idname);
293
294 return weak_ref;
295}
296
298 const char *idname,
299 const char *filepath)
300{
301 AssetWeakReference weak_ref;
303 weak_ref.relative_asset_identifier = BLI_sprintfN("%s/%s/%s/%s",
305 BLI_path_basename(filepath),
307 idname);
308
309 return weak_ref;
310}
311
312std::optional<std::string> asset_edit_id_save_as(Main &global_main,
313 const ID &id,
314 const StringRefNull name,
315 const bUserAssetLibrary &user_library,
316 AssetWeakReference &r_weak_ref,
317 ReportList &reports)
318{
319 const std::string filepath = asset_blendfile_path_for_save(
320 user_library, name, GS(id.name), reports);
321
322 std::string final_full_asset_filepath;
323 const bool success = asset_write_in_library(
324 global_main, id, name, filepath, final_full_asset_filepath, reports);
325 if (!success) {
326 BKE_report(&reports, RPT_ERROR, "Failed to write to asset library");
327 return std::nullopt;
328 }
329
331 user_library, GS(id.name), name.c_str(), filepath.c_str());
332
333 BKE_reportf(&reports, RPT_INFO, "Saved \"%s\"", filepath.c_str());
334
335 return final_full_asset_filepath;
336}
337
338bool asset_edit_id_save(Main &global_main, const ID &id, ReportList &reports)
339{
340 if (!asset_edit_id_is_editable(id)) {
341 return false;
342 }
343
344 std::string final_full_asset_filepath;
345 const bool success = asset_write_in_library(global_main,
346 id,
347 id.name + 2,
348 id.lib->runtime.filepath_abs,
349 final_full_asset_filepath,
350 reports);
351
352 if (!success) {
353 BKE_report(&reports, RPT_ERROR, "Failed to write to asset library");
354 return false;
355 }
356
357 return true;
358}
359
360ID *asset_edit_id_revert(Main &global_main, ID &id, ReportList &reports)
361{
362 if (!asset_edit_id_is_editable(id)) {
363 return nullptr;
364 }
365
366 return asset_reload(global_main, id, &reports);
367}
368
369bool asset_edit_id_delete(Main &global_main, ID &id, ReportList &reports)
370{
372 if (BLI_delete(id.lib->runtime.filepath_abs, false, false) != 0) {
373 BKE_report(&reports, RPT_ERROR, "Failed to delete asset library file");
374 return false;
375 }
376 }
377
378 BKE_id_delete(&global_main, &id);
379
380 return true;
381}
382
384 const ID_Type id_type,
385 const AssetWeakReference &weak_ref)
386{
387 /* Don't do this in file load. */
388 BLI_assert(!global_main.is_locked_for_linking);
389
390 char asset_full_path_buffer[FILE_MAX_LIBEXTRA];
391 char *asset_lib_path, *asset_group, *asset_name;
392
394 &weak_ref, asset_full_path_buffer, &asset_lib_path, &asset_group, &asset_name);
395 if (asset_lib_path == nullptr && asset_group == nullptr && asset_name == nullptr) {
396 return nullptr;
397 }
398
399 /* If this is the same file as we have open, use local datablock. */
400 if (asset_lib_path && STREQ(asset_lib_path, global_main.filepath)) {
401 asset_lib_path = nullptr;
402 }
403
404 BLI_assert(asset_name != nullptr);
405
406 /* Test if asset has been loaded already. */
408 &global_main, id_type, asset_name, asset_lib_path);
409 if (local_asset) {
410 return local_asset;
411 }
412
413 /* Try linking in the required file. */
414 if (asset_lib_path == nullptr) {
415 return nullptr;
416 }
417
418 return asset_link_id(global_main, id_type, asset_lib_path, asset_name);
419}
420
421std::optional<AssetWeakReference> asset_edit_weak_reference_from_id(const ID &id)
422{
423 /* Brush is local to the file. */
424 if (!id.lib) {
425 AssetWeakReference weak_ref;
426
427 weak_ref.asset_library_type = eAssetLibraryType::ASSET_LIBRARY_LOCAL;
429 "%s/%s", BKE_idtype_idcode_to_name(GS(id.name)), id.name + 2);
430
431 return weak_ref;
432 }
433
434 if (!asset_edit_id_is_editable(id)) {
435 return std::nullopt;
436 }
437
439 &U, id.lib->runtime.filepath_abs);
440
441 const short idcode = GS(id.name);
442
443 if (user_library && user_library->dirpath[0]) {
445 *user_library, idcode, id.name + 2, id.lib->runtime.filepath_abs);
446 }
447
448 return asset_weak_reference_for_essentials(idcode, id.name + 2, id.lib->runtime.filepath_abs);
449}
450
452{
453 return (id.lib && (id.lib->runtime.tag & LIBRARY_ASSET_EDITABLE));
454}
455
457{
458 return asset_edit_id_is_editable(id) && (id.lib->runtime.tag & LIBRARY_ASSET_FILE_WRITABLE);
459}
460
461} // namespace blender::bke
void AS_asset_full_path_explode_from_weak_ref(const AssetWeakReference *asset_reference, char r_path_buffer[1090], 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:150
const IDTypeInfo * BKE_idtype_get_info_from_idcode(short id_code)
Definition idtype.cc:145
const char * BKE_idtype_idcode_to_name(short idcode)
Definition idtype.cc:168
const char * BKE_idtype_idcode_to_name_plural(short idcode)
Definition idtype.cc:175
void BKE_id_delete(Main *bmain, void *idv) ATTR_NONNULL()
@ LIB_ID_COPY_ASSET_METADATA
@ LIB_ID_CREATE_NO_USER_REFCOUNT
@ LIB_ID_CREATE_NO_DEG_TAG
ID * BKE_libblock_find_name_and_library_filepath(Main *bmain, short type, const char *name, const char *lib_filepath_abs)
Definition lib_id.cc:1715
void id_fake_user_set(ID *id)
Definition lib_id.cc:389
IDNewNameResult BKE_libblock_rename(Main &bmain, ID &id, blender::StringRefNull name, const IDNewNameMode mode=IDNewNameMode::RenameExistingNever)
Definition lib_id.cc:2316
ID * BKE_id_copy_ex(Main *bmain, const ID *id, ID **new_id_p, int flag)
Definition lib_id.cc:760
const char * BKE_id_name(const ID &id)
void BKE_main_id_refcount_recompute(Main *bmain, bool do_linked_only)
Definition lib_id.cc:1981
void BKE_libblock_remap_multiple_raw(Main *bmain, blender::bke::id::IDRemapper &mappings, const int remap_flags)
Definition lib_remap.cc:669
@ ID_REMAP_SKIP_USER_CLEAR
#define FOREACH_MAIN_ID_END
Definition BKE_main.hh:500
Main * BKE_main_new(void)
Definition main.cc:45
void BKE_main_free(Main *bmain)
Definition main.cc:175
#define FOREACH_MAIN_ID_BEGIN(_bmain, _id)
Definition BKE_main.hh:494
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
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:125
#define BLI_assert(a)
Definition BLI_assert.h:50
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:391
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:438
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.c:40
#define STRNCPY(dst, src)
Definition BLI_string.h:593
char BLI_toupper_ascii(const char c) ATTR_WARN_UNUSED_RESULT
Definition string.c:947
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
void BLO_expand_main(void *fdhandle, Main *mainvar, BLOExpandDoitCallback callback)
Definition readfile.cc:4159
external writefile.cc function prototypes.
bool BLO_write_file(Main *mainvar, const char *filepath, int write_flags, const BlendFileWriteParams *params, ReportList *reports)
@ BLO_WRITE_PATH_REMAP_RELATIVE
void DEG_relations_tag_update(Main *bmain)
#define ID_IS_LINKED(_id)
Definition DNA_ID.h:654
@ ID_TAG_NEED_EXPAND
Definition DNA_ID.h:879
@ ID_TAG_DOIT
Definition DNA_ID.h:1003
@ LIBRARY_IS_ASSET_EDIT_FILE
Definition DNA_ID.h:556
@ LIBRARY_ASSET_EDITABLE
Definition DNA_ID.h:549
@ LIBRARY_ASSET_FILE_WRITABLE
Definition DNA_ID.h:551
ID_Type
@ ASSET_LIBRARY_CUSTOM
@ ASSET_LIBRARY_ESSENTIALS
#define FILE_MAX_LIBEXTRA
Read Guarded memory(de)allocation.
unsigned int U
Definition btGjkEpa3.h:78
constexpr int64_t size() const
constexpr const char * data() const
constexpr const char * c_str() const
void add(ID *old_id, ID *new_id)
#define GS(x)
Definition iris.cc:202
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:94
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 void asset_main_create_expander(void *, Main *, void *vid)
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)
bool asset_edit_id_is_writable(const 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:47
static Main * asset_main_create_from_ID(Main &bmain_src, ID &id_asset, ID **id_asset_new)
bool asset_edit_id_is_editable(const ID &id)
bool asset_edit_id_save(Main &global_main, const ID &id, ReportList &reports)
const char * relative_asset_identifier
const char * asset_library_identifier
eBLO_WritePathRemap remap_mode
const char * name
const char * name_plural
Definition DNA_ID.h:413
int tag
Definition DNA_ID.h:434
struct Library * lib
Definition DNA_ID.h:419
char name[66]
Definition DNA_ID.h:425
struct Library_Runtime runtime
Definition DNA_ID.h:535
bool is_locked_for_linking
Definition BKE_main.hh:176
bool is_asset_edit_file
Definition BKE_main.hh:151
char filepath[1024]
Definition BKE_main.hh:136
static DynamicLibrary lib