From 56ce51d1f75a5966977847aeafd25a60a9f44260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 1 Oct 2021 15:21:44 +0200 Subject: Asset Catalogs: add catalog filter for the asset browser Given an "active catalog" (i.e. the one selected in the UI), construct an `AssetCatalogFilter` instance. This filter can determine whether an asset should be shown or not. It returns `true` when The asset's catalog ID is: - the active catalog, - an alias of the active catalog (so different UUID that maps to the same path), - a sub-catalog of the active catalog. Not yet hooked up to the UI. --- source/blender/blenkernel/BKE_asset_catalog.hh | 26 ++++++++++ source/blender/blenkernel/intern/asset_catalog.cc | 37 ++++++++++++++ .../blenkernel/intern/asset_catalog_test.cc | 56 ++++++++++++++++++++++ 3 files changed, 119 insertions(+) diff --git a/source/blender/blenkernel/BKE_asset_catalog.hh b/source/blender/blenkernel/BKE_asset_catalog.hh index 052cf8e9d1e..65082f06f3a 100644 --- a/source/blender/blenkernel/BKE_asset_catalog.hh +++ b/source/blender/blenkernel/BKE_asset_catalog.hh @@ -26,6 +26,7 @@ #include "BLI_function_ref.hh" #include "BLI_map.hh" +#include "BLI_set.hh" #include "BLI_string_ref.hh" #include "BLI_uuid.h" #include "BLI_vector.hh" @@ -48,6 +49,7 @@ using CatalogFilePath = std::string; class AssetCatalog; class AssetCatalogDefinitionFile; class AssetCatalogTree; +class AssetCatalogFilter; /* Manages the asset catalogs of a single asset library (i.e. of catalogs defined in a single * directory hierarchy). */ @@ -100,6 +102,14 @@ class AssetCatalogService { * efficient call as it's just a linear search over the catalogs. */ AssetCatalog *find_catalog_by_path(const AssetCatalogPath &path) const; + /** + * Create a filter object that can be used to determine whether an asset belongs to the given + * catalog, or any of the catalogs in the sub-tree rooted at the given catalog. + * + * \see #AssetCatalogFilter + */ + AssetCatalogFilter create_catalog_filter(CatalogID active_catalog_id) const; + /** Create a catalog with some sensible auto-generated catalog ID. * The catalog will be saved to the default catalog file.*/ AssetCatalog *create_catalog(const AssetCatalogPath &catalog_path); @@ -331,4 +341,20 @@ struct AssetCatalogPathCmp { * Being a set, duplicates are removed. The catalog's simple name is ignored in this. */ using AssetCatalogOrderedSet = std::set; +/** + * Filter that can determine whether an asset should be visible or not, based on its catalog ID. + * + * \see AssetCatalogService::create_filter() + */ +class AssetCatalogFilter { + public: + bool contains(CatalogID asset_catalog_id) const; + + protected: + friend AssetCatalogService; + const Set matching_catalog_ids; + + explicit AssetCatalogFilter(Set &&matching_catalog_ids); +}; + } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/asset_catalog.cc b/source/blender/blenkernel/intern/asset_catalog.cc index 34bd1fd49db..4973de20fb3 100644 --- a/source/blender/blenkernel/intern/asset_catalog.cc +++ b/source/blender/blenkernel/intern/asset_catalog.cc @@ -85,6 +85,33 @@ AssetCatalog *AssetCatalogService::find_catalog_by_path(const AssetCatalogPath & return nullptr; } +AssetCatalogFilter AssetCatalogService::create_catalog_filter( + const CatalogID active_catalog_id) const +{ + Set matching_catalog_ids; + matching_catalog_ids.add(active_catalog_id); + + const AssetCatalog *active_catalog = find_catalog(active_catalog_id); + if (!active_catalog) { + /* If the UUID is unknown (i.e. not mapped to an actual Catalog), it is impossible to determine + * its children. The filter can still work on the given UUID. */ + return AssetCatalogFilter(std::move(matching_catalog_ids)); + } + + /* This cannot just iterate over tree items to get all the required data, because tree items only + * represent single UUIDs. It could be used to get the main UUIDs of the children, though, and + * then only do an exact match on the path (instead of the more complex `is_contained_in()` + * call). Without an extra indexed-by-path acceleration structure, this is still going to require + * a linear search, though. */ + for (const auto &catalog_uptr : this->catalogs_.values()) { + if (catalog_uptr->path.is_contained_in(active_catalog->path)) { + matching_catalog_ids.add(catalog_uptr->catalog_id); + } + } + + return AssetCatalogFilter(std::move(matching_catalog_ids)); +} + void AssetCatalogService::delete_catalog(CatalogID catalog_id) { std::unique_ptr *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id); @@ -754,4 +781,14 @@ std::string AssetCatalog::sensible_simple_name_for_path(const AssetCatalogPath & return "..." + name.substr(name.length() - 60); } +AssetCatalogFilter::AssetCatalogFilter(Set &&matching_catalog_ids) + : matching_catalog_ids(std::move(matching_catalog_ids)) +{ +} + +bool AssetCatalogFilter::contains(const CatalogID asset_catalog_id) const +{ + return matching_catalog_ids.contains(asset_catalog_id); +} + } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/asset_catalog_test.cc b/source/blender/blenkernel/intern/asset_catalog_test.cc index f3e1f7984a4..e8d40771b89 100644 --- a/source/blender/blenkernel/intern/asset_catalog_test.cc +++ b/source/blender/blenkernel/intern/asset_catalog_test.cc @@ -906,4 +906,60 @@ TEST_F(AssetCatalogTest, create_missing_catalogs_after_loading) EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ružena")); } +TEST_F(AssetCatalogTest, create_catalog_filter) +{ + AssetCatalogService service(asset_library_root_); + service.load_from_disk(); + + /* Alias for the same catalog as the main one. */ + AssetCatalog *alias_ruzena = service.create_catalog("character/Ružena/poselib"); + /* Alias for a sub-catalog. */ + AssetCatalog *alias_ruzena_hand = service.create_catalog("character/Ružena/poselib/hand"); + + AssetCatalogFilter filter = service.create_catalog_filter(UUID_POSES_RUZENA); + + /* Positive test for loaded-from-disk catalogs. */ + EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA)) + << "Main catalog should be included in the filter."; + EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_HAND)) + << "Sub-catalog should be included in the filter."; + EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_FACE)) + << "Sub-catalog should be included in the filter."; + + /* Positive test for newly-created catalogs. */ + EXPECT_TRUE(filter.contains(alias_ruzena->catalog_id)) + << "Alias of main catalog should be included in the filter."; + EXPECT_TRUE(filter.contains(alias_ruzena_hand->catalog_id)) + << "Alias of sub-catalog should be included in the filter."; + + /* Negative test for unrelated catalogs. */ + EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included."; + EXPECT_FALSE(filter.contains(UUID_ID_WITHOUT_PATH)); + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE)); + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_WHITESPACE)); + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_TRAILING_SLASH)); + EXPECT_FALSE(filter.contains(UUID_WITHOUT_SIMPLENAME)); +} + +TEST_F(AssetCatalogTest, create_catalog_filter_for_unknown_uuid) +{ + AssetCatalogService service; + const bUUID unknown_uuid = BLI_uuid_generate_random(); + + AssetCatalogFilter filter = service.create_catalog_filter(unknown_uuid); + EXPECT_TRUE(filter.contains(unknown_uuid)); + + EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included."; + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE)); +} + +TEST_F(AssetCatalogTest, create_catalog_filter_for_unassigned_assets) +{ + AssetCatalogService service; + + AssetCatalogFilter filter = service.create_catalog_filter(BLI_uuid_nil()); + EXPECT_TRUE(filter.contains(BLI_uuid_nil())); + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE)); +} + } // namespace blender::bke::tests -- cgit v1.2.3