Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Get All Assets in Category

    Cinema 4D SDK
    python
    2
    3
    715
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • d_keithD
      d_keith
      last edited by d_keith

      Question

      How can I retrieve all image assets that live in a given category (and sub-categories) in the assets browser?

      651f1f86-0df5-47c1-a993-ef189fa3c946-image.png

      Ideally, I could use something like Python glob syntax:

      glog.glob("./Textures/Imperfections/**/*.png")

      And get back a list of assets

      [
      "si-v1_deposits_01_15cm.png",
      "si-v1_desposits_02_15cm.png",
      ...
      ]
      

      Related

      • Asset Metadata - Search for Asset Categories by Path : Cinema 4D C++ SDK
      • Can I download a Tex in Asset Browser and add into select RS node mat? | PluginCafé
      • Get asset from asset browser python | PluginCafé
      • Get All Assets in Category | PluginCafé
      • Asset API : Cinema 4D C++ SDK
      • cinema4d_py_sdk_extended/scripts/05_modules/assets at master · PluginCafe/cinema4d_py_sdk_extended · GitHub
      1 Reply Last reply Reply Quote 0
      • d_keithD
        d_keith
        last edited by d_keith

        Prints a list of Media Assets in the Cinema 4D Assets Browser to the Console [GitHub Gist]

        """Name-en-US: Print Media in Category
        Description-en-US: Prints a list of all media assets belonging to a category ID to the console.
        
        References:
        https://developers.maxon.net/forum/topic/14214/get-asset-from-asset-browser-python/4
        
        ## License
        
        MIT No Attribution
        
        Copyright 2022 Donovan Keith
        
        Permission is hereby granted, free of charge, to any person obtaining a copy of this
        software and associated documentation files (the "Software"), to deal in the Software
        without restriction, including without limitation the rights to use, copy, modify,
        merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
        permit persons to whom the Software is furnished to do so.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
        INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
        PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
        HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
        OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
        SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
        """
        
        # Imports
        
        from importlib.metadata import metadata
        from multiprocessing.sharedctypes import Value
        import c4d
        import maxon
        from typing import Optional
        
        # Helper Functions
        
        
        def FindAssetsByType(type) -> Optional[list[maxon.Asset]]:
            repository = maxon.AssetInterface.GetUserPrefsRepository()
            if not repository:
                raise RuntimeError("Unable to get User Repository.")
        
            # Find Assets:
            # https://developers.maxon.net/docs/py/2023_2/modules/maxon_generated/frameworks/asset/interface/maxon.AssetRepositoryInterface.html?highlight=findassets#maxon.AssetRepositoryInterface.FindAssets
            assets = repository.FindAssets(
                assetType=maxon.AssetTypes.File(),
                aid=maxon.Id(),
                version=maxon.Id(),
                findMode=maxon.ASSET_FIND_MODE.LATEST,
                receiver=None
            )
        
            return assets
        
        
        def FindFileAssets():
            return FindAssetsByType(type=maxon.AssetTypes.File())
        
        
        def GetAssetCategory(asset: maxon.AssetInterface):
            if not asset:
                raise ValueError("No asset provided.")
        
            meta_data = asset.GetMetaData()
            if not meta_data:
                raise ValueError("Unable to get asset meta data.")
        
            return meta_data.Get(maxon.ASSETMETADATA.Category)
        
        
        def GetAssetName(asset: maxon.AssetDescription) -> Optional[str]:
            if not asset:
                return
        
            metadata: maxon.AssetMetaData = asset.GetMetaData()
            if metadata is None:
                return
        
            name: str = asset.GetMetaString(
                maxon.OBJECT.BASE.NAME, maxon.Resource.GetCurrentLanguage(), "")
        
            return name
        
        
        def IsAssetAnImage(asset: maxon.AssetDescription) -> bool:
            if not asset:
                return
        
            metadata: maxon.AssetMetaData = asset.GetMetaData()
            if metadata is None:
                return
        
            sub_type: maxon.Id = metadata.Get(maxon.ASSETMETADATA.SubType, None)
            if (sub_type is None or maxon.InternedId(sub_type) != maxon.ASSETMETADATA.SubType_ENUM_MediaImage):
                return False
        
            return True
        
        
        def GetCategoryIdFromUser() -> str:
            imperfections_id_string = "category@e780d216ed404547942dcbfcbbe009e5"
        
            category_id_string = c4d.gui.InputDialog(
                "Input Category ID", preset=imperfections_id_string)
            if not category_id_string:
                raise ValueError("Invalid ID String")
        
            return maxon.Id(category_id_string)
        
        
        def main():
            category_id = GetCategoryIdFromUser()
            file_assets = FindFileAssets()
            imperfections_assets = [asset for asset in file_assets if (
                IsAssetAnImage(asset) and GetAssetCategory(asset) == category_id)]
            for asset in imperfections_assets:
                print(GetAssetName(asset))
        
        
        if __name__ == "__main__":
            main()
        
        
        ferdinandF 1 Reply Last reply Reply Quote 1
        • ferdinandF
          ferdinand @d_keith
          last edited by ferdinand

          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()
          

          MAXON SDK Specialist
          developers.maxon.net

          1 Reply Last reply Reply Quote 1
          • First post
            Last post