Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/asset')
-rw-r--r--source/blender/editors/asset/CMakeLists.txt2
-rw-r--r--source/blender/editors/asset/ED_asset_catalog.h2
-rw-r--r--source/blender/editors/asset/ED_asset_catalog.hh25
-rw-r--r--source/blender/editors/asset/ED_asset_filter.h14
-rw-r--r--source/blender/editors/asset/ED_asset_handle.h6
-rw-r--r--source/blender/editors/asset/ED_asset_indexer.h50
-rw-r--r--source/blender/editors/asset/ED_asset_library.h20
-rw-r--r--source/blender/editors/asset/ED_asset_list.h26
-rw-r--r--source/blender/editors/asset/ED_asset_temp_id_consumer.h5
-rw-r--r--source/blender/editors/asset/intern/asset_catalog.cc23
-rw-r--r--source/blender/editors/asset/intern/asset_filter.cc12
-rw-r--r--source/blender/editors/asset/intern/asset_handle.cc6
-rw-r--r--source/blender/editors/asset/intern/asset_indexer.cc781
-rw-r--r--source/blender/editors/asset/intern/asset_library_reference_enum.cc46
-rw-r--r--source/blender/editors/asset/intern/asset_list.cc36
-rw-r--r--source/blender/editors/asset/intern/asset_ops.cc330
16 files changed, 1290 insertions, 94 deletions
diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt
index b9ef8e82bba..2391f4af14d 100644
--- a/source/blender/editors/asset/CMakeLists.txt
+++ b/source/blender/editors/asset/CMakeLists.txt
@@ -24,6 +24,7 @@ set(INC
../../makesdna
../../makesrna
../../windowmanager
+ ../../../../intern/clog
../../../../intern/guardedalloc
)
@@ -34,6 +35,7 @@ set(SRC
intern/asset_catalog.cc
intern/asset_filter.cc
intern/asset_handle.cc
+ intern/asset_indexer.cc
intern/asset_library_reference.cc
intern/asset_library_reference_enum.cc
intern/asset_list.cc
diff --git a/source/blender/editors/asset/ED_asset_catalog.h b/source/blender/editors/asset/ED_asset_catalog.h
index 451ec0d5984..be99de01173 100644
--- a/source/blender/editors/asset/ED_asset_catalog.h
+++ b/source/blender/editors/asset/ED_asset_catalog.h
@@ -16,6 +16,8 @@
/** \file
* \ingroup edasset
+ *
+ * Supplement for `ED_asset_catalog.hh`. Part of the same API but usable in C.
*/
#pragma once
diff --git a/source/blender/editors/asset/ED_asset_catalog.hh b/source/blender/editors/asset/ED_asset_catalog.hh
index 8da8fc0d6c9..d4df15561d0 100644
--- a/source/blender/editors/asset/ED_asset_catalog.hh
+++ b/source/blender/editors/asset/ED_asset_catalog.hh
@@ -16,10 +16,17 @@
/** \file
* \ingroup edasset
+ *
+ * UI/Editor level API for catalog operations, creating richer functionality than the BKE catalog
+ * API provides (which this uses internally).
+ *
+ * Note that `ED_asset_catalog.h` is part of this API.
*/
#pragma once
+#include <optional>
+
#include "BKE_asset_catalog.hh"
#include "BLI_string_ref.hh"
@@ -37,6 +44,18 @@ void ED_asset_catalog_remove(AssetLibrary *library, const blender::bke::CatalogI
void ED_asset_catalog_rename(AssetLibrary *library,
blender::bke::CatalogID catalog_id,
blender::StringRefNull new_name);
-void ED_asset_catalog_move(AssetLibrary *library,
- blender::bke::CatalogID src_catalog_id,
- blender::bke::CatalogID dst_parent_catalog_id);
+/**
+ * Reinsert catalog identified by \a src_catalog_id as child to catalog identified by \a
+ * dst_parent_catalog_id. If \a dst_parent_catalog_id is not set, the catalog is moved to the root
+ * level of the tree.
+ * The name of the reinserted catalog is made unique within the parent. Note that moving a catalog
+ * to the same level it was before will also change its name, since the name uniqueness check isn't
+ * smart enough to ignore the item to be reinserted. So the caller is expected to handle this case
+ * to avoid unwanted renames.
+ *
+ * Nothing is done (debug builds run into an assert) if the given catalog IDs can't be identified.
+ */
+void ED_asset_catalog_move(
+ AssetLibrary *library,
+ blender::bke::CatalogID src_catalog_id,
+ std::optional<blender::bke::CatalogID> dst_parent_catalog_id = std::nullopt);
diff --git a/source/blender/editors/asset/ED_asset_filter.h b/source/blender/editors/asset/ED_asset_filter.h
index 340c4c9dbe7..3b92baea117 100644
--- a/source/blender/editors/asset/ED_asset_filter.h
+++ b/source/blender/editors/asset/ED_asset_filter.h
@@ -16,6 +16,8 @@
/** \file
* \ingroup edasset
+ *
+ * Functions for filtering assets.
*/
#pragma once
@@ -27,6 +29,18 @@ extern "C" {
struct AssetFilterSettings;
struct AssetHandle;
+/**
+ * Compare \a asset against the settings of \a filter.
+ *
+ * Individual filter parameters are ORed with the asset properties. That means:
+ * * The asset type must be one of the ID types filtered by, and
+ * * The asset must contain at least one of the tags filtered by.
+ * However for an asset to be matching it must have one match in each of the parameters. I.e. one
+ * matching type __and__ at least one matching tag.
+ *
+ * \returns True if the asset should be visible with these filter settings (parameters match).
+ * Otherwise returns false (mismatch).
+ */
bool ED_asset_filter_matches_asset(const struct AssetFilterSettings *filter,
const struct AssetHandle *asset);
diff --git a/source/blender/editors/asset/ED_asset_handle.h b/source/blender/editors/asset/ED_asset_handle.h
index efb99410d3d..dce851164a5 100644
--- a/source/blender/editors/asset/ED_asset_handle.h
+++ b/source/blender/editors/asset/ED_asset_handle.h
@@ -16,6 +16,12 @@
/** \file
* \ingroup edasset
+ *
+ * Asset-handle is a temporary design, not part of the core asset system design.
+ *
+ * Currently asset-list items are just file directory items (#FileDirEntry). So an asset-handle
+ * just wraps a pointer to this. We try to abstract away the fact that it's just a file entry,
+ * although that doesn't always work (see #rna_def_asset_handle()).
*/
#pragma once
diff --git a/source/blender/editors/asset/ED_asset_indexer.h b/source/blender/editors/asset/ED_asset_indexer.h
new file mode 100644
index 00000000000..33558d8cda5
--- /dev/null
+++ b/source/blender/editors/asset/ED_asset_indexer.h
@@ -0,0 +1,50 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup edasset
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "ED_file_indexer.h"
+
+/**
+ * File Indexer Service for indexing asset files.
+ *
+ * Opening and parsing a large collection of asset files inside a library can take a lot of time.
+ * To reduce the time it takes the files are indexed.
+ *
+ * - Index files are created for each blend file in the asset library, even when the blend file
+ * doesn't contain any assets.
+ * - Indexes are stored in an persistent cache folder (`BKE_appdir_folder_caches` +
+ * `asset_library_indexes/{asset_library_dir}/{asset_index_file.json}`).
+ * - The content of the indexes are used when:
+ * - Index exists and can be opened
+ * - Last modification date is earlier than the file it represents.
+ * - The index file version is the latest.
+ * - Blend files without any assets can be determined by the size of the index file for some
+ * additional performance.
+ */
+extern const FileIndexerType file_indexer_asset;
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/editors/asset/ED_asset_library.h b/source/blender/editors/asset/ED_asset_library.h
index 905d097d223..62a7d6ffa9b 100644
--- a/source/blender/editors/asset/ED_asset_library.h
+++ b/source/blender/editors/asset/ED_asset_library.h
@@ -26,9 +26,27 @@
extern "C" {
#endif
+/**
+ * Return an index that can be used to uniquely identify \a library, assuming
+ * that all relevant indices were created with this function.
+ */
int ED_asset_library_reference_to_enum_value(const AssetLibraryReference *library);
+/**
+ * Return an asset library reference matching the index returned by
+ * #ED_asset_library_reference_to_enum_value().
+ */
AssetLibraryReference ED_asset_library_reference_from_enum_value(int value);
-const struct EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(void);
+/**
+ * Translate all available asset libraries to an RNA enum, whereby the enum values match the result
+ * of #ED_asset_library_reference_to_enum_value() for any given library.
+ *
+ * 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.
+ */
+const struct EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(
+ bool include_local_library);
#ifdef __cplusplus
}
diff --git a/source/blender/editors/asset/ED_asset_list.h b/source/blender/editors/asset/ED_asset_list.h
index 61d7729b651..669bb6dbe36 100644
--- a/source/blender/editors/asset/ED_asset_list.h
+++ b/source/blender/editors/asset/ED_asset_list.h
@@ -31,21 +31,47 @@ struct ID;
struct bContext;
struct wmNotifier;
+/**
+ * Invoke asset list reading, potentially in a parallel job. Won't wait until the job is done,
+ * and may return earlier.
+ */
void ED_assetlist_storage_fetch(const struct AssetLibraryReference *library_reference,
const struct bContext *C);
void ED_assetlist_ensure_previews_job(const struct AssetLibraryReference *library_reference,
struct bContext *C);
void ED_assetlist_clear(const struct AssetLibraryReference *library_reference, struct bContext *C);
bool ED_assetlist_storage_has_list_for_library(const AssetLibraryReference *library_reference);
+/**
+ * Tag all asset lists in the storage that show main data as needing an update (re-fetch).
+ *
+ * This only tags the data. If the asset list is visible on screen, the space is still responsible
+ * for ensuring the necessary redraw. It can use #ED_assetlist_listen() to check if the asset-list
+ * needs a redraw for a given notifier.
+ */
void ED_assetlist_storage_tag_main_data_dirty(void);
+/**
+ * Remapping of ID pointers within the asset lists. Typically called when an ID is deleted to clear
+ * all references to it (\a id_new is null then).
+ */
void ED_assetlist_storage_id_remap(struct ID *id_old, struct ID *id_new);
+/**
+ * Can't wait for static deallocation to run. There's nested data allocated with our guarded
+ * allocator, it will complain about unfreed memory on exit.
+ */
void ED_assetlist_storage_exit(void);
struct ImBuf *ED_assetlist_asset_image_get(const AssetHandle *asset_handle);
const char *ED_assetlist_library_path(const struct AssetLibraryReference *library_reference);
+/**
+ * \return True if the region needs a UI redraw.
+ */
bool ED_assetlist_listen(const struct AssetLibraryReference *library_reference,
const struct wmNotifier *notifier);
+/**
+ * \return The number of assets stored in the asset list for \a library_reference, or -1 if there
+ * is no list fetched for it.
+ */
int ED_assetlist_size(const struct AssetLibraryReference *library_reference);
#ifdef __cplusplus
diff --git a/source/blender/editors/asset/ED_asset_temp_id_consumer.h b/source/blender/editors/asset/ED_asset_temp_id_consumer.h
index 9a47e435612..0848f4225bd 100644
--- a/source/blender/editors/asset/ED_asset_temp_id_consumer.h
+++ b/source/blender/editors/asset/ED_asset_temp_id_consumer.h
@@ -16,6 +16,11 @@
/** \file
* \ingroup edasset
+ *
+ * API to abstract away details for temporary loading of an ID from an asset. If the ID is stored
+ * in the current file (or more precisely, in the #Main given when requesting an ID) no loading is
+ * performed and the ID is returned. Otherwise it's imported for temporary access using the
+ * `BLO_library_temp` API.
*/
#pragma once
diff --git a/source/blender/editors/asset/intern/asset_catalog.cc b/source/blender/editors/asset/intern/asset_catalog.cc
index 9634665be7b..f6c02f86f0a 100644
--- a/source/blender/editors/asset/intern/asset_catalog.cc
+++ b/source/blender/editors/asset/intern/asset_catalog.cc
@@ -122,7 +122,7 @@ void ED_asset_catalog_rename(::AssetLibrary *library,
void ED_asset_catalog_move(::AssetLibrary *library,
const CatalogID src_catalog_id,
- const CatalogID dst_parent_catalog_id)
+ const std::optional<CatalogID> dst_parent_catalog_id)
{
bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(library);
if (!catalog_service) {
@@ -131,9 +131,24 @@ void ED_asset_catalog_move(::AssetLibrary *library,
}
AssetCatalog *src_catalog = catalog_service->find_catalog(src_catalog_id);
- AssetCatalog *dst_catalog = catalog_service->find_catalog(dst_parent_catalog_id);
+ if (!src_catalog) {
+ BLI_assert_unreachable();
+ return;
+ }
+ AssetCatalog *dst_catalog = dst_parent_catalog_id ?
+ catalog_service->find_catalog(*dst_parent_catalog_id) :
+ nullptr;
+ if (!dst_catalog && dst_parent_catalog_id) {
+ BLI_assert_unreachable();
+ return;
+ }
- const AssetCatalogPath new_path = dst_catalog->path / StringRef(src_catalog->path.name());
+ std::string unique_name = catalog_name_ensure_unique(
+ *catalog_service, src_catalog->path.name(), dst_catalog ? dst_catalog->path.c_str() : "");
+ /* If a destination catalog was given, construct the path using that. Otherwise, the path is just
+ * the name of the catalog to be moved, which means it ends up at the root level. */
+ const AssetCatalogPath new_path = dst_catalog ? (dst_catalog->path / unique_name) :
+ AssetCatalogPath{unique_name};
const AssetCatalogPath clean_new_path = new_path.cleanup();
if (new_path == src_catalog->path || clean_new_path == src_catalog->path) {
@@ -158,7 +173,7 @@ void ED_asset_catalogs_save_from_main_path(::AssetLibrary *library, const Main *
/* Since writing to disk also means loading any on-disk changes, it may be a good idea to store
* an undo step. */
catalog_service->undo_push();
- catalog_service->write_to_disk(bmain->name);
+ catalog_service->write_to_disk(bmain->filepath);
}
void ED_asset_catalogs_set_save_catalogs_when_file_is_saved(const bool should_save)
diff --git a/source/blender/editors/asset/intern/asset_filter.cc b/source/blender/editors/asset/intern/asset_filter.cc
index c22bbc923eb..70e3e2f55ea 100644
--- a/source/blender/editors/asset/intern/asset_filter.cc
+++ b/source/blender/editors/asset/intern/asset_filter.cc
@@ -27,18 +27,6 @@
#include "ED_asset_filter.h"
#include "ED_asset_handle.h"
-/**
- * Compare \a asset against the settings of \a filter.
- *
- * Individual filter parameters are ORed with the asset properties. That means:
- * * The asset type must be one of the ID types filtered by, and
- * * The asset must contain at least one of the tags filtered by.
- * However for an asset to be matching it must have one match in each of the parameters. I.e. one
- * matching type __and__ at least one matching tag.
- *
- * \returns True if the asset should be visible with these filter settings (parameters match).
- * Otherwise returns false (mismatch).
- */
bool ED_asset_filter_matches_asset(const AssetFilterSettings *filter, const AssetHandle *asset)
{
ID_Type asset_type = ED_asset_handle_get_id_type(asset);
diff --git a/source/blender/editors/asset/intern/asset_handle.cc b/source/blender/editors/asset/intern/asset_handle.cc
index 363bd9226da..a2029d3cc50 100644
--- a/source/blender/editors/asset/intern/asset_handle.cc
+++ b/source/blender/editors/asset/intern/asset_handle.cc
@@ -16,12 +16,6 @@
/** \file
* \ingroup edasset
- *
- * Asset-handle is a temporary design, not part of the core asset system design.
- *
- * Currently asset-list items are just file directory items (#FileDirEntry). So an asset-handle is
- * just wraps a pointer to this. We try to abstract away the fact that it's just a file entry,
- * although that doesn't always work (see #rna_def_asset_handle()).
*/
#include <string>
diff --git a/source/blender/editors/asset/intern/asset_indexer.cc b/source/blender/editors/asset/intern/asset_indexer.cc
new file mode 100644
index 00000000000..4107d28b5e3
--- /dev/null
+++ b/source/blender/editors/asset/intern/asset_indexer.cc
@@ -0,0 +1,781 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup edasset
+ */
+
+#include <fstream>
+#include <iomanip>
+#include <optional>
+
+#include "ED_asset_indexer.h"
+
+#include "DNA_asset_types.h"
+#include "DNA_userdef_types.h"
+
+#include "BLI_fileops.h"
+#include "BLI_hash.hh"
+#include "BLI_linklist.h"
+#include "BLI_path_util.h"
+#include "BLI_serialize.hh"
+#include "BLI_set.hh"
+#include "BLI_string_ref.hh"
+#include "BLI_uuid.h"
+
+#include "BKE_appdir.h"
+#include "BKE_asset.h"
+#include "BKE_asset_catalog.hh"
+#include "BKE_preferences.h"
+
+#include "CLG_log.h"
+
+static CLG_LogRef LOG = {"ed.asset"};
+
+namespace blender::ed::asset::index {
+
+using namespace blender::io::serialize;
+using namespace blender::bke;
+
+/**
+ * \file asset_indexer.cc
+ * \brief Indexer for asset libraries.
+ *
+ * Indexes are stored per input file. Each index can contain zero to multiple asset entries.
+ * The indexes are grouped together per asset library. They are stored in
+ * #BKE_appdir_folder_caches +
+ * /asset-library-indices/<asset-library-hash>/<asset-index-hash>_<asset_file>.index.json.
+ *
+ * The structure of an index file is
+ * \code
+ * {
+ * "version": <file version number>,
+ * "entries": [{
+ * "name": "<asset name>",
+ * "catalog_id": "<catalog_id>",
+ * "catalog_name": "<catalog_name>",
+ * "description": "<description>",
+ * "author": "<author>",
+ * "tags": ["<tag>"]
+ * }]
+ * }
+ * \endcode
+ *
+ * NOTE: entries, author, description and tags are optional attributes.
+ *
+ * NOTE: File browser uses name and idcode separate. Inside the index they are joined together like
+ * #ID.name.
+ * NOTE: File browser group name isn't stored in the index as it is a translatable name.
+ */
+constexpr StringRef ATTRIBUTE_VERSION("version");
+constexpr StringRef ATTRIBUTE_ENTRIES("entries");
+constexpr StringRef ATTRIBUTE_ENTRIES_NAME("name");
+constexpr StringRef ATTRIBUTE_ENTRIES_CATALOG_ID("catalog_id");
+constexpr StringRef ATTRIBUTE_ENTRIES_CATALOG_NAME("catalog_name");
+constexpr StringRef ATTRIBUTE_ENTRIES_DESCRIPTION("description");
+constexpr StringRef ATTRIBUTE_ENTRIES_AUTHOR("author");
+constexpr StringRef ATTRIBUTE_ENTRIES_TAGS("tags");
+
+/** Abstract class for #BlendFile and #AssetIndexFile. */
+class AbstractFile {
+ public:
+ virtual ~AbstractFile() = default;
+
+ virtual const char *get_file_path() const = 0;
+
+ bool exists() const
+ {
+ return BLI_exists(get_file_path());
+ }
+
+ size_t get_file_size() const
+ {
+ return BLI_file_size(get_file_path());
+ }
+};
+
+/**
+ * \brief Reference to a blend file that can be indexed.
+ */
+class BlendFile : public AbstractFile {
+ StringRefNull file_path_;
+
+ public:
+ BlendFile(StringRefNull file_path) : file_path_(file_path)
+ {
+ }
+
+ uint64_t hash() const
+ {
+ DefaultHash<StringRefNull> hasher;
+ return hasher(file_path_);
+ }
+
+ std::string get_filename() const
+ {
+ char filename[FILE_MAX];
+ BLI_split_file_part(get_file_path(), filename, sizeof(filename));
+ return std::string(filename);
+ }
+
+ const char *get_file_path() const override
+ {
+ return file_path_.c_str();
+ }
+};
+
+/**
+ * \brief Single entry inside a #AssetIndexFile for reading.
+ */
+struct AssetEntryReader {
+ private:
+ /**
+ * \brief Lookup table containing the elements of the entry.
+ */
+ ObjectValue::Lookup lookup;
+
+ StringRefNull get_name_with_idcode() const
+ {
+ return lookup.lookup(ATTRIBUTE_ENTRIES_NAME)->as_string_value()->value();
+ }
+
+ public:
+ AssetEntryReader(const ObjectValue &entry) : lookup(entry.create_lookup())
+ {
+ }
+
+ ID_Type get_idcode() const
+ {
+ const StringRefNull name_with_idcode = get_name_with_idcode();
+ return GS(name_with_idcode.c_str());
+ }
+
+ StringRef get_name() const
+ {
+ const StringRefNull name_with_idcode = get_name_with_idcode();
+ return name_with_idcode.substr(2);
+ }
+
+ bool has_description() const
+ {
+ return lookup.contains(ATTRIBUTE_ENTRIES_DESCRIPTION);
+ }
+
+ StringRefNull get_description() const
+ {
+ return lookup.lookup(ATTRIBUTE_ENTRIES_DESCRIPTION)->as_string_value()->value();
+ }
+
+ bool has_author() const
+ {
+ return lookup.contains(ATTRIBUTE_ENTRIES_AUTHOR);
+ }
+
+ StringRefNull get_author() const
+ {
+ return lookup.lookup(ATTRIBUTE_ENTRIES_AUTHOR)->as_string_value()->value();
+ }
+
+ StringRefNull get_catalog_name() const
+ {
+ return lookup.lookup(ATTRIBUTE_ENTRIES_CATALOG_NAME)->as_string_value()->value();
+ }
+
+ CatalogID get_catalog_id() const
+ {
+ const std::string &catalog_id =
+ lookup.lookup(ATTRIBUTE_ENTRIES_CATALOG_ID)->as_string_value()->value();
+ CatalogID catalog_uuid(catalog_id);
+ return catalog_uuid;
+ }
+
+ void add_tags_to_meta_data(AssetMetaData *asset_data) const
+ {
+ const ObjectValue::LookupValue *value_ptr = lookup.lookup_ptr(ATTRIBUTE_ENTRIES_TAGS);
+ if (value_ptr == nullptr) {
+ return;
+ }
+
+ const ArrayValue *array_value = (*value_ptr)->as_array_value();
+ const ArrayValue::Items &elements = array_value->elements();
+ for (const ArrayValue::Item &item : elements) {
+ const StringRefNull tag_name = item->as_string_value()->value();
+ BKE_asset_metadata_tag_add(asset_data, tag_name.c_str());
+ }
+ }
+};
+
+struct AssetEntryWriter {
+ private:
+ ObjectValue::Items &attributes;
+
+ public:
+ AssetEntryWriter(ObjectValue &entry) : attributes(entry.elements())
+ {
+ }
+
+ /**
+ * \brief add id + name to the attributes.
+ *
+ * NOTE: id and name are encoded like #ID.name
+ */
+ void add_id_name(const short idcode, const StringRefNull name)
+ {
+ char idcode_prefix[2];
+ /* Similar to `BKE_libblock_alloc`. */
+ *((short *)idcode_prefix) = idcode;
+ std::string name_with_idcode = std::string(idcode_prefix, sizeof(idcode_prefix)) + name;
+
+ attributes.append_as(std::pair(ATTRIBUTE_ENTRIES_NAME, new StringValue(name_with_idcode)));
+ }
+
+ void add_catalog_id(const CatalogID &catalog_id)
+ {
+ char catalog_id_str[UUID_STRING_LEN];
+ BLI_uuid_format(catalog_id_str, catalog_id);
+ attributes.append_as(std::pair(ATTRIBUTE_ENTRIES_CATALOG_ID, new StringValue(catalog_id_str)));
+ }
+
+ void add_catalog_name(const StringRefNull catalog_name)
+ {
+ attributes.append_as(std::pair(ATTRIBUTE_ENTRIES_CATALOG_NAME, new StringValue(catalog_name)));
+ }
+
+ void add_description(const StringRefNull description)
+ {
+ attributes.append_as(std::pair(ATTRIBUTE_ENTRIES_DESCRIPTION, new StringValue(description)));
+ }
+
+ void add_author(const StringRefNull author)
+ {
+ attributes.append_as(std::pair(ATTRIBUTE_ENTRIES_AUTHOR, new StringValue(author)));
+ }
+
+ void add_tags(const ListBase /* AssetTag */ *asset_tags)
+ {
+ ArrayValue *tags = new ArrayValue();
+ attributes.append_as(std::pair(ATTRIBUTE_ENTRIES_TAGS, tags));
+ ArrayValue::Items &tag_items = tags->elements();
+
+ LISTBASE_FOREACH (AssetTag *, tag, asset_tags) {
+ tag_items.append_as(new StringValue(tag->name));
+ }
+ }
+};
+
+static void init_value_from_file_indexer_entry(AssetEntryWriter &result,
+ const FileIndexerEntry *indexer_entry)
+{
+ const BLODataBlockInfo &datablock_info = indexer_entry->datablock_info;
+
+ result.add_id_name(indexer_entry->idcode, datablock_info.name);
+
+ const AssetMetaData &asset_data = *datablock_info.asset_data;
+ result.add_catalog_id(asset_data.catalog_id);
+ result.add_catalog_name(asset_data.catalog_simple_name);
+
+ if (asset_data.description != nullptr) {
+ result.add_description(asset_data.description);
+ }
+ if (asset_data.author != nullptr) {
+ result.add_author(asset_data.author);
+ }
+
+ if (!BLI_listbase_is_empty(&asset_data.tags)) {
+ result.add_tags(&asset_data.tags);
+ }
+
+ /* TODO: asset_data.IDProperties */
+}
+
+static void init_value_from_file_indexer_entries(ObjectValue &result,
+ const FileIndexerEntries &indexer_entries)
+{
+ ArrayValue *entries = new ArrayValue();
+ ArrayValue::Items &items = entries->elements();
+
+ for (LinkNode *ln = indexer_entries.entries; ln; ln = ln->next) {
+ const FileIndexerEntry *indexer_entry = static_cast<const FileIndexerEntry *>(ln->link);
+ /* We also get non asset types (brushes, workspaces), when browsing using the asset browser. */
+ if (indexer_entry->datablock_info.asset_data == nullptr) {
+ continue;
+ }
+ ObjectValue *entry_value = new ObjectValue();
+ AssetEntryWriter entry(*entry_value);
+ init_value_from_file_indexer_entry(entry, indexer_entry);
+ items.append_as(entry_value);
+ }
+
+ /* When no entries to index, we should not store the entries attribute as this would make the
+ * size bigger than the #MIN_FILE_SIZE_WITH_ENTRIES. */
+ if (items.is_empty()) {
+ delete entries;
+ return;
+ }
+
+ ObjectValue::Items &attributes = result.elements();
+ attributes.append_as(std::pair(ATTRIBUTE_ENTRIES, entries));
+}
+
+static void init_indexer_entry_from_value(FileIndexerEntry &indexer_entry,
+ const AssetEntryReader &entry)
+{
+ indexer_entry.idcode = entry.get_idcode();
+
+ const std::string &name = entry.get_name();
+ BLI_strncpy(
+ indexer_entry.datablock_info.name, name.c_str(), sizeof(indexer_entry.datablock_info.name));
+
+ AssetMetaData *asset_data = BKE_asset_metadata_create();
+ indexer_entry.datablock_info.asset_data = asset_data;
+
+ if (entry.has_description()) {
+ const std::string &description = entry.get_description();
+ char *description_c_str = static_cast<char *>(MEM_mallocN(description.length() + 1, __func__));
+ BLI_strncpy(description_c_str, description.c_str(), description.length() + 1);
+ asset_data->description = description_c_str;
+ }
+ if (entry.has_author()) {
+ const std::string &author = entry.get_author();
+ char *author_c_str = static_cast<char *>(MEM_mallocN(author.length() + 1, __func__));
+ BLI_strncpy(author_c_str, author.c_str(), author.length() + 1);
+ asset_data->author = author_c_str;
+ }
+
+ const std::string &catalog_name = entry.get_catalog_name();
+ BLI_strncpy(asset_data->catalog_simple_name,
+ catalog_name.c_str(),
+ sizeof(asset_data->catalog_simple_name));
+
+ asset_data->catalog_id = entry.get_catalog_id();
+
+ entry.add_tags_to_meta_data(asset_data);
+}
+
+static int init_indexer_entries_from_value(FileIndexerEntries &indexer_entries,
+ const ObjectValue &value)
+{
+ const ObjectValue::Lookup attributes = value.create_lookup();
+ const ObjectValue::LookupValue *entries_value = attributes.lookup_ptr(ATTRIBUTE_ENTRIES);
+ BLI_assert(entries_value != nullptr);
+
+ if (entries_value == nullptr) {
+ return 0;
+ }
+
+ int num_entries_read = 0;
+ const ArrayValue::Items elements = (*entries_value)->as_array_value()->elements();
+ for (ArrayValue::Item element : elements) {
+ const AssetEntryReader asset_entry(*element->as_object_value());
+
+ FileIndexerEntry *entry = static_cast<FileIndexerEntry *>(
+ MEM_callocN(sizeof(FileIndexerEntry), __func__));
+ init_indexer_entry_from_value(*entry, asset_entry);
+
+ BLI_linklist_prepend(&indexer_entries.entries, entry);
+ num_entries_read += 1;
+ }
+
+ return num_entries_read;
+}
+
+/**
+ * \brief References the asset library directory.
+ *
+ * The #AssetLibraryIndex instance is used to keep track of unused file indices. When reading any
+ * used indices are removed from the list and when reading is finished the unused
+ * indices are removed.
+ */
+struct AssetLibraryIndex {
+ /**
+ * Tracks indices that haven't been used yet.
+ *
+ * Contains absolute paths to the indices.
+ */
+ Set<std::string> unused_file_indices;
+
+ /**
+ * \brief Absolute path where the indices of `library` are stored.
+ *
+ * \NOTE: includes trailing directory separator.
+ */
+ std::string indices_base_path;
+
+ std::string library_path;
+
+ public:
+ AssetLibraryIndex(const StringRef library_path) : library_path(library_path)
+ {
+ init_indices_base_path();
+ }
+
+ uint64_t hash() const
+ {
+ DefaultHash<StringRefNull> hasher;
+ return hasher(get_library_file_path());
+ }
+
+ StringRefNull get_library_file_path() const
+ {
+ return library_path;
+ }
+
+ /**
+ * \brief Initializes #AssetLibraryIndex.indices_base_path.
+ *
+ * `BKE_appdir_folder_caches/asset-library-indices/<asset-library-name-hash>/`
+ */
+ void init_indices_base_path()
+ {
+ char index_path[FILE_MAX];
+ BKE_appdir_folder_caches(index_path, sizeof(index_path));
+
+ BLI_path_append(index_path, sizeof(index_path), "asset-library-indices");
+
+ std::stringstream ss;
+ ss << std::setfill('0') << std::setw(16) << std::hex << hash() << "/";
+ BLI_path_append(index_path, sizeof(index_path), ss.str().c_str());
+
+ indices_base_path = std::string(index_path);
+ }
+
+ /**
+ * \return absolute path to the index file of the given `asset_file`.
+ *
+ * `{indices_base_path}/{asset-file_hash}_{asset-file-filename}.index.json`.
+ */
+ std::string index_file_path(const BlendFile &asset_file) const
+ {
+ std::stringstream ss;
+ ss << indices_base_path;
+ ss << std::setfill('0') << std::setw(16) << std::hex << asset_file.hash() << "_"
+ << asset_file.get_filename() << ".index.json";
+ return ss.str();
+ }
+
+ /**
+ * Initialize to keep track of unused file indices.
+ */
+ void init_unused_index_files()
+ {
+ const char *index_path = indices_base_path.c_str();
+ if (!BLI_is_dir(index_path)) {
+ return;
+ }
+ struct direntry *dir_entries = nullptr;
+ int num_entries = BLI_filelist_dir_contents(index_path, &dir_entries);
+ for (int i = 0; i < num_entries; i++) {
+ struct direntry *entry = &dir_entries[i];
+ if (BLI_str_endswith(entry->relname, ".index.json")) {
+ unused_file_indices.add_as(std::string(entry->path));
+ }
+ }
+
+ BLI_filelist_free(dir_entries, num_entries);
+ }
+
+ void mark_as_used(const std::string &filename)
+ {
+ unused_file_indices.remove(filename);
+ }
+
+ int remove_unused_index_files() const
+ {
+ int num_files_deleted = 0;
+ for (const std::string &unused_index : unused_file_indices) {
+ const char *file_path = unused_index.c_str();
+ CLOG_INFO(&LOG, 2, "Remove unused index file [%s].", file_path);
+ BLI_delete(file_path, false, false);
+ num_files_deleted++;
+ }
+
+ return num_files_deleted;
+ }
+};
+
+/**
+ * Instance of this class represents the contents of an asset index file.
+ *
+ * \code
+ * {
+ * "version": {version},
+ * "entries": ...
+ * }
+ * \endcode
+ */
+struct AssetIndex {
+ /**
+ * \brief Version to store in new index files.
+ *
+ * Versions are written to each index file. When reading the version is checked against
+ * `CURRENT_VERSION` to make sure we can use the index. Developer should increase
+ * `CURRENT_VERSION` when changes are made to the structure of the stored index.
+ */
+ static const int CURRENT_VERSION = 1;
+
+ /**
+ * Version number to use when version couldn't be read from an index file.
+ */
+ const int UNKNOWN_VERSION = -1;
+
+ /**
+ * `blender::io::serialize::Value` representing the contents of an index file.
+ *
+ * Value is used over #ObjectValue as the contents of the index could be corrupted and doesn't
+ * represent an object. In case corrupted files are detected the `get_version` would return
+ * `UNKNOWN_VERSION`.
+ */
+ std::unique_ptr<Value> contents;
+
+ /**
+ * Constructor for when creating/updating an asset index file.
+ * #AssetIndex.contents are filled from the given \p indexer_entries.
+ */
+ AssetIndex(const FileIndexerEntries &indexer_entries)
+ {
+ std::unique_ptr<ObjectValue> root = std::make_unique<ObjectValue>();
+ ObjectValue::Items &root_attributes = root->elements();
+ root_attributes.append_as(std::pair(ATTRIBUTE_VERSION, new IntValue(CURRENT_VERSION)));
+ init_value_from_file_indexer_entries(*root, indexer_entries);
+
+ contents = std::move(root);
+ }
+
+ /**
+ * Constructor when reading an asset index file.
+ * #AssetIndex.contents are read from the given \p value.
+ */
+ AssetIndex(std::unique_ptr<Value> &value) : contents(std::move(value))
+ {
+ }
+
+ int get_version() const
+ {
+ const ObjectValue *root = contents->as_object_value();
+ if (root == nullptr) {
+ return UNKNOWN_VERSION;
+ }
+ const ObjectValue::Lookup attributes = root->create_lookup();
+ const ObjectValue::LookupValue *version_value = attributes.lookup_ptr(ATTRIBUTE_VERSION);
+ if (version_value == nullptr) {
+ return UNKNOWN_VERSION;
+ }
+ return (*version_value)->as_int_value()->value();
+ }
+
+ bool is_latest_version() const
+ {
+ return get_version() == CURRENT_VERSION;
+ }
+
+ /**
+ * Extract the contents of this index into the given \p indexer_entries.
+ *
+ * \return The number of entries read from the given entries.
+ */
+ int extract_into(FileIndexerEntries &indexer_entries) const
+ {
+ const ObjectValue *root = contents->as_object_value();
+ const int num_entries_read = init_indexer_entries_from_value(indexer_entries, *root);
+ return num_entries_read;
+ }
+};
+
+class AssetIndexFile : public AbstractFile {
+ public:
+ AssetLibraryIndex &library_index;
+ /**
+ * Asset index files with a size smaller than this attribute would be considered to not contain
+ * any entries.
+ */
+ const size_t MIN_FILE_SIZE_WITH_ENTRIES = 32;
+ std::string filename;
+
+ AssetIndexFile(AssetLibraryIndex &library_index, BlendFile &asset_filename)
+ : library_index(library_index), filename(library_index.index_file_path(asset_filename))
+ {
+ }
+
+ void mark_as_used()
+ {
+ library_index.mark_as_used(filename);
+ }
+
+ const char *get_file_path() const override
+ {
+ return filename.c_str();
+ }
+
+ /**
+ * Returns whether the index file is older than the given asset file.
+ */
+ bool is_older_than(BlendFile &asset_file) const
+ {
+ return BLI_file_older(get_file_path(), asset_file.get_file_path());
+ }
+
+ /**
+ * Check whether the index file contains entries without opening the file.
+ */
+ bool constains_entries() const
+ {
+ const size_t file_size = get_file_size();
+ return file_size >= MIN_FILE_SIZE_WITH_ENTRIES;
+ }
+
+ std::unique_ptr<AssetIndex> read_contents() const
+ {
+ JsonFormatter formatter;
+ std::ifstream is;
+ is.open(filename);
+ std::unique_ptr<Value> read_data = formatter.deserialize(is);
+ is.close();
+
+ return std::make_unique<AssetIndex>(read_data);
+ }
+
+ bool ensure_parent_path_exists() const
+ {
+ /* `BLI_make_existing_file` only ensures parent path, otherwise than expected from the name of
+ * the function. */
+ return BLI_make_existing_file(get_file_path());
+ }
+
+ void write_contents(AssetIndex &content)
+ {
+ JsonFormatter formatter;
+ if (!ensure_parent_path_exists()) {
+ CLOG_ERROR(&LOG, "Index not created: couldn't create folder [%s].", get_file_path());
+ return;
+ }
+
+ std::ofstream os;
+ os.open(filename, std::ios::out | std::ios::trunc);
+ formatter.serialize(os, *content.contents);
+ os.close();
+ }
+};
+
+static eFileIndexerResult read_index(const char *filename,
+ FileIndexerEntries *entries,
+ int *r_read_entries_len,
+ void *user_data)
+{
+ AssetLibraryIndex &library_index = *static_cast<AssetLibraryIndex *>(user_data);
+ BlendFile asset_file(filename);
+ AssetIndexFile asset_index_file(library_index, asset_file);
+
+ if (!asset_index_file.exists()) {
+ return FILE_INDEXER_NEEDS_UPDATE;
+ }
+
+ /* Mark index as used, even when it will be recreated. When not done it would remove the index
+ * when the indexing has finished (see `AssetLibraryIndex.remove_unused_index_files`), thereby
+ * removing the newly created index.
+ */
+ asset_index_file.mark_as_used();
+
+ if (asset_index_file.is_older_than(asset_file)) {
+ CLOG_INFO(
+ &LOG,
+ 3,
+ "Asset index file [%s] needs to be refreshed as it is older than the asset file [%s].",
+ asset_index_file.filename.c_str(),
+ filename);
+ return FILE_INDEXER_NEEDS_UPDATE;
+ }
+
+ if (!asset_index_file.constains_entries()) {
+ CLOG_INFO(&LOG,
+ 3,
+ "Asset file index is to small to contain any entries. [%s]",
+ asset_index_file.filename.c_str());
+ *r_read_entries_len = 0;
+ return FILE_INDEXER_ENTRIES_LOADED;
+ }
+
+ std::unique_ptr<AssetIndex> contents = asset_index_file.read_contents();
+ if (!contents->is_latest_version()) {
+ CLOG_INFO(&LOG,
+ 3,
+ "Asset file index is ignored; expected version %d but file is version %d [%s].",
+ AssetIndex::CURRENT_VERSION,
+ contents->get_version(),
+ asset_index_file.filename.c_str());
+ return FILE_INDEXER_NEEDS_UPDATE;
+ }
+
+ const int read_entries_len = contents->extract_into(*entries);
+ CLOG_INFO(&LOG, 1, "Read %d entries from asset index for [%s].", read_entries_len, filename);
+ *r_read_entries_len = read_entries_len;
+
+ return FILE_INDEXER_ENTRIES_LOADED;
+}
+
+static void update_index(const char *filename, FileIndexerEntries *entries, void *user_data)
+{
+ AssetLibraryIndex &library_index = *static_cast<AssetLibraryIndex *>(user_data);
+ BlendFile asset_file(filename);
+ AssetIndexFile asset_index_file(library_index, asset_file);
+ CLOG_INFO(&LOG,
+ 1,
+ "Update asset index for [%s] store index in [%s].",
+ asset_file.get_file_path(),
+ asset_index_file.get_file_path());
+
+ AssetIndex content(*entries);
+ asset_index_file.write_contents(content);
+}
+
+static void *init_user_data(const char *root_directory, size_t root_directory_maxlen)
+{
+ AssetLibraryIndex *library_index = OBJECT_GUARDED_NEW(
+ AssetLibraryIndex,
+ StringRef(root_directory, BLI_strnlen(root_directory, root_directory_maxlen)));
+ library_index->init_unused_index_files();
+ return library_index;
+}
+
+static void free_user_data(void *user_data)
+{
+ OBJECT_GUARDED_DELETE(user_data, AssetLibraryIndex);
+}
+
+static void filelist_finished(void *user_data)
+{
+ AssetLibraryIndex &library_index = *static_cast<AssetLibraryIndex *>(user_data);
+ const int num_indices_removed = library_index.remove_unused_index_files();
+ if (num_indices_removed > 0) {
+ CLOG_INFO(&LOG, 1, "Removed %d unused indices.", num_indices_removed);
+ }
+}
+
+constexpr FileIndexerType asset_indexer()
+{
+ FileIndexerType indexer = {nullptr};
+ indexer.read_index = read_index;
+ indexer.update_index = update_index;
+ indexer.init_user_data = init_user_data;
+ indexer.free_user_data = free_user_data;
+ indexer.filelist_finished = filelist_finished;
+ return indexer;
+}
+
+} // namespace blender::ed::asset::index
+
+extern "C" {
+const FileIndexerType file_indexer_asset = blender::ed::asset::index::asset_indexer();
+}
diff --git a/source/blender/editors/asset/intern/asset_library_reference_enum.cc b/source/blender/editors/asset/intern/asset_library_reference_enum.cc
index 1a2d3f5837a..05526f222a5 100644
--- a/source/blender/editors/asset/intern/asset_library_reference_enum.cc
+++ b/source/blender/editors/asset/intern/asset_library_reference_enum.cc
@@ -35,10 +35,6 @@
#include "ED_asset_library.h"
-/**
- * Return an index that can be used to uniquely identify \a library, assuming
- * that all relevant indices were created with this function.
- */
int ED_asset_library_reference_to_enum_value(const AssetLibraryReference *library)
{
/* Simple case: Predefined repository, just set the value. */
@@ -57,10 +53,6 @@ int ED_asset_library_reference_to_enum_value(const AssetLibraryReference *librar
return ASSET_LIBRARY_LOCAL;
}
-/**
- * Return an asset library reference matching the index returned by
- * #ED_asset_library_reference_to_enum_value().
- */
AssetLibraryReference ED_asset_library_reference_from_enum_value(int value)
{
AssetLibraryReference library;
@@ -92,31 +84,27 @@ AssetLibraryReference ED_asset_library_reference_from_enum_value(int value)
return library;
}
-/**
- * Translate all available asset libraries to an RNA enum, whereby the enum values match the result
- * of #ED_asset_library_reference_to_enum_value() for any given library.
- *
- * Since this is meant for UI display, skips non-displayable libraries, that is, libraries with an
- * empty name or path.
- */
-const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf()
+const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(
+ const bool include_local_library)
{
- const EnumPropertyItem predefined_items[] = {
- /* For the future. */
- // {ASSET_REPO_BUNDLED, "BUNDLED", 0, "Bundled", "Show the default user assets"},
- {ASSET_LIBRARY_LOCAL,
- "LOCAL",
- ICON_BLENDER,
- "Current File",
- "Show the assets currently available in this Blender session"},
- {0, nullptr, 0, nullptr, nullptr},
- };
-
EnumPropertyItem *item = nullptr;
int totitem = 0;
- /* Add predefined items. */
- RNA_enum_items_add(&item, &totitem, predefined_items);
+ if (include_local_library) {
+ const EnumPropertyItem predefined_items[] = {
+ /* For the future. */
+ // {ASSET_REPO_BUNDLED, "BUNDLED", 0, "Bundled", "Show the default user assets"},
+ {ASSET_LIBRARY_LOCAL,
+ "LOCAL",
+ ICON_CURRENT_FILE,
+ "Current File",
+ "Show the assets currently available in this Blender session"},
+ {0, nullptr, 0, nullptr, nullptr},
+ };
+
+ /* Add predefined items. */
+ RNA_enum_items_add(&item, &totitem, predefined_items);
+ }
/* Add separator if needed. */
if (!BLI_listbase_is_empty(&U.asset_libraries)) {
diff --git a/source/blender/editors/asset/intern/asset_list.cc b/source/blender/editors/asset/intern/asset_list.cc
index c1b1e33d428..1bfdd83b8e3 100644
--- a/source/blender/editors/asset/intern/asset_list.cc
+++ b/source/blender/editors/asset/intern/asset_list.cc
@@ -44,10 +44,15 @@
#include "../space_file/filelist.h"
#include "ED_asset_handle.h"
+#include "ED_asset_indexer.h"
#include "ED_asset_list.h"
#include "ED_asset_list.hh"
#include "asset_library_reference.hh"
+/* Enable asset indexing. Currently disabled as ID properties aren't indexed yet and is needed for
+ * object snapping. See {D12990}. */
+//#define SPACE_FILE_ENABLE_ASSET_INDEXING
+
namespace blender::ed::asset {
/* -------------------------------------------------------------------- */
@@ -169,6 +174,10 @@ void AssetList::setup()
"",
"");
+#ifdef SPACE_FILE_ENABLE_ASSET_INDEXING
+ filelist_setindexer(files, &file_indexer_asset);
+#endif
+
char path[FILE_MAXDIR] = "";
if (user_library) {
BLI_strncpy(path, user_library->path, sizeof(path));
@@ -309,6 +318,7 @@ StringRef AssetList::filepath() const
{
return filelist_dir(filelist_);
}
+
/** \} */
/* -------------------------------------------------------------------- */
@@ -425,10 +435,6 @@ AssetListStorage::AssetListMap &AssetListStorage::global_storage()
using namespace blender::ed::asset;
-/**
- * Invoke asset list reading, potentially in a parallel job. Won't wait until the job is done,
- * and may return earlier.
- */
void ED_assetlist_storage_fetch(const AssetLibraryReference *library_reference, const bContext *C)
{
AssetListStorage::fetch_library(*library_reference, *C);
@@ -523,9 +529,6 @@ const char *ED_assetlist_library_path(const AssetLibraryReference *library_refer
return nullptr;
}
-/**
- * \return True if the region needs a UI redraw.
- */
bool ED_assetlist_listen(const AssetLibraryReference *library_reference,
const wmNotifier *notifier)
{
@@ -536,10 +539,6 @@ bool ED_assetlist_listen(const AssetLibraryReference *library_reference,
return false;
}
-/**
- * \return The number of assets stored in the asset list for \a library_reference, or -1 if there
- * is no list fetched for it.
- */
int ED_assetlist_size(const AssetLibraryReference *library_reference)
{
AssetList *list = AssetListStorage::lookup_list(*library_reference);
@@ -549,31 +548,16 @@ int ED_assetlist_size(const AssetLibraryReference *library_reference)
return -1;
}
-/**
- * Tag all asset lists in the storage that show main data as needing an update (re-fetch).
- *
- * This only tags the data. If the asset list is visible on screen, the space is still responsible
- * for ensuring the necessary redraw. It can use #ED_assetlist_listen() to check if the asset-list
- * needs a redraw for a given notifier.
- */
void ED_assetlist_storage_tag_main_data_dirty()
{
AssetListStorage::tagMainDataDirty();
}
-/**
- * Remapping of ID pointers within the asset lists. Typically called when an ID is deleted to clear
- * all references to it (\a id_new is null then).
- */
void ED_assetlist_storage_id_remap(ID *id_old, ID *id_new)
{
AssetListStorage::remapID(id_old, id_new);
}
-/**
- * Can't wait for static deallocation to run. There's nested data allocated with our guarded
- * allocator, it will complain about unfreed memory on exit.
- */
void ED_assetlist_storage_exit()
{
AssetListStorage::destruct();
diff --git a/source/blender/editors/asset/intern/asset_ops.cc b/source/blender/editors/asset/intern/asset_ops.cc
index f7c567c89f6..e4edff19a21 100644
--- a/source/blender/editors/asset/intern/asset_ops.cc
+++ b/source/blender/editors/asset/intern/asset_ops.cc
@@ -19,12 +19,23 @@
*/
#include "BKE_asset_library.hh"
+#include "BKE_bpath.h"
#include "BKE_context.h"
+#include "BKE_global.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
+#include "BKE_preferences.h"
#include "BKE_report.h"
+#include "BLI_fileops.h"
+#include "BLI_fnmatch.h"
+#include "BLI_path_util.h"
+#include "BLI_set.hh"
+
#include "ED_asset.h"
+#include "ED_asset_catalog.hh"
+#include "ED_screen.h"
+#include "ED_util.h"
/* XXX needs access to the file list, should all be done via the asset system in future. */
#include "ED_fileselect.h"
@@ -33,6 +44,10 @@
#include "WM_api.h"
+#include "DNA_space_types.h"
+
+#include "BLO_writefile.h"
+
using namespace blender;
/* -------------------------------------------------------------------- */
@@ -377,8 +392,14 @@ static void ASSET_OT_clear(wmOperatorType *ot)
/* -------------------------------------------------------------------- */
-static bool asset_list_refresh_poll(bContext *C)
+static bool asset_library_refresh_poll(bContext *C)
{
+ if (ED_operator_asset_browsing_active(C)) {
+ return true;
+ }
+
+ /* While not inside an Asset Browser, check if there's a asset list stored for the active asset
+ * library (stored in the workspace, obtained via context). */
const AssetLibraryReference *library = CTX_wm_asset_library_ref(C);
if (!library) {
return false;
@@ -387,23 +408,38 @@ static bool asset_list_refresh_poll(bContext *C)
return ED_assetlist_storage_has_list_for_library(library);
}
-static int asset_list_refresh_exec(bContext *C, wmOperator *UNUSED(unused))
+static int asset_library_refresh_exec(bContext *C, wmOperator *UNUSED(unused))
{
- const AssetLibraryReference *library = CTX_wm_asset_library_ref(C);
- ED_assetlist_clear(library, C);
+ /* Execution mode #1: Inside the Asset Browser. */
+ if (ED_operator_asset_browsing_active(C)) {
+ SpaceFile *sfile = CTX_wm_space_file(C);
+ ED_fileselect_clear(CTX_wm_manager(C), sfile);
+ WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, nullptr);
+ }
+ else {
+ /* Execution mode #2: Outside the Asset Browser, use the asset list. */
+ const AssetLibraryReference *library = CTX_wm_asset_library_ref(C);
+ ED_assetlist_clear(library, C);
+ }
+
return OPERATOR_FINISHED;
}
-static void ASSET_OT_list_refresh(struct wmOperatorType *ot)
+/**
+ * This operator currently covers both cases, the File/Asset Browser file list and the asset list
+ * used for the asset-view template. Once the asset list design is used by the Asset Browser, this
+ * can be simplified to just that case.
+ */
+static void ASSET_OT_library_refresh(struct wmOperatorType *ot)
{
/* identifiers */
- ot->name = "Refresh Asset List";
- ot->description = "Trigger a reread of the assets";
- ot->idname = "ASSET_OT_list_refresh";
+ ot->name = "Refresh Asset Library";
+ ot->description = "Reread assets and asset catalogs from the asset library on disk";
+ ot->idname = "ASSET_OT_library_refresh";
/* api callbacks */
- ot->exec = asset_list_refresh_exec;
- ot->poll = asset_list_refresh_poll;
+ ot->exec = asset_library_refresh_exec;
+ ot->poll = asset_library_refresh_poll;
}
/* -------------------------------------------------------------------- */
@@ -601,7 +637,7 @@ static bool asset_catalogs_save_poll(bContext *C)
}
const Main *bmain = CTX_data_main(C);
- if (!bmain->name[0]) {
+ if (!bmain->filepath[0]) {
CTX_wm_operator_poll_msg_set(C, "Cannot save asset catalogs before the Blender file is saved");
return false;
}
@@ -643,7 +679,274 @@ static void ASSET_OT_catalogs_save(struct wmOperatorType *ot)
/* -------------------------------------------------------------------- */
-void ED_operatortypes_asset(void)
+static bool could_be_asset_bundle(const Main *bmain);
+static const bUserAssetLibrary *selected_asset_library(struct wmOperator *op);
+static bool is_contained_in_selected_asset_library(struct wmOperator *op, const char *filepath);
+static bool set_filepath_for_asset_lib(const Main *bmain, struct wmOperator *op);
+static bool has_external_files(Main *bmain, struct ReportList *reports);
+
+static bool asset_bundle_install_poll(bContext *C)
+{
+ /* This operator only works when the asset browser is set to Current File. */
+ const SpaceFile *sfile = CTX_wm_space_file(C);
+ if (sfile == nullptr) {
+ return false;
+ }
+ if (!ED_fileselect_is_local_asset_library(sfile)) {
+ return false;
+ }
+
+ const Main *bmain = CTX_data_main(C);
+ if (!could_be_asset_bundle(bmain)) {
+ return false;
+ }
+
+ /* Check whether this file is already located inside any asset library. */
+ const struct bUserAssetLibrary *asset_lib = BKE_preferences_asset_library_containing_path(
+ &U, bmain->filepath);
+ if (asset_lib) {
+ return false;
+ }
+
+ return true;
+}
+
+static int asset_bundle_install_invoke(struct bContext *C,
+ struct wmOperator *op,
+ const struct wmEvent * /*event*/)
+{
+ Main *bmain = CTX_data_main(C);
+ if (has_external_files(bmain, op->reports)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ WM_event_add_fileselect(C, op);
+
+ /* Make the "Save As" dialog box default to "${ASSET_LIB_ROOT}/${CURRENT_FILE}.blend". */
+ if (!set_filepath_for_asset_lib(bmain, op)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int asset_bundle_install_exec(bContext *C, wmOperator *op)
+{
+ Main *bmain = CTX_data_main(C);
+ if (has_external_files(bmain, op->reports)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ /* Check file path, copied from #wm_file_write(). */
+ char filepath[PATH_MAX];
+ RNA_string_get(op->ptr, "filepath", filepath);
+ const size_t len = strlen(filepath);
+
+ if (len == 0) {
+ BKE_report(op->reports, RPT_ERROR, "Path is empty, cannot save");
+ return OPERATOR_CANCELLED;
+ }
+
+ if (len >= FILE_MAX) {
+ BKE_report(op->reports, RPT_ERROR, "Path too long, cannot save");
+ return OPERATOR_CANCELLED;
+ }
+
+ /* Check that the destination is actually contained in the selected asset library. */
+ if (!is_contained_in_selected_asset_library(op, filepath)) {
+ BKE_reportf(op->reports, RPT_ERROR, "Selected path is outside of the selected asset library");
+ return OPERATOR_CANCELLED;
+ }
+
+ WM_cursor_wait(true);
+ bke::AssetCatalogService *cat_service = get_catalog_service(C);
+ /* Store undo step, such that on a failed save the 'prepare_to_merge_on_write' call can be
+ * un-done. */
+ cat_service->undo_push();
+ cat_service->prepare_to_merge_on_write();
+
+ const int operator_result = WM_operator_name_call(
+ C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, op->ptr);
+ WM_cursor_wait(false);
+
+ if (operator_result != OPERATOR_FINISHED) {
+ cat_service->undo();
+ return operator_result;
+ }
+
+ const bUserAssetLibrary *lib = selected_asset_library(op);
+ BLI_assert_msg(lib, "If the asset library is not known, how did we get here?");
+ BKE_reportf(op->reports,
+ RPT_INFO,
+ R"(Saved "%s" to asset library "%s")",
+ BLI_path_basename(bmain->filepath),
+ lib->name);
+ return OPERATOR_FINISHED;
+}
+
+static const EnumPropertyItem *rna_asset_library_reference_itemf(bContext *UNUSED(C),
+ PointerRNA *UNUSED(ptr),
+ PropertyRNA *UNUSED(prop),
+ bool *r_free)
+{
+ const EnumPropertyItem *items = ED_asset_library_reference_to_rna_enum_itemf(false);
+ if (!items) {
+ *r_free = false;
+ }
+
+ *r_free = true;
+ return items;
+}
+
+static void ASSET_OT_bundle_install(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Copy to Asset Library";
+ ot->description =
+ "Copy the current .blend file into an Asset Library. Only works on standalone .blend files "
+ "(i.e. when no other files are referenced)";
+ ot->idname = "ASSET_OT_bundle_install";
+
+ /* api callbacks */
+ ot->exec = asset_bundle_install_exec;
+ ot->invoke = asset_bundle_install_invoke;
+ ot->poll = asset_bundle_install_poll;
+
+ ot->prop = RNA_def_property(ot->srna, "asset_library_ref", PROP_ENUM, PROP_NONE);
+ RNA_def_property_flag(ot->prop, PROP_HIDDEN);
+ RNA_def_enum_funcs(ot->prop, rna_asset_library_reference_itemf);
+
+ WM_operator_properties_filesel(ot,
+ FILE_TYPE_FOLDER | FILE_TYPE_BLENDER,
+ FILE_BLENDER,
+ FILE_SAVE,
+ WM_FILESEL_FILEPATH,
+ FILE_DEFAULTDISPLAY,
+ FILE_SORT_DEFAULT);
+}
+
+/* Cheap check to see if this is an "asset bundle" just by checking main file name.
+ * A proper check will be done in the exec function, to ensure that no external files will be
+ * referenced. */
+static bool could_be_asset_bundle(const Main *bmain)
+{
+ return fnmatch("*_bundle.blend", bmain->filepath, FNM_CASEFOLD) == 0;
+}
+
+static const bUserAssetLibrary *selected_asset_library(struct wmOperator *op)
+{
+ const int enum_value = RNA_enum_get(op->ptr, "asset_library_ref");
+ const AssetLibraryReference lib_ref = ED_asset_library_reference_from_enum_value(enum_value);
+ const bUserAssetLibrary *lib = BKE_preferences_asset_library_find_from_index(
+ &U, lib_ref.custom_library_index);
+ return lib;
+}
+
+static bool is_contained_in_selected_asset_library(struct wmOperator *op, const char *filepath)
+{
+ const bUserAssetLibrary *lib = selected_asset_library(op);
+ if (!lib) {
+ return false;
+ }
+ return BLI_path_contains(lib->path, filepath);
+}
+
+/**
+ * Set the "filepath" RNA property based on selected "asset_library_ref".
+ * \return true if ok, false if error.
+ */
+static bool set_filepath_for_asset_lib(const Main *bmain, struct wmOperator *op)
+{
+ /* Find the directory path of the selected asset library. */
+ const bUserAssetLibrary *lib = selected_asset_library(op);
+ if (lib == nullptr) {
+ return false;
+ }
+
+ /* Concatenate the filename of the current blend file. */
+ const char *blend_filename = BLI_path_basename(bmain->filepath);
+ if (blend_filename == nullptr || blend_filename[0] == '\0') {
+ return false;
+ }
+
+ char file_path[PATH_MAX];
+ BLI_join_dirfile(file_path, sizeof(file_path), lib->path, blend_filename);
+ RNA_string_set(op->ptr, "filepath", file_path);
+
+ return true;
+}
+
+struct FileCheckCallbackInfo {
+ struct ReportList *reports;
+ Set<std::string> external_files;
+};
+
+static bool external_file_check_callback(BPathForeachPathData *bpath_data,
+ char * /*path_dst*/,
+ const char *path_src)
+{
+ FileCheckCallbackInfo *callback_info = static_cast<FileCheckCallbackInfo *>(
+ bpath_data->user_data);
+ callback_info->external_files.add(std::string(path_src));
+ return false;
+}
+
+/**
+ * Do a check on any external files (.blend, textures, etc.) being used.
+ * The ASSET_OT_bundle_install operator only works on standalone .blend files
+ * (catalog definition files are fine, though).
+ *
+ * \return true when there are external files, false otherwise.
+ */
+static bool has_external_files(Main *bmain, struct ReportList *reports)
+{
+ struct FileCheckCallbackInfo callback_info = {reports, Set<std::string>()};
+
+ eBPathForeachFlag flag = static_cast<eBPathForeachFlag>(
+ BKE_BPATH_FOREACH_PATH_SKIP_PACKED /* Packed files are fine. */
+ | BKE_BPATH_FOREACH_PATH_SKIP_MULTIFILE /* Only report multi-files once, it's enough. */
+ | BKE_BPATH_TRAVERSE_SKIP_WEAK_REFERENCES); /* Only care about actually used files. */
+
+ BPathForeachPathData bpath_data = {
+ /* bmain */ bmain,
+ /* callback_function */ &external_file_check_callback,
+ /* flag */ flag,
+ /* user_data */ &callback_info,
+ /* absolute_base_path */ nullptr,
+ };
+ BKE_bpath_foreach_path_main(&bpath_data);
+
+ if (callback_info.external_files.is_empty()) {
+ /* No external dependencies. */
+ return false;
+ }
+
+ if (callback_info.external_files.size() == 1) {
+ /* Only one external dependency, report it directly. */
+ BKE_reportf(callback_info.reports,
+ RPT_ERROR,
+ "Unable to copy bundle due to external dependency: \"%s\"",
+ callback_info.external_files.begin()->c_str());
+ return true;
+ }
+
+ /* Multiple external dependencies, report the aggregate and put details on console. */
+ BKE_reportf(
+ callback_info.reports,
+ RPT_ERROR,
+ "Unable to copy bundle due to %ld external dependencies; more details on the console",
+ callback_info.external_files.size());
+ printf("Unable to copy bundle due to %ld external dependencies:\n",
+ callback_info.external_files.size());
+ for (const std::string &path : callback_info.external_files) {
+ printf(" \"%s\"\n", path.c_str());
+ }
+ return true;
+}
+
+/* -------------------------------------------------------------------- */
+
+void ED_operatortypes_asset()
{
WM_operatortype_append(ASSET_OT_mark);
WM_operatortype_append(ASSET_OT_clear);
@@ -654,6 +957,7 @@ void ED_operatortypes_asset(void)
WM_operatortype_append(ASSET_OT_catalog_undo);
WM_operatortype_append(ASSET_OT_catalog_redo);
WM_operatortype_append(ASSET_OT_catalog_undo_push);
+ WM_operatortype_append(ASSET_OT_bundle_install);
- WM_operatortype_append(ASSET_OT_list_refresh);
+ WM_operatortype_append(ASSET_OT_library_refresh);
}