Assets: "All" asset library

Adds a new built-in asset library that contains all other asset
libraries visible in the asset library selector menu. This also means
all their asset catalogs will be displayed as a single merged tree. The
asset catalogs are not editable, since this would require support for
writing multiple catalog definition files, which isn't there yet.

Often it's not relevant where an asset comes from. Users just want to be
able to get an asset quickly, comparable to how people use a search
engine to browse images or the web itself, instead of first going to a
dedicated platform. They don't want to bother with first choosing where
they want the result to come from.
This especially is needed for the Asset Shelf (T102879) that is being
developed for the brush assets project (T101895). With this, users will
have access to all their brushes efficiently from the 3D view, without
much browsing.

Did an informal review of the asset system bits with Sybren.
This commit is contained in:
Julian Eisel 2023-01-10 15:27:28 +01:00
parent 250eda36b8
commit 35e54b52e6
Notes: blender-bot 2023-02-14 09:43:37 +01:00
Referenced by issue #102879, Asset Shelf (for brush assets, pose libraries, etc.)
20 changed files with 458 additions and 89 deletions

View File

@ -45,11 +45,17 @@ class AssetCatalogService {
Vector<std::unique_ptr<AssetCatalogCollection>> undo_snapshots_;
Vector<std::unique_ptr<AssetCatalogCollection>> redo_snapshots_;
const bool is_read_only_ = false;
public:
static const CatalogFilePath DEFAULT_CATALOG_FILENAME;
struct read_only_tag {
};
public:
AssetCatalogService();
explicit AssetCatalogService(read_only_tag);
explicit AssetCatalogService(const CatalogFilePath &asset_library_root);
/**
@ -62,11 +68,24 @@ class AssetCatalogService {
void tag_has_unsaved_changes(AssetCatalog *edited_catalog);
bool has_unsaved_changes() const;
/**
* Check if this is a read-only service meaning the user shouldn't be able to do edits. This is
* not enforced by internal catalog code, the catalog service user is responsible for it. For
* example the UI should disallow edits.
*/
bool is_read_only() const;
/** Load asset catalog definitions from the files found in the asset library. */
void load_from_disk();
/** Load asset catalog definitions from the given file or directory. */
void load_from_disk(const CatalogFilePath &file_or_directory_path);
/**
* Duplicate the catalogs from \a other_service into this one. Does not rebuild the tree, this
* needs to be done by the caller (call #rebuild_tree()!).
*/
void add_from_existing(const AssetCatalogService &other_service);
/**
* Write the catalog definitions to disk.
*
@ -105,6 +124,15 @@ class AssetCatalogService {
*/
void reload_catalogs();
/**
* Make sure the tree is updated to the latest collection of catalogs stored in this service.
* Does not depend on a CDF file being available so this can be called on a service that stores
* catalogs that are not stored in a CDF.
* Most API functions that modify catalog data will trigger this, unless otherwise specified (for
* batch operations).
*/
void rebuild_tree();
/** Return catalog with the given ID. Return nullptr if not found. */
AssetCatalog *find_catalog(CatalogID catalog_id) const;
@ -222,7 +250,6 @@ class AssetCatalogService {
const CatalogFilePath &blend_file_path);
std::unique_ptr<AssetCatalogTree> read_into_tree();
void rebuild_tree();
/**
* For every catalog, ensure that its parent path also has a known catalog.
@ -270,6 +297,11 @@ class AssetCatalogCollection {
AssetCatalogCollection(AssetCatalogCollection &&other) noexcept = default;
std::unique_ptr<AssetCatalogCollection> deep_copy() const;
/**
* Copy the catalogs from \a other and append them to this collection. Copies no other data
* otherwise.
*/
void add_catalogs_from_existing(const AssetCatalogCollection &other);
protected:
static OwningAssetCatalogMap copy_catalog_map(const OwningAssetCatalogMap &orig);

View File

@ -56,6 +56,8 @@ class AssetLibrary {
*/
std::unique_ptr<AssetStorage> asset_storage_;
std::function<void(AssetLibrary &self)> on_refresh_;
bCallbackFuncStore on_save_callback_store_{};
public:
@ -65,6 +67,8 @@ class AssetLibrary {
std::unique_ptr<AssetCatalogService> catalog_service;
friend class AssetLibraryService;
public:
/**
* \param root_path: If this is an asset library on disk, the top-level directory path.
@ -72,6 +76,16 @@ class AssetLibrary {
AssetLibrary(StringRef root_path = "");
~AssetLibrary();
/**
* Execute \a fn for every asset library that is loaded. The asset library is passed to the
* \a fn call.
*
* \param skip_all_library: When true, the \a fn will also be executed for the "All" asset
* library. This is just a combination of the other ones, so usually
* iterating over it is redundant.
*/
static void foreach_loaded(FunctionRef<void(AssetLibrary &)> fn, bool include_all_library);
void load_catalogs();
/** Load catalogs that have changed on disk. */
@ -128,9 +142,6 @@ class AssetLibrary {
AssetIdentifier asset_identifier_from_library(StringRef relative_asset_path);
StringRefNull root_path() const;
private:
std::optional<int> find_asset_index(const AssetRepresentation &asset);
};
Vector<AssetLibraryReference> all_valid_asset_library_refs();
@ -138,12 +149,22 @@ Vector<AssetLibraryReference> all_valid_asset_library_refs();
} // namespace blender::asset_system
/**
* Load the data for an asset library, but not the asset representations themselves (loading these
* is currently not done in the asset system).
*
* For the "All" asset library (#ASSET_LIBRARY_ALL), every other known asset library will be
* loaded as well. So a call to #AssetLibrary::foreach_loaded() can be expected to iterate over all
* libraries.
*
* \warning Catalogs are reloaded, invalidating catalog pointers. Do not store catalog pointers,
* store CatalogIDs instead and lookup the catalog where needed.
*/
blender::asset_system::AssetLibrary *AS_asset_library_load(
const Main *bmain, const AssetLibraryReference &library_reference);
std::string AS_asset_library_root_path_from_library_ref(
const AssetLibraryReference &library_reference);
/**
* Try to find an appropriate location for an asset library root from a file or directory path.
* Does not check if \a input_path exists.

View File

@ -43,6 +43,11 @@ AssetCatalogService::AssetCatalogService()
{
}
AssetCatalogService::AssetCatalogService(read_only_tag) : AssetCatalogService()
{
const_cast<bool &>(is_read_only_) = true;
}
AssetCatalogService::AssetCatalogService(const CatalogFilePath &asset_library_root)
: AssetCatalogService()
{
@ -51,6 +56,8 @@ AssetCatalogService::AssetCatalogService(const CatalogFilePath &asset_library_ro
void AssetCatalogService::tag_has_unsaved_changes(AssetCatalog *edited_catalog)
{
BLI_assert(!is_read_only_);
if (edited_catalog) {
edited_catalog->flags.has_unsaved_changes = true;
}
@ -85,6 +92,11 @@ bool AssetCatalogService::has_unsaved_changes() const
return catalog_collection_->has_unsaved_changes_;
}
bool AssetCatalogService::is_read_only() const
{
return is_read_only_;
}
void AssetCatalogService::tag_all_catalogs_as_unsaved_changes()
{
for (auto &catalog : catalog_collection_->catalogs_.values()) {
@ -322,6 +334,11 @@ void AssetCatalogService::load_from_disk(const CatalogFilePath &file_or_director
rebuild_tree();
}
void AssetCatalogService::add_from_existing(const AssetCatalogService &other_service)
{
catalog_collection_->add_catalogs_from_existing(*other_service.catalog_collection_);
}
void AssetCatalogService::load_directory_recursive(const CatalogFilePath &directory_path)
{
/* TODO(@sybren): implement proper multi-file support. For now, just load
@ -452,6 +469,8 @@ bool AssetCatalogService::is_catalog_known_with_unsaved_changes(const CatalogID
bool AssetCatalogService::write_to_disk(const CatalogFilePath &blend_file_path)
{
BLI_assert(!is_read_only_);
if (!write_to_disk_ex(blend_file_path)) {
return false;
}
@ -625,6 +644,7 @@ void AssetCatalogService::undo()
void AssetCatalogService::redo()
{
BLI_assert(!is_read_only_);
BLI_assert_msg(is_redo_possbile(), "Redo stack is empty");
undo_snapshots_.append(std::move(catalog_collection_));
@ -634,6 +654,7 @@ void AssetCatalogService::redo()
void AssetCatalogService::undo_push()
{
BLI_assert(!is_read_only_);
std::unique_ptr<AssetCatalogCollection> snapshot = catalog_collection_->deep_copy();
undo_snapshots_.append(std::move(snapshot));
redo_snapshots_.clear();
@ -657,15 +678,24 @@ std::unique_ptr<AssetCatalogCollection> AssetCatalogCollection::deep_copy() cons
return copy;
}
static void copy_catalog_map_into_existing(const OwningAssetCatalogMap &source,
OwningAssetCatalogMap &dest)
{
for (const auto &orig_catalog_uptr : source.values()) {
auto copy_catalog_uptr = std::make_unique<AssetCatalog>(*orig_catalog_uptr);
dest.add_new(copy_catalog_uptr->catalog_id, std::move(copy_catalog_uptr));
}
}
void AssetCatalogCollection::add_catalogs_from_existing(const AssetCatalogCollection &other)
{
copy_catalog_map_into_existing(other.catalogs_, catalogs_);
}
OwningAssetCatalogMap AssetCatalogCollection::copy_catalog_map(const OwningAssetCatalogMap &orig)
{
OwningAssetCatalogMap copy;
for (const auto &orig_catalog_uptr : orig.values()) {
auto copy_catalog_uptr = std::make_unique<AssetCatalog>(*orig_catalog_uptr);
copy.add_new(copy_catalog_uptr->catalog_id, std::move(copy_catalog_uptr));
}
copy_catalog_map_into_existing(orig, copy);
return copy;
}

View File

@ -65,6 +65,12 @@ bool AS_asset_library_has_any_unsaved_catalogs()
return service->has_any_unsaved_catalogs();
}
std::string AS_asset_library_root_path_from_library_ref(
const AssetLibraryReference &library_reference)
{
return AssetLibraryService::root_path_from_library_ref(library_reference);
}
std::string AS_asset_library_find_suitable_root_path_from_path(
const blender::StringRefNull input_path)
{
@ -114,9 +120,11 @@ void AS_asset_library_refresh_catalog_simplename(struct ::AssetLibrary *asset_li
void AS_asset_library_remap_ids(const IDRemapper *mappings)
{
AssetLibraryService *service = AssetLibraryService::get();
service->foreach_loaded_asset_library([mappings](asset_system::AssetLibrary &library) {
library.remap_ids_and_remove_invalid(*mappings);
});
service->foreach_loaded_asset_library(
[mappings](asset_system::AssetLibrary &library) {
library.remap_ids_and_remove_invalid(*mappings);
},
true);
}
namespace blender::asset_system {
@ -135,6 +143,13 @@ AssetLibrary::~AssetLibrary()
}
}
void AssetLibrary::foreach_loaded(FunctionRef<void(AssetLibrary &)> fn,
const bool include_all_library)
{
AssetLibraryService *service = AssetLibraryService::get();
service->foreach_loaded_asset_library(fn, include_all_library);
}
void AssetLibrary::load_catalogs()
{
auto catalog_service = std::make_unique<AssetCatalogService>(root_path());
@ -144,7 +159,9 @@ void AssetLibrary::load_catalogs()
void AssetLibrary::refresh()
{
this->catalog_service->reload_catalogs();
if (on_refresh_) {
on_refresh_(*this);
}
}
AssetRepresentation &AssetLibrary::add_external_asset(StringRef relative_asset_path,

View File

@ -14,6 +14,7 @@
#include "CLG_log.h"
#include "AS_asset_catalog_tree.hh"
#include "AS_asset_library.hh"
#include "asset_library_service.hh"
#include "utils.hh"
@ -56,23 +57,29 @@ void AssetLibraryService::destroy()
AssetLibrary *AssetLibraryService::get_asset_library(
const Main *bmain, const AssetLibraryReference &library_reference)
{
if (library_reference.type == ASSET_LIBRARY_LOCAL) {
/* For the "Current File" library we get the asset library root path based on main. */
std::string root_path = bmain ? AS_asset_library_find_suitable_root_path_from_main(bmain) : "";
const eAssetLibraryType type = eAssetLibraryType(library_reference.type);
if (root_path.empty()) {
/* File wasn't saved yet. */
return get_asset_library_current_file();
switch (type) {
case ASSET_LIBRARY_LOCAL: {
/* For the "Current File" library we get the asset library root path based on main. */
std::string root_path = bmain ? AS_asset_library_find_suitable_root_path_from_main(bmain) :
"";
if (root_path.empty()) {
/* File wasn't saved yet. */
return get_asset_library_current_file();
}
return get_asset_library_on_disk(root_path);
}
case ASSET_LIBRARY_ALL:
return get_asset_library_all(bmain);
case ASSET_LIBRARY_CUSTOM: {
std::string root_path = root_path_from_library_ref(library_reference);
return get_asset_library_on_disk(root_path);
}
if (library_reference.type == ASSET_LIBRARY_CUSTOM) {
bUserAssetLibrary *user_library = BKE_preferences_asset_library_find_from_index(
&U, library_reference.custom_library_index);
if (user_library) {
return get_asset_library_on_disk(user_library->path);
if (!root_path.empty()) {
return get_asset_library_on_disk(root_path);
}
break;
}
}
@ -100,6 +107,8 @@ AssetLibrary *AssetLibraryService::get_asset_library_on_disk(StringRefNull root_
lib->on_blend_save_handler_register();
lib->load_catalogs();
/* Reload catalogs on refresh. */
lib->on_refresh_ = [](AssetLibrary &self) { self.catalog_service->reload_catalogs(); };
on_disk_libraries_.add_new(normalized_root_path, std::move(lib_uptr));
CLOG_INFO(&LOG, 2, "get \"%s\" (loaded)", normalized_root_path.c_str());
@ -110,6 +119,7 @@ AssetLibrary *AssetLibraryService::get_asset_library_current_file()
{
if (current_file_library_) {
CLOG_INFO(&LOG, 2, "get current file lib (cached)");
current_file_library_->refresh();
}
else {
CLOG_INFO(&LOG, 2, "get current file lib (loaded)");
@ -121,6 +131,74 @@ AssetLibrary *AssetLibraryService::get_asset_library_current_file()
return lib;
}
static void rebuild_all_library(AssetLibrary &all_library, const bool reload_catalogs)
{
/* Start with empty catalog storage. */
all_library.catalog_service = std::make_unique<AssetCatalogService>(
AssetCatalogService::read_only_tag());
AssetLibrary::foreach_loaded(
[&](AssetLibrary &nested) {
if (reload_catalogs) {
nested.catalog_service->reload_catalogs();
}
all_library.catalog_service->add_from_existing(*nested.catalog_service);
},
false);
all_library.catalog_service->rebuild_tree();
}
AssetLibrary *AssetLibraryService::get_asset_library_all(const Main *bmain)
{
/* (Re-)load all other asset libraries. */
for (AssetLibraryReference &library_ref : all_valid_asset_library_refs()) {
/* Skip self :) */
if (library_ref.type == ASSET_LIBRARY_ALL) {
continue;
}
/* Ensure all asset libraries are loaded. */
get_asset_library(bmain, library_ref);
}
if (all_library_) {
CLOG_INFO(&LOG, 2, "get all lib (cached)");
all_library_->refresh();
return all_library_.get();
}
CLOG_INFO(&LOG, 2, "get all lib (loaded)");
all_library_ = std::make_unique<AssetLibrary>();
/* Don't reload catalogs on this initial read, they've just been loaded above. */
rebuild_all_library(*all_library_, /*reload_catlogs=*/false);
all_library_->on_refresh_ = [](AssetLibrary &all_library) {
rebuild_all_library(all_library, /*reload_catalogs=*/true);
};
return all_library_.get();
}
std::string AssetLibraryService::root_path_from_library_ref(
const AssetLibraryReference &library_reference)
{
if (ELEM(library_reference.type, ASSET_LIBRARY_ALL, ASSET_LIBRARY_LOCAL)) {
return "";
}
BLI_assert(library_reference.type == ASSET_LIBRARY_CUSTOM);
BLI_assert(library_reference.custom_library_index >= 0);
bUserAssetLibrary *user_library = BKE_preferences_asset_library_find_from_index(
&U, library_reference.custom_library_index);
if (!user_library || !user_library->path[0]) {
return "";
}
return user_library->path;
}
void AssetLibraryService::allocate_service_instance()
{
instance_ = std::make_unique<AssetLibraryService>();
@ -164,21 +242,25 @@ void AssetLibraryService::app_handler_unregister()
bool AssetLibraryService::has_any_unsaved_catalogs() const
{
if (current_file_library_ && current_file_library_->catalog_service->has_unsaved_changes()) {
return true;
}
bool has_unsaved_changes = false;
for (const auto &asset_lib_uptr : on_disk_libraries_.values()) {
if (asset_lib_uptr->catalog_service->has_unsaved_changes()) {
return true;
}
}
return false;
foreach_loaded_asset_library(
[&has_unsaved_changes](AssetLibrary &library) {
if (library.catalog_service->has_unsaved_changes()) {
has_unsaved_changes = true;
}
},
true);
return has_unsaved_changes;
}
void AssetLibraryService::foreach_loaded_asset_library(FunctionRef<void(AssetLibrary &)> fn) const
void AssetLibraryService::foreach_loaded_asset_library(FunctionRef<void(AssetLibrary &)> fn,
const bool include_all_library) const
{
if (include_all_library && all_library_) {
fn(*all_library_);
}
if (current_file_library_) {
fn(*current_file_library_);
}

View File

@ -40,6 +40,8 @@ class AssetLibraryService {
* the file was saved, a valid path for the library can be determined and #on_disk_libraries_
* above should be used. */
std::unique_ptr<AssetLibrary> current_file_library_;
/** The "all" asset library, merging all other libraries into one. */
std::unique_ptr<AssetLibrary> all_library_;
/* Handlers for managing the life cycle of the AssetLibraryService instance. */
bCallbackFuncStore on_load_callback_store_;
@ -55,6 +57,8 @@ class AssetLibraryService {
/** Destroy the AssetLibraryService singleton. It will be reallocated by #get() if necessary. */
static void destroy();
static std::string root_path_from_library_ref(const AssetLibraryReference &library_reference);
AssetLibrary *get_asset_library(const Main *bmain,
const AssetLibraryReference &library_reference);
@ -66,10 +70,15 @@ class AssetLibraryService {
/** Get the "Current File" asset library. */
AssetLibrary *get_asset_library_current_file();
/** Get the "All" asset library, which loads all others and merges them into one. */
AssetLibrary *get_asset_library_all(const Main *bmain);
/** Returns whether there are any known asset libraries with unsaved catalog edits. */
bool has_any_unsaved_catalogs() const;
void foreach_loaded_asset_library(FunctionRef<void(AssetLibrary &)> fn) const;
/** See AssetLibrary::foreach_loaded(). */
void foreach_loaded_asset_library(FunctionRef<void(AssetLibrary &)> fn,
bool include_all_library) const;
protected:
/** Allocate a new instance of the service and assign it to `instance_`. */

View File

@ -19,6 +19,8 @@ struct Main;
void ED_asset_catalogs_save_from_main_path(struct AssetLibrary *library, const struct Main *bmain);
/** Saving catalog edits when the file is saved is a global option shared for each asset library,
* and as such ignores the per asset library #ED_asset_catalogs_read_only(). */
void ED_asset_catalogs_set_save_catalogs_when_file_is_saved(bool should_save);
bool ED_asset_catalogs_get_save_catalogs_when_file_is_saved(void);

View File

@ -6,6 +6,10 @@
* UI/Editor level API for catalog operations, creating richer functionality than the asset system
* catalog API provides (which this uses internally).
*
* Functions can be expected to not perform any change when #ED_asset_catalogs_read_only() returns
* true. Generally UI code should disable such functionality in this case, so these functions are
* not called at all.
*
* Note that `ED_asset_catalog.h` is part of this API.
*/
@ -19,6 +23,12 @@
struct AssetLibrary;
/**
* Returns if the catalogs of \a library are allowed to be editable, or if the UI should forbid
* edits.
*/
[[nodiscard]] bool ED_asset_catalogs_read_only(const AssetLibrary &library);
blender::asset_system::AssetCatalog *ED_asset_catalog_add(
AssetLibrary *library, blender::StringRefNull name, blender::StringRef parent_path = nullptr);
void ED_asset_catalog_remove(AssetLibrary *library,

View File

@ -29,10 +29,13 @@ AssetLibraryReference ED_asset_library_reference_from_enum_value(int value);
* Since this is meant for UI display, skips non-displayable libraries, that is, libraries with an
* empty name or path.
*
* \param include_local_library: Whether to include the "Current File" library or not.
* \param include_generated: Whether to include libraries that are generated and thus cannot be
* written to. Setting this to false means only custom libraries will be
* included, since they are stored on disk with a single root directory,
* thus have a well defined location that can be written to.
*/
const struct EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(
bool include_local_library);
bool include_generated);
#ifdef __cplusplus
}

View File

@ -15,6 +15,21 @@ struct AssetLibraryReference;
struct FileDirEntry;
struct bContext;
namespace blender::asset_system {
class AssetLibrary;
}
/**
* Get the asset library being read into an asset-list and identified using \a library_reference.
*
* \note The asset library may be allocated and loaded asynchronously, so it's not available right
* after fetching, and this function will return null. The asset list code sends `NC_ASSET |
* ND_ASSET_LIST_READING` notifiers until loading is done, they can be used to continuously
* call this function to retrieve the asset library once available.
*/
blender::asset_system::AssetLibrary *ED_assetlist_library_get_once_available(
const AssetLibraryReference &library_reference);
/* Can return false to stop iterating. */
using AssetListIterFn = blender::FunctionRef<bool(AssetHandle)>;
void ED_assetlist_iterate(const AssetLibraryReference &library_reference, AssetListIterFn fn);

View File

@ -19,6 +19,13 @@
using namespace blender;
using namespace blender::asset_system;
bool ED_asset_catalogs_read_only(const ::AssetLibrary &library)
{
asset_system::AssetCatalogService *catalog_service = AS_asset_library_get_catalog_service(
&library);
return catalog_service->is_read_only();
}
struct CatalogUniqueNameFnData {
const AssetCatalogService &catalog_service;
StringRef parent_path;
@ -53,6 +60,9 @@ asset_system::AssetCatalog *ED_asset_catalog_add(::AssetLibrary *library,
if (!catalog_service) {
return nullptr;
}
if (ED_asset_catalogs_read_only(*library)) {
return nullptr;
}
std::string unique_name = catalog_name_ensure_unique(*catalog_service, name, parent_path);
AssetCatalogPath fullpath = AssetCatalogPath(parent_path) / unique_name;
@ -76,6 +86,9 @@ void ED_asset_catalog_remove(::AssetLibrary *library, const CatalogID &catalog_i
BLI_assert_unreachable();
return;
}
if (ED_asset_catalogs_read_only(*library)) {
return;
}
catalog_service->undo_push();
catalog_service->tag_has_unsaved_changes(nullptr);
@ -93,6 +106,9 @@ void ED_asset_catalog_rename(::AssetLibrary *library,
BLI_assert_unreachable();
return;
}
if (ED_asset_catalogs_read_only(*library)) {
return;
}
AssetCatalog *catalog = catalog_service->find_catalog(catalog_id);
@ -120,6 +136,9 @@ void ED_asset_catalog_move(::AssetLibrary *library,
BLI_assert_unreachable();
return;
}
if (ED_asset_catalogs_read_only(*library)) {
return;
}
AssetCatalog *src_catalog = catalog_service->find_catalog(src_catalog_id);
if (!src_catalog) {
@ -161,6 +180,9 @@ void ED_asset_catalogs_save_from_main_path(::AssetLibrary *library, const Main *
BLI_assert_unreachable();
return;
}
if (ED_asset_catalogs_read_only(*library)) {
return;
}
/* Since writing to disk also means loading any on-disk changes, it may be a good idea to store
* an undo step. */

View File

@ -47,7 +47,7 @@ AssetLibraryReference ED_asset_library_reference_from_enum_value(int value)
if (value < ASSET_LIBRARY_CUSTOM) {
library.type = value;
library.custom_library_index = -1;
BLI_assert(ELEM(value, ASSET_LIBRARY_LOCAL));
BLI_assert(ELEM(value, ASSET_LIBRARY_ALL, ASSET_LIBRARY_LOCAL));
return library;
}
@ -70,16 +70,18 @@ AssetLibraryReference ED_asset_library_reference_from_enum_value(int value)
return library;
}
const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(
const bool include_local_library)
const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(const bool include_generated)
{
EnumPropertyItem *item = nullptr;
int totitem = 0;
if (include_local_library) {
const EnumPropertyItem predefined_items[] = {
/* For the future. */
// {ASSET_REPO_BUNDLED, "BUNDLED", 0, "Bundled", "Show the default user assets"},
if (include_generated) {
const EnumPropertyItem generated_items[] = {
{ASSET_LIBRARY_ALL,
"ALL",
ICON_NONE,
"All",
"Show assets from all of the listed asset libraries"},
{ASSET_LIBRARY_LOCAL,
"LOCAL",
ICON_CURRENT_FILE,
@ -88,8 +90,9 @@ const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(
{0, nullptr, 0, nullptr, nullptr},
};
/* Add predefined items. */
RNA_enum_items_add(&item, &totitem, predefined_items);
/* Add predefined libraries that are generated and not simple directories that can be written
* to. */
RNA_enum_items_add(&item, &totitem, generated_items);
}
/* Add separator if needed. */

View File

@ -12,6 +12,8 @@
#include <optional>
#include <string>
#include "AS_asset_library.hh"
#include "BKE_context.h"
#include "BLI_map.hh"
@ -112,6 +114,7 @@ class AssetList : NonCopyable {
bool needsRefetch() const;
bool isLoaded() const;
asset_system::AssetLibrary *asset_library() const;
void iterate(AssetListIterFn fn) const;
bool listen(const wmNotifier &notifier) const;
int size() const;
@ -127,16 +130,7 @@ AssetList::AssetList(eFileSelectType filesel_type, const AssetLibraryReference &
void AssetList::setup()
{
FileList *files = filelist_;
bUserAssetLibrary *user_library = nullptr;
/* Ensure valid repository, or fall-back to local one. */
if (library_ref_.type == ASSET_LIBRARY_CUSTOM) {
BLI_assert(library_ref_.custom_library_index >= 0);
user_library = BKE_preferences_asset_library_find_from_index(
&U, library_ref_.custom_library_index);
}
std::string asset_lib_path = AS_asset_library_root_path_from_library_ref(library_ref_);
/* Relevant bits from file_refresh(). */
/* TODO pass options properly. */
@ -158,13 +152,10 @@ void AssetList::setup()
filelist_setindexer(files, use_asset_indexer ? &file_indexer_asset : &file_indexer_noop);
char path[FILE_MAXDIR] = "";
if (user_library) {
BLI_strncpy(path, user_library->path, sizeof(path));
filelist_setdir(files, path);
}
else {
filelist_setdir(files, path);
if (!asset_lib_path.empty()) {
BLI_strncpy(path, asset_lib_path.c_str(), sizeof(path));
}
filelist_setdir(files, path);
}
void AssetList::fetch(const bContext &C)
@ -195,6 +186,11 @@ bool AssetList::isLoaded() const
return filelist_is_ready(filelist_);
}
asset_system::AssetLibrary *AssetList::asset_library() const
{
return reinterpret_cast<asset_system::AssetLibrary *>(filelist_asset_library(filelist_));
}
void AssetList::iterate(AssetListIterFn fn) const
{
FileList *files = filelist_;
@ -373,7 +369,9 @@ void AssetListStorage::remapID(ID *id_new, ID *id_old)
std::optional<eFileSelectType> AssetListStorage::asset_library_reference_to_fileselect_type(
const AssetLibraryReference &library_reference)
{
switch (library_reference.type) {
switch (eAssetLibraryType(library_reference.type)) {
case ASSET_LIBRARY_ALL:
return FILE_ASSET_LIBRARY_ALL;
case ASSET_LIBRARY_CUSTOM:
return FILE_ASSET_LIBRARY;
case ASSET_LIBRARY_LOCAL:
@ -412,6 +410,7 @@ AssetListStorage::AssetListMap &AssetListStorage::global_storage()
/** \name C-API
* \{ */
using namespace blender;
using namespace blender::ed::asset;
void ED_assetlist_storage_fetch(const AssetLibraryReference *library_reference, const bContext *C)
@ -462,6 +461,16 @@ void ED_assetlist_iterate(const AssetLibraryReference &library_reference, AssetL
}
}
asset_system::AssetLibrary *ED_assetlist_library_get_once_available(
const AssetLibraryReference &library_reference)
{
const AssetList *list = AssetListStorage::lookup_list(library_reference);
if (!list) {
return nullptr;
}
return list->asset_library();
}
ImBuf *ED_assetlist_asset_image_get(const AssetHandle *asset_handle)
{
ImBuf *imbuf = filelist_file_getimage(asset_handle->file_data);

View File

@ -435,7 +435,18 @@ static void ASSET_OT_library_refresh(struct wmOperatorType *ot)
static bool asset_catalog_operator_poll(bContext *C)
{
const SpaceFile *sfile = CTX_wm_space_file(C);
return sfile && ED_fileselect_active_asset_library_get(sfile);
if (!sfile) {
return false;
}
const AssetLibrary *asset_library = ED_fileselect_active_asset_library_get(sfile);
if (!asset_library) {
return false;
}
if (ED_asset_catalogs_read_only(*asset_library)) {
CTX_wm_operator_poll_msg_set(C, "Asset catalogs cannot be edited in this asset library");
return false;
}
return true;
}
static int asset_catalog_new_exec(bContext *C, wmOperator *op)

View File

@ -119,6 +119,8 @@ class AssetCatalogDropController : public ui::AbstractViewItemDropController {
static AssetCatalog *get_drag_catalog(const wmDrag &drag, const ::AssetLibrary &asset_library);
static bool has_droppable_asset(const wmDrag &drag, const char **r_disabled_hint);
static bool can_modify_catalogs(const ::AssetLibrary &asset_library,
const char **r_disabled_hint);
static bool drop_assets_into_catalog(struct bContext *C,
const AssetCatalogTreeView &tree_view,
const wmDrag &drag,
@ -321,7 +323,9 @@ void AssetCatalogTreeViewItem::build_context_menu(bContext &C, uiLayout &column)
bool AssetCatalogTreeViewItem::supports_renaming() const
{
return true;
const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>(
get_tree_view());
return !ED_asset_catalogs_read_only(*tree_view.asset_library_);
}
bool AssetCatalogTreeViewItem::rename(StringRefNull new_name)
@ -360,7 +364,12 @@ AssetCatalogDropController::AssetCatalogDropController(AssetCatalogTreeView &tre
bool AssetCatalogDropController::can_drop(const wmDrag &drag, const char **r_disabled_hint) const
{
if (drag.type == WM_DRAG_ASSET_CATALOG) {
const AssetCatalog *drag_catalog = get_drag_catalog(drag, get_asset_library());
const ::AssetLibrary &library = get_asset_library();
if (!can_modify_catalogs(library, r_disabled_hint)) {
return false;
}
const AssetCatalog *drag_catalog = get_drag_catalog(drag, library);
/* NOTE: Technically it's not an issue to allow this (the catalog will just receive a new
* path and the catalog system will generate missing parents from the path). But it does
* appear broken to users, so disabling entirely. */
@ -512,6 +521,16 @@ bool AssetCatalogDropController::has_droppable_asset(const wmDrag &drag,
return false;
}
bool AssetCatalogDropController::can_modify_catalogs(const ::AssetLibrary &library,
const char **r_disabled_hint)
{
if (ED_asset_catalogs_read_only(library)) {
*r_disabled_hint = "Catalogs cannot be edited in this asset library";
return false;
}
return true;
}
::AssetLibrary &AssetCatalogDropController::get_asset_library() const
{
return *get_view<AssetCatalogTreeView>().asset_library_;
@ -579,9 +598,12 @@ bool AssetCatalogTreeViewAllItem::DropController::can_drop(const wmDrag &drag,
if (drag.type != WM_DRAG_ASSET_CATALOG) {
return false;
}
::AssetLibrary &library = *get_view<AssetCatalogTreeView>().asset_library_;
if (!AssetCatalogDropController::can_modify_catalogs(library, r_disabled_hint)) {
return false;
}
const AssetCatalog *drag_catalog = AssetCatalogDropController::get_drag_catalog(
drag, *get_view<AssetCatalogTreeView>().asset_library_);
const AssetCatalog *drag_catalog = AssetCatalogDropController::get_drag_catalog(drag, library);
if (drag_catalog->path.parent() == "") {
*r_disabled_hint = "Catalog is already placed at the highest level";
return false;

View File

@ -320,6 +320,10 @@ static void filelist_readjob_main_assets(FileListReadJob *job_params,
bool *stop,
bool *do_update,
float *progress);
static void filelist_readjob_all_asset_library(FileListReadJob *job_params,
bool *stop,
bool *do_update,
float *progress);
/* helper, could probably go in BKE actually? */
static int groupname_to_code(const char *group);
@ -1362,11 +1366,10 @@ static bool filelist_checkdir_main(FileList *filelist, char *r_dir, const bool d
return filelist_checkdir_lib(filelist, r_dir, do_change);
}
static bool filelist_checkdir_main_assets(FileList * /*filelist*/,
char * /*r_dir*/,
const bool /*do_change*/)
static bool filelist_checkdir_return_always_valid(struct FileList * /*filelist*/,
char * /*r_dir*/,
const bool /*do_change*/)
{
/* Main is always valid. */
return true;
}
@ -1768,12 +1771,19 @@ void filelist_settype(FileList *filelist, short type)
filelist->tags |= FILELIST_TAGS_USES_MAIN_DATA;
break;
case FILE_MAIN_ASSET:
filelist->check_dir_fn = filelist_checkdir_main_assets;
filelist->check_dir_fn = filelist_checkdir_return_always_valid;
filelist->read_job_fn = filelist_readjob_main_assets;
filelist->prepare_filter_fn = prepare_filter_asset_library;
filelist->filter_fn = is_filtered_main_assets;
filelist->tags |= FILELIST_TAGS_USES_MAIN_DATA | FILELIST_TAGS_NO_THREADS;
break;
case FILE_ASSET_LIBRARY_ALL:
filelist->check_dir_fn = filelist_checkdir_return_always_valid;
filelist->read_job_fn = filelist_readjob_all_asset_library;
filelist->prepare_filter_fn = prepare_filter_asset_library;
filelist->filter_fn = is_filtered_asset_library;
filelist->tags |= FILELIST_TAGS_USES_MAIN_DATA;
break;
default:
filelist->check_dir_fn = filelist_checkdir_dir;
filelist->read_job_fn = filelist_readjob_dir;
@ -2852,6 +2862,9 @@ void filelist_entry_parent_select_set(FileList *filelist,
bool filelist_islibrary(FileList *filelist, char *dir, char **r_group)
{
if (filelist->asset_library) {
return true;
}
return BLO_library_path_explode(filelist->filelist.root, dir, r_group, nullptr);
}
@ -2893,6 +2906,11 @@ struct FileListReadJob {
* `Materials/Material.001`). */
char cur_relbase[FILE_MAX_LIBEXTRA];
/** The current asset library to load. Usually the same as #FileList.asset_library, however
* sometimes the #FileList one is a combination of multiple other ones ("All" asset library),
* which need to be loaded individually. Then this can be set to override the #FileList library.
* Use this in all loading code. */
asset_system::AssetLibrary *load_asset_library;
/** Set to request a partial read that only adds files representing #Main data (IDs). Used when
* #Main may have received changes of interest (e.g. asset removed or renamed). */
bool only_main_data;
@ -3070,7 +3088,6 @@ static void filelist_readjob_list_lib_add_datablock(FileListReadJob *job_params,
const int idcode,
const char *group_name)
{
FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */
FileListInternEntry *entry = MEM_cnew<FileListInternEntry>(__func__);
if (prefix_relpath_with_group_name) {
std::string datablock_path = StringRef(group_name) + "/" + datablock_info->name;
@ -3086,7 +3103,7 @@ static void filelist_readjob_list_lib_add_datablock(FileListReadJob *job_params,
if (datablock_info->asset_data) {
entry->typeflag |= FILE_TYPE_ASSET;
if (filelist->asset_library) {
if (job_params->load_asset_library) {
/* Take ownership over the asset data (shallow copies into unique_ptr managed memory) to
* pass it on to the asset system. */
std::unique_ptr metadata = std::make_unique<AssetMetaData>(*datablock_info->asset_data);
@ -3096,7 +3113,7 @@ static void filelist_readjob_list_lib_add_datablock(FileListReadJob *job_params,
datablock_info->asset_data = metadata.get();
datablock_info->free_asset_data = false;
entry->asset = &filelist->asset_library->add_external_asset(
entry->asset = &job_params->load_asset_library->add_external_asset(
entry->relpath, datablock_info->name, std::move(metadata));
}
}
@ -3600,7 +3617,7 @@ static void filelist_readjob_recursive_dir_add_items(const bool do_lib,
}
/* Only load assets when browsing an asset library. For normal file browsing we return all
* entries. `FLF_ASSETS_ONLY` filter can be enabled/disabled by the user. */
if (filelist->asset_library) {
if (job_params->load_asset_library) {
list_lib_options |= LIST_LIB_ASSETS_ONLY;
}
std::optional<int> lib_entries_num = filelist_readjob_list_lib(
@ -3715,6 +3732,8 @@ static void filelist_readjob_load_asset_library_data(FileListReadJob *job_params
* #filelist_readjob_endjob() will move it into the real filelist. */
tmp_filelist->asset_library = AS_asset_library_load(job_params->current_main,
*job_params->filelist->asset_library_ref);
/* Set asset library to load (may be overridden later for loading nested ones). */
job_params->load_asset_library = tmp_filelist->asset_library;
*do_update = true;
}
@ -3752,8 +3771,8 @@ static void filelist_readjob_main_assets_add_items(FileListReadJob *job_params,
entry->local_data.preview_image = BKE_asset_metadata_preview_get_from_id(id_iter->asset_data,
id_iter);
entry->local_data.id = id_iter;
if (filelist->asset_library) {
entry->asset = &filelist->asset_library->add_local_id_asset(entry->relpath, *id_iter);
if (job_params->load_asset_library) {
entry->asset = &job_params->load_asset_library->add_local_id_asset(entry->relpath, *id_iter);
}
entries_num++;
BLI_addtail(&tmp_entries, entry);
@ -3780,6 +3799,10 @@ static void filelist_readjob_main_assets_add_items(FileListReadJob *job_params,
*/
static bool filelist_contains_main(const FileList *filelist, const Main *bmain)
{
if (filelist->asset_library_ref && (filelist->asset_library_ref->type == ASSET_LIBRARY_ALL)) {
return true;
}
const char *blendfile_path = BKE_main_blendfile_path(bmain);
return blendfile_path[0] && BLI_path_contains(filelist->filelist.root, blendfile_path);
}
@ -3797,6 +3820,8 @@ static void filelist_readjob_asset_library(FileListReadJob *job_params,
/* A valid, but empty file-list from now. */
filelist->filelist.entries_num = 0;
BLI_assert(job_params->filelist->asset_library_ref != nullptr);
/* NOP if already read. */
filelist_readjob_load_asset_library_data(job_params, do_update);
@ -3834,6 +3859,57 @@ static void filelist_readjob_main_assets(FileListReadJob *job_params,
filelist_readjob_main_assets_add_items(job_params, stop, do_update, progress);
}
static void filelist_readjob_all_asset_library(FileListReadJob *job_params,
bool *stop,
bool *do_update,
float *progress)
{
FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */
BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) &&
(filelist->filelist.entries_num == FILEDIR_NBR_ENTRIES_UNSET));
filelist_readjob_load_asset_library_data(job_params, do_update);
/* A valid, but empty file-list from now. */
filelist->filelist.entries_num = 0;
filelist_readjob_main_assets_add_items(job_params, stop, do_update, progress);
/* When only doing partialy reload for main data, we're done. */
if (job_params->only_main_data) {
return;
}
/* Count how many asset libraries need to be loaded, for progress reporting. Not very precise. */
int library_count = 0;
asset_system::AssetLibrary::foreach_loaded([&library_count](auto &) { library_count++; }, false);
BLI_assert(filelist->asset_library != nullptr);
int libraries_done_count = 0;
/* The "All" asset library was loaded, which means all other asset libraries are also loaded.
* Load their assets from disk into the "All" library. */
asset_system::AssetLibrary::foreach_loaded(
[&](asset_system::AssetLibrary &nested_library) {
StringRefNull root_path = nested_library.root_path();
if (root_path.is_empty()) {
return;
}
/* Override library info to read this library. */
job_params->load_asset_library = &nested_library;
BLI_strncpy(filelist->filelist.root, root_path.c_str(), sizeof(filelist->filelist.root));
float progress_this = 0.0f;
filelist_readjob_recursive_dir_add_items(
true, job_params, stop, do_update, &progress_this);
libraries_done_count++;
*progress = float(libraries_done_count) / library_count;
},
false);
}
/**
* Check if the read-job is requesting a partial reread of the file list only.
*/

View File

@ -423,16 +423,20 @@ static void fileselect_refresh_asset_params(FileAssetSelectParams *asset_params)
}
switch (library->type) {
case ASSET_LIBRARY_ALL:
base_params->dir[0] = '\0';
base_params->type = FILE_ASSET_LIBRARY_ALL;
break;
case ASSET_LIBRARY_LOCAL:
base_params->dir[0] = '\0';
base_params->type = FILE_MAIN_ASSET;
break;
case ASSET_LIBRARY_CUSTOM:
BLI_assert(user_library);
BLI_strncpy(base_params->dir, user_library->path, sizeof(base_params->dir));
base_params->type = FILE_ASSET_LIBRARY;
break;
}
base_params->type = (library->type == ASSET_LIBRARY_LOCAL) ? FILE_MAIN_ASSET :
FILE_ASSET_LIBRARY;
}
void fileselect_refresh_params(SpaceFile *sfile)

View File

@ -21,7 +21,7 @@
#define _DNA_DEFAULT_AssetLibraryReference \
{ \
.type = ASSET_LIBRARY_LOCAL, \
/* Not needed really (should be ignored for #ASSET_LIBRARY_LOCAL), but helps debugging. */ \
/* Not needed really (should be ignored for anything but #ASSET_LIBRARY_CUSTOM), but helps debugging. */ \
.custom_library_index = -1, \
}

View File

@ -89,8 +89,7 @@ typedef enum eAssetLibraryType {
// ASSET_LIBRARY_BUNDLED = 0,
/** Display assets from the current session (current "Main"). */
ASSET_LIBRARY_LOCAL = 1,
/* For the future. Display assets for the current project. */
// ASSET_LIBRARY_PROJECT = 2,
ASSET_LIBRARY_ALL = 2,
/** Display assets from custom asset libraries, as defined in the preferences
* (#bUserAssetLibrary). The name will be taken from #FileSelectParams.asset_library_ref.idname

View File

@ -1001,6 +1001,8 @@ typedef enum eFileSelectType {
FILE_MAIN_ASSET = 3,
/** Load assets of an asset library containing external files. */
FILE_ASSET_LIBRARY = 4,
/** Load all asset libraries. */
FILE_ASSET_LIBRARY_ALL = 5,
FILE_UNIX = 8,
FILE_BLENDER = 8, /* don't display relative paths */