Hello @d_keith,
thank you for reaching out to us and your extensive documentation efforts. Much appreciated! What you did there is correct, but in the spirit of simplicity, I think the code could be a bit condensed. There snuck in a few unused (and unrelated) includes and being so generous with separating things into help functions can impact readability and execution times. I also added the "expand asset category into sub-categories" thing you wanted.
In short: I think this is a good occasion to use a receiver callback function, as this structures the code naturally. Find my take below.
Cheers,
Ferdinand
The result:
categoryIds = [maxon.Id('category@b9c32d04a12d449ca1c758ddb3c695b0'), maxon.Id('category@985a9913c47341e4a373ca71d8e73b18')]
name = 'Cloudy - VHDRI.hdr', asset.GetId() = 'file_e02d0a81b02be4fa'
name = 'Default HDR.hdr', asset.GetId() = 'file_2792c7829905f40d'
name = 'Desert.exr', asset.GetId() = 'file_0b3eb8e7595c1b5d'
name = 'jhdri-v1_ext_sunset_seastar_acescg.exr', asset.GetId() = 'file_dc156adf0cc146e8'
name = 'HDR012.hdr', asset.GetId() = 'file_b1db2c38badb130c'
The code:
"""Retrieves all MediaImage assets that are attached to the "Textures/HDR" category or one of its
child categories.
This is an application of what I dubbed "Filtered Asset searches" in the C++ Docs, find the Python
example here [1].
References:
[1] https://github.com/PluginCafe/cinema4d_py_sdk_extended/blob/master/scripts/05_modules/assets/asset_databases_r26.py#L416
"""
import typing
import maxon
def ExpandAssetCategoryId(repo: maxon.AssetRepositoryInterface, cid: maxon.Id) -> list[maxon.Id]:
"""Retrieves the IDs of all descendant asset categories of #cid.
"""
# Get all asset category assets in #repo.
categoryAssets: maxon.AssetDescription = repo.FindAssets(
maxon.AssetTypes.Category(), maxon.Id(),maxon.Id(), maxon.ASSET_FIND_MODE.LATEST)
# The IDs we have currently to check for descendants and the final result list.
idsToCheck: list[maxon.Id] = [cid]
results: list[maxon.Id] = []
# Try to empty #idsToCheck while popping elements from it and adding new ones.
while idsToCheck:
# Remove the first element and add it to the results.
cid: maxon.Id = idsToCheck.pop()
results.append(cid)
# Find all category assets which have #cid as their parent category and add their IDs as
# to be resolved category IDs. We stop the search here early, i.e., we assume the category
# tree to be mono-hierarchical (one category can only be attached to one category). There
# is not really anything in the Asset API which would enforce this, but it is how asset
# categories are currently handled.
for asset in categoryAssets:
if cid == maxon.CategoryAssetInterface.GetParentCategory(asset):
aid: typing.Any = asset.GetId()
idsToCheck.append(aid if isinstance(aid, maxon.Id) else maxon.Id(aid))
break
return results
def main() -> None :
"""Runs the example.
"""
# Make sure the asset databases have been loaded and get the user repository, i.e., the
# repository where more or less all content can be found.
if not maxon.AssetDataBasesInterface.WaitForDatabaseLoading():
raise RuntimeError("Could not load asset databases.")
repo: maxon.AssetRepositoryRef = maxon.AssetInterface.GetUserPrefsRepository()
if not repo:
raise RuntimeError("Unable to retrieve user repository.")
# The final #results list where we put all the asset descriptions of image assets which are
# parented to category assets with any of the IDs in #categoryIds.
results: list[tuple[str, maxon.AssetDescription]] = []
# The root category we are interested in, this is the id for the "Textures/HDR" category.
rootCategoryId: maxon.Id = maxon.Id("category@b9c32d04a12d449ca1c758ddb3c695b0")
# Expand #rootCategoryId into a list of all its attached child categories.
categoryIds: list[maxon.Id] = ExpandAssetCategoryId(repo, rootCategoryId)
# The language Cinema 4D is running in, use GetDefaultLanguage() to evaluate things in engUS.
currentLanguage: maxon.LanguageRef = maxon.Resource.GetCurrentLanguage()
def onSearchHit(asset: maxon.AssetDescription) -> bool:
"""Called for each asset by the FindAssets() call below.
Assets which match the search criteria are written to #results.
"""
# Get the metadata of the asset and assert that its subtype is MediaImage, step over an asset
# when these operations fail.
metadata: maxon.AssetMetaData = asset.GetMetaData()
if metadata is None:
return True
sid: maxon.InternedId = maxon.InternedId(metadata.Get(maxon.ASSETMETADATA.SubType, maxon.Id()))
if sid != maxon.ASSETMETADATA.SubType_ENUM_MediaImage:
return True
# Append the name of the asset and the asset itself to the results when #asset is parented
# to a category asset with an ID which is contained ins #categoryIds.
if maxon.CategoryAssetInterface.GetParentCategory(asset) in categoryIds:
results.append((asset.GetMetaString(maxon.OBJECT.BASE.NAME, currentLanguage, ""), asset))
return True
# Search #repo for all file type assets (this includes MediaImage assets) with any id and pass
# the data through onSearchHit.
repo.FindAssets(
assetType=maxon.AssetTypes.File(),
aid=maxon.Id(),
version=maxon.Id(),
findMode=maxon.ASSET_FIND_MODE.LATEST,
receiver=onSearchHit)
# Print the expanded category IDs and the first five asset matches associated with them.
print (f"{categoryIds = }")
for name, asset in results[:5]:
print (f"{name = }, {asset.GetId() = }")
if __name__ == "__main__":
main()