Blender V4.5
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,
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 {
129 const std::string filepath = root_path + SEP + base_name_filesafe + BLENDER_ASSET_FILE_SUFFIX;
130 if (!BLI_is_file(filepath.c_str())) {
131 return filepath;
132 }
133 }
134
135 /* Avoid overwriting existing file by adding number suffix. */
136 for (int i = 1;; i++) {
137 const std::string filepath = root_path + SEP + base_name_filesafe + "_" + std::to_string(i++) +
139 if (!BLI_is_file(filepath.c_str())) {
140 return filepath;
141 }
142 }
143
144 return "";
145}
146
147static bool asset_write_in_library(Main &bmain,
148 const ID &id_const,
149 const StringRef name,
150 const StringRefNull filepath,
151 std::string &final_full_file_path,
153{
154 using namespace blender::bke::blendfile;
155
156 ID &id = const_cast<ID &>(id_const);
157
158 PartialWriteContext lib_write_ctx{BKE_main_blendfile_path(&bmain)};
159 ID *new_id = lib_write_ctx.id_add(&id,
163
164 std::string new_name = name;
165 BKE_libblock_rename(lib_write_ctx.bmain, *new_id, new_name);
166
167 BKE_packedfile_pack_all(&lib_write_ctx.bmain, nullptr, false);
168 lib_write_ctx.bmain.is_asset_edit_file = true;
169
170 const int write_flags = G_FILE_COMPRESS | G_FILE_ASSET_EDIT_FILE;
171 const int remap_mode = BLO_WRITE_PATH_REMAP_RELATIVE;
172 const bool success = lib_write_ctx.write(filepath.c_str(), write_flags, remap_mode, reports);
173
174 if (success) {
175 const IDTypeInfo *idtype = BKE_idtype_get_info_from_id(&id);
176 final_full_file_path = std::string(filepath) + SEP + std::string(idtype->name) + SEP + name;
177 }
178
179 return success;
180}
181
182static ID *asset_reload(Main &global_main, ID &id, ReportList *reports)
183{
185
186 const std::string name = BKE_id_name(id);
187 const std::string filepath = id.lib->runtime->filepath_abs;
188 const ID_Type id_type = GS(id.name);
189
190 /* TODO: There's no API to reload a single data block (and its dependencies) yet. For now
191 * deleting the brush and re-linking it is the best way to get reloading to work. */
192 BKE_id_delete(&global_main, &id);
193 ID *new_id = asset_link_id(global_main, id_type, filepath.c_str(), name.c_str(), reports);
194
195 /* Recreate dependency graph to include new IDs. */
196 DEG_relations_tag_update(&global_main);
197
198 return new_id;
199}
200
202 const bUserAssetLibrary &user_library,
203 const short idcode,
204 const char *idname,
205 const char *filepath)
206{
207 AssetWeakReference weak_ref;
209 weak_ref.asset_library_identifier = BLI_strdup(user_library.name);
210
211 /* BLI_path_rel requires a trailing slash. */
212 char user_library_dirpath[FILE_MAX];
213 STRNCPY(user_library_dirpath, user_library.dirpath);
214 BLI_path_slash_ensure(user_library_dirpath, sizeof(user_library_dirpath));
215
216 char relative_filepath[FILE_MAX];
217 STRNCPY(relative_filepath, filepath);
218 BLI_path_rel(relative_filepath, user_library_dirpath);
219 const char *asset_blend_path = relative_filepath + 2; /* Strip out // prefix. */
220
222 "%s/%s/%s", asset_blend_path, BKE_idtype_idcode_to_name(idcode), idname);
223
224 return weak_ref;
225}
226
228 const char *idname,
229 const char *filepath)
230{
231 AssetWeakReference weak_ref;
233 weak_ref.relative_asset_identifier = BLI_sprintfN("%s/%s/%s/%s",
235 BLI_path_basename(filepath),
237 idname);
238
239 return weak_ref;
240}
241
242std::optional<std::string> asset_edit_id_save_as(Main &global_main,
243 const ID &id,
244 const StringRefNull name,
245 const bUserAssetLibrary &user_library,
246 AssetWeakReference &r_weak_ref,
248{
249 const std::string filepath = asset_blendfile_path_for_save(
250 user_library, name, GS(id.name), reports);
251
252 std::string final_full_asset_filepath;
253 const bool success = asset_write_in_library(
254 global_main, id, name, filepath, final_full_asset_filepath, reports);
255 if (!success) {
256 BKE_report(&reports, RPT_ERROR, "Failed to write to asset library");
257 return std::nullopt;
258 }
259
261 user_library, GS(id.name), name.c_str(), filepath.c_str());
262
263 BKE_reportf(&reports, RPT_INFO, "Saved \"%s\"", filepath.c_str());
264
265 return final_full_asset_filepath;
266}
267
268bool asset_edit_id_save(Main &global_main, const ID &id, ReportList &reports)
269{
270 if (!asset_edit_id_is_writable(id)) {
271 return false;
272 }
273
274 std::string final_full_asset_filepath;
275 const bool success = asset_write_in_library(global_main,
276 id,
277 id.name + 2,
278 id.lib->runtime->filepath_abs,
279 final_full_asset_filepath,
280 reports);
281
282 if (!success) {
283 BKE_report(&reports, RPT_ERROR, "Failed to write to asset library");
284 return false;
285 }
286
287 return true;
288}
289
291{
292 if (!asset_edit_id_is_editable(id)) {
293 return nullptr;
294 }
295
296 return asset_reload(global_main, id, &reports);
297}
298
300{
302 if (BLI_delete(id.lib->runtime->filepath_abs, false, false) != 0) {
303 BKE_report(&reports, RPT_ERROR, "Failed to delete asset library file");
304 return false;
305 }
306 }
307
308 BKE_id_delete(&global_main, &id);
309
310 return true;
311}
312
314 const ID_Type id_type,
315 const AssetWeakReference &weak_ref)
316{
317 /* Don't do this in file load. */
318 BLI_assert(!global_main.is_locked_for_linking);
319
320 char asset_full_path_buffer[FILE_MAX_LIBEXTRA];
321 char *asset_lib_path, *asset_group, *asset_name;
322
324 &weak_ref, asset_full_path_buffer, &asset_lib_path, &asset_group, &asset_name);
325 if (asset_lib_path == nullptr && asset_group == nullptr && asset_name == nullptr) {
326 return nullptr;
327 }
328
329 /* If this is the same file as we have open, use local datablock. */
330 if (asset_lib_path && STREQ(asset_lib_path, global_main.filepath)) {
331 asset_lib_path = nullptr;
332 }
333
334 BLI_assert(asset_name != nullptr);
335
336 /* Test if asset has been loaded already. */
338 &global_main, id_type, asset_name, asset_lib_path);
339 if (local_asset) {
340 return local_asset;
341 }
342
343 /* Try linking in the required file. */
344 if (asset_lib_path == nullptr) {
345 return nullptr;
346 }
347
348 return asset_link_id(global_main, id_type, asset_lib_path, asset_name);
349}
350
351std::optional<AssetWeakReference> asset_edit_weak_reference_from_id(const ID &id)
352{
353 /* Brush is local to the file. */
354 if (!id.lib) {
355 AssetWeakReference weak_ref;
356
357 weak_ref.asset_library_type = eAssetLibraryType::ASSET_LIBRARY_LOCAL;
359 "%s/%s", BKE_idtype_idcode_to_name(GS(id.name)), id.name + 2);
360
361 return weak_ref;
362 }
363
364 if (!asset_edit_id_is_editable(id)) {
365 return std::nullopt;
366 }
367
369 &U, id.lib->runtime->filepath_abs);
370
371 const short idcode = GS(id.name);
372
373 if (user_library && user_library->dirpath[0]) {
375 *user_library, idcode, id.name + 2, id.lib->runtime->filepath_abs);
376 }
377
378 return asset_weak_reference_for_essentials(idcode, id.name + 2, id.lib->runtime->filepath_abs);
379}
380
382{
383 return (id.lib && (id.lib->runtime->tag & LIBRARY_ASSET_EDITABLE));
384}
385
387{
388 return asset_edit_id_is_editable(id) && (id.lib->runtime->tag & LIBRARY_ASSET_FILE_WRITABLE);
389}
390
392{
393 if (!asset_edit_id_is_editable(id)) {
394 return &id;
395 }
396
397 return BKE_main_library_weak_reference_find(&global_main, id.lib->filepath, id.name);
398}
399
401{
402 ID *local_id = asset_edit_id_find_local(global_main, id);
403 if (local_id) {
404 return local_id;
405 }
406
407 /* Make local and create weak library reference for reuse. */
408 BKE_lib_id_make_local(&global_main,
409 &id,
412 BLI_assert(id.newid != nullptr);
413 BKE_main_library_weak_reference_add(id.newid, id.lib->filepath, id.name);
414
415 return id.newid;
416}
417
418} // 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:147
const IDTypeInfo * BKE_idtype_get_info_from_idcode(short id_code)
Definition idtype.cc:142
const char * BKE_idtype_idcode_to_name(short idcode)
Definition idtype.cc:165
const char * BKE_idtype_idcode_to_name_plural(short idcode)
Definition idtype.cc:172
void BKE_id_delete(Main *bmain, void *idv) ATTR_NONNULL()
ID * BKE_libblock_find_name_and_library_filepath(Main *bmain, short type, const char *name, const char *lib_filepath_abs)
Definition lib_id.cc:1749
IDNewNameResult BKE_libblock_rename(Main &bmain, ID &id, blender::StringRefNull name, const IDNewNameMode mode=IDNewNameMode::RenameExistingNever)
Definition lib_id.cc:2350
@ LIB_ID_MAKELOCAL_INDIRECT
@ LIB_ID_MAKELOCAL_ASSET_DATA_CLEAR
@ LIB_ID_MAKELOCAL_FORCE_COPY
bool BKE_lib_id_make_local(Main *bmain, ID *id, int flags)
Definition lib_id.cc:586
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:788
const char * BKE_main_blendfile_path(const Main *bmain) ATTR_NONNULL()
Definition main.cc:872
ID * BKE_main_library_weak_reference_find(Main *bmain, const char *library_filepath, const char *library_id_name)
Definition main.cc:757
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:126
#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: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:461
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:961
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
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.
ID_Type
@ ASSET_LIBRARY_CUSTOM
@ ASSET_LIBRARY_ESSENTIALS
#define FILE_MAX_LIBEXTRA
ReportList * reports
Definition WM_types.hh:1025
#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 ID_IS_LINKED(_id)
#define GS(a)
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 * relative_asset_identifier
const char * asset_library_identifier
const char * name
const char * name_plural
Definition DNA_ID.h:404
struct Library * lib
Definition DNA_ID.h:410
char name[66]
Definition DNA_ID.h:415
LibraryRuntimeHandle * runtime
Definition DNA_ID.h:516
bool is_locked_for_linking
Definition BKE_main.hh:196
bool is_asset_edit_file
Definition BKE_main.hh:171
char filepath[1024]
Definition BKE_main.hh:155
i
Definition text_draw.cc:230
static DynamicLibrary lib