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
    1. Maxon Developers Forum
    2. ferdinand
    • Profile
    • Following 0
    • Followers 15
    • Topics 53
    • Posts 3,073
    • Best 746
    • Controversial 1
    • Groups 2

    ferdinand

    @ferdinand

    918
    Reputation
    770
    Profile views
    3.1k
    Posts
    15
    Followers
    0
    Following
    Joined Last Online

    ferdinand Unfollow Follow
    Global Moderator administrators

    Best posts made by ferdinand

    • How to implement an image control that can receive and generate image data drag events

      Dear community,

      We recently got a non-public support request about handling drag and drop events for a custom image control in a dialog. And while we have shown this, drag event handling, multiple times before, I thought it does not hurt to answer this publicly, as this very case - an image control - is quite common, and it does not hurt having a bit more verbose public example for it.

      edit: I have updated this topic with new code and a new video, to also handle outgoing asset drag events, and talk a bit about the pros and cons of different approaches, and what you could do when not implementing a code example as I did here, and therefore have a bit more leverage room when it comes to complexity.

      In general, doing all this is possible. It is just that not everything is a ready-made solution for you, where you just call a function. At some point you have to "swim" here yourself, as we cannot write your code for you, but I hope the example helps shedding some light on the probably uncessarily complicated drag handling in Cinema 4D.

      Cheers,
      Ferdinand

      Result

      Code

      """Provides an example for implementing a custom control that can generate and receive drag events.
      
      The example implements a dialog which holds multiple "cards" (BitmapCard) that display an image
      and can receive drag events. The user can drag images from the file system into these cards (file
      paths) or drag texture assets from the Asset Browser into these cards. The cards can also generate
      drag events themselves, e.g., when the user drags from a card, it will generate a drag event. 
      Showcased (selectable via the combo box in the menu of the dialog) are three drag types:
      
      1. File paths: Dragging a card will generate an image file path drag event, which for example could 
         be dragged into a file path field of a shader, onto an object in the viewport, or onto other 
         BitmapCard controls. The advantage of this approach is that we do not have to create
         materials or assets, but can just use the file path directly.
      
      2. Materials: Dragging a card will generate an atom drag event, here populated with a Redshift
         material that has the texture of the card as an input. This material can then be dragged onto
         anything that accepts materials, like objects in the viewport. The disadvantage of this
         approach is that we have to create a material, and that we have to exactly define how the 
         material is constructed.
      
      3. Assets: Dragging a card will generate an asset drag event, here populated with a texture asset
         that has the texture of the card as an input. This asset can then be dragged onto anything that
         texture accepts assets, such as an object in the viewport. The disadvantage of this
         approach is that we have to create an asset.
      
         - The example also showcases a variation oof this approach, where we exploit a bit how the
           texture asset drag handling works, to avoid having to create an asset.
      
      
      Overview:
      
      - BitmapCard: A user area control that can receive drag events and generate drag events itself. Here
                    you will find almost all the relevant code for drag and drop handling
      - BitmapStackDialog: A dialog that holds multiple BitmapCard controls and allows the user to select
                      the drag type via a combo box. This dialog is the main entry point of the example
                      but does not contain much relevant code itself.
      """
      __author__ = "Ferdinand Hoppe"
      __copyright__ = "Copyright 2025 Maxon Computer GmbH"
      
      import os
      import re
      
      import c4d
      import maxon
      import mxutils
      
      # The file types that are supported to be dragged into a BitmapCard control.
      DRAG_IN_FILE_TYPES: list[str] = [".png", ".jpg", ".jpeg", ".tif"]
      
      # A regex to check of a string (which is meant to be a path/url) already as a scheme (like file:///
      # or http:///). The protocol we are mostly checking for is "asset:///", but we keep this regex generic
      # to allow for other schemes as well.
      RE_URL_HAS_SCHEME = re.compile("^[a-zA-Z][a-zA-Z\d+\-.]*:\/\/\/")
      
      # A non-public core message which need when we want to properly generate asset drag events.
      COREMSG_SETCOMMANDEDITMODE: int = 300001000
      
      
      class BitmapCard(c4d.gui.GeUserArea):
          """Implements a control that can receive bitmap path drag events and generates drag events itself.
          """
      
          def __init__(self, host: "BitmapStackDialog") -> None:
              """Constructs a new BitmapCard object.
              """
              # The host dialog that holds this card. This is used to access the drag type selected in the
              # combo box of the dialog.
              self._host: BitmapStackDialog = host
      
              # The image file path and the bitmap that is displayed in this card.
              self._path: str | None = ""
              self._bitmap: c4d.bitmaps.BaseBitmap | None = None
      
          def GetMinSize(self) -> tuple[int, int]:
              """Called by Cinema 4d to evaluate the minimum size of the user area.
              """
              return 250, 100
      
          def DrawMsg(self, x1, y1, x2, y2, msg_ref):
              """Called by Cinema 4D to let the user area draw itself.
              """
              self.OffScreenOn()
              self.SetClippingRegion(x1, y1, x2, y2)
      
              # Draw the background and then the bitmap if it exists. In the real world, we would have to
              # either adapt #GetMinSize to the size of the bitmap, or draw the bitmap in a way that it
              # fits into the user area, e.g., by scaling it down. Here we just draw it at a fixed size.
              self.DrawSetPen(c4d.gui.GetGuiWorldColor(c4d.COLOR_BGGADGET))
              self.DrawRectangle(x1, y1, x2, y2)
              if self._bitmap:
                  w, h = self._bitmap.GetSize()
                  self.DrawBitmap(self._bitmap, 5, 5, 240, 90, 0,
                                  0, w, h, c4d.BMP_NORMALSCALED)
      
          def Message(self, msg: c4d.BaseContainer, result: c4d.BaseContainer) -> int:
              """Called by Cinema 4D to handle messages sent to the user area.
      
              Here we implement receiving drag events when the user drags something onto this user area.
              """
              # A drag event is coming in.
              if msg.GetId() == c4d.BFM_DRAGRECEIVE:
                  # Get out when the drag event has been discarded.
                  if msg.GetInt32(c4d.BFM_DRAG_LOST) or msg.GetInt32(c4d.BFM_DRAG_ESC):
                      return self.SetDragDestination(c4d.MOUSE_FORBIDDEN)
      
                  # Get out when this is not a drag type we support (we just support images). Note that we
                  # cannot make up drag types ourselves, so when we want to send/receive complex data, we
                  # must use the DRAGTYPE_ATOMARRAY type and pack our data into a BaseContainer attached
                  # to the nodes we drag/send.
                  data: dict = self.GetDragObject(msg)
                  dragType: int = data.get("type", 0)
                  if dragType not in [c4d.DRAGTYPE_FILENAME_IMAGE, maxon.DRAGTYPE_ASSET]:
                      return self.SetDragDestination(c4d.MOUSE_FORBIDDEN)
      
                  # Here we could optionally check the drag event hitting some target area.
                  # yPos: float = self.GetDragPosition(msg).get('y', 0.0)
                  # if not 0 < yPos < 50 or not self.CheckDropArea(msg, True, True):
                  #     return self.SetDragDestination(c4d.MOUSE_FORBIDDEN)
      
                  # After this point, we are dealing with a valid drag event.
      
                  # The drag is still going on, we just set the mouse cursor to indicate that we are
                  # ready to receive the drag event.
                  if msg.GetInt32(c4d.BFM_DRAG_FINISHED) == 0:
                      return self.SetDragDestination(c4d.MOUSE_MOVE)
                  # The drag event is finished, we can now process the data.
                  else:
                      # Unpack a file being dragged directly.
                      path: str | None = None
                      if dragType == c4d.DRAGTYPE_FILENAME_IMAGE:
                          path = data.get("object", None)
                      # Unpack an asset being dragged.
                      elif dragType == maxon.DRAGTYPE_ASSET:
                          array: maxon.DragAndDropDataAssetArray = data.get(
                              "object", None)
                          descriptions: tuple[tuple[maxon.AssetDescription, maxon.Url, maxon.String]] = (
                              array.GetAssetDescriptions())
                          if not descriptions:
                              # Invalid asset but we are not in an error state.
                              return True
      
                          # Check that we are dealing with an image asset.
                          asset: maxon.AssetDescription = descriptions[0][0]
                          metadata: maxon.BaseContainer = asset.GetMetaData()
                          subType: maxon.Id = metadata.Get(
                              maxon.ASSETMETADATA.SubType, maxon.Id())
                          if (subType != maxon.ASSETMETADATA.SubType_ENUM_MediaImage):
                              # Invalid asset but we are not in an error state.
                              return True
      
                          path = str(maxon.AssetInterface.GetAssetUrl(asset, True))
      
                      if not isinstance(path, str):
                          return False  # Critical failure.
      
                      if (dragType == c4d.DRAGTYPE_FILENAME_IMAGE and
                          (not os.path.exists(path) or
                           os.path.splitext(path)[1].lower() not in DRAG_IN_FILE_TYPES)):
                          # Invalid file type but we are not in an error state.
                          return True
      
                      self._path = path
                      self._bitmap = c4d.bitmaps.BaseBitmap()
                      if self._bitmap.InitWith(path)[0] != c4d.IMAGERESULT_OK:
                          return False  # Critical failure.
      
                      self.Redraw()  # Redraw the user area to show the new bitmap.
      
                      return True
      
              # Process other messages, doing this is very important, as we otherwise break the
              # message chain.
              return c4d.gui.GeUserArea.Message(self, msg, result)
      
          def InputEvent(self, msg: c4d.BaseContainer) -> bool:
              """Called by Cinema 4D when the user area receives input events.
      
              Here we implement creating drag events when the user drags from this user area. The type of
              drag event which is initiated is determined by the drag type selected in the combo box
              of the dialog.
              """
              # When this is not a left mouse button event on this user area, we just get out without
              # consuming the event (by returning False).
              if (msg.GetInt32(c4d.BFM_INPUT_DEVICE) != c4d.BFM_INPUT_MOUSE or
                      msg.GetInt32(c4d.BFM_INPUT_CHANNEL) != c4d.BFM_INPUT_MOUSELEFT):
                  return False
      
              # Get the type of drag event that should be generated, and handle it.
              dragType: int = self._host.GetInt32(self._host.ID_DRAG_TYPE)
              return self.HandleDragEvent(msg, dragType)
          
          # --- Custom drag handling methods -------------------------------------------------------------
      
          # I have split up thing into three methods for readability, this all could also be done directly
          # in the InputEvent method.
      
          def HandleDragEvent(self, event: c4d.BaseContainer, dragType: int) -> bool:
              """Handles starting a drag event by generating the drag data and sending it to the system.
      
              This is called when the user starts dragging from this user area.
              """
              # This requires us to modify the document, so we must be on the main thread (technically
              # not true when the type is DRAGTYPE_FILENAME_IMAGE, but that would be for you to optimize).
              if not c4d.threading.GeIsMainThread():
                  raise False
      
              # Generate our drag data, either a file path, a material, or an asset.
              doc: c4d.documents.BaseDocument = c4d.documents.GetActiveDocument()
              data: object = self.GenerateDragData(doc, dragType)
      
              # Now we set off the drag event. When we are dragging assets, we have to sandwich the event
              # in these core message calls, as otherwise the palette edit mode toggling will not work
              # correctly.
              if dragType == maxon.DRAGTYPE_ASSET:
                  bc: c4d.BaseContainer = c4d.BaseContainer(
                      COREMSG_SETCOMMANDEDITMODE)
                  bc.SetInt32(1, True)
                  c4d.SendCoreMessage(c4d.COREMSG_CINEMA, bc, 0)
      
              # When #HandleMouseDrag returns #False, this means that the user has cancelled the drag
              # event, one case could be that the user actually did not intend to drag, but just
              # clicked on the user area.
              #
              # In this case we remove the drag data we generated, as it is not needed anymore. Note that
              # this will NOT catch the case that the drag event is cancelled by the recipient, e.g., the
              # user drags a material onto something that does not accept materials.
              if not self.HandleMouseDrag(event, dragType, data, 0):
                  self.RemoveDragData(doc, data)
                  return True
      
              # Other half of the sandwich, we toggle the palette edit mode back to normal.
              if dragType == maxon.DRAGTYPE_ASSET:
                  bc: c4d.BaseContainer = c4d.BaseContainer(
                      COREMSG_SETCOMMANDEDITMODE)
                  bc.SetInt32(1, False)
                  c4d.SendCoreMessage(c4d.COREMSG_CINEMA, bc, 0)
      
              return True
      
          def GenerateDragData(self, doc: c4d.documents.BaseDocument, dragType
                               ) -> str | c4d.BaseMaterial | maxon.DragAndDropDataAssetArray:
              """Generates the drag data for the given drag type.
      
              Each tile just encapsulates a file path, but we realize dragging them as file paths,
              materials, or assets. So, when the type is material or asset, the drag data must be
              generated from the file path. Which is why we have this method here.
              """
              if not c4d.threading.GeIsMainThread():
                  raise RuntimeError(
                      "GenerateDragData must be called from the main thread.")
      
              # The user has selected "File" as the drag type, we just return the file path.
              if dragType == c4d.DRAGTYPE_FILENAME_IMAGE:
                  return self._path
              # The user has selected "Material" as the drag type, we create a Redshift material with the
              # texture as input and return that material.
              elif dragType == c4d.DRAGTYPE_ATOMARRAY:
                  material: c4d.BaseMaterial = mxutils.CheckType(
                      c4d.BaseMaterial(c4d.Mmaterial))
      
                  # Create a simple graph using that texture, and insert the material into the
                  # active document.
                  maxon.GraphDescription.ApplyDescription(
                      maxon.GraphDescription.GetGraph(
                          material, maxon.NodeSpaceIdentifiers.RedshiftMaterial),
                      {
                          "$type": "Output",
                          "Surface": {
                              "$type": "Standard Material",
                              "Base/Color": {
                                  "$type": "Texture",
                                  # Set the texture. When our source is a file we will just have a plain
                                  # path, e.g. "C:/path/to/file.png" and we have to prefix with a scheme
                                  # (file) to make it a valid URL. When we are dealing with an asset, the
                                  # texture path will already be a valid URL in the "asset:///" scheme.
                                  "Image/Filename/Path": (maxon.Url(f"{self._path}")
                                                          if RE_URL_HAS_SCHEME.match(self._path) else
                                                          maxon.Url(f"file:///{self._path}"))
                              }
                          }
                      })
                  doc.InsertMaterial(material)
                  return [material]
              # The user has selected "Asset" as the drag type, we create a texture asset and return that.
              elif dragType == maxon.DRAGTYPE_ASSET:
                  # So we have to cases here, either that we want truly want to generate an asset drag
                  # event or that we just want to piggy-back onto the texture asset drag and drop
                  # mechanism of Cinema 4D, which is what we do here.
      
                  # Code for truly generating an asset drag event, where we would store the asset in the
                  # scene repository.
                  generateAssets: bool = False
                  if generateAssets:
                      # Get the scene repository and create an asset storage structure for the asset.
                      repo: maxon.AssetRepository = doc.GetSceneRepository(True)
                      store: maxon.StoreAssetStruct = maxon.StoreAssetStruct(
                          maxon.Id(), repo, repo)
      
                      # Now save the texture asset to the repository.
                      url: maxon.Url = maxon.Url(self._path)
                      name: str = url.GetName()
                      asset, _ = maxon.AssetCreationInterface.SaveTextureAsset(
                          url, name, store, (), True)
      
                      # Create a drag array for that asset. It is important that we use the URL of the
                      # asset (maxon.AssetInterface.GetAssetUrl) and not #url or asset.GetUrl(), as both
                      # are physical file paths, and will then cause the drag and drop handling to use
                      # these physical files, including the popup asking for wether the file should be
                      # copied. We will exploit exactly this behavior in the second case.
                      dragArray: maxon.DragAndDropDataAssetArray = maxon.DragAndDropDataAssetArray()
                      dragArray.SetLookupRepository(repo)
                      dragArray.SetAssetDescriptions((
                          (asset, maxon.AssetInterface.GetAssetUrl(asset, True), maxon.String(name)),
                      ))
      
                      return dragArray
                  # With this case we can piggy-back onto the existing drag and drop texture asset
                  # handling of Cinema 4D, without actually having to create an asset. THIS IS A
                  # WORKAROUND, we could decide at any moment to change how the drag and drop
                  # handling of assets works, possibly rendering this approach obsolete.
                  else:
                      # The slight disadvantage of this approach (besides it being a hack and us possibly
                      # removing it at some point) is that we have to pay the download cost of
                      # #self._host._dummyAssetId once. I.e., the user has to download that texture once
                      # either by using it in the Asset Browser or by running this code. Once the asset has
                      # been cached, this will not happen again.
                      #
                      # But since we search below in a 'whoever comes first' manner, what is the first
                      # texture asset could change, and this could then be an asset which has not been
                      # downloaded yet. So, in a more robust world we would pick a fixed asset below. But
                      # this comes then with the burden of maintenance, as we could remove one of the
                      # builtin texture assets at any time. So, the super advanced solution would be to
                      # ship the plugin with its own asset database, and mount and use that. Here we could
                      # use a hard-coded asset ID, and would have to have never pay download costs, as
                      # the repository would be local.
      
                      # Find the asset by its Id, which we have stored in the dialog.
                      repo: maxon.AssetRepositoryInterface = maxon.AssetInterface.GetUserPrefsRepository()
                      dummy: maxon.AssetDescription = repo.FindLatestAsset(
                          maxon.AssetTypes.File().GetId(), self._host._dummyAssetId, maxon.Id(),
                          maxon.ASSET_FIND_MODE.LATEST)
      
                      # Setup the drag array with the asset. Note that #asset must be a valid texture
                      # asset, so just passing maxon.AssetDescription() will not work. But the backend
                      # for drag and drop handling will actually ignore the asset except for type checking
                      # it, and use the URL we provided instead.
                      dragArray: maxon.DragAndDropDataAssetArray = maxon.DragAndDropDataAssetArray()
                      dragArray.SetLookupRepository(repo)
                      dragArray.SetAssetDescriptions((
                          (dummy, maxon.Url(self._path), maxon.String(os.path.basename(self._path))),
                      ))
                  return dragArray
              else:
                  raise ValueError(f"Unsupported drag type: {dragType}")
      
          def RemoveDragData(self, doc: c4d.documents.BaseDocument,
                             data: str | list[c4d.BaseMaterial] | maxon.DragAndDropDataAssetArray) -> bool:
              """Removes generated content when the user cancels a drag event.
      
              Sometimes the user starts a drag event, but then cancels it, e.g., by pressing the
              escape key. In this case, we have to remove the generated content, e.g., the material or
              asset that we created for the drag event.
              """
              if not c4d.threading.GeIsMainThread():
                  return False
      
              if isinstance(data, list):
                  # Remove the dragged materials from the document. Since we are always generating a new
                  # material, we can just remove them. That is in general probably not the best design,
                  # and a real world application should avoid duplicating materials.
                  for item in data:
                      if isinstance(item, c4d.BaseList2D):
                          item.Remove()
              elif isinstance(data, str):
                  # Here we could technically remove a file.
                  pass
              elif isinstance(data, maxon.DragAndDropDataAssetArray):
                  # We could remove the asset, but other than for the material, there is no guarantee
                  # that we are the only user of it, as the Asset API has a duplicate preventing
                  # mechanism. So, the #SaveTextureAsset call above could have returned an already
                  # existing asset. Since we could operate with a document bound repository, we could
                  # search the whole document for asset references and only when we find none, remove
                  # the asset.
                  #
                  # We use here the little hack that we actually do not create an asset, to just to piggy-
                  # back onto the material drag and drop mechanism of assets, so we can just ignore
                  # this case.
                  pass
      
              return True
      
      
      class BitmapStackDialog(c4d.gui.GeDialog):
          """Implements a a simple dialog that stacks multiple BitmapCard controls.
          """
          ID_DRAG_TYPE: int = 1002
      
          def __init__(self):
              """Constructs a new ExampleDialog object.
              """
              # The user area controls that will generate and receive drag events.
              self._cards: list[BitmapCard] = [
                  BitmapCard(host=self) for _ in range(4)]
      
              # The id for the dummy asset that used to generate 'fake' texture asset drag events. We
              # search for it here once, so that we do not have to do it in the __init__ of each
              # BitmapCard, or even worse, in the HandleDragEvent of each BitmapCard.
      
              # We could technically also store here the AssetDescription instead of the Id, but that
              # reference can go stale, so we just store the Id and then grab with it the asset when we
              # need it. Which is much faster than searching asset broadly as we do here. In the end, we
              # could probably also do all this in the drag handling of the BitmapCard, but a little bit
              # of optimization does not hurt.
              self._dummyAssetId: maxon.Id | None = None
              if not maxon.AssetDataBasesInterface.WaitForDatabaseLoading():
                  raise RuntimeError("Could not initialize the asset databases.")
      
              def findTexture(asset: maxon.AssetDescription) -> bool:
                  """Finds the first texture asset in the user preferences repository.
                  """
                  # When this is not a texture asset, we just continue searching.
                  meta: maxon.AssetMetaData = asset.GetMetaData()
                  if (meta.Get(maxon.ASSETMETADATA.SubType, maxon.Id()) !=
                          maxon.ASSETMETADATA.SubType_ENUM_MediaImage):
                      return True
      
                  # When it is a texture asset, we store it as the dummy asset and stop searching.
                  self._dummyAssetId = asset.GetId()
                  return False
      
              # Search for our dummy asset in the user preferences repository.
              repo: maxon.AssetRepositoryInterface = maxon.AssetInterface.GetUserPrefsRepository()
              repo.FindAssets(maxon.AssetTypes.File().GetId(), maxon.Id(), maxon.Id(),
                              maxon.ASSET_FIND_MODE.LATEST, findTexture)
      
          def CreateLayout(self):
              """Called by Cinema 4D to populate the dialog with controls.
              """
              self.SetTitle("Drag and Drop Example")
              self.GroupSpace(5, 5)
              self.GroupBorderSpace(5, 5, 5, 5)
      
              # Build the combo box in the menu bar of the dialog.
              self.GroupBeginInMenuLine()
              self.GroupBegin(1000, c4d.BFH_LEFT | c4d.BFV_TOP, cols=2)
              self.GroupBorderSpace(5, 5, 5, 5)
              self.GroupSpace(5, 5)
              self.AddStaticText(1001, c4d.BFH_LEFT |
                                 c4d.BFV_CENTER, name="Drag Events as:")
              self.AddComboBox(1002, c4d.BFH_RIGHT | c4d.BFV_TOP)
              self.GroupEnd()
              self.GroupEnd()
      
              # Add the BitmapCard controls to the dialog.
              for i, card in enumerate(self._cards):
                  self.AddUserArea(2000 + i, c4d.BFH_LEFT | c4d.BFV_TOP)
                  self.AttachUserArea(card, 2000 + i)
      
              # Add items to the combo box to select the drag type.
              self.AddChild(self.ID_DRAG_TYPE, c4d.DRAGTYPE_FILENAME_IMAGE, "Files")
              self.AddChild(self.ID_DRAG_TYPE, c4d.DRAGTYPE_ATOMARRAY, "Materials")
              self.AddChild(self.ID_DRAG_TYPE, maxon.DRAGTYPE_ASSET, "Assets")
              self.SetInt32(self.ID_DRAG_TYPE, c4d.DRAGTYPE_FILENAME_IMAGE)
      
              return True
      
      
      # Define a global variable of the dialog to keep it alive in a Script Manager script. ASYNC dialogs
      # should not be opened in production code from a Script Manager script, as this results in a
      # dangling dialog. Implement a command plugin when you need async dialogs in production.
      dlg: BitmapStackDialog = BitmapStackDialog()
      if __name__ == "__main__":
          dlg.Open(dlgtype=c4d.DLG_TYPE_ASYNC, defaultw=150, defaulth=250)
      
      
      posted in Cinema 4D SDK 2025 python
      ferdinandF
      ferdinand
    • RE: Welcome Mr. Hoppe

      Hi,

      thanks for the kind words both from Maxon and the community. I am looking forward to my upcoming adventures with the SDK Team and Cinema community.

      Cheers,
      Ferdinand

      posted in News & Information
      ferdinandF
      ferdinand
    • RE: API for new behavior of opnening Windows in Layout

      Hello @holgerbiebrach,

      please excuse the wait. So, this is possible in Python and quite easy to do. This new behavior is just the old dialog folding which has been reworked a little bit. I have provided a simple example at the end of the posting. There is one problem regarding title bars which is sort of an obstacle for plugin developers which want to distribute their plugins, it is explained in the example below.

      I hope this helps and cheers,
      Ferdinand

      The result:
      3453535.gif
      The code:

      """Example for a command plugin with a foldable dialog as provided with the
      Asset Browser or Coordinate Manger in Cinema 4D R25.
      
      The core of this is just the old GeDialog folding mechanic which has been
      changed slightly with R25 as it will now also hide the title bar of a folded
      dialog, i.e., the dialog will be hidden completely.
      
      The structure shown here mimics relatively closely what the Coordinate Manger
      does. There is however one caveat: Even our internal implementations do not
      hide the title bar of a dialog when unfolded. Instead, this is done via 
      layouts, i.e., by clicking onto the ≡ icon of the dialog and unchecking the
      "Show Window Title" option and then saving such layout. If you would want
      to provide a plugin which exactly mimics one of the folding managers, you
      would have to either ask your users to take these steps or provide a layout.
      
      Which is not ideal, but I currently do not see a sane way to hide the title
      bar of a dialog. What you could do, is open the dialog as an async popup which 
      would hide the title bar. But that would also remove the ability to dock the 
      dialog. You could then invoke `GeDialog.AddGadegt(c4d.DIALOG_PIN, SOME_ID)`to 
      manually add a pin back to your dialog, so that you can dock it. But that is 
      not how it is done internally by us, as we simply rely on layouts for that.
      """
      
      import c4d
      
      
      class ExampleDialog (c4d.gui.GeDialog):
          """Example dialog that does nothing.
      
          The dialog itself has nothing to do with the implementation of the
          folding.
          """
          ID_GADGETS_START = 1000
          ID_GADGET_GROUP = 0
          ID_GADGET_LABEL = 1
          ID_GADGET_TEXT = 2
      
          GADGET_STRIDE = 10
          GADEGT_COUNT = 5
      
          def CreateLayout(self) -> bool:
              """Creates dummy gadgets.
              """
              self.SetTitle("ExampleDialog")
              flags = c4d.BFH_SCALEFIT
      
              for i in range(self.GADEGT_COUNT):
                  gid = self.ID_GADGETS_START + i * self.GADGET_STRIDE
                  name = f"Item {i}"
      
                  self.GroupBegin(gid + self.ID_GADGET_GROUP, flags, cols=2)
                  self.GroupBorderSpace(5, 5, 5, 5)
                  self.GroupSpace(2, 2)
                  self.AddStaticText(gid + self.ID_GADGET_LABEL, flags, name=name)
                  self.AddEditText(gid + self.ID_GADGET_TEXT, flags)
                  self.GroupEnd()
              return True
      
      
      class FoldingManagerCommand (c4d.plugins.CommandData):
          """Provides the implementation for a command with a foldable dialog.
          """
          ID_PLUGIN = 1058525
          REF_DIALOG = None
      
          @property
          def Dialog(self) -> ExampleDialog:
              """Returns a class bound ExampleDialog instance.
              """
              if FoldingManagerCommand.REF_DIALOG is None:
                  FoldingManagerCommand.REF_DIALOG = ExampleDialog()
      
              return FoldingManagerCommand.REF_DIALOG
      
          def Execute(self, doc: c4d.documents.BaseDocument) -> bool:
              """Folds or unfolds the dialog.
      
              The core of the folding logic as employed by the Asset Browser
              or the Coordinate manager in R25.
              """
              # Get the class bound dialog reference.
              dlg = self.Dialog
              # Fold the dialog, i.e., hide it if it is open and unfolded. In C++
              # you would also want to test for the dialog being visible with
              # GeDialog::IsVisible, but we cannot do that in Python.
              if dlg.IsOpen() and not dlg.GetFolding():
                  dlg.SetFolding(True)
              # Open or unfold the dialog. The trick here is that calling
              # GeDialog::Open will also unfold the dialog.
              else:
                  dlg.Open(c4d.DLG_TYPE_ASYNC, FoldingManagerCommand.ID_PLUGIN)
      
              return True
      
          def RestoreLayout(self, secret: any) -> bool:
              """Restores the dialog on layout changes.
              """
              return self.Dialog.Restore(FoldingManagerCommand.ID_PLUGIN, secret)
      
          def GetState(self, doc: c4d.documents.BaseDocument) -> int:
              """Sets the command icon state of the plugin.
      
              This is not required, but makes it a bit nicer, as it will indicate
              in the command icon when the dialog is folded and when not.
              """
              dlg = self.Dialog
              result = c4d.CMD_ENABLED
              if dlg.IsOpen() and not dlg.GetFolding():
                  result |= c4d.CMD_VALUE
      
              return result
      
      
      def RegisterFoldingManagerCommand() -> bool:
          """Registers the example.
          """
          return c4d.plugins.RegisterCommandPlugin(
              id=FoldingManagerCommand.ID_PLUGIN,
              str="FoldingManagerCommand",
              info=c4d.PLUGINFLAG_SMALLNODE,
              icon=None,
              help="FoldingManagerCommand",
              dat=FoldingManagerCommand())
      
      
      if __name__ == '__main__':
          if not RegisterFoldingManagerCommand():
              raise RuntimeError(
                  f"Failed to register {FoldingManagerCommand} plugin.")
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • Projecting Points from Object/World Space into Texture Space

      Dear Community,

      this question reached us via email-support in the context of C++, but I thought the answer might be interesting for other users too.

      The underlying question in this case was how to project points from object or world space into the texture space of an object with UV data. I am showing here deliberately an approach that can be followed both in C++ and Python, so that all users can benefit from this. In C++ one has also the option of using VolumeData and its methods VolumeData::GetUvw or VolumeData::ProjectPoint but must then either implement a volume shader (as otherwise the volume data attached to the ChannelData passed to ShaderData::Output will be nullptr), or use VolumeData:: AttachVolumeDataFake to access ::ProjectPoint. There is however no inherent necessity to take this shader bound route as shown by the example.

      Cheers,
      Ferdinand

      Result

      The script has created a texture with red pixels for the intersection points of the rays cast from each vertex of the spline towards the origin of the polygon object. The script also created the null object rays to visualize the rays which have been cast.
      820ac56e-be8c-4e02-adde-62301f1dfd79-image.png

      raycast_texture.c4d : The scene file.

      Code

      ⚠ You must save the script to disk before running it, as the script infers from the script location the place to save the generated texture to.

      """Demonstrates how to project points from world or object space to UV space.
      
      This script assumes that the user has selected a polygon object and a spline object in the order
      mentioned. The script projects the points of the spline object onto the polygon object and creates
      a texture from the UV coordinates of the projected points. The texture is then applied to the
      polygon object.
      
      The script uses the `GeRayCollider` class to find the intersection of rays cast from the points of
      the spline object to the polygon object. The UV coordinates of the intersection points are then
      calculated using the `HairLibrary` class. In the C++ API, one should use maxon::
      GeometryUtilsInterface::CalculatePolygonPointST() instead.
      
      Finally, using GeRayCollider is only an example for projecting points onto the mesh. In practice,
      any other method can be used as long as it provides points that lie in the plane(s) of a polygon.
      
      The meat of the example is in the `main()` function. The other functions are just fluff.
      """
      
      import os
      import c4d
      import mxutils
      import uuid
      
      from mxutils import CheckType
      
      doc: c4d.documents.BaseDocument  # The currently active document.
      op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
      
      def CreateTexture(points: list[c4d.Vector], path: str, resolution: int = 1000) -> None:
          """Creates a texture from the given `points` and saves it to the given `path`.
      
          Parameters:
              path (str): The path to save the texture to.
              points (list[c4d.Vector]): The points to create the texture from.
          """
          # Check the input values for validity.
          if os.path.exists(path):
              raise FileExistsError(f"File already exists at path: {path}")
          if not path.endswith(".png"):
              raise ValueError("The path must end with '.png'.")
      
          # Create a drawing canvas to draw the points on.
          canvas: c4d.bitmaps.GeClipMap = CheckType(c4d.bitmaps.GeClipMap())
          if not canvas.Init(resolution, resolution, 24):
              raise MemoryError("Failed to initialize GeClipMap.")
      
          # Fill the canvas with white.
          canvas.BeginDraw()
          canvas.SetColor(255, 255, 255)
          canvas.FillRect(0, 0, resolution, resolution)
      
          # Draw the points on the canvas.
          canvas.SetColor(255, 0, 0)
          for p in points:
              x: int = int(p.x * resolution)
              y: int = int(p.y * resolution)
              x0: int = max(0, x - 1)
              y0: int = max(0, y - 1)
              x1: int = min(resolution, x + 1)
              y1: int = min(resolution, y + 1)
              canvas.FillRect(x0, y0, x1, y1)
      
          canvas.EndDraw()
      
          # Save the canvas to the given path.
          bitmap: c4d.bitmaps.BaseBitmap = CheckType(canvas.GetBitmap())
          bitmap.Save(path, c4d.FILTER_PNG)
      
          c4d.bitmaps.ShowBitmap(bitmap)
      
      def ApplyTexture(obj: c4d.BaseObject, path: str) -> None:
          """Applies the texture at the given `path` to the given `obj`.
          """
          CheckType(obj, c4d.BaseObject)
      
          # Check the input values for validity.
          if not os.path.exists(path):
              raise FileNotFoundError(f"File does not exist at path: {path}")
      
          # Create a material and apply the texture to it.
          material: c4d.BaseMaterial = CheckType(c4d.BaseMaterial(c4d.Mmaterial), c4d.BaseMaterial)
          obj.GetDocument().InsertMaterial(material)
      
          shader: c4d.BaseShader = CheckType(c4d.BaseShader(c4d.Xbitmap), c4d.BaseShader)
          shader[c4d.BITMAPSHADER_FILENAME] = path
          material.InsertShader(shader)
          material[c4d.MATERIAL_COLOR_SHADER] = shader
          material[c4d.MATERIAL_PREVIEWSIZE] = c4d.MATERIAL_PREVIEWSIZE_1024
      
          # Apply the material to the object.
          tag: c4d.TextureTag = CheckType(obj.MakeTag(c4d.Ttexture))
          tag[c4d.TEXTURETAG_PROJECTION] = c4d.TEXTURETAG_PROJECTION_UVW
          tag[c4d.TEXTURETAG_MATERIAL] = material
      
      def CreateDebugRays(spline: c4d.SplineObject, p: c4d.Vector) -> None:
          """Adds spline objects to the document to visualize the rays from the given `p` to the points of
          the given `spline`.
          """
          doc: c4d.documents.BaseDocument = CheckType(spline.GetDocument(), c4d.documents.BaseDocument)
          rays: c4d.BaseObject = c4d.BaseObject(c4d.Onull)
          rays.SetName("Rays")
          doc.InsertObject(rays)
      
          for q in spline.GetAllPoints():
              ray: c4d.SplineObject = c4d.SplineObject(2, c4d.SPLINETYPE_LINEAR)
              ray.SetPoint(0, p)
              ray.SetPoint(1, q * spline.GetMg())
              ray.Message(c4d.MSG_UPDATE)
              ray.InsertUnder(rays)
      
      def main() -> None:
          """Carries out the main logic of the script.
          """
          # Check the object selection for being meaningful input.
          selected: list[c4d.BaseObject] = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER)
          if (len(selected) != 2 or not selected[0].CheckType(c4d.Opolygon) or
              not selected[1].CheckType(c4d.Ospline)):
              raise ValueError("Please select a polygon object and a spline object.")
      
          polygonObject, splineObject = selected
      
          # Get the uvw tag, the points, and the polygons of the polygon object.
          uvwTag: c4d.UvwTag = mxutils.CheckType(polygonObject.GetTag(c4d.Tuvw))
          points: list[c4d.Vector] = [polygonObject.GetMg() * p for p in polygonObject.GetAllPoints()]
          polys: list[c4d.CPolygon] = polygonObject.GetAllPolygons()
      
          # We are casting here in a dumb manner towards the center of the polygon object. In practice,
          # one should cast rays towards the plane of the polygon object. Or even better, use another
          # method to project the points onto the polygon object, as GeRayCollider is not the most 
          # efficient thing in the world.
          rayTarget: c4d.Vector = polygonObject.GetMg().off
          CreateDebugRays(splineObject, rayTarget)
      
          # Initialize the GeRayCollider to find the intersection of rays cast from the points of the
          # spline object to the polygon object.
          collider: c4d.utils.GeRayCollider = c4d.utils.GeRayCollider()
          if not collider.Init(polygonObject):
              raise MemoryError("Failed to initialize GeRayCollider.")
      
      
          # Init our output list and iterate over the points of the spline object.
          uvPoints: list[c4d.Vector] = []
          for p in splineObject.GetAllPoints():
      
              # Transform the point from object to world space (q) and then to the polygon object's space
              # (ro). Our ray direction always points towards the center of the polygon object.
              q: c4d.Vector = splineObject.GetMg() * p
              ro: c4d.Vector = ~polygonObject.GetMg() * q
              rd: c4d.Vector = rayTarget - ro
      
              # Cast the ray and check if it intersects with the polygon object.
              if not collider.Intersect(ro, rd, 1E6) or collider.GetIntersectionCount() < 1:
                  continue
              
              # Get the hit position and the polygon ID of the intersection.
              hit: dict = collider.GetNearestIntersection()
              pos: c4d.Vector = mxutils.CheckType(hit.get("hitpos", None), c4d.Vector)
              pid: int = mxutils.CheckType(hit.get("face_id", None), int)
      
              # One mistake would be now to use the barycentric coordinates that are in the intersection
              # data, as Cinema uses an optimized algorithm to interpolate in a quad and not the standard
              # cartesian-barycentric conversion. In Python these polygon weights are only exposed in a 
              # bit weird place, the hair library. In C++ these barycentric coordinates make sense because
              # there exist methods to convert them to weights. In Python the barycentric coordinates are
              # pretty much useless as we do not have such a conversion function here.
      
              # Compute the weights s, t for the intersection point in the polygon.
              s, t = c4d.modules.hair.HairLibrary().GetPolyPointST(
                  pos, points[polys[pid].a], points[polys[pid].b],
                       points[polys[pid].c], points[polys[pid].d], True)
      
              # Get the uv polygon and bilinearly interpolate the coordinates using the weights. It would
              # be better to use the more low-level variable tag data access functions in VariableTag 
              # than UvwTag.GetSlow() in a real-world scenario.
              uvw: list[c4d.Vector] = list(uvwTag.GetSlow(pid).values())
              t0: c4d.Vector = c4d.utils.MixVec(uvw[0], uvw[1], s)
              t1: c4d.Vector = c4d.utils.MixVec(uvw[3], uvw[2], s)
              uv: c4d.Vector = c4d.utils.MixVec(t0, t1, t)
      
              # Append the UV coordinates to the output list.
              uvPoints.append(uv)
      
          # Write the UV coordinates to a texture and apply it to the polygon object.
          path: str = os.path.join(os.path.dirname(__file__), f"image-{uuid.uuid4()}.png")
          CreateTexture(uvPoints, path, resolution=1024)
          ApplyTexture(polygonObject, path)
      
          c4d.EventAdd()
      
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK 2024 python
      ferdinandF
      ferdinand
    • RE: Reading proper decimal values on lower numbers?

      Hi,

      that your script is not working has not anything to do with pseudo decimals, but the fact that you are treating numbers as strings (which is generally a bad idea) in a not very careful manner. When you truncate the string representation of a number which is represented in scientific notation (with an exponent), then you also truncate that exponent and therefor change the value of the number.

      To truncate a float you can either take the floor of my_float * 10 ** digits and then divide by 10 ** digits again or use the keyword round.

      data = [0.03659665587738824,
              0.00018878623163019122,
              1.1076812650509394e-03,
              1.3882258325566638e-06]
      
      for n in data:
          rounded = round(n, 4)
          floored = int(n * 10000) / 10000
          print(n, rounded, floored)
      
      0.03659665587738824 0.0366 0.0365
      0.00018878623163019122 0.0002 0.0001
      0.0011076812650509394 0.0011 0.0011
      1.3882258325566637e-06 0.0 0.0
      [Finished in 0.1s]
      

      Cheers
      zipit

      posted in General Talk
      ferdinandF
      ferdinand
    • Forum and Documentation Maintenance on the 18th and 22nd

      Dear community,

      We will have to touch multiple parts of developers.maxon.net on the 18.01.2024 and 19.01.2024 22.01.2024. This will result in outages of our documentation and the forum these days. I will try to keep the outage times to a minimum and it will certainly not span the whole two days. But especially one task I will do on Friday might take hours to complete and I can only do that on a forum which is in maintenance mode.

      Please make sure to download a recent offline documentation in case you plan to do extended development work the next two days. As a result, forum support might also be delayed on these days.

      Cheers,
      Ferdinand

      posted in News & Information forum news
      ferdinandF
      ferdinand
    • RE: Modified Pop Up Menu

      Hi,

      as @Cairyn said the problem is unreachable code. I also just saw now that you did assign the same ID to all your buttons in your CreateLayout(). Ressource and dialog element IDs should be unique. I would generally recommend to define your dialogs using a resource, but here is an example on how to do it in code.

      BUTTON_BASE_ID = 1000
      BUTTON_NAMES = ["Button1", "Button2", "Button3", "Button4", "Button5"]
      BUTTON_DATA = {BUTTON_BASE_ID + i: name for i, name in enumerate(BUTTON_NAMES)}
      
      class MyDialog(gui.GeDialog):
      
          def CreateLayout(self):
              """
              """
              self.GroupBegin(id=1013, flags=c4d.BFH_SCALEFIT, cols=5, rows=4)
              for element_id, element_name in BUTTON_DATA.items():
                  self.AddButton(element_id, c4d.BFV_MASK, initw=100, 
                                 name=element_name)
              self.GroupEnd()
              return True
      
          def Command(self, id, msg):
              """
              """
              if id == BUTTON_BASE_ID:
                  print "First button has been clicked"
              elif id == BUTTON_BASE_ID + 1:
                  print "Second button has been clicked"
              # ...
              if id in BUTTON_DATA.keys(): # or just if id in BUTTON_DATA
                  self.Close()
              return True
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • 2025.0.0 SDK Release

      Dear development community,

      On September the 10th, 2024, Maxon Computer released Cinema 4D 2025.0.0. For an overview of the new features of Cinema 4D 2025.0, please refer to the release announcement. Alongside this release, a new Cinema 4D SDK and SDK documentation have been released, reflecting the API changes for 2025.0.0. The major changes are:

      C++ API

      • What was formerly has been know as the Classic API has been deprecated in favour of the Cinema API. Alongside this a new cinema namespace has been introduced which contains all the entities which were formerly in the anonymous global namespace known as the Classic API. Plugin authors must adopt their code to this new API, although the changes are not nearly as extensive as for 2024. See the 2025 migration guide for details. Code examples and documentation have been updated to now refer to a Cinema API.
      • 2025 uses OCIO as the default color management mode, brings an improved color picker, and made general improvements to the consistency of the OCIO implementation. This had some effects on the underlying OCIO API which are reflected in two new code examples in the OCIO Manual and a new plugin in the SDK.

      Python API

      • Python also received the update from Classic to Cinema API. But here the change was more of a cosmetic nature confined to the documentation. The c4d package remains the home for all formerly Classic and now Cinema API entities.
      • The mxutils package received updates around standardized scene traversal, random number generation, and more.
      • Graph descriptions now support variadic ports of arbitrary complexity and explicit port references.

      Head to our download section for the newest SDK downloads, or the C++ and Python API change notes for an in detail overview of the changes.

      ⚠ We discovered late in the cycle bugs in the Asset API code examples and OCIO code in the Python SDK. Which is why the publication of the Python SDK and GitHub code examples has been postponed until these bugs are fixed. They should be ready latest by Friday the 13th of September. But the Python online documentation is accessible and error free (to our knowledge).

      ⚠ We had to make some last minute changes to the C++ SDK regarding OCIO code examples. Only the extended C++ SDK contains these changes. The application provided sdk.zip will catch up with the next release of Cinema 4D.

      Happy rendering and coding,
      the Maxon SDK Team

      ℹ Cloudflare unfortunately still does interfere with our server cache. And you might have to refresh your cache manually.

      When you are not automatically redirected to the new versions, and also do not see 2024.5 in the version selector, please press CTRL + F5 or press CTRL and click on the reload icon of your browser anywhere on developers.maxon.net/docs/ to refresh your cache. You only have to do this once and it will apply to all documentations at once. Otherwise your cache will automatically update latest by 19/07/2024 00:00.

      posted in News & Information cinema 4d news c++ python sdk
      ferdinandF
      ferdinand
    • RE: GetAllTextures from materials only

      Hi,

      sorry for all the confusion. You have to pass actual instances of objects. The following code does what you want (and this time I actually tried it myself ;)).

      import c4d
      
      def main():
          """
          """
          bc = doc.GetAllTextures(ar=doc.GetMaterials())
          for cid, value in bc:
              print cid, value
      
      if __name__=='__main__':
         main()
      

      Cheers,
      zipit

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • 2025.3.0 SDK Release

      Dear development community,

      On June the 18th, 2025, Maxon Computer released Cinema 4D 2025.3.0. For an overview of the new features of Cinema 4D 2025.3.0, please refer to the 2025.3.0 release notes. Alongside this release, a new Cinema 4D SDK and SDK documentation have been released, reflecting the API changes for 2025.3.0. The major changes are:

      C++ API

      • After the introduction of the CMake build system generator in Cinema 4D 2025.2.0, the C++ SDK now only supports CMake as its build system generator. The Legacy Build System is no longer supported. When you have not yet switched to CMake, please refer to our Build Systems manual for more information on how to switch to CMake.
      • Updated the required Windows SDK version to Windows 10 SDK 10.0.20348.0. You might have to update Visual Studio and install the SDK via the Visual Studio installer app.

      Python API

      ⚠ In 2025.3.0, there is a critical issue with c4dpy that will cause it to halt indefinitely when run for the first time. See the c4dpy manual for an explanation and workaround. This issue will be fixed with a hotfix for 2025.3.0.

      • Added features to c4d.documents.BatchRender class, to have more control over render settings, cameras, and takes for a given job.
      • Added a suite of new code examples around the subject of dialogs, including both simple beginner examples, as well as more complex examples covering subjects such as dynamic GUIs, value and layout persistence, and using resources to define dialogs and string translations. The new examples all begin with the prefix py-cmd_gui_, see our plugin examples overview for details.

      Head to our download section to grab the newest SDK downloads, or read the C++ or Python API change notes for an in detail overview of the changes.

      Happy rendering and coding,
      the Maxon SDK Team

      ℹ Cloudflare unfortunately still does interfere with our server cache. You might have to refresh your cache manually to see new data when you read this posting within 24 hours of its release.

      posted in News & Information news cinema 4d c++ python sdk information
      ferdinandF
      ferdinand

    Latest posts made by ferdinand

    • RE: [GeUserArea] Click detection selects wrong item when clicking on lower part of cell (image + text area)

      Hey,

      thanks for the extended code, but I still cannot run this, as it is only a fragment 😉 So, I only have a very rough understanding of what is going wrong.

      A likely issue apart from you just having a bug in your hit logic, is that you use the wrong coordinate system.

      if msg[c4d.BFM_INPUT_DEVICE] == c4d.BFM_INPUT_MOUSE \
          and msg[c4d.BFM_INPUT_CHANNEL] == c4d.BFM_INPUT_MOUSELEFT \
          and msg[c4d.BFM_INPUT_VALUE]:
      
          local_x = msg[c4d.BFM_INPUT_X]
          local_y = msg[c4d.BFM_INPUT_Y]
      
          scroll_y = self.dialog.get_scroll_offset()
      

      I would have to check myself, but there is some inconsistency with which dialogs send mouse input messages regarding the used coordinate system. Sometimes they send messages in the coordinate system of the dialog and sometimes messages in the coordinate system of the gadget. Just print out your (local_x and local_y) and check if the values make sense as local coordinates as you seem to treat them. On GeUserArea are multiple coordinate conversion methods. I think BFM_INPUT in this context is in local user area coordinates but I am not sure, it could also be that you have to convert them.

      The other thing is of course that you add this self.dialog.get_scroll_offset() on top of things. I assume this is sourced by something like GeDialog.GetVisibleArea? There you could also unintentionally mix coordinate systems.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: [GeUserArea] Click detection selects wrong item when clicking on lower part of cell (image + text area)

      Hey @Amazing_iKe,

      thank you for reaching out to us. I cannot execute your code example like this. Please provide an executable example. And as far as I can see, that also seems to be your own code which fails there and not one of our API calls? We cannot debug your code for you. Or is there a specific API call which is failing for you?

      My recommendation would be more abstraction, all this function shuffling especially in the context of GUIs gets really confusing really quick. I would write myself a Grid and a GridItem class which encapsulate the items and their hit logic. What matters also is how you feed def on_click(self, x, y). There are local (e.g. within a user area) and global (e.g., within a dialog or screen) coordinate systems.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Finding, Renaming, and Updating all References to Assets

      Hey @d_keith,

      Thank you for reaching out to us. Please file this request (which I assume is not private) internally, the forum is not the right place for this.

      I am not quite sure what the exact question of yours is :D. GetAllAssets(New) is the middleware for the Project Asset Inspector (PAI) and the backend messages MSG_GETALLASSETS and MSG_RENAMETEXTURES. The latter is not wrapped at all for Python and the former only as an outgoing message. So, GetAllAssets is the 'helper' method used by the PAI to realize all its functionalities you are looking for.

      There was once the thread Get all textures for a material which was in scope very similar to yours. We very recently also had this thread where we talked about how you can abstract away some stuff.

      As always, we cannot debug code for users, so I did not run your code to find potential problems. At first glance it looks okay. I would however recommend to use the flag ASSETDATA_FLAG_MULTIPLEUSE as you will otherwise step over nodes using an already yielded asset. Which you probably do not want to do in your case.

      You are also very broad in your terminology - ' finding & updating all references to assets in a Cinema 4D document'. The rest of your posting seems to be however very focused on textures, while your code then again does not really make an attempt to distinguish what kind of asset you are handling. Documents can contain media (textures, movies, audio), scene data (documents, volumes, xrefs, substances, ies, ...) and font assets. There is also some super exotic internal data you sometimes come along. Not filtering at all what asset data you handle does not seem to be a good idea.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: [API Question] How to List All Parameters in RS Shader Graph Material?

      Hello @writing-qi,

      Welcome to the Maxon developers forum and its community, it is great to have you with us!

      Getting Started

      Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.

      • Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
      • Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
      • Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.

      It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: How to Ask Questions.

      About your First Question

      Please familiarize yourself with our support guidelines, your first question is composed out of multiple explicit and implicit questions and also lacks executable code, which makes it rather hard to answer.

      GetDescription() returns parameters but all show "Access Failed"

      There is no such error in our API. I assume you mean an error like this instead: AttributeError: Parameter value not accessible (object unknown in Python).

      The Cinema 4D API is primarily a C++ API and therefore exist data types that simply have not been ported to Python. For example, many objects have the parameters ID_BASELIST_ICON_PRESET_BUTTONS and ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS (which are usually hidden) and when you try to access them, it will raise an error, because the respective data types simply have not been wrapped, so the Python API has no idea what to do with the data held by these parameters.

      In Redshift, which started out as a third party plugin and is still an optional feature of Cinema 4D, many data types have never been ported to Python which makes them simply inaccessible. But Redshift usually decomposes their data via channel decomposition. I.e., the parameter for a "Texture" (CUSTOMDATATYPE_RSFILE) itself is not exposed, but its sub channels such as the path, tiling, etc. are. This has been discussed quite a few times before on the forum, for example here.

      What is the correct API to enumerate all parameters in RS Shader Graph?

      Well, descriptions are the tool for that, but descriptions also contain things where you cannot get/set a parameter, either because the data type is not exposed to Python or simply because there is nothing to get and set. The next SDK release will also contain a function called mxutils.GetParameterTreeString which allows you to visualize the sensible parameters of a scene element. But neither it nor c4d.Description.__iter__ will automatically unfold sub-channels. E.g., when you come along a node which has a parameter of type DYTPE_VECTOR at ID_MY_VECTOR_PRAM, the description will yield you a parameter at ID_MY_VECTOR_PRAM but not its sub-channels (ID_MY_VECTOR_PRAM, VECTOR_X), (ID_MY_VECTOR_PRAM, VECTOR_Y), and (ID_MY_VECTOR_PRAM, VECTOR_Z). The same goes for Redshift datatypes, when you come across a parameter of type CUSTOMDATATYPE_RSFILE, e.g., for the 'Image > File' (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0) parameter of an RS Texture node. The description will list the parameter for REDSHIFT_SHADER_TEXTURESAMPLER_TEX0 for that node but not its nested parameters, such as [c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0,c4d.REDSHIFT_FILE_PATH], [..,c4d.REDSHIFT_FILE_LAYER], [...,c4d.REDSHIFT_FILE_COLORSPACE], etc. will not be listed.

      I am afraid there is no abstracted way to solve this in Python. You would need access to the datatype registries which you do not have in Python. And even in the public C++ API, I currently do not see a way to do this. You need access to our internal API to be able to query a data type for the sub-channels it does register.

      But you could of course just hardcode the cases, I have given a small example below for CUSTOMDATATYPE_RSFILE, it is not that much work for one data type. But Redshift did use a lot of custom data types in its old Xpresso system, so wrapping them all could be a bit of work (but should still be doable).

      Cheers,
      Ferdinand

      Output

      For this graph:
      72414249-863b-4c13-8acd-5247941e64b3-image.png

      RS Xpresso Material: <c4d.BaseMaterial object called RS C4D Shader/RS Shader Graph with ID 1036224 at 1819322820416>
      	Found Xpresso node: <c4d.modules.graphview.GvNode object called Shader Graph/XGroup with ID 1001102 at 1818547015360>
      		Parameter 'Icon File / ID'(c4d.ID_BASELIST_ICON_FILE) =
      		Parameter 'Icon Color'(c4d.None) = 0
      		Parameter 'Color'(c4d.ID_BASELIST_ICON_COLOR) = Vector(0, 0, 0)
      		Parameter ''(c4d.ID_BASELIST_ICON_PRESET_BUTTONS) = Inaccessible in Python
      		Parameter 'Name'(c4d.ID_BASELIST_NAME) = Shader Graph
      		Parameter 'Layer'(c4d.ID_LAYER_LINK) = None
      		Parameter ''(c4d.ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS) = Inaccessible in Python
      		Parameter 'Enabled'(c4d.ID_GVBASE_ENABLE) = 1
      		Parameter 'Title Color'(c4d.ID_GVBASE_COLOR) = Vector(0.38, 0.384, 0.392)
      		Parameter 'Remark'(c4d.ID_GVBASE_REMARK) =
      		Parameter 'Inputs First'(c4d.GV_GROUP_INPUTS_FIRST) = 0
      		Parameter 'Active'(c4d.GV_GROUP_ACTIVE) = 0
      	Found Xpresso node: <c4d.modules.graphview.GvNode object called Output/Output with ID 1001101 at 1819032444352>
      		Parameter 'Icon File / ID'(c4d.ID_BASELIST_ICON_FILE) =
      		Parameter 'Icon Color'(c4d.None) = 0
      		Parameter 'Color'(c4d.ID_BASELIST_ICON_COLOR) = Vector(0, 0, 0)
      		Parameter ''(c4d.ID_BASELIST_ICON_PRESET_BUTTONS) = Inaccessible in Python
      		Parameter 'Name'(c4d.ID_BASELIST_NAME) = Output
      		Parameter 'Layer'(c4d.ID_LAYER_LINK) = None
      		Parameter ''(c4d.ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS) = Inaccessible in Python
      		Parameter 'Enabled'(c4d.ID_GVBASE_ENABLE) = 1
      		Parameter 'Title Color'(c4d.ID_GVBASE_COLOR) = Vector(0.286, 0.329, 0.463)
      		Parameter 'Remark'(c4d.ID_GVBASE_REMARK) =
      		Parameter 'Material ID'(c4d.GV_REDSHIFT_OUTPUT_MATERIAL_ID) = 0
      	Found Xpresso node: <c4d.modules.graphview.GvNode object called RS Material/RS Shader with ID 1001101 at 1819032542400>
      		Parameter 'Icon File / ID'(c4d.ID_BASELIST_ICON_FILE) =
      		Parameter 'Icon Color'(c4d.None) = 0
      		Parameter 'Color'(c4d.ID_BASELIST_ICON_COLOR) = Vector(0, 0, 0)
      		Parameter ''(c4d.ID_BASELIST_ICON_PRESET_BUTTONS) = Inaccessible in Python
      		Parameter 'Name'(c4d.ID_BASELIST_NAME) = RS Material
      		Parameter 'Layer'(c4d.ID_LAYER_LINK) = None
      		Parameter ''(c4d.ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS) = Inaccessible in Python
      		Parameter 'Enabled'(c4d.ID_GVBASE_ENABLE) = 1
      		Parameter 'Title Color'(c4d.ID_GVBASE_COLOR) = Vector(0.529, 0.345, 0.333)
      		Parameter 'Remark'(c4d.ID_GVBASE_REMARK) =
      		Parameter 'Version'(c4d.GV_REDSHIFT_SHADER_META_VERSION) = 17
      		Parameter 'Shader Class'(c4d.GV_REDSHIFT_SHADER_META_CLASSNAME) = Material
      		Parameter 'preset'(c4d.REDSHIFT_SHADER_MATERIAL_PRESET) = 65536
      		Parameter 'diffuse_color'(c4d.REDSHIFT_SHADER_MATERIAL_DIFFUSE_COLOR) = Vector(0.5, 0.5, 0.5)
      		Parameter 'diffuse_weight'(c4d.REDSHIFT_SHADER_MATERIAL_DIFFUSE_WEIGHT) = 1.0
      		Parameter 'diffuse_roughness'(c4d.REDSHIFT_SHADER_MATERIAL_DIFFUSE_ROUGHNESS) = 0.0
      		Parameter 'transl_color'(c4d.REDSHIFT_SHADER_MATERIAL_TRANSL_COLOR) = Vector(0.5, 0.5, 0.5)
      		Parameter 'transl_weight'(c4d.REDSHIFT_SHADER_MATERIAL_TRANSL_WEIGHT) = 0.0
      		Parameter 'refl_color'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_COLOR) = Vector(1, 1, 1)
      		Parameter 'refl_weight'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_WEIGHT) = 1.0
      		Parameter 'refl_roughness'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_ROUGHNESS) = 0.0
      		Parameter 'refl_samples'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_SAMPLES) = 16
      		Parameter 'refl_brdf'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_BRDF) = 0
      		Parameter 'refl_aniso'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_ANISO) = 0.0
      		Parameter 'refl_aniso_rotation'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_ANISO_ROTATION) = 0.0
      		Parameter 'refl_fresnel_mode'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_FRESNEL_MODE) = 3
      		Parameter 'refl_reflectivity'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_REFLECTIVITY) = Vector(0.04, 0.04, 0.04)
      		Parameter 'refl_edge_tint'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_EDGE_TINT) = Vector(0, 0, 0)
      		Parameter 'refl_ior3'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_IOR3) = Vector(1.5, 1.5, 1.5)
      		Parameter 'refl_k3'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_K3) = Vector(0, 0, 0)
      		Parameter 'refl_metalness'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_METALNESS) = 0.0
      		Parameter 'refl_ior'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_IOR) = 1.5
      		Parameter 'sheen_color'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_COLOR) = Vector(1, 1, 1)
      		Parameter 'sheen_weight'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_WEIGHT) = 0.0
      		Parameter 'sheen_roughness'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_ROUGHNESS) = 0.30000001192092896
      		Parameter 'sheen_samples'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_SAMPLES) = 64
      		Parameter 'refr_color'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_COLOR) = Vector(1, 1, 1)
      		Parameter 'refr_weight'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_WEIGHT) = 0.0
      		Parameter 'refr_roughness'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_ROUGHNESS) = 0.0
      		Parameter 'refr_samples'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_SAMPLES) = 8
      		Parameter 'refr_ior'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_IOR) = 1.5
      		Parameter 'refr_use_base_IOR'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_USE_BASE_IOR) = 1
      		Parameter 'refr_abbe'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_ABBE) = 0.0
      		Parameter 'refr_thin_walled'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_THIN_WALLED) = 0
      		Parameter 'ss_unitsMode'(c4d.REDSHIFT_SHADER_MATERIAL_SS_UNITSMODE) = 0
      		Parameter 'refr_transmittance'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_TRANSMITTANCE) = Vector(1, 1, 1)
      		Parameter 'refr_absorption_scale'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_ABSORPTION_SCALE) = 0.009999999776482582
      		Parameter 'ss_extinction_coeff'(c4d.REDSHIFT_SHADER_MATERIAL_SS_EXTINCTION_COEFF) = Vector(0, 0, 0)
      		Parameter 'ss_extinction_scale'(c4d.REDSHIFT_SHADER_MATERIAL_SS_EXTINCTION_SCALE) = 0.009999999776482582
      		Parameter 'ss_scatter_coeff'(c4d.REDSHIFT_SHADER_MATERIAL_SS_SCATTER_COEFF) = Vector(1, 1, 1)
      		Parameter 'ss_amount'(c4d.REDSHIFT_SHADER_MATERIAL_SS_AMOUNT) = 0.0
      		Parameter 'ss_phase'(c4d.REDSHIFT_SHADER_MATERIAL_SS_PHASE) = 0.0
      		Parameter 'ss_samples'(c4d.REDSHIFT_SHADER_MATERIAL_SS_SAMPLES) = 16
      		Parameter 'ms_amount'(c4d.REDSHIFT_SHADER_MATERIAL_MS_AMOUNT) = 0.0
      		Parameter 'ms_radius_scale'(c4d.REDSHIFT_SHADER_MATERIAL_MS_RADIUS_SCALE) = 100.0
      		Parameter 'ms_mode'(c4d.REDSHIFT_SHADER_MATERIAL_MS_MODE) = 1
      		Parameter 'ms_samples'(c4d.REDSHIFT_SHADER_MATERIAL_MS_SAMPLES) = 64
      		Parameter 'ms_include_mode'(c4d.REDSHIFT_SHADER_MATERIAL_MS_INCLUDE_MODE) = 1
      		Parameter 'ms_color0'(c4d.REDSHIFT_SHADER_MATERIAL_MS_COLOR0) = Vector(1, 0.9, 0.7)
      		Parameter 'ms_weight0'(c4d.REDSHIFT_SHADER_MATERIAL_MS_WEIGHT0) = 1.0
      		Parameter 'ms_radius0'(c4d.REDSHIFT_SHADER_MATERIAL_MS_RADIUS0) = 1.0
      		Parameter 'ms_color1'(c4d.REDSHIFT_SHADER_MATERIAL_MS_COLOR1) = Vector(1, 0.9, 0.7)
      		Parameter 'ms_weight1'(c4d.REDSHIFT_SHADER_MATERIAL_MS_WEIGHT1) = 0.0
      		Parameter 'ms_radius1'(c4d.REDSHIFT_SHADER_MATERIAL_MS_RADIUS1) = 1.0
      		Parameter 'ms_color2'(c4d.REDSHIFT_SHADER_MATERIAL_MS_COLOR2) = Vector(1, 0.9, 0.7)
      		Parameter 'ms_weight2'(c4d.REDSHIFT_SHADER_MATERIAL_MS_WEIGHT2) = 0.0
      		Parameter 'ms_radius2'(c4d.REDSHIFT_SHADER_MATERIAL_MS_RADIUS2) = 1.0
      		Parameter 'coat_color'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_COLOR) = Vector(1, 1, 1)
      		Parameter 'coat_weight'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_WEIGHT) = 0.0
      		Parameter 'coat_roughness'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_ROUGHNESS) = 0.0
      		Parameter 'coat_samples'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_SAMPLES) = 16
      		Parameter 'coat_brdf'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_BRDF) = 0
      		Parameter 'coat_fresnel_mode'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_FRESNEL_MODE) = 3
      		Parameter 'coat_reflectivity'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_REFLECTIVITY) = Vector(0.027, 0.027, 0.027)
      		Parameter 'coat_ior3'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_IOR3) = Vector(1.4, 1.4, 1.4)
      		Parameter 'coat_ior'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_IOR) = 1.399999976158142
      		Parameter 'coat_transmittance'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_TRANSMITTANCE) = Vector(1, 1, 1)
      		Parameter 'coat_thickness'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_THICKNESS) = 0.10000000149011612
      		Parameter 'coat_bump_input'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_BUMP_INPUT) = Vector(0, 0, 0)
      		Parameter 'overall_color'(c4d.REDSHIFT_SHADER_MATERIAL_OVERALL_COLOR) = Vector(1, 1, 1)
      		Parameter 'opacity_color'(c4d.REDSHIFT_SHADER_MATERIAL_OPACITY_COLOR) = Vector(1, 1, 1)
      		Parameter 'emission_color'(c4d.REDSHIFT_SHADER_MATERIAL_EMISSION_COLOR) = Vector(0, 0, 0)
      		Parameter 'emission_weight'(c4d.REDSHIFT_SHADER_MATERIAL_EMISSION_WEIGHT) = 0.0
      		Parameter 'bump_input'(c4d.REDSHIFT_SHADER_MATERIAL_BUMP_INPUT) = Vector(0, 0, 0)
      		Parameter 'depth_override'(c4d.REDSHIFT_SHADER_MATERIAL_DEPTH_OVERRIDE) = 0
      		Parameter 'refl_depth'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_DEPTH) = 3
      		Parameter 'refl_enablecutoff'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_ENABLECUTOFF) = 0
      		Parameter 'refl_cutoff'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_CUTOFF) = 0.009999999776482582
      		Parameter 'skip_inside_refl'(c4d.REDSHIFT_SHADER_MATERIAL_SKIP_INSIDE_REFL) = 1
      		Parameter 'refr_depth'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_DEPTH) = 5
      		Parameter 'refr_enablecutoff'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_ENABLECUTOFF) = 0
      		Parameter 'refr_cutoff'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_CUTOFF) = 0.009999999776482582
      		Parameter 'combined_depth'(c4d.REDSHIFT_SHADER_MATERIAL_COMBINED_DEPTH) = 6
      		Parameter 'diffuse_direct'(c4d.REDSHIFT_SHADER_MATERIAL_DIFFUSE_DIRECT) = 1.0
      		Parameter 'diffuse_indirect'(c4d.REDSHIFT_SHADER_MATERIAL_DIFFUSE_INDIRECT) = 1.0
      		Parameter 'refl_direct'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_DIRECT) = 1.0
      		Parameter 'refl_indirect'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_INDIRECT) = 1.0
      		Parameter 'refl_isGlossiness'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_ISGLOSSINESS) = 0
      		Parameter 'coat_direct'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_DIRECT) = 1.0
      		Parameter 'coat_indirect'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_INDIRECT) = 1.0
      		Parameter 'coat_isGlossiness'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_ISGLOSSINESS) = 0
      		Parameter 'sheen_direct'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_DIRECT) = 1.0
      		Parameter 'sheen_indirect'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_INDIRECT) = 1.0
      		Parameter 'sheen_isGlossiness'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_ISGLOSSINESS) = 0
      		Parameter 'refr_isGlossiness'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_ISGLOSSINESS) = 0
      		Parameter 'decoupleIORFromRoughness'(c4d.REDSHIFT_SHADER_MATERIAL_DECOUPLEIORFROMROUGHNESS) = 0
      		Parameter 'shadow_opacity'(c4d.REDSHIFT_SHADER_MATERIAL_SHADOW_OPACITY) = 0.0
      		Parameter 'affects_alpha'(c4d.REDSHIFT_SHADER_MATERIAL_AFFECTS_ALPHA) = 1
      		Parameter 'block_volumes'(c4d.REDSHIFT_SHADER_MATERIAL_BLOCK_VOLUMES) = 1
      		Parameter 'energyCompMode'(c4d.REDSHIFT_SHADER_MATERIAL_ENERGYCOMPMODE) = 1
      		Parameter 'overallAffectsEmission'(c4d.REDSHIFT_SHADER_MATERIAL_OVERALLAFFECTSEMISSION) = 0
      		Parameter 'refl_endmode'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_ENDMODE) = 1
      		Parameter 'anisotropy_orientation'(c4d.REDSHIFT_SHADER_MATERIAL_ANISOTROPY_ORIENTATION) = 2
      		Parameter 'anisotropy_uvChannel'(c4d.REDSHIFT_SHADER_MATERIAL_ANISOTROPY_UVCHANNEL) =
      		Parameter 'anisotropy_tangentChannel'(c4d.REDSHIFT_SHADER_MATERIAL_ANISOTROPY_TANGENTCHANNEL) =
      	Found Xpresso node: <c4d.modules.graphview.GvNode object called Baked Texture/RS Shader with ID 1001101 at 1819592565952>
      		Parameter 'Icon File / ID'(c4d.ID_BASELIST_ICON_FILE) =
      		Parameter 'Icon Color'(c4d.None) = 0
      		Parameter 'Color'(c4d.ID_BASELIST_ICON_COLOR) = Vector(0, 0, 0)
      		Parameter ''(c4d.ID_BASELIST_ICON_PRESET_BUTTONS) = Inaccessible in Python
      		Parameter 'Name'(c4d.ID_BASELIST_NAME) = Baked Texture
      		Parameter 'Layer'(c4d.ID_LAYER_LINK) = None
      		Parameter ''(c4d.ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS) = Inaccessible in Python
      		Parameter 'Enabled'(c4d.ID_GVBASE_ENABLE) = 1
      		Parameter 'Title Color'(c4d.ID_GVBASE_COLOR) = Vector(0.663, 0.624, 0.424)
      		Parameter 'Remark'(c4d.ID_GVBASE_REMARK) =
      		Parameter 'Version'(c4d.GV_REDSHIFT_SHADER_META_VERSION) = 17
      		Parameter 'Shader Class'(c4d.GV_REDSHIFT_SHADER_META_CLASSNAME) = TextureSampler
      		Parameter 'tex0'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0) = Inaccessible in Python
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1000) =
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1010) =
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1011) =
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1001) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1006) = <c4d.BaseTime object at 0x000001A85FC0EDC0>
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1007) = <c4d.BaseTime object at 0x000001A85FC1F440>
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1009) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1003) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1004) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1005) = 30
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1008) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1002) = 0
      		Parameter 'tex0_layername'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0_LAYERNAME) =
      		Parameter 'tex0_colorSpace'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0_COLORSPACE) =
      		Parameter 'tex0_gamma'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0_GAMMA) = 1.0
      		Parameter 'tspace_id'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TSPACE_ID) =
      		Parameter 'mirrorU'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_MIRRORU) = 0
      		Parameter 'mirrorV'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_MIRRORV) = 0
      		Parameter 'wrapU'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_WRAPU) = 1
      		Parameter 'wrapV'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_WRAPV) = 1
      		Parameter 'scale'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_SCALE) = Vector(1, 1, 0)
      		Parameter 'offset'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_OFFSET) = Vector(0, 0, 0)
      		Parameter 'rotate'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ROTATE) = 0.0
      		Parameter 'color_multiplier'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_COLOR_MULTIPLIER) = Vector(1, 1, 1)
      		Parameter 'color_offset'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_COLOR_OFFSET) = Vector(0, 0, 0)
      		Parameter 'alpha_multiplier'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ALPHA_MULTIPLIER) = 1.0
      		Parameter 'alpha_offset'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ALPHA_OFFSET) = 0.0
      		Parameter 'alpha_is_luminance'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ALPHA_IS_LUMINANCE) = 0
      		Parameter 'invalid_color'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_INVALID_COLOR) = Inaccessible in Python
      		Parameter 'filter_enable_type'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_FILTER_ENABLE_TYPE) = 2
      		Parameter 'filter_bicubic'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_FILTER_BICUBIC) = 0
      		Parameter 'prefer_sharp'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_PREFER_SHARP) = 1
      		Parameter 'mip_bias'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_MIP_BIAS) = 0.0
      		Parameter 'tone_map_enable'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TONE_MAP_ENABLE) = 0
      		Parameter 'default_uv_if_invalid'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_DEFAULT_UV_IF_INVALID) = 1
      	Found Xpresso node: <c4d.modules.graphview.GvNode object called C4D Brick Shader/C4D Shader with ID 1001101 at 1819592570688>
      		Parameter 'Icon File / ID'(c4d.ID_BASELIST_ICON_FILE) =
      		Parameter 'Icon Color'(c4d.None) = 0
      		Parameter 'Color'(c4d.ID_BASELIST_ICON_COLOR) = Vector(0, 0, 0)
      		Parameter ''(c4d.ID_BASELIST_ICON_PRESET_BUTTONS) = Inaccessible in Python
      		Parameter 'Name'(c4d.ID_BASELIST_NAME) = C4D Brick Shader
      		Parameter 'Layer'(c4d.ID_LAYER_LINK) = None
      		Parameter ''(c4d.ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS) = Inaccessible in Python
      		Parameter 'Enabled'(c4d.ID_GVBASE_ENABLE) = 1
      		Parameter 'Title Color'(c4d.ID_GVBASE_COLOR) = Vector(0.424, 0.392, 0.541)
      		Parameter 'Remark'(c4d.ID_GVBASE_REMARK) =
      		Parameter 'Width'(c4d.GV_REDSHIFT_BAKER_TEXTURE_WIDTH) = 128
      		Parameter 'Height'(c4d.GV_REDSHIFT_BAKER_TEXTURE_HEIGHT) = 128
      		Parameter 'Depth'(c4d.GV_REDSHIFT_BAKER_TEXTURE_DEPTH) = 0
      		Parameter 'Shader'(c4d.GV_REDSHIFT_BAKER_SHADER) = <c4d.BaseShader object called Brick/Brick with ID 5804 at 1822672588480>
      	Found Xpresso node: <c4d.modules.graphview.GvNode object called file_730884e9656be535~/RS Shader with ID 1001101 at 1819592599488>
      		Parameter 'Icon File / ID'(c4d.ID_BASELIST_ICON_FILE) =
      		Parameter 'Icon Color'(c4d.None) = 0
      		Parameter 'Color'(c4d.ID_BASELIST_ICON_COLOR) = Vector(0, 0, 0)
      		Parameter ''(c4d.ID_BASELIST_ICON_PRESET_BUTTONS) = Inaccessible in Python
      		Parameter 'Name'(c4d.ID_BASELIST_NAME) = file_730884e9656be535~
      		Parameter 'Layer'(c4d.ID_LAYER_LINK) = None
      		Parameter ''(c4d.ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS) = Inaccessible in Python
      		Parameter 'Enabled'(c4d.ID_GVBASE_ENABLE) = 1
      		Parameter 'Title Color'(c4d.ID_GVBASE_COLOR) = Vector(0.663, 0.624, 0.424)
      		Parameter 'Remark'(c4d.ID_GVBASE_REMARK) =
      		Parameter 'Version'(c4d.GV_REDSHIFT_SHADER_META_VERSION) = 17
      		Parameter 'Shader Class'(c4d.GV_REDSHIFT_SHADER_META_CLASSNAME) = TextureSampler
      		Parameter 'tex0'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0) = Inaccessible in Python
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1000) = asset:///file_730884e9656be535~.jpg?name=back-2.jpg&db=MaxonAssets.db (assets.maxon.net)
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1010) =
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1011) =
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1001) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1006) = <c4d.BaseTime object at 0x000001A797214AC0>
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1007) = <c4d.BaseTime object at 0x000001A79721BFC0>
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1009) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1003) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1004) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1005) = 30
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1008) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1002) = 0
      		Parameter 'tex0_layername'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0_LAYERNAME) =
      		Parameter 'tex0_colorSpace'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0_COLORSPACE) =
      		Parameter 'tex0_gamma'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0_GAMMA) = 1.0
      		Parameter 'tspace_id'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TSPACE_ID) =
      		Parameter 'mirrorU'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_MIRRORU) = 0
      		Parameter 'mirrorV'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_MIRRORV) = 0
      		Parameter 'wrapU'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_WRAPU) = 1
      		Parameter 'wrapV'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_WRAPV) = 1
      		Parameter 'scale'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_SCALE) = Vector(1, 1, 0)
      		Parameter 'offset'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_OFFSET) = Vector(0, 0, 0)
      		Parameter 'rotate'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ROTATE) = 0.0
      		Parameter 'color_multiplier'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_COLOR_MULTIPLIER) = Vector(1, 1, 1)
      		Parameter 'color_offset'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_COLOR_OFFSET) = Vector(0, 0, 0)
      		Parameter 'alpha_multiplier'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ALPHA_MULTIPLIER) = 1.0
      		Parameter 'alpha_offset'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ALPHA_OFFSET) = 0.0
      		Parameter 'alpha_is_luminance'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ALPHA_IS_LUMINANCE) = 0
      		Parameter 'invalid_color'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_INVALID_COLOR) = Inaccessible in Python
      		Parameter 'filter_enable_type'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_FILTER_ENABLE_TYPE) = 2
      		Parameter 'filter_bicubic'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_FILTER_BICUBIC) = 0
      		Parameter 'prefer_sharp'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_PREFER_SHARP) = 1
      		Parameter 'mip_bias'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_MIP_BIAS) = 0.0
      		Parameter 'tone_map_enable'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TONE_MAP_ENABLE) = 0
      		Parameter 'default_uv_if_invalid'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_DEFAULT_UV_IF_INVALID) = 1
      

      Code

      """Prints the parameter values of all Xpresso nodes attached to the materials of a document.
      
      Adds custom handling for the Redshift data type `CUSTOMDATATYPE_RSFILE` to unfold its sub-channels.
      
      Note:
          This script requires Cinema 4D 2025.0.0 or higher to run because it uses mxutils.RecurseGraph to
          find all Xpresso nodes in a material. The general approach of this script can however be applied
          to much older versions of Cinema 4D (up to R21 guaranteed, maybe even older versions).
      """
      
      import c4d
      import mxutils
      
      doc: c4d.documents.BaseDocument  # The currently active document.
      op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
      
      # A bunch of data types to step over in descriptions because we do not care about them.
      NO_VALUE_TYPES: list[int] = [
              c4d.DTYPE_BUTTON, c4d.DTYPE_CHILDREN, c4d.DTYPE_CHILDS, c4d.DTYPE_DYNAMIC,
              c4d.DTYPE_GROUP, c4d.DTYPE_MULTIPLEDATA, c4d.DTYPE_NONE,
              c4d.DTYPE_SEPARATOR, c4d.DTYPE_SUBCONTAINER, c4d.DTYPE_POPUP
      ]
      
      def main() -> None:
          """Called by Cinema 4D when the script is being executed.
          """
          # Iterate over all materials in the document, stepping over everything that is not an
           # RS Xpresso material.
          for material in doc.GetMaterials():
              if not material.CheckType(c4d.Mrsgraph):
                  continue
              
              # Iterate over all Xpresso nodes owned by this material. #RecurseGraph is a 2025.0.0+ feature. 
              # You can achieve the same effect with the Redshift API in earlier versions of Cinema 4D. But
              # since you did not share your code, I took this shortcut instead of writing some lengthy code 
              # to find all RS Xpresso nodes for this material.
              print(f"RS Xpresso Material: {material}")
              for node in mxutils.RecurseGraph(material, yieldBranches=True, nodeFilter=[c4d.GVbase]):
                  print(f"\tFound Xpresso node: {node}")
      
                  # Iterate over the description of the node, stepping over all irrelevant data types.
                  description: c4d.Description = node.GetDescription(c4d.DESCFLAGS_DESC_0)
                  data: c4d.BaseContainer
                  pid: c4d.DescID
                  for data, pid, _ in description:
                      if pid[0].dtype in NO_VALUE_TYPES:
                          continue
      
                      # Now we try to access the data at #pid. This can fail because the data type of #pid
                      # might not be accessible in Python.
                      value: any | None = None
                      try:
                          value = node[pid]
                      except:
                          value = "Inaccessible in Python"
                      
                      print(f"\t\tParameter '{data[c4d.DESC_NAME]}'(c4d.{data[c4d.DESC_IDENT]}) = {value}")
      
                      # Hardcode one of the data types (CUSTOMDATATYPE_RSFILE) and unfold its sub-channels.
                      if pid[0].dtype == c4d.CUSTOMDATATYPE_RSFILE and len(pid) == 1:
                          for subChannel in  (c4d.REDSHIFT_FILE_PATH, 
                                              c4d.REDSHIFT_FILE_LAYER, 
                                              c4d.REDSHIFT_FILE_COLORSPACE,
                                              c4d.REDSHIFT_FILE_ANIMATION_MODE, 
                                              c4d.REDSHIFT_FILE_ANIMATION_RANGE_START,
                                              c4d.REDSHIFT_FILE_ANIMATION_RANGE_END, 
                                              c4d.REDSHIFT_FILE_ANIMATION_LOOP_COUNT,
                                              c4d.REDSHIFT_FILE_ANIMATION_FRAME_START,
                                              c4d.REDSHIFT_FILE_ANIMATION_FRAME_END,
                                              c4d.REDSHIFT_FILE_ANIMATION_FRAME_RATE,
                                              c4d.REDSHIFT_FILE_ANIMATION_FRAME_OFFSET,
                                              c4d.REDSHIFT_FILE_ANIMATION_TIMING_MODE):
                              try:
                                  value = node[pid[0].id, subChannel]
                              except:
                                  # This, that the sub-chanel is inaccessible too, can also happen, as the 
                                  # sub-channel could be as much an unwrapped data type as its parent 
                                  # parameter. Technically, a DescID can be composed out of three 
                                  # DescLevel, i.e., you could have a parameter X with N sub-channels
                                  # where each of the sub-channels could also have N sub-channels, e.g.,
                                  # (ID_FOO, ID_SUB_1, ID_SUB_1_1). None of these sub-channels has to use
                                  # a data type that is wrapped in Python. But something like this is 
                                  # rather rare. Parameters where the first desc level is not some kind of
                                  # container (e.g., DTYPE_SUBCONTAINER), usually only decomposes into 
                                  # atomic data types that can also be read from Python. But there is no
                                  # hard guarantee that this is the case.
                                  value = "Sub-channel is inaccessible in Python"
      
                              print(f"\t\t\tSub-channel parameter ({data[c4d.DESC_IDENT]}, "
                                    f"{subChannel}) = {value}")
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Motion Blur, ExecutePasses in VideoPostData

      Hello @Aaron,

      Thank you for reaching out to us. Please follow our support procedures.

      • You should consolidate your postings until we answer, i.e., update your last posting instead of creating new ones. I have consolidated your postings for you.
      • You should also spend more time on making your problems reproducible for us. I know it can be easy to overlook in the heat of the battle but your question(s) are not as obvious to me as you probably think they are. As lined out in our support procedures, example code and/or scene data is usually required.

      I cannot answer every possible angle and interpretation this topic of yours could take, but I can state three things which come to mind when reading your two postings.

      What's the way to get several steps of motion blur transformation matrices for BaseObjects of the BaseDocument.

      That is simply not possible. A document has no notion of motion blur as it is a render effects implemented by a render engine, not something documents implement. Cinema's time system BaseTime allows for sub-frame accuracy, e.g., you could set a 10fps document to BaseTime(i, 30) and with that step with three sub-frames through the document. Simple things like position animations or other interpolated values will also work doing this. But simulations might yield different results than stepping with the natural frame rate through the document.

      And the most important thing is that there is absolutely no guarantee that this approach will result in 'matrices', i.e., transforms, that will match with what a render engine produces when you add some kind of sub-frame-based motion blur. Using this BaseTime approach is one way how the render engine could do it, but since render engines always have their own scene format, they could also do the calculation there, yielding different results than what Cinema's animation system would yield.

      When we want to add Motion Blur and call document->ExecutePasses(...) for some time then this POC is flushed and has no polygons or vertices, so it's not collected and can't be rendered

      It is too opaque for me what you are doing there to give a real answer, but:

      • There is no guarantee that the caches of an object are built at any point except for when a render engine gets a scene state to render or inside GetVirtualsObjects when you manually build your dependent objcts. Usually, caches are built but depending on where you are, the cache of another object might not have been built or updated yet, resulting in either an empty cache or you looking at the cache output from the previous scene update.
      • ExecutePasses has become a bit too popular tool recently. I guess that is partially my fault, because I have shown its usage in some examples. But executing the passes on a document is a very expensive operation and should only be carried out when absolutely necessary.
      • I would absolutely not be surprised if something like liquids do not support stepping through a document with a frame rate lower than the documents frame rate. But without concrete data and example code I am just guessing.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: How to Modify Button Parameters?

      Hey @Ross,

      Thank you for reaching out to us. You are right, we cannot support third party APIs, but questions about our API do not automatically become moot or out of scope of support, just because they happen in the context of a plugin. We will of course still answer such questions. We will just never write code for a third-party API or test user code that requires a plugin.

      Your question '[I] need help modifying the "Import Type" button parameters of an ImageTexture node' is however ambiguous. A parameter is Cinema 4D slang for an element in a description, one of the ways of how Cinema 4D defines UIs. This includes things that users would naturally call a parameter such as a textbox holding a string, a number edit holding a float, or a combo box holding a selection, i.e., things that hold a value. But it also includes things that are just part of the UI of a node that cannot hold a value, e.g., a group, a separator, or a button.

      • When someone talks about 'modifying' a parameter I would usually assume that they talk about changing the description of a node, specifically at that parameter. E.g., changing the label of a button, adding or removing elements to/from a drop down, disabling an element, etc. You can only do this for nodes you own, i.e., implement yourself. You cannot change the UI of alien elements, because for you their description is read only.
      • But I assume you are actually talking here about getting or setting the parameter of an element, e.g., set a float slider to the value '4.13'.

      What gets a bit in the way here in your case, is that Cinema 4D summarizes everything under the label of paramater, even things that do not naturally have a value which could be gotten or set. So, I assume you want to press this button, for that you have two options:

      • Call c4d.CallButton
      • Send the message c4d.MSG_DESCRIPTION_COMMAND

      Please note that both approaches are restricted to being called from the main thread only when the scene element upon which they are called is part of a loaded scene. E.g., calling one of them on a node in the active document from outside of the main thread: forbidden. Calling them from inside a GetVirtualObjects (or any other off-main thread context) on a scene element in a dummy document which has been constructed just for this purpose and is not loaded by Cinema 4D: absolutely fine.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Script to change parameters across all similar nodes material

      Hey @annoyedUser,

      Thank you for reaching out to us. Your code looks very much like generated by a chatbot or generated with the help of a chatbot. Please note our Support Procedures: Scope of Support regrading the usage of AI. Please also post into the correct forum for future topics, I have moved your topic into the Cinema 4D SDK forum.

      Your code does not make too much sense, I cannot dissect all the issues there. It is possible to do with the Nodes API what you want to do but it is more an API for experts. You can find here some code examples for it. Novice users should rather use graph descriptions which is a less powerful but also simpler API.

      Cheers,
      Ferdinand

      To change the external thickness of each Contour node in a document, you could write something like using a graph description query:

      """Sets the external thickness of all 'Contour' nodes in Redshift material graphs of the active 
      document to 2.
      """
      
      import c4d
      import maxon
      
      doc: c4d.documents.BaseDocument  # The currently active document.
      op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
      
      def main() -> None:
          """Called by Cinema 4D when the script is being executed.
          """
          # Iterate over the material graphs of all Redshift materials in the document.
          for graph in maxon.GraphDescription.GetMaterialGraphs(
              doc, maxon.NodeSpaceIdentifiers.RedshiftMaterial):
              # Apply a graph description to each of them which ...
              maxon.GraphDescription.ApplyDescription(graph, 
              {
                  # ... queries all nodes of type 'Contour' in the graph ...
                  "$query": {
                      "$qmode": maxon.GraphDescription.QUERY_FLAGS.MATCH_ALL | 
                                maxon.GraphDescription.QUERY_FLAGS.MATCH_MAYBE,
                      "$type": "Contour",
                  },
                  # ... so that we can set the thickness value of all matching nodes to 2.
                  "External/Thickness": 2
              })
      
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: How to get explicit import node space?

      Hey @Dunhou,

      Thank you for reaching out to us. This subject comes up from time to time, and the answer is that you have to eval the description at runtime. I probably should write a more prominent example for that. In case you have already considered this option and deemed it unsafe, there is no safer route.

      Cheers,
      Ferdinand

      """Demonstrates how to evaluate a dynamically built description at runtime.
      
      Things like cycles (Cinema slang for a combo box) are often built at runtime in Cinema 4D. I.e., 
      there is no resource where one could look up all the cycle elements in advance. And this of course
      makes sense, because something like all the installed render engines cannot be known in advance.
      
      The solution to this to evaluate the description of the item at runtime, to figure out what cycle
      index corresponds to which string label. This is of course a bit unsafe, but there is no better way
      for dynamic elements as their defining characteristics is that they do not have a static identifier.
      """
      
      import c4d
      import mxutils
      
      doc: c4d.documents.BaseDocument  # The currently active document.
      op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
      
      def main() -> None:
          """Called by Cinema 4D when the script is being executed.
          """
          # Get the import/export preferences plugin.
          pref: c4d.plugins.BasePlugin = mxutils.CheckType(
              c4d.plugins.FindPlugin(c4d.PREFS_IMPORTEXPORT, c4d.PLUGINTYPE_PREFS))
          
          # Get its description and there the container for `PREF_IMEXPORT_NODESPACE`.
          desc: c4d.Description = pref.GetDescription(c4d.DESCFLAGS_DESC_NONE)
          param: c4d.BaseContainer = mxutils.CheckType(desc.GetParameter(c4d.PREF_IMEXPORT_NODESPACE))
      
          # Get the cycle element in the parameter container and print all values.
          cycle: c4d.BaseContainer = mxutils.CheckType(param[c4d.DESC_CYCLE])
          for i, v in cycle:
              print(f"Cycle element {i}: {v}")
      
          # Build a translation map from string labels to indices. This assumes that there are no exactly
          # identical render engine labels in the cycle. Which is reasonable but not a hard guarantee.
          engineMap: dict[str, int] = {v: i for i, v in cycle}
      
          # Now set the render engine in the settings using a string and a fallback value in case an
          # engine with our string is not present.
          pref[c4d.PREF_IMEXPORT_NODESPACE] = engineMap.get("CentiLeo", 0)
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Graph Description Explicit Connections can not work in 3rd-renderer (Arnold).

      Hey DunHou,

      the error is a bit different one as I would have expected. It also seems to treat '>output.r' as a reference and not as an ID. I will have to take a look what is going wrong there, this does not look right.

      This could be an edge case of what is also happening for standard. Vectors are not actually exposed data types for which you could find data descriptions in a data description database. So, the information the data type Vector has there components x, y, and z and the datatype ColorA has the components r, g, b, and a cannot be evaluated in this manner. But the Standard renderer has add custom code which decomposes all its color ports into color port bundles of the form Color {r, g, b}. When then the API is caching the graph description lookup, it only sees that outer Color port parent, but not its bundle children {r, g, b} which are added at runtime. Which is exactly why I added validateAbsolutePaths, so that you can forcibly side step the cache. Because even if you use explicit IDs, graph descriptions check under the hood if what you wrote makes sense (to avoid cryptic errors or worse - something failing silently).

      But what is Arnold is doing there is not a port bundle but just four ports next to each other. But maybe they do something similar like Standard where they dynamically create ports at runtime outside of the 'normal' bounds of a variadic port (where dynamic ports are expected and somewhat predictable).

      I will have a look when I have time. What you could do, is check the resource of the Arnold image node in the resource editor. When you cannot see there the r output port, it means it is irregularly added at runtime, and therefore cannot be cached or predicted. Maybe @peter_horvath knows more?

      Or there could of course also just be a bug in how graph descriptions handle explicit output port references.


      Regarding the forum, the errors you have there are 'normal', it is just the emoji plugin of NoddeBB. And you have no notifications at all? I.e., this here?

      ebb40f5a-9ae9-491d-aba9-f3bf641f3213-image.png

      My daily driver is Edge, and there it works on Win, MacOS, and Linux for me. I also checked on Safari and FF and it seems to work there too.

      But these broken notifications are a common NodeBB problem, and are usually tied to dangling sessions. But I have not seen a case as persistent as yours before. You could try to go User Icon > Settings > Three Dots > Login Sessions and there manually revoke all your sessions.

      On top of that you can try to either completely delete the browser cache for developers.maxon.net or just force the site rebuilding it py pressing CTRL + F5 or similar key combos.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Updated Redshift node material examples

      Hey @Dunhou,

      without having run your script, the answer lies probably in the posting above. The Nodes API allows you to manipulate the undo stack in a special way with the settings for the transaction (there is also a maxon.nodes.UNDO_MODE.NONE). And Graph descriptions do not expose these settings, I agree however that it would be a nice feature for experts to expose them. I added a little task to expose them.

      Currently there is probably not much you can do about it in an elegant way. What you could try to do, is just carry out the description, then call BaseDocument.GetUndoPtr, copy it, undo things, then open your own undo item, do the stuff you want, and replace the material with the result from BaseDocument.GetUndoPtr, and close the undo item. So that 'your stuff' and the graph transaction are consolidated in one undo item. But there is a lot which could go wrong here, especially with the Nodes API which does quite a bit of dark magic under the hood.

      Cheers,
      Ferdinand

      posted in Bugs
      ferdinandF
      ferdinand