Hello @d_keith,
Thank you for reaching out to us. So, I had a look, and I could not find a bug with ExpandAssetCategoryId. I tried built-in categories, custom categories, and custom categories inside a custom database. My traversal method does return everything what I would expect to be returned. Find a more elaborate variant of ExpandAssetCategoryId below (I turned it into a class for easier inspection of the data).
There are a few things which could go wrong here:
You misunderstood the purpose of ExpandAssetCategoryId, it expands a category tree in a top-down fashion. E.g., for A->B->C, ExpandAssetCategoryId(B) will yield B, C but not A. It could certainly be done differently but that would be up to you.
There is a bug in your code in another place.
There might exist special conditions in the environment you are working in, but that is nothing for the forum.
Find below my example which will expand asset category trees from a root in a more visual and therefore easier to check manner.
Cheers,
Ferdinand
The result:
--------------------------------------------------------------------------------
AssetCategoryHandler at 0X7FD2F3350B90 (repo = database@U1pOb6rnHdar41NpWahfnn):
Managed IDs:
(maxon.Id('category@3d2621c1bc48485aa2b7ceead989e421'),
maxon.Id('category_32a5e2eefcc10592'),
maxon.Id('category_3e39b2c258ae9313'),
maxon.Id('category_f7cb610b3f172a93'),
maxon.Id('category_8f51f303e1bbf875'),
maxon.Id('category_bb4c48de0af7b8b3'),
maxon.Id('category_28801c2ab359e01e'),
maxon.Id('category_fd68f940d0b615ab'),
maxon.Id('category_993d6d73f3c2629e'),
maxon.Id('category_fce5f1171ae19de7'),
maxon.Id('category_50bbe4f91e99090b'),
maxon.Id('category_074c78c3e2d70ab9'),
maxon.Id('category_c92b8c257f9e7517'),
maxon.Id('category@23efef776140438f80b9ca1b0eef53f7'),
maxon.Id('category_a800e5a243f9f385'),
maxon.Id('category_9676f914364e4e27'),
maxon.Id('category_e024f65cd280795e'),
maxon.Id('category_2ab08ca489795a07'),
maxon.Id('category_c04b37955dcb3895'),
maxon.Id('category_34ed541e2020f38e'),
maxon.Id('category_f7316fed1fc11137'),
maxon.Id('category_efb8eae1116900c2'),
maxon.Id('category_c0e06630879697c2'),
maxon.Id('category_f4afa4f900193b95'),
maxon.Id('category_476ff23d55425ca4'),
maxon.Id('category_58e6a054402a25fd'),
maxon.Id('category_ddcdf72383424d7e'),
maxon.Id('category_ca1600eb107c6082'),
maxon.Id('category_1b0948c77cb2f61e'),
maxon.Id('category@c19d76abc1bf46f296ed837fd9e8a215'),
maxon.Id('category_f40806b20b84f759'),
maxon.Id('category_8ea059e3d503175a'),
maxon.Id('category_9147f39c273d8342'),
maxon.Id('category@6608dc0e700442f196c788a53170a078'),
maxon.Id('category@d8567d0c206145979ef94bf495128c00'),
maxon.Id('category@1bc8d3a8b3764acfb82bf5409646fe9f'),
maxon.Id('category@8b5f3e8dfbb2489caff67db176800a50'),
maxon.Id('category@d1b7bff693344889aa2b98b2f77e80ac'),
maxon.Id('category@edad962d94254a26a90a492fae589221'),
maxon.Id('category@7cd25e2b9d5c408986cded4f33d59c47'),
maxon.Id('category@ed2c2d5352cc461389526a4d9006febc'),
maxon.Id('category@5daaf21dde924b7abf18a25256282b69'),
maxon.Id('category@338d4b421ddb4ac1a1d7df21c0376911'),
maxon.Id('category@c0455a450d0e40eba0c09ad0673d876a'),
maxon.Id('category@88057ec2780148708363b96272234ded'),
maxon.Id('category@9d1a886ec1584d9f8794c59187c3d3d1'),
maxon.Id('category@cec99ab7a44f4b0bac4cedd538f44e02'),
maxon.Id('category@13549430f6a54bee9562d3541cb85614'),
maxon.Id('category@323d8d63b88b4ab4ba5bed368d895048'),
maxon.Id('category@a28830367831413f9fa5b66e4a5dd65f'),
maxon.Id('category@94fc8cb5718a492d9fd34f907b89720d'),
maxon.Id('category@bae2bf9f00d949c7b5df05cd6cba7966'),
maxon.Id('category_bb2ed911f3303be2'),
maxon.Id('category_4c6f230c27d35ad7'),
maxon.Id('category_e2204ff5b9252604'),
maxon.Id('category_0359297d725f327a'),
maxon.Id('category_4cc30dbe0f38a155'),
maxon.Id('category_1fee6083977acfee'),
maxon.Id('category_5904e55a542a55eb'),
maxon.Id('category@3448f9feaed44cedac87d499a4567a26'),
maxon.Id('category_248c7a50c5c70f4c'),
maxon.Id('category_8509ac933bece593'),
maxon.Id('category@3be598900c7a428eb0fcbced997655ba'),
maxon.Id('category_64f67f3727141f1a'),
maxon.Id('category@e33ef358a9b24d6cab80454a4179e419'),
maxon.Id('category_63ebef6aab44bd4d'),
maxon.Id('category_045526cd4a11b6dc'),
maxon.Id('category@a91d4cacaeba4170bf0c202d583fe2e0'),
maxon.Id('category@35ee4e3871414784a3767fc2037c305b'),
maxon.Id('category_04cdfd3b558329b6'),
maxon.Id('category_24b5ae0d89e20ccc'),
maxon.Id('category_cd5ad0a08c825d60'),
maxon.Id('category_1c643f453b71129e'),
maxon.Id('category_f9bd2fcc800c8279'),
maxon.Id('category_468266206e683e39'),
maxon.Id('category_172e168c2640e535'),
maxon.Id('category@c9f25306ba024d939ae97d0999d70995'),
maxon.Id('category_ab975c5373b7ba07'),
maxon.Id('category@5e2b516fbc0541ad9d59781d675ad8f9'),
maxon.Id('category_0133f046d03a46e7'),
maxon.Id('category_74ec3ae6ecaab0de'),
maxon.Id('category_fcf83cd1ff7e0fea'),
maxon.Id('category_32750a41ff5ae804'),
maxon.Id('category_f3dfa1fc22e1760f'),
maxon.Id('category_8c28445d28cba3b6'),
maxon.Id('category_65263c7c1c8c423c'),
maxon.Id('category_67a056c194d9897c'),
maxon.Id('category_73d82da307ebac4f'),
maxon.Id('category_fa850c478d745f9e'),
maxon.Id('category@780d427523e243028086a1aa33745b66'),
maxon.Id('category_8cbecf2e6c1c53e6'),
maxon.Id('category_66c6a982b7227e77'),
maxon.Id('category_a2fde34845c656bd'),
maxon.Id('category_95b2c4bc77dbeb2d'),
maxon.Id('category@f7ac3097518e4141a80ec19bc0c8548b'),
maxon.Id('category@93c3e0df81e54acfae70a129ad8f7fc0'),
maxon.Id('category@9812f958facf4bc39d90fb79cc4fe783'),
maxon.Id('category_ecfd5ef0b32fdc6e'),
maxon.Id('category@4d87bd4452b8493aa2ffd9e9500cba56'),
maxon.Id('category_de343673ae7ff879'),
maxon.Id('category_2aa95f92d383989e'),
maxon.Id('category_aded6c15b065a4a5'),
maxon.Id('category_fe8c8340d4cfebef'),
maxon.Id('category_2d90a72a03048a0a'),
maxon.Id('category_8191022b0575a8c8'),
maxon.Id('category_94bc6daab0db3454'),
maxon.Id('category@7f080054d14f4758aefb36dfae705b88'),
maxon.Id('category@d7bd914db4194b13baebbb49f981f395'),
maxon.Id('category_bdac9c772d258396'),
maxon.Id('category@ee5c708a346740a591544bb9be87cf8d'),
maxon.Id('category_f72dcc94be532fa4'),
maxon.Id('category_d6e2d304d1c0eb76'),
maxon.Id('category_819c3f327c46106c'),
maxon.Id('category_a7dd93c148997ea2'),
maxon.Id('category_adbf1096d2400c47'),
maxon.Id('category_6bf21b118e626b8f'),
maxon.Id('category_66478df5e0c296b2'),
maxon.Id('category_5ff49c9a181cad8c'),
maxon.Id('category_06eaa38e9750e5e3'),
maxon.Id('category_732f5bf076c873c7'),
maxon.Id('category_781d1c68184a4319'),
maxon.Id('category_b6d6fd4c864f01d8'),
maxon.Id('category_bcc53614ed7486b6'),
maxon.Id('category_20a0233895be23f3'),
maxon.Id('category_d42b5d6450873989'),
maxon.Id('category_cea3e864eb47b596'),
maxon.Id('category_3385066daeaf9819'),
maxon.Id('category@1445293c87e64c7684011d9c0477754e'),
maxon.Id('category@1f03b25c815e421789036e5a84a3ccf1'),
maxon.Id('category@9a2779a603bc4e1f93b9577abe5ecad1'),
maxon.Id('category@3064bcb4ff4e4ae890730837caea6f10'),
maxon.Id('category@1ba9498066ff42b6b2916242f8d8e7bb'),
maxon.Id('category@ef89ec1e9b5a47a5b245e05c5c8b1418'),
maxon.Id('category@b674f3ffaa9247f390fdbd49a08111a7'),
maxon.Id('category@9a75f45dc5984049b0307b5dc1aea91e'),
maxon.Id('category_ef962b4216a17f64'),
maxon.Id('category_5a9141a0036aa0e2'),
maxon.Id('category_e682fa27d7eb619d'),
maxon.Id('category_899783790d811f25'),
maxon.Id('category@beb1c0ee13d948faa8e2b084e6b23ab6'))
Tree:
'/Objects'
'/Objects/Info Graphics'
'/Objects/Humans'
'/Objects/Humans/3D Posable Silhouettes'
'/Objects/Humans/3D People - For Animation'
'/Objects/Humans/3D People [Low Resolution]'
'/Objects/Humans/3D People [Medium Resolution]'
'/Objects/Humans/Cutout'
'/Objects/Packaging'
'/Objects/Tools'
'/Objects/Vehicles'
'/Objects/Periodicals'
'/Objects/Appliances'
'/Objects/Eyewear'
'/Objects/Garments'
'/Objects/Finance'
'/Objects/Plants'
'/Objects/Plants/European Trees Young'
'/Objects/Plants/Garden & Exotic'
'/Objects/Plants/European Trees Mature'
'/Objects/Plants/Houseplants'
'/Objects/Plants/Cutouts'
'/Objects/Plants/Grass Elements'
'/Objects/Plants/Grass Elements/Low Resolution'
'/Objects/Plants/Grass Elements/Medium Resolution'
'/Objects/Pots'
'/Objects/Shelving'
'/Objects/Shelving/Modular Cabinets & Doors'
'/Objects/Shelving/Modular Cabinets & Doors/Misc Cabinet Examples'
'/Objects/Shelving/Modular Cabinets & Doors/Cabinet & Door Pieces'
'/Objects/Shelving/Modular Cabinets & Doors/Misc Door Examples'
'/Objects/Shelving/Living Room & Bedroom'
'/Objects/Celebration'
'/Objects/Kitbash'
'/Objects/Kitbash/Piping'
'/Objects/Kitbash/Piping/Corners'
'/Objects/Kitbash/Piping/Pipes'
'/Objects/Kitbash/Piping/Pipe'
'/Objects/Kitbash/Details'
'/Objects/Kitbash/Details/Squares'
'/Objects/Kitbash/Details/Crosses'
'/Objects/Kitbash/Details/Geometric'
'/Objects/Kitbash/Details/Triangles'
'/Objects/Kitbash/Details/Hexagons'
'/Objects/Kitbash/Details/Arrows'
'/Objects/Kitbash/Details/Rectangles'
'/Objects/Kitbash/Details/Circles'
'/Objects/Kitbash/Details/Pattern'
'/Objects/Kitbash/Tubes'
'/Objects/Kitbash/Connectors'
'/Objects/Kitbash/Joints'
'/Objects/Toys'
'/Objects/Outdoor Objects'
'/Objects/Outdoor Objects/Scaffolds'
'/Objects/Outdoor Objects/Trash Cans'
'/Objects/Outdoor Objects/Buildings'
'/Objects/Outdoor Objects/Buildings/Houses'
'/Objects/Outdoor Objects/Buildings/Cityscape'
'/Objects/Outdoor Objects/Buildings/Misc'
'/Objects/Outdoor Objects/Wall Decor'
'/Objects/Outdoor Objects/Manholes'
'/Objects/Outdoor Objects/Miscellaneous'
'/Objects/Outdoor Objects/Traffic Lights'
'/Objects/Outdoor Objects/Antennas'
'/Objects/Outdoor Objects/Pavement'
'/Objects/Outdoor Objects/Street Lights'
'/Objects/Outdoor Objects/Bus Stops'
'/Objects/Outdoor Objects/Fire Escapes'
'/Objects/Outdoor Objects/Infrastructure'
'/Objects/Outdoor Objects/Road Signs'
'/Objects/Outdoor Objects/Barriers & Barricades'
'/Objects/Tables'
'/Objects/Tables/Bedside Tables'
'/Objects/Tables/Game Tables'
'/Objects/Tables/Office Tables'
'/Objects/Tables/Coffee Tables'
'/Objects/Tables/Dining Tables'
'/Objects/Arts & Crafts'
'/Objects/Stairs'
'/Objects/Cogwheel Objects'
'/Objects/Cogwheel Objects/Saws'
'/Objects/Cogwheel Objects/Gears'
'/Objects/Cogwheel Objects/Miscellaneous'
'/Objects/Cogwheel Objects/Clutches'
'/Objects/Cogwheel Objects/Ratchet'
'/Objects/Cogwheel Objects/Watch Gears'
'/Objects/Kitchen'
'/Objects/Kitchen/Cutlery'
'/Objects/Kitchen/Dinnerware'
'/Objects/Kitchen/Accessories'
'/Objects/Kitchen/Cookware'
'/Objects/Kitchen/Serveware'
'/Objects/Kitchen/Glassware'
'/Objects/Screws'
'/Objects/Landscape'
'/Objects/Landscape/Wood'
'/Objects/Landscape/Stones'
'/Objects/Vases'
'/Objects/Window Treatments'
'/Objects/Seating'
'/Objects/Seating/Sofas'
'/Objects/Seating/Waiting Areas'
'/Objects/Seating/Benches'
'/Objects/Seating/Chairs'
'/Objects/Lighting & Ceiling Fans'
'/Objects/Lighting & Ceiling Fans/Ceiling Lighting'
'/Objects/Lighting & Ceiling Fans/Home Safety & Security'
'/Objects/Lighting & Ceiling Fans/Outdoor Lighting'
'/Objects/Lighting & Ceiling Fans/Wall Lighting'
'/Objects/Lighting & Ceiling Fans/Ceiling Fans'
'/Objects/Lighting & Ceiling Fans/Lamps'
'/Objects/Lighting & Ceiling Fans/Light Bulbs'
'/Objects/Lighting & Ceiling Fans/Light Stands'
'/Objects/Music'
'/Objects/Bedroom'
'/Objects/Bath'
'/Objects/Bath/Toilets'
'/Objects/Bath/Bathroom Vanities'
'/Objects/Bath/Sinks'
'/Objects/Bath/Towels'
'/Objects/Bath/Bathroom Accessories'
'/Objects/Bath/Bathtubs & Showers'
'/Objects/Bath/Faucets'
'/Objects/Gambling'
'/Objects/Stationary'
'/Objects/Sculpting Base Meshes'
'/Objects/Weather'
'/Objects/Home Decor'
'/Objects/Home Decor/Candles'
'/Objects/Home Decor/Books'
'/Objects/Home Decor/Picture Frames'
'/Objects/Home Decor/Decorative Storage'
'/Objects/Home Decor/Candleholders'
'/Objects/Home Decor/Decorative Boxes'
'/Objects/Home Decor/Clocks'
'/Objects/Electronics & Technology'
'/Objects/Miscellaneous'
'/Objects/Sports Items'
'/Objects/Food'
'/Objects/Doors & Windows'
--------------------------------------------------------------------------------
AssetCategoryHandler at 0X7FD2F3368910 (repo = net.maxon.repository.userprefs):
Managed IDs:
(maxon.Id('category@0ad833e88d774ac1ae4fd277e57ecacc'),
maxon.Id('category@d8bc777f959e48bda659d1674bb1d5f3'),
maxon.Id('category@0399404fe6fc46558b8f98f63d49ce28'),
maxon.Id('category@08ab3faa66e348fe86574469cbabb2c7'),
maxon.Id('category@793e20a462f04c64a5cea28209f9a38b'))
Tree:
'/MyUserCategory'
'/MyUserCategory/Blah'
'/MyUserCategory/Blah/Blub'
'/MyUserCategory/Blub'
'/MyUserCategory/Blub/Blah'
--------------------------------------------------------------------------------
AssetCategoryHandler at 0X7FD2F1CBED10 (repo = database@CGBLAN4TBpzrDfyA9gjMTn):
Managed IDs:
(maxon.Id('category@d1233ed9975945debc8942a30edfd706'),
maxon.Id('category@cfce060493cd4c61b92a115624d33eb1'),
maxon.Id('category@fa80f5a53cea42b7a72f494f0d673377'),
maxon.Id('category@ae9c2c99dee74deeb8a7a7d711d57ffe'),
maxon.Id('category@ffc383ed61454affa841f13931f93125'))
Tree:
'/AlsoUserCategory'
'/AlsoUserCategory/Foo'
'/AlsoUserCategory/Foo/Bar'
'/AlsoUserCategory/Blah'
'/AlsoUserCategory/Blah/Blub'
The code:
"""Provides a type to expand asset categories into trees.
"""
import c4d
import maxon
import typing
import pprint
class AssetCategoryHandler:
"""Handles an asset category that is a root to zero to many descendant categories.
"""
def __init__(self,
asset: maxon.AssetDescription,
categories: list[maxon.AssetDescription] = [],
language: maxon.LanguageRef = maxon.Resource.GetCurrentLanguage(),
path: str = ""):
"""Initializes the handler.
Args:
asset (maxon.AssetDescription): The category asset to expand.
categories (list[maxon.AssetDescription], optional): A list of categories which should
be respected for expansion, when the empty list is passed, the assets will be gathered
from the repository #asset is attached to. Defaults to [].
language (maxon.LanguageRef, optional): The language to retrieve asset strings in.
Defaults to maxon.Resource.GetCurrentLanguage().
path (str, optional): [internal] The current parent path. Defaults to "".
Raises:
TypeError: On type assertion failures.
"""
# Type checks and retrieving some data from the asset.
if not isinstance(asset, maxon.AssetDescription) or asset.IsNullValue():
raise TypeError(f"{asset = }")
if not isinstance(categories, list):
raise TypeError(f"{categories = }")
if not isinstance(language, maxon.LanguageRef) or language.IsNullValue():
raise TypeError(f"{language = }")
self._asset: maxon.AssetDescription = asset
self._aid: maxon.Id = maxon.Id(asset.GetId()) if isinstance(asset.GetId(), str) else asset.GetId()
self._name: str = asset.GetMetaString(maxon.OBJECT.BASE.NAME, language, "")
self._path: str = f"{path}/{self._name}"
self._repo: maxon.AssetRepositoryRef = asset.GetRepository()
self._categories: list[maxon.AssetDescription] = categories
self._language: maxon.LanguageRef = language
self._children: list["AssetCategoryHandler"] = []
# Populate _categories when empty.
if len(self._categories) < 1:
self._categories = self._repo.FindAssets(
maxon.AssetTypes.Category(), maxon.Id(),maxon.Id(), maxon.ASSET_FIND_MODE.LATEST)
# Expand the tree and cache the IDs.
self.__expand__()
self._ids: tuple[maxon.Id] = tuple(self.__yieldids__())
def __repr__(self) -> str:
"""Returns a string representation of the handler.
"""
return f"{self.__class__.__name__} at {str(hex(id(self))).upper()}"
def __expand__(self):
"""Expands the handler into a tree of handlers, one for each of the descendant categories
of the wrapped category asset.
"""
for asset in self._categories:
if self._aid != maxon.CategoryAssetInterface.GetParentCategory(asset):
continue
child = AssetCategoryHandler(asset, self._categories, self._language, self._path)
self._children.append(child)
def __yieldids__(self) -> typing.Generator[maxon.Id, None, None]:
"""Provides a generator for all asset IDs associated with this handler.
Use the property Ids instead, unless rebuilding this data is desired.
"""
yield self._aid
for child in self._children:
for aid in child.__yieldids__():
yield aid
@property
def Ids (self) -> tuple[maxon.Id]:
"""Returns a tuple of all asset IDs associated with this handler.
"""
return self._ids
def PrintTree(self, indent: int = 0) -> None:
"""Prints an asset tree for this handler.
"""
print(f"{' ' * indent}'{self._path}'")
for handler in self._children:
handler.PrintTree(indent + 1)
def main() -> None :
"""Runs the example.
"""
# Get the user preferences asset repository, in a production environment it will contain all
# relevant assets as of 2023.1.0.
if not maxon.AssetDataBasesInterface.WaitForDatabaseLoading():
raise RuntimeError("Could not load asset databases.")
repo: maxon.AssetRepositoryInterface = maxon.AssetInterface.GetUserPrefsRepository()
if not repo:
raise RuntimeError("Unable to retrieve user repository.")
# Used to shorten the call signatures of the FindLatestAsset() calls below.
kwargs: dict = {
"type": maxon.AssetTypes.Category(), "version": maxon.Id(),
"findMode": maxon.ASSET_FIND_MODE.LATEST }
# Get a couple of root level asset categories.
categoryRoots: list[maxon.AssetDescription] = [
# The /Objects category in the Asset Browser, a built-in category.
repo.FindLatestAsset(aid=maxon.Id("category@3d2621c1bc48485aa2b7ceead989e421"), **kwargs),
# # /Example Scenes
# repo.FindLatestAsset(aid= maxon.Id("category@4e785a69ef3749738bfd9d2b191535d5"), **kwargs),
# # /Materials
# repo.FindLatestAsset(aid= maxon.Id("category_a1ba084a9eeedb9b"), **kwargs),
# # /Nodes
# repo.FindLatestAsset(aid= maxon.Id("category@52d8f01357834200aa0dc28f0e61bbb3"), **kwargs),
# # /Textures
# repo.FindLatestAsset(aid= maxon.Id("category@8c76a408c56f4b5ca9f585dbe0ece9b7"), **kwargs),
# Custom category trees, they will be filtered out on other machines then mine, since other
# machines won't find these assets.
# /MyUserCategory (stored in the database in the user prefs)
repo.FindLatestAsset(aid= maxon.Id("category@0ad833e88d774ac1ae4fd277e57ecacc"), **kwargs),
# /AlsoUserCategory (stored in a custom database)
repo.FindLatestAsset(aid= maxon.Id("category@d1233ed9975945debc8942a30edfd706"), **kwargs),
]
# Wrap each one of them into a handler.
categoryHandlerList: list["AssetCategoryHandler"] = [
AssetCategoryHandler(asset) for asset in categoryRoots
if isinstance(asset, maxon.AssetDescription) and not asset.IsNullValue()]
# Iterate over the handlers and inspect their data.
for handler in categoryHandlerList:
print ("\n", "-" * 80)
print (f"{handler} (repo = {handler._asset.GetRepositoryId()}):\n")
print ("\nManaged IDs:\n")
pprint.pprint(handler.Ids)
print ("\nTree:\n")
handler.PrintTree(indent=1)
if __name__ == "__main__":
main()