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
    • Register
    • Register
    • Login
    1. Maxon Developers Forum
    2. d_keith
    3. Posts
    • Profile
    • Following 0
    • Followers 0
    • Topics 7
    • Posts 16
    • Best 2
    • Controversial 0
    • Groups 0

    Posts made by d_keith

    • RE: Finding, Renaming, and Updating all References to Assets

      Thanks for the response!

      I appreciate the ASSETDATA_FLAG_MULTIPLEUSE tip.

      My hope was that there was some sort of existing RenameAsset SDK method that would also update all references to that asset without implementing custom handling for various element types (Redshift Nodal Material, Cinema 4D Classic Material, Arnold Dome Light, XRef, etc). Evidently such a public python SDK method doesn't exist.

      My example code was texture specific as that's the part of the problem that's most pressing to me and I was able to implement with custom per-type handling - if I manage to get other types working, I'll attempt to update over time.

      posted in Cinema 4D SDK
      d_keithD
      d_keith
    • Finding, Renaming, and Updating all References to Assets

      Hi,

      Are there any helper methods for finding & updating all references to assets in a Cinema 4D document?

      I'm attempting to write a script that lets the user rename textures. GetAllAssetsNew() makes it easy to find the assets, but not all elements support owner[channelId] = newName style assignment, and many of the channelId's come back as -1.

      Cinema 4D includes native functionality that can discover & rename all usages of a give asset in the Project Asset Inspector, Scene Info, and the RS Asset Manager:

      Scene Info

      Project Asset Inspector

      RS Asset Manager

      Is there a simple method for updating all asset references (alembic, caches, sound effector, etc, textures, etc)? Manually handling different asset types (like Standard vs RS Materials) feels really brittle and not particularly future proof.


      I've create a script (with liberal assistance of GitHub Copilot and one of @ferdinand's examples) that lets me rename Redshift textures, but I'm looking for a more general solution that doesn't force me to write custom interactions for every renderer / object type:

      """Name-en-US: Find and Rename Textures
      Description-en-US: Lists all assets in scene and lets you find/replace parts of their names.
      
      Credit:
      - Donovan Keith
      - GitHub Copilot with Claude 3.7
      - @ferdinand
      
      References:
      - https://developers.maxon.net/forum/topic/14388/change-textures-path-from-getallassetnew-in-the-new-node-editor
      - https://developers.maxon.net/docs/py/2024_4_0a/modules/c4d.documents/BaseDocument/index.html#BaseDocument.GetAllTextures
      - https://developers.maxon.net/docs/py/2024_4_0a/modules/c4d.documents/index.html#c4d.documents.GetAllAssetsNew
      """
      
      import c4d
      import maxon
      import os
      
      
      def get_scene_assets(doc):
          """Get all assets from the active document."""
          if not doc:
              return []
          
          asset_list = []
          last_path = ""
          c4d.documents.GetAllAssetsNew(
              doc, 
              allowDialogs=False, 
              lastPath=last_path, 
              flags=c4d.ASSETDATA_FLAG_NODOCUMENT, 
              assetList=asset_list
          )
          return asset_list
      
      
      def get_search_replace_strings():
          """Prompt user for search and replace strings."""
          search_str = c4d.gui.InputDialog("Enter the string to find in asset names:", preset="old-name")
          if search_str is None:
              return None, None
              
          replace_str = c4d.gui.InputDialog("Enter the replacement string for asset names:", preset="new-name")
          if replace_str is None:  # User canceled
              return None, None
          
          return search_str, replace_str
      
      
      def find_assets_to_rename(asset_list, search_str, replace_str):
          """Find assets containing the search string."""
          assets_to_rename = []
          for asset in asset_list:
              if search_str in asset['assetname']:
                  new_name = asset['assetname'].replace(search_str, replace_str)
                  assets_to_rename.append((asset, new_name))
          return assets_to_rename
      
      
      def show_preview_dialog(assets_to_rename):
          """Show preview of assets to be renamed and ask for confirmation."""
          if not assets_to_rename:
              c4d.gui.MessageDialog("No assets found with the specified string.")
              return False
              
          preview_msg = f"Found {len(assets_to_rename)} assets to rename:\n\n"
          MAX_PREVIEW_ITEMS = 10
          for i, (asset, new_name) in enumerate(assets_to_rename[:MAX_PREVIEW_ITEMS], 1):
              preview_msg += f"{i}. {asset['assetname']} → {new_name}\n"
              
          if len(assets_to_rename) > MAX_PREVIEW_ITEMS:
              preview_msg += f"...and {len(assets_to_rename) - MAX_PREVIEW_ITEMS} more\n"
      
          preview_msg += "\nDo you want to continue?"
          return c4d.gui.QuestionDialog(preview_msg)
      
      
      def rename_file_on_disk(file_path, new_name):
          """Rename the actual file on disk."""
          if not file_path or not os.path.exists(file_path):
              return False
          
          try:
              # Create new file path with the same directory but new name
              new_file_path = os.path.join(os.path.dirname(file_path), os.path.basename(new_name))
              
              # Create destination folder if it doesn't exist
              new_dir = os.path.dirname(new_file_path)
              if not os.path.exists(new_dir):
                  os.makedirs(new_dir)
                  
              # Rename the file
              os.rename(file_path, new_file_path)
              print(f"Renamed file on disk: {file_path} → {new_file_path}")
              return True
          except Exception as e:
              print(f"Error renaming file: {e}")
              return False
      
      
      def update_standard_shader(owner, param_id, new_name):
          """Update standard shader asset reference."""
          owner[param_id] = new_name
          print(f"Updated standard shader: {owner.GetName()}")
      
      
      def update_redshift_node(owner, node_path, node_space, original_name, new_name):
          """Update Redshift nodal material references."""
          # Get the node material associated with the material
          node_material = owner.GetNodeMaterialReference()
          if not node_material:
              return False
              
          graph = node_material.GetGraph(node_space)
          if graph.IsNullValue():
              print(f"Invalid node space for {owner.GetName()}: {node_space}")
              return False
          
          # Start a graph transaction and get the node
          with graph.BeginTransaction() as transaction:
              node = graph.GetNode(maxon.NodePath(node_path))
              if node.IsNullValue():
                  print(f"Could not retrieve target node {node_path}")
                  return False
              
              try:
                  path_port = node.GetInputs().FindChild(
                      "com.redshift3d.redshift4c4d.nodes.core.texturesampler.tex0").FindChild("path")
                  if path_port.IsNullValue():
                      print("Could not find path port for Redshift texture node")
                      return False
                  
                  # Set the new value
                  path_port.SetPortValue(new_name)
                  print(f"Updated Redshift texture reference in material: {owner.GetName()}")
                  
                  # Get the current node name
                  node_name = node.GetValue(maxon.NODE.BASE.NAME)
                  
                  # Get the texture filename (without path)
                  old_filename = os.path.basename(original_name)
                  new_filename = os.path.basename(new_name)
                  
                  # If node name matches the old texture filename, update it
                  if node_name == old_filename or node_name == os.path.splitext(old_filename)[0]:
                      node.SetValue(maxon.NODE.BASE.NAME, new_filename)
                      print(f"Renamed node from '{node_name}' to '{new_filename}'")
                  
                  transaction.Commit()
                  return True
              
              except Exception as e:
                  print(f"Error updating Redshift node: {e}")
                  return False
      
      
      def process_asset_rename(doc, asset, new_name, rename_files):
          """Process the renaming of a single asset."""
          original_name = asset['assetname']
          print(f"Renaming asset: {original_name} to {new_name}")
      
          owner = asset.get('owner')
          if not owner:
              return False
              
          doc.AddUndo(c4d.UNDOTYPE_CHANGE, owner)
          
          # Rename the actual file if requested
          if rename_files:
              file_path = asset.get('filename', '')
              rename_file_on_disk(file_path, new_name)
          
          # Handle different types of assets
          
          # 1. Handle standard shader assets (like Bitmap shader)
          param_id = asset.get('paramId', -1)
          if isinstance(owner, c4d.BaseShader) and param_id != -1 and param_id != c4d.NOTOK:
              if asset.get('nodePath', '') == '':  # Regular shader
                  update_standard_shader(owner, param_id, new_name)
          
          # 2. Handle Redshift nodal material references
          node_path = asset.get('nodePath', '')
          node_space = asset.get('nodeSpace', '')
          
          if isinstance(owner, c4d.BaseMaterial) and node_path and node_space:
              update_redshift_node(owner, node_path, node_space, original_name, new_name)
          
          return True
      
      
      def main():
          doc = c4d.documents.GetActiveDocument()
          if not doc:
              return
          
          # Get all assets from the scene
          asset_list = get_scene_assets(doc)
          
          # Print found assets for debugging
          for asset in asset_list:
              print(f"Found asset: {asset['assetname']}")
      
          # Get search and replace strings
          search_str, replace_str = get_search_replace_strings()
          if not search_str or replace_str is None:
              return
              
          # Find assets to rename
          assets_to_rename = find_assets_to_rename(asset_list, search_str, replace_str)
          
          # Show preview and ask for confirmation
          if not show_preview_dialog(assets_to_rename):
              return
              
          # Ask if user wants to rename files on disk as well
          rename_files = c4d.gui.QuestionDialog("Do you also want to rename the files on disk?")
          
          # Process rename operations
          doc.StartUndo()
          for asset, new_name in assets_to_rename:
              process_asset_rename(doc, asset, new_name, rename_files)
          doc.EndUndo()
      
      
      if __name__ == '__main__':
          main()
      

      Research / Related Posts
      https://developers.maxon.net/forum/topic/12893/changing-assets-paths-feature-request
      https://developers.maxon.net/forum/topic/14241/api-for-project-asset-inspector - Seems like there's a method for finding all assets.

      posted in Cinema 4D SDK python 2025
      d_keithD
      d_keith
    • RE: Unable to Sign In to Old Forum to Generate Plugin ID

      Thanks Ferdinand - Up and running again!

      posted in Cinema 4D SDK
      d_keithD
      d_keith
    • Unable to Sign In to Old Forum to Generate Plugin ID

      Hi,

      I'd like to generate a new Plugin ID using:
      https://developers.maxon.net/forum/pid

      While I have a working username/password for the new forum, the same credentials don't work for the old plugincafe forum.

      If I click on "reset password" the login page simply refreshes and I never receive a reset password email.

      If I attempt to register a new account, it tells me my email is taken (expected).

      Any suggestions?
      Thanks!

      posted in Cinema 4D SDK python
      d_keithD
      d_keith
    • RE: Adding an Object to a New Cinema 4D Asset Database

      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

      1. Should I be able to mount a directory and have it auto-create a DB?
      2. Any idea why I need to re-try creating the Repo for it to work?
      3. 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?
      4. 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!

      posted in Cinema 4D SDK
      d_keithD
      d_keith
    • 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.py

      In 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".
      46ca82ad-cc20-4793-a4f5-b1f23e909280-image.png
      e5cc6f01-1e88-49d8-b0b8-ac3326704e21-image.png

      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?

      posted in Cinema 4D SDK python macos 2025
      d_keithD
      d_keith
    • RE: Hiding Node Previews for all Redshift Nodes in Redshift Materials

      @Dunhou - Thanks so much for the sample code, it works brilliantly. Your Renderer lib looks incredible, I love how compact/accessible that version is. Unfortunately I'll need to share these scripts with a variety of external parners and I'm not sure I can trust they'll have Boghma installed.

      @ferdinand - I'll avoid diary-style requests in the future. Thanks for pointing me to Graph Descriptions. I knew that something like this existed, but I couldn't track it down. Perhaps this would make a good SDK example?

      @ferdinand - How would you query all nodes except the main Output node?

      Here's a fully working script:

      """Name-en-US: Hide RS Material Node Previews
      Description-en-US: Finds all previews in Redshift nodal materials except for their final Output node.
      
      ## References
      
      - [Hiding Node Previews for all Redshift Nodes in Redshift Materials](https://developers.maxon.net/forum/topic/16119/hiding-node-previews-for-all-redshift-nodes-in-redshift-materials)
      
      ## Change Log
      
      - v1.0.0: Converted to script and added undo support, documentation, and reference.
      - v0.9.0: Initial working version by Dunhou.
      - v0.1.0: Prototype by Donovan.
      
      ## Thanks
      
      Special thanks to Dunhou and Ferdinand.
      
      """
      
      __authors__ = [
          "Dunhou (https://www.boghma.com/)",
          "Donovan Keith <[email protected]> (https://www.maxon.net/en/capsules)",
      ]
      
      __version__ = "1.0.0"
      
      import c4d
      import maxon
      
      doc: c4d.documents.BaseDocument
      
      
      def main():
          global doc
      
          if doc is None:
              print("No active document.")
              return
      
          doc.StartUndo()
          for mat in doc.GetMaterials():
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, mat)
      
              nodeMaterial: c4d.NodeMaterial = mat.GetNodeMaterialReference()
              if not nodeMaterial.HasSpace(maxon.NodeSpaceIdentifiers.RedshiftMaterial):
                  continue
      
              graph: maxon.NodesGraphModelRef = nodeMaterial.GetGraph(
                  maxon.NodeSpaceIdentifiers.RedshiftMaterial
              )
              root: maxon.GraphNode = graph.GetViewRoot()
              nimbusRef: maxon.NimbusBaseRef = mat.GetNimbusRef(
                  maxon.NodeSpaceIdentifiers.RedshiftMaterial
              )
              endNodePath: maxon.NodePath = nimbusRef.GetPath(
                  maxon.NIMBUS_PATH.MATERIALENDNODE
              )
              endNode: maxon.GraphNode = graph.GetNode(endNodePath)
      
              with graph.BeginTransaction() as transaction:
                  node: maxon.GraphNode
                  for node in root.GetInnerNodes(
                      mask=maxon.NODE_KIND.NODE, includeThis=False
                  ):
                      node.SetValue(maxon.NODE.BASE.DISPLAYPREVIEW, maxon.Bool(False))
      
                  endNode.SetValue(maxon.NODE.BASE.DISPLAYPREVIEW, maxon.Bool(True))
      
                  transaction.Commit()
          doc.EndUndo()
      
          c4d.EventAdd()
      
      
      if __name__ == "__main__":
          main()
      
      
      posted in Cinema 4D SDK
      d_keithD
      d_keith
    • RE: Hiding Node Previews for all Redshift Nodes in Redshift Materials

      Another hacky approach that at least gets me a semi-automated solution. I broke my previous hack into two scripts to get around a limit in the number of CallCommands you can invoke with predictable results. If you dock the icons for the two scripts next to each other, you can click your way to all previews for all materials being closed. Doesn't solve for keeping the final Output node's preview up.

      1. Select Next Material: Selects the next material in the Material Manager.

      """Name-en-US: Select Next Material
      Description-en-US: Selects the material after the first active material.
      
      A bit hacky as it is using CallCommand.
      """
      
      import c4d
      
      def main():
          doc = c4d.documents.GetActiveDocument()
          materials = doc.GetMaterials()
          active_material = doc.GetActiveMaterial()
      
          if not active_material:
              c4d.gui.MessageDialog("No active material found.")
              return
      
          active_material = doc.GetActiveMaterial()
          next_material = None
          if not active_material:
              next_material = doc.GetFirstMaterial()
          else:
              doc.AddUndo(c4d.UNDOTYPE_CHANGE_SELECTION, active_material)
              next_material = active_material.GetNext()
              if not next_material:
                  next_material = doc.GetFirstMaterial()
      
          if not next_material:
              c4d.gui.MessageDialog("No materials in this scene.")
              return
          
          doc.StartUndo()
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SELECTION, next_material)
          doc.SetActiveMaterial(next_material)
          doc.EndUndo()
          c4d.EventAdd()
          c4d.CallCommand(16297) # Scroll To Selection
      
      if __name__ == '__main__':
          main()
      

      2. Toggle Node Previews: Selects all nodes in the active material, toggles the preview, and then deselects all.

      """Name-en-US: Hide Node Previews
      Descripton-en-US: Toggles the Show Preview command for all nodes.
      
      Might not actually hide if all previews start closed.
      Use in conjunction with select-next-material.py to quickly
      toggle all materials in a scene.
      """
      
      import c4d
      
      def main():
          c4d.CallCommand(465002309) # Select All Nodes
          c4d.CallCommand(465002350) # Show Preview
          c4d.CallCommand(465002310) # Deselect All
      
      if __name__ == "__main__":
          main()
      
      posted in Cinema 4D SDK
      d_keithD
      d_keith
    • Hiding Node Previews for all Redshift Nodes in Redshift Materials

      Hi,

      I'm trying to write a python script that hides material previews for all nodes in all Redshift materials except for the final Output node.

      In python pseudocode, it would look something like this:

      materials = doc.GetAllMaterials()
      for mat in materials:
         if mat.GetType() != "redshift":
             continue
          nodes = mat.GetAllNodes()
          for node in nodes:
              if node.GetType() == "Output":
                 continue
              node.SetPreview(False)
      

      Unfortunately, it seems it is quite a bit trickier to do this than I hoped.
      I got as far as finding the GraphModelHelper but there doesn't seem to be a way to retrieve all nodes without a filter; and I'm not sure how to find list of IDs for user-visible nodes.

      There are some 3rd party libs on GitHub that would seem to make some of this easier, but I'd like to use native methods if possible.

      Any suggestions/guidance would be greatly appreciated!

      Edit: Here's an ultra-hacky script that doesn't do what I hoped either.

      """Hide All Node Previews in Materials
      
      Hacky script to hide all node previews by invoking the user-visible commands in the Node Editor.
      
      I grabbed the CallCommand IDs from the C4D Script Log.
      
      Doesn't seem to work.
      """
      
      import c4d
      
      doc: c4d.documents.BaseDocument # The active document.
      
      def main():
          materials = doc.GetMaterials()
          if not materials:
              return
          
          # Open the Node Editor
          c4d.CallCommand(465002211) # Node Editor
      
          for mat in materials:
              doc.SetActiveMaterial(mat)
              c4d.EventAdd()
      
              c4d.CallCommand(465002309) # Select All Nodes
              c4d.CallCommand(465002350) # Show Preview
      
          c4d.EventAdd()
      
      if __name__ == "__main__":
          main()
      

      Edit 2: A little closer

      Okay, I'm a bit closer now thanks to this post by @m_adam

      """Hide All Node Previews in Materials
      """
      
      import c4d
      import maxon
      
      doc: c4d.documents.BaseDocument # The active document.
      
      def main():
          materials = doc.GetMaterials()
          if not materials:
              return
          
          doc.StartUndo()
      
          for material in materials:
              # Reference
              # https://developers.maxon.net/forum/topic/16108/automating-colorspace-change-for-textures-in-redshift/3
              nodeMaterial: c4d.NodeMaterial = material.GetNodeMaterialReference()
              if not nodeMaterial.HasSpace(maxon.NodeSpaceIdentifiers.RedshiftMaterial):
                  continue
      
              graph: maxon.NodesGraphModelRef = maxon.GraphDescription.GetGraph(material, maxon.NodeSpaceIdentifiers.RedshiftMaterial)
      
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, material)
              with graph.BeginTransation() as transaction:
                  # TODO: Get all nodes that have Previews
                  # TODO: Iterate over all nodes
                  # TODO: Hide all node previews
                  pass
      
              transaction.Commit()
      
          doc.EndUndo()
          c4d.EventAdd()
      
      if __name__ == "__main__":
          main()
      
      posted in Cinema 4D SDK python 2025
      d_keithD
      d_keith
    • RE: Traversing Asset Categories

      Update: There seems to be a bug in ExpandAssetCategoryId as I can retrieve all the assets (even those in custom DBs) if I start from a manually generated list of category Ids, but I only geta couple of the categories using this method.

      posted in Cinema 4D SDK
      d_keithD
      d_keith
    • Traversing Asset Categories

      Hi @ferdinand - Thank you so much for this!

      This works great for Maxon's built-in assets databases. How would I retrieve all assets that belong to a category, even if they live in custom user-mounted databases?

      GetUserPrefsRepository() seems like the best bet, but it doesn't seem to include the many assets I have in custom DBs, and I don't see any other promising methods exposed in the SDK.

      edit: @ferdinand - Forked from Get All Assets in Category, although the other thread was named appropriately, it was more about general asset traversal and not the specific makeup of asset category trees.

      posted in Cinema 4D SDK python 2023
      d_keithD
      d_keith
    • RE: Get All Assets in Category

      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()
      
      
      posted in Cinema 4D SDK
      d_keithD
      d_keith
    • Get All Assets in Category

      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
      posted in Cinema 4D SDK python
      d_keithD
      d_keith
    • RE: Looking for Scene Nodes Capsules Creators

      Hi All,

      Thank you so much for your interest and taking the time to apply. We ended up with more applicants than could fit in one workshop. If you received an email from me (Maybe in your spam folder? look for d_keith ) you've likely been accepted. If not, I do intend to host similar workshops in the future. Candidates were given priority based on:

      1. Prior programming expertise in C/C++/Python or similar languages.
      2. Interest and ability to actively participate in the lectures/homework projects.
      3. Availability to potentially create capsules for the Maxon Capsules Library in the future on a freelance basis.
      4. And for auditors, special preference was given to MAXON employees who will be documenting/developing/training Scene Nodes workflows.

      I hope to host similar workshops in the future and will prioritize previous applicants at that time.
      Sincerely,

      Donovan Keith

      posted in General Talk
      d_keithD
      d_keith
    • RE: Looking for Scene Nodes Capsules Creators

      Thanks all for your replies! I'll be reviewing this week and will target a July 1 start.

      posted in General Talk
      d_keithD
      d_keith
    • Looking for Scene Nodes Capsules Creators

      Hi!

      I'm a Product Manager and Assets Producer at Maxon. We recently collaborated with the team at Rocket Lasso to produce some great new FUI Spline Capsules (

      , &
      ) and we're hoping to replicate that success with additional third party partners.

      Are you a plugin developer or technical director? Have you been interested in Scene Nodes, but not sure where or why to start?

      We're looking to grow the number of qualified C4D Scene Nodes capsule creators, and improve scene nodes workflows in Cinema 4D. To that end, we're training/supporting a small group of interested developers/technical directors over a ~4 week period. If you're interested in participating, please fill out this form before 2022-06-20:

      https://forms.office.com/r/Mqyq37fXST

      Specifics will largely depend on the group we select, but we're looking to provide:

      • Live weekly video training/Q&A sessions (Time TBD, but probably Friday nights in Europe / Friday mornings in the US), with possible supplemental pre-recorded videos.
      • Best practices documentation.
      • Direct access to a Maxon product manager.
      • Suggestions for how to improve your node setups and UX to match C4D performance/UX standards.
      • A private forum for discussing ideas/issues with other Scene Nodes Assets creators.
      • A private repo of utility node groups for solving common issues.

      You'll be asked to:

      • Complete 1+ Spline/Geo Primitive, Geo Modifier, Selection Modifier, or Node Group that feels and functions like a native Cinema 4D primitive/deformer/node.
      • Document your development process.
      • Provide honest feedback about Scene Nodes workflows and documentation.
      • Log any bugs/UX issues you encounter as you work.
      posted in General Talk
      d_keithD
      d_keith