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 = [email protected]):
Managed IDs:
(maxon.Id('[email protected]'),
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('[email protected]'),
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('[email protected]'),
maxon.Id('category_f40806b20b84f759'),
maxon.Id('category_8ea059e3d503175a'),
maxon.Id('category_9147f39c273d8342'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
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('[email protected]'),
maxon.Id('category_248c7a50c5c70f4c'),
maxon.Id('category_8509ac933bece593'),
maxon.Id('[email protected]'),
maxon.Id('category_64f67f3727141f1a'),
maxon.Id('[email protected]'),
maxon.Id('category_63ebef6aab44bd4d'),
maxon.Id('category_045526cd4a11b6dc'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
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('[email protected]'),
maxon.Id('category_ab975c5373b7ba07'),
maxon.Id('[email protected]'),
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('[email protected]'),
maxon.Id('category_8cbecf2e6c1c53e6'),
maxon.Id('category_66c6a982b7227e77'),
maxon.Id('category_a2fde34845c656bd'),
maxon.Id('category_95b2c4bc77dbeb2d'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('category_ecfd5ef0b32fdc6e'),
maxon.Id('[email protected]'),
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('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('category_bdac9c772d258396'),
maxon.Id('[email protected]40a591544bb9be87cf8d'),
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('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('category_ef962b4216a17f64'),
maxon.Id('category_5a9141a0036aa0e2'),
maxon.Id('category_e682fa27d7eb619d'),
maxon.Id('category_899783790d811f25'),
maxon.Id('[email protected]'))
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('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]0a462f04c64a5cea28209f9a38b'))
Tree:
'/MyUserCategory'
'/MyUserCategory/Blah'
'/MyUserCategory/Blah/Blub'
'/MyUserCategory/Blub'
'/MyUserCategory/Blub/Blah'
--------------------------------------------------------------------------------
AssetCategoryHandler at 0X7FD2F1CBED10 (repo = [email protected]):
Managed IDs:
(maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'),
maxon.Id('[email protected]'))
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("[email protected]"), **kwargs),
# # /Example Scenes
# repo.FindLatestAsset(aid= maxon.Id("[email protected]"), **kwargs),
# # /Materials
# repo.FindLatestAsset(aid= maxon.Id("category_a1ba084a9eeedb9b"), **kwargs),
# # /Nodes
# repo.FindLatestAsset(aid= maxon.Id("[email protected]"), **kwargs),
# # /Textures
# repo.FindLatestAsset(aid= maxon.Id("[email protected]"), **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("[email protected]"), **kwargs),
# /AlsoUserCategory (stored in a custom database)
repo.FindLatestAsset(aid= maxon.Id("[email protected]"), **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()