Adding an Object to a New Cinema 4D Asset Database
-
Hi,
I'm attempting to write a script that will allow me to bulk-import the contents of a directcory full of
.c4d
files as Object Assets in a new Cinema 4D Asset Databse.I've found what feels like a perfect starting point by @ferdinand:
https://github.com/Maxon-Computer/Cinema-4D-Python-API-Examples/blob/master/scripts/05_modules/assets/asset_databases_r26.pyIn the documentation for
MountDatabases()
, he mentions:When the selected path does not contain an asset database, the necessary metadata will be created in that location by Cinema 4D.
However, when I attempt to do something similar in a script, the folder mounts, but no metadata files are created and C4D doesn't react when I attempt to disable/delete the "database stub".
Here's my current source code - if it looks practically identical to Ferdinand's that's because I re-wrote a lot of his methods by hand (hoping to better understand the API).
"""Name-en-US: Create Test Database Description-en-US: Creates a new C4D Asset Database named `_my-database.db` on your desktop.""" import c4d import os import maxon def MountAssetDatabase(path): # Wait for all existing dbs to load if not maxon.AssetDataBasesInterface.WaitForDatabaseLoading(): return RuntimeError("Could not load asset databases.") # Build a maxon url from the path url = maxon.Url(path) databaseCollection = maxon.AssetDataBasesInterface.GetDatabases() # Check if DB is already mounted for database in databaseCollection: print(database) if database._dbUrl == url: database._active = True maxon.AssetDataBasesInterface.SetDatabases(databaseCollection) return database database = maxon.AssetDatabaseStruct(url) databaseCollection.append(database) maxon.AssetDataBasesInterface.SetDatabases(databaseCollection) print(f"Created new DB for '{url}'") return database def main(): # Get the user's desktop path desktop_path = c4d.storage.GeGetC4DPath(c4d.C4D_PATH_DESKTOP) # Define the database file path name = "_my-database.db" path = os.path.join(desktop_path, name) # Create the database if it doesn't exist if not os.path.exists(path): os.makedirs(path, exist_ok=True) database = MountAssetDatabase(path) c4d.storage.ShowInFinder(path, open=True) # Execute the script if __name__ == '__main__': main()
Am I misunderstanding what should happen - or is it not working as expected?
-
Update: I've found found a workable solution, but still have some questions (listed at bottom)
@ferdinand - I tried to edit the original post to avoid a "diary" post, but ran into the following error:
You are only allowed to edit posts for 3600 second(s) after posting
"""Name-en-US: Import Directory as Object Assets Database Description-en-US: Creates a new C4D Asset Database named `_my-database.db` on your desktop, and loads the first object of each .c4d project in `_assets` References: https://github.com/Maxon-Computer/Cinema-4D-Python-API-Examples/blob/master/scripts/05_modules/assets/asset_databases_r26.py https://github.com/Maxon-Computer/Cinema-4D-Python-API-Examples/blob/master/scripts/05_modules/assets/asset_types_r26.py """ ## Imports import c4d import os import maxon ## User Inputs # TODO: Add input paths, otherwise default paths will be used INPUT_PROJECTS_DIR = "" OUTPUT_ASSETS_DB = "" ## Helpers def CreateRepoFromPath(path) -> maxon.UpdatableAssetRepositoryRef: # Wait for all existing dbs to load # Not sure if this is needed or superstitious if not maxon.AssetDataBasesInterface.WaitForDatabaseLoading(): return RuntimeError("Could not load asset databases.") url = maxon.Url(path) if url.IoDetect() == maxon.IODETECT.ERRORSTATE: raise RuntimeError(f"Directory {url} is invalid") repo_id = maxon.AssetInterface.MakeUuid(str(url), True) bases = maxon.BaseArray(maxon.AssetRepositoryRef) try: repository = maxon.AssetInterface.CreateRepositoryFromUrl( repo_id, maxon.AssetRepositoryTypes.AssetDatabase(), bases, url, True, False, False, ) except Exception as e: # If at first you don't succeed, try... try... again. repository = maxon.AssetInterface.CreateRepositoryFromUrl( repo_id, maxon.AssetRepositoryTypes.AssetDatabase(), bases, url, True, False, False, ) if not repository: raise RuntimeError("Could not create Repository.") return repository def MountAssetDatabase(path): # Wait for all existing dbs to load if not maxon.AssetDataBasesInterface.WaitForDatabaseLoading(): return RuntimeError("Could not load asset databases.") # Build a maxon url from the path url = maxon.Url(path) databaseCollection = maxon.AssetDataBasesInterface.GetDatabases() # Check if DB is already mounted for database in databaseCollection: print(database) if database._dbUrl == url: database._active = True maxon.AssetDataBasesInterface.SetDatabases(databaseCollection) return database database = maxon.AssetDatabaseStruct(url) databaseCollection.append(database) maxon.AssetDataBasesInterface.SetDatabases(databaseCollection) return database def AddObjectToRepository( repo: maxon.UpdatableAssetRepositoryRef, doc: c4d.documents.BaseDocument, obj: c4d.BaseObject, ): if repo is None: raise ValueError("Invalid repo") if obj is None: raise ValueError("Input obj does not exist") asset_id = maxon.AssetInterface.MakeUuid(prefix="object", compact=False) asset_name = obj.GetName() asset_version = ( "0.1.0" # Using Semantic Versioning, as we rarely get it right the first time. ) asset_metadata = maxon.AssetMetaData() asset_category_id = maxon.Id("net.maxon.assetcategory.uncategorized") store_asset_struct = maxon.StoreAssetStruct(asset_category_id, repo, repo) asset = maxon.AssetCreationInterface.CreateObjectAsset( obj, doc, store_asset_struct, asset_id, asset_name, asset_version, asset_metadata, True, ) return asset def OpenAssetBrowser(): # The command id for the Asset Browser. CID_ASSET_BROWSER = 1054225 # Open the Asset Browser when it is not already open. if not c4d.IsCommandChecked(CID_ASSET_BROWSER): c4d.CallCommand(CID_ASSET_BROWSER) ## Main def main(): # Get the user's desktop path desktop_path = c4d.storage.GeGetC4DPath(c4d.C4D_PATH_DESKTOP) # Get the Input File Path assets_dir = INPUT_PROJECTS_DIR if not assets_dir: assets_dir = os.path.join(desktop_path, "_assets") # Define the output database path database_name = "_my-database.db" database_path = OUTPUT_ASSETS_DB if not database_path: database_path = os.path.abspath(os.path.join(desktop_path, database_name)) # Create the database if it doesn't exist if not os.path.exists(database_path): os.makedirs(database_path, exist_ok=True) repository = CreateRepoFromPath(database_path) doc = c4d.documents.GetActiveDocument() obj = doc.GetActiveObject() # Iterate through all *.c4d files in the assets directory assets = [] for file_name in os.listdir(assets_dir): if file_name.endswith(".c4d"): file_path = os.path.join(assets_dir, file_name) # Load the C4D file silently loaded_doc = c4d.documents.LoadDocument( file_path, c4d.SCENEFILTER_OBJECTS | c4d.SCENEFILTER_MATERIALS, None ) if loaded_doc is None: print(f"Failed to load {file_path}") continue # Get the first object in the loaded document obj = loaded_doc.GetFirstObject() if obj is None: print(f"No objects found in {file_path}") continue # Add the object to the repository asset = AddObjectToRepository(repository, loaded_doc, obj) if asset is None: raise RuntimeError(f"Unable to ingest {file_name}") assets.append(asset) # Unload/close the loaded document c4d.documents.KillDocument(loaded_doc) database = MountAssetDatabase(database_path) maxon.AssetManagerInterface.RevealAsset(assets) c4d.EventAdd() c4d.storage.ShowInFinder(database_path, open=True) # Execute the script if __name__ == "__main__": main()
Questions
- Should I be able to mount a directory and have it auto-create a DB?
- Any idea why I need to re-try creating the Repo for it to work?
- If I run this script multiple times, I end up with multiple DBs in the same directory - any way to get
CreateRepoFromUrl()
to detect that there's already a repo so it doesn't need to create one, and can instead just load it? - Show Assets doesn't seem to be doing what I want. I typically have to manually close/reopen the Assets Browser a couple of times to see an update. Is there an event I need to add to immediately show the assets?
Thanks!
-
Hey @d_keith,
I am briefly on vacation, so it will probably take a week before I can have a look. But you are in good hands with the rest of the SDK team
But, yes, you should be able to create a repo (i.e., asset database) with
CreateRepositoryFromUrl
and when the passed URL already contains a repo, it should not create a new one. That was at least its S26 behavior, it could be that Tilo has changed that since then. If I remember correctly, Daniel reported something similar about that function not expecting existing repos, or at least that it created a database asset each time for being called (not the same as an entire new repo and probably what you mean).The TLDR aside from the concrete support case is: Please create tickets for bugs, when the documentation says one thing, and the function does another thing, this is at least worthy of being a bug candidate. When this is super urgent for you and Ilia or Maxime do not have an answer right away, you could also ask Tilo or talk with Daniel what he did.