Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python 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
    • Recent
    • Tags
    • Users
    • Register
    • Login
    1. Maxon Developers Forum
    2. ferdinand
    3. Posts
    Offline
    • Profile
    • Following 0
    • Followers 17
    • Topics 58
    • Posts 3,274
    • Groups 2

    Posts

    Recent Best Controversial
    • RE: Problem with Undo when using COLOR field in a description resource

      Hey @spedler,

      I did give this a shot. I cannot reproduce this after even quite a few attempts, but one of my colleagues (Fritz) could. There is probably some undo code missing in the color chip dialog. I assume that was what you did, Steve, right? You set the color via the popup color chip dialog and not with the inline control? Fritz could reproduce this without your plugin. So, my hunch was right, your plugin is not the culprit.

      Since I cannot fix what I cannot reproduce on my machine, I have created a bug ticket for this and sent it off to the owners of the COLOR control. The ticket is ITEM#652737 Parameters of type COLOR do not always correctly invoke UNDOs in descriptions.

      Out of curiosity: Can you also reproduce this on your machine with a a builtin object, e.g., the Cube object? Fritz and I were both on macOS while testing. But this being OS-specific seems a bit unlikely.

      Cheers,
      Ferdinand

      I have moved this topic into "bugs".

      edit: As always with these things now I can reproduce it too 😄

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: Programmatically created Redshift Lights in an Object Plugin converts lights into Null-Objekts without an function after making Objekt-Plugin editable

      Hey @ThomasB,

      so I did a clean writing of your plugin demo code in preparation to debug the output and I cannot reproduce the issue you report. For me the light works. There were some issues with your code, but none of them should cause the light to malfunction as your reported on LoadDocument. What you did there with self.container was a bit dicey and could have caused the issue but it seems somewhat unlikely.

      I am today on my Mac, it seems quite unlikely that this is an OS related issue.

      The object after pressing the button in your plugin:
      f6aea3b3-a566-4779-99f4-e71a61cea4f0-image.png
      And the object after collapsing it:
      56138dee-a9f5-415d-bc69-19e0a7695df1-image.png

      Cheers,
      Ferdinand

      from c4d import plugins, bitmaps, BaseObject, GeListNode
      
      import c4d
      import mxutils
      import os
      
      
      PLUGIN_ID: int = 1000200
      PY_ADD_LIGHTS: int = 10000
      
      
      class RedshiftLightsTest(plugins.ObjectData):
          """
          """
          HOUSE_PATH: str = os.path.join(os.path.dirname(__file__), "res", "models", "build_1.c4d")
      
          def __init__(self) -> None:
              """
              """
              self.SetOptimizeCache(True)
              self._asset_doc: c4d.documents.BaseDocument | None = None
      
              # No, never store a reference to what you return as your cache in GVO!
              # self.container = None
      
          def Init(self, op, isCloneInit: bool = False) -> bool:
              """
              """
              if not os.path.exists(self.HOUSE_PATH):
                  raise FileNotFoundError(f"House model not found at {self.HOUSE_PATH}")
              
              # For cloning it would be better to copy over the document to avoid reloading it, but since
              # we do not implement NodeData.CopyTo() we will just reload it for now.
              self._asset_doc = mxutils.CheckType(
                  c4d.documents.LoadDocument(self.HOUSE_PATH, c4d.SCENEFILTER_OBJECTS | c4d.SCENEFILTER_MATERIALS))
      
              return True
          
          def GetAssetPayload(self) -> c4d.BaseObject | None:
              """Returns a copy of the asset payload for the plugin.
              """
              if not isinstance(self._asset_doc, c4d.documents.BaseDocument):
                  raise RuntimeError("Asset not loaded correctly.") # Should not happen, but just in case.
              
              root: c4d.BaseObject = self._asset_doc.GetFirstObject()
              if not root:
                  raise RuntimeError("Asset document has no root object.")
              
              payload: c4d.BaseObject = root.GetDown()
              if not payload:
                  raise RuntimeError("Asset document has no payload object.")
              
              return payload.GetClone()
          
      
          def GetVirtualObjects(self, op: BaseObject, hh: object) -> BaseObject | None:
              """
              """
              # Return the cached object if it is not dirty, otherwise rebuild it.
              dirty: bool = op.CheckCache(hh) or op.IsDirty(c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_ALL)
              if not dirty:
                  return op.GetCache(hh)
              
              # Returning None in an ObjectData.GetVirtualObjects() call will tell Cinema that a memory error
              # occurred, and with that cause Cinema to stop calling/building the object. This is almost never
              # what we want, return a Null object instead.
              null: c4d.BaseObject | None = c4d.BaseObject(c4d.Onull)
              if not null:
                  return c4d.BaseObject(c4d.Onull)
              
              payload: c4d.BaseObject | None = self.GetAssetPayload()
              if payload:
                  payload.InsertUnder(null)
      
              return null
      
          def Message(self, node: GeListNode, type: int, data: object) -> bool:
              """
              """
              if type == c4d.MSG_DESCRIPTION_COMMAND:
                  if data["id"][0].id == PY_ADD_LIGHTS:
                      self.ImportLights(node)
                      return True
      
              return True
          
          def ImportLights(self, node: GeListNode) -> bool:
              """
              """
              if not isinstance(node, c4d.BaseObject) or not c4d.threading.GeIsMainThreadAndNoDrawThread():
                  return False
              
              # This is a bit tedious but each of these calls can fail, and you cannot, or better, should
              # not just do chain calls such as GetDown().GetDown().GetNext().GetNext(). One could abstract
              # this away with a function such as Traverse(node, "DDNN") where D = Down, N = Next, but for 
              # this example I just did it manually.
              doc: c4d.documents.BaseDocument = node.GetDocument()
              if not doc:
                  return False
              
              payload: c4d.BaseObject | None = self.GetAssetPayload()
              if not payload:
                  return False
              
              down: c4d.BaseObject | None = payload.GetDown()
              if not down:
                  return False
              
              item: c4d.BaseObject | None = down.GetNext()
              if not item:
                  return False
              
              light: c4d.BaseObject | None = item.GetNext()
              if not light:
                  return False
              
              clone: c4d.BaseObject | None = light.GetClone()
              if not clone:
                  return False
      
              doc.InsertObject(clone)
              c4d.EventAdd()
              return True
      
      if __name__ == "__main__":
          path, file = os.path.split(__file__)
          file = "icon.tif"
          new_path = os.path.join(path, "res", file)
          bitmap = bitmaps.BaseBitmap()
          bitmap.InitWith(new_path)
          plugins.RegisterObjectPlugin(id=PLUGIN_ID, str="redshift_lights_test", g=RedshiftLightsTest, description="redshift_lights_test", icon=bitmap,
                                       info=c4d.OBJECT_GENERATOR)
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Problem with Undo when using COLOR field in a description resource

      Hey @Anlv,

      thank you for the confirmation and for the precise debug report.

      Cheers,
      Ferdinand

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: Problem with Undo when using COLOR field in a description resource

      Hey, could you please try again 🙂

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: Problem with Undo when using COLOR field in a description resource

      POST https://developers.maxon.net/forum/api/post/upload 403 (Forbidden)

      That is the culprit. That is the data upload endpoint and something is not letting you even talk with it. Could be NodeBB misbehaving or some shenanigans by Cloudflare. I will see if I can find out more.

      edit: now I see it too, this is some cloudflare misfire

      12ddb934-c8e9-4f1e-bb65-3a3db35ea7b1-image.png

      Thanks for the info @Anlv

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: Problem with Undo when using COLOR field in a description resource

      Hey Steve,

      The toast messages of NodeBB are often not very helpful. What I meant was the JS console.

      I do not even see the failed upload attempts in my server logs; which in combination with "Error. Something went wrong while parsing server response" sounds a bit like a severed session? I.e., the client is sending and server does not even consider looking at the data, resulting in a timeout?

      E.g. this. Here it fails because I am using an unsupported filetype.

      32a9e2f1-1db8-49be-8c1e-b9fd6ae6fac7-image.png

      Just right click on the page, select inspect and then go to the console tab (which is usually the selected default). All modern browsers have this.

      Cheers,
      Ferdinand

      PS: Please also expand the call stack like so:

      3d2ba39a-51ae-46ee-9c3a-698a46a830f9-image.png

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: Problem with Undo when using COLOR field in a description resource

      Hey Steve,

      Something with the forum uploads is buggy. For some people it works and for some it does not, it is rather mysterious. Could you share the console output of when the upload fails? I neither see something in the server logs nor can I reproduce this from my work machines or private machines. We will replace the forum soon, so I will not put too much work into this, but maybe there is a quick fix.

      Regarding your issue: That sounds indeed like a bug, I will have a look. At first glance I do neither see anything in your code that stands out, such as dangerous NodeData::SetParameter code, nor are there issues like giving multiple elements the same ID in odiamond.h. We did touch the color picker a while ago (2025.X), so I would not rule out that we added some kind of bug.

      Cheers,
      Ferdinand

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: Programmatically created Redshift Lights in an Object Plugin converts lights into Null-Objekts without an function after making Objekt-Plugin editable

      Hey,

      sorry for the delay, I did read your reply. When I set the parameter in your scene the issue disappears. I did not check the LoadDocument/MergeDocument aspect yet. But I am 99.99% sure that there is no bug in Orslight in the conventional sense, it is just that the Orslight::GetVirtualObjects method is a bit of a fever dream.

      The issue is Orslight::GetVirtualObjects returns a null object in some cases and a null with a light attached in other cases. LoadDocument/MergeDocument having a significant enough impact on the scene graph that causes Orslight::GetVirtualObjects to change its cache building behaviour from "null + light" to "null" sounds a bit unlikely but is not impossible (that would mean the scene importer is buggy).

      I will try to have another look tomorrow or next week. When you want to help yourself, you could just do what I did: Diff the light from the loaded scene with a light your have already in the document which you know that works.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Accessing the Mixamo Control Rig auto-adjustment logic

      Hello @leah.hayes,

      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

      It is a bit difficult to answer you first question, as it is multiple questions of which some are open ended, please have a look at Support Procedures: How to Ask Questions for future postings.

      Where is the Python code of the built-in character templates ...

      They are stored in {c4d installation folder}/library.zip/characters.

      Is there a supported way to extract or view a built-in template (for example through c4d.modules.character.builder.Template) so I can study how the auto-adjustment is implemented?

      I am not quite sure that I follow. All these rigs are just our standard CA component system decorated with some Xpresso and Python. Since there can be literally hundreds of nodes in a rig, I attached below a little helper script to scan for specific Python content or just the scripts in general that are embedded somewhere in the rig.

      Is there a public API entry point to trigger or implement this kind of auto-adjustment for other templates?

      That auto adjustment seems to be some kind of Python script which the authors of this rig provided. These CA rigs have been created by (technical) artists and not the members of the Maxon development team. See the print out below for details on the artist code for "auto-adjustment".

      Cheers,
      Ferdinand

      Result

      
      
      ====================================================================================================
      Node: Character Component (Character Component) | Attribute: (2158, 130, 1022113)
      
      Value:
      
      import c4d
      from c4d import gui
      import c4d.modules.character as ca
      import c4d.utils as utils
      #Welcome to the world of Python
      
      def Bl2DIterator(bl2D):
          while bl2D:
              yield bl2D
              for bl2DChild in Bl2DIterator(bl2D.GetDown()):
                  yield bl2DChild
              bl2D = bl2D.GetNext()
      
      def SearchInHierarchy(obj, name):
          allChildrenWithName = [bl2D for bl2D in Bl2DIterator(obj.GetDown()) if bl2D.GetName() == name]
          return allChildrenWithName[0]
      
      def ShowLayer(layersList, showName):
          for layer in layersList:
              layerName = layer[c4d.ID_BASELIST_NAME]
              if showName in layerName:
                  layer[c4d.ID_LAYER_VIEW] = True
                  layer[c4d.ID_LAYER_MANAGER] = True
      
      def HideLayer(layersList, hideName):
          for layer in layersList:
              layerName = layer[c4d.ID_BASELIST_NAME]
              if hideName in layerName:
                  layer[c4d.ID_LAYER_VIEW] = False
                  layer[c4d.ID_LAYER_MANAGER] = False
      
      def CharacterAnimateMode(characterObject):
          characterObject[c4d.ID_CA_CHARACTER_COMPONENT_HIGHLIGHT_OVER] = 4
          characterObject[c4d.ID_CA_CHARACTER_COMPONENT_MOUSEOVER] = 0
          characterObject[c4d.ID_CA_CHARACTER_COMPONENT_VISIBLE] = 2
          characterObject[c4d.ID_CA_CHARACTER_OM_DISPLAY] = 5
          characterObject[c4d.ID_CA_CHARACTER_LOCK_AM] = False
      
      def CharacterEditMode(characterObject):
          characterObject[c4d.ID_CA_CHARACTER_COMPONENT_HIGHLIGHT_OVER] = 3
          characterObject[c4d.ID_CA_CHARACTER_COMPONENT_MOUSEOVER] = 4
          characterObject[c4d.ID_CA_CHARACTER_COMPONENT_VISIBLE] = 3
          characterObject[c4d.ID_CA_CHARACTER_OM_DISPLAY] = 1
          characterObject[c4d.ID_CA_CHARACTER_LOCK_AM] = True
      
      def CharacterBindMode(characterObject):
          characterObject[c4d.ID_CA_CHARACTER_COMPONENT_HIGHLIGHT_OVER] = 4
          characterObject[c4d.ID_CA_CHARACTER_COMPONENT_MOUSEOVER] = 4
          characterObject[c4d.ID_CA_CHARACTER_COMPONENT_VISIBLE] = 1
          characterObject[c4d.ID_CA_CHARACTER_OM_DISPLAY] = 5
          characterObject[c4d.ID_CA_CHARACTER_LOCK_AM] = True
      
      def CatchEmAll(obj, stop, listy):
          if obj is None: return
      
          #Actions go here
          if obj.GetType()==1019362:
              listy.append(obj)
          if not stop:
              if obj.GetDown():
                  CatchEmAll(obj.GetDown(), stop, listy)
              if obj.GetNext():
                  CatchEmAll(obj.GetNext(), stop, listy)
      
          return listy
      
      def SetGlobalRotation(obj, rot):
          """
          Please remember, Cinema 4D handles rotation in radians.
      
          Example for H=10, P=20, B=30:
      
          import c4d
          from c4d import utils
          #...
          hpb = c4d.Vector(utils.Rad(10), utils.Rad(20), utils.Rad(30))
          SetGlobalRotation(obj, hpb) #object's rotation is 10, 20, 30
          """
          m = obj.GetMg()
          pos = m.off
          scale = c4d.Vector( m.v1.GetLength(),
                              m.v2.GetLength(),
                              m.v3.GetLength())
      
          m = utils.HPBToMatrix(rot)
      
          m.off = pos
          m.v1 = m.v1.GetNormalized() * scale.x
          m.v2 = m.v2.GetNormalized() * scale.y
          m.v3 = m.v3.GetNormalized() * scale.z
      
          obj.SetMg(m)
      
      def SetGlobalScale(obj, scale):
          m = obj.GetMg()
      
          m.v1 = m.v1.GetNormalized() * scale.x
          m.v2 = m.v2.GetNormalized() * scale.y
          m.v3 = m.v3.GetNormalized() * scale.z
      
          obj.SetMg(m)
      
      def ModeChanged():
          characterObject = charop.GetObject()
          #Turning Off the Annotation
          # Main function
      
          leftAnnotationTag = SearchInHierarchy(characterObject, "LeftLeg_bind").GetLastTag()
          print(leftAnnotationTag)
          if leftAnnotationTag.GetType() == c4d.Tannotation:
              leftAnnotationTag[c4d.ANNOTATIONTAG_VIEWPORT_SHOW] = False
      
          #Hiding the Rig's Layers
          root = doc.GetLayerObjectRoot() #Gets the layer manager
          layersList = root.GetChildren() #Get Layer list
          for layer in layersList:
              layerName = layer[c4d.ID_BASELIST_NAME]
              if "Mixamo_Rig" in layerName:
                  layer[c4d.ID_LAYER_VIEW] = False
                  layer[c4d.ID_LAYER_MANAGER] = False
                  mixamoLayers = layer.GetChildren()
                  for layer in mixamoLayers:
                      layerName = layer[c4d.ID_BASELIST_NAME]
                      if "Retarget_Hierarchy" in layerName or \
                      "ANIMDATA_Nulls" in layerName or \
                      "Control Hierarchy" in layerName:
                          layer[c4d.ID_LAYER_VIEW] = False
                          layer[c4d.ID_LAYER_MANAGER] = False
      
          masterControl = SearchInHierarchy(characterObject, "Master_con+")
          hips = SearchInHierarchy(characterObject, "Hips")
          if nmode==c4d.ID_CA_CHARACTER_MODE_BUILD or \
          nmode==c4d.ID_CA_CHARACTER_MODE_ADJUST:
              CharacterEditMode(characterObject)
      
          if nmode==c4d.ID_CA_CHARACTER_MODE_BIND:
              CharacterBindMode(characterObject)
      
          if nmode==c4d.ID_CA_CHARACTER_MODE_ADJUST and omode==c4d.ID_CA_CHARACTER_MODE_BUILD:
              #Changed search method to only search in Character Object
              #hips=doc.SearchObject("Hips")
              #masterControl=doc.SearchObject("Master_con+")
              #added Namespace Code
              nameSpace=masterControl[c4d.ID_USERDATA,16]
      
              results=CatchEmAll(hips, hips.GetNext(), [])
              for result in results:
                  mixamo=doc.SearchObject(nameSpace+result.GetName())
                  if mixamo:
                      matR = result.GetMg()
                      matM = mixamo.GetMg()
                      matR.off = matM.off
                      matR.v1 = matM.v1.GetNormalized()
                      matR.v2 = matM.v2.GetNormalized()
                      matR.v3 = matM.v3.GetNormalized()
                      result.SetMg(matR)
      
      
                  if result.GetName()=='Neck1':
                      #neck=doc.SearchObject(nameSpace+'Neck')
                      #head=doc.SearchObject(nameSpace+'Head')
                      neck = SearchInHierarchy(characterObject, nameSpace+'Neck')
                      head = SearchInHierarchy(characterObject, nameSpace+'Head')
                      if head and neck:
                          result.SetMg((neck.GetMg()+head.GetMg())/2)
      
              print("Auto-Adjustment Complete")
      
              #Turning On the Annotation
              if leftAnnotationTag.GetType() == c4d.Tannotation:
                  leftAnnotationTag[c4d.ANNOTATIONTAG_VIEWPORT_SHOW] = True
      
          if omode==c4d.ID_CA_CHARACTER_MODE_ADJUST and nmode!=c4d.ID_CA_CHARACTER_MODE_BUILD:
              L_Leg=doc.SearchObject("Left_IK_parent_rot_algn")
              #L_Leg = charop.FindObject("Left_IK_parent_rot_algn")
              if L_Leg:
                  conTag=L_Leg.GetFirstTag()
                  conTag[c4d.EXPRESSION_ENABLE]=False
              R_Leg=doc.SearchObject("Right_IK_parent_rot_algn")
              #R_Leg = charop.FindObject("Right_IK_parent_rot_algn")
              if R_Leg:
                  conTag=R_Leg.GetFirstTag()
                  conTag[c4d.EXPRESSION_ENABLE]=False
      
          if nmode==c4d.ID_CA_CHARACTER_MODE_ANIMATE:
              CharacterAnimateMode(characterObject)
              doc.SetActiveObject(masterControl, c4d.SELECTION_NEW)
          c4d.EventAdd()
      

      Code

      """Scans the document for all nodes that have multi-line string parameters that contain the word "adjustment" 
      (case-insensitive) and prints the node name, type, parameter ID, and value to the console.
      """
      
      import c4d
      import mxutils
      
      doc: c4d.documents.BaseDocument
      
      def main() -> None:
          """
          """
          node: c4d.BaseList2D
          pid: c4d.DescID
      
          # Iterate over all objects and tags in the document that live outside of caches and within the 
          # object branch of the document.
          for node in mxutils.RecurseGraph(doc.GetFirstObject(), yieldBranches=True, yieldHierarchy=True, 
                                           branchFilter=[c4d.Obase, c4d.Tbase]):
              # Iterate over the description, i.e., parameters of the node.
              for data, pid, _ in node.GetDescription(c4d.DESCFLAGS_DESC_NONE):
                  # The first level of this parameter is not of type string or does not use the
                  # multi-line string GUI, so  we skip it.
                  if pid[0].dtype != c4d.DTYPE_STRING or data[c4d.DESC_CUSTOMGUI] != c4d.CUSTOMGUI_STRINGMULTI:
                      continue
                  
                  # Try to read the value, the exception block is needed as not all parameter types are 
                  # accessible in Python (and this could be some kind of exotic multi level string data type).
                  try:
                      value: str | None = str(node[pid]).strip()
                      if not isinstance(value, str) or not value:
                          continue
                  except Exception:
                      continue
      
                  # Check if the value contains the word "adjustment" (case-insensitive), if not, skip it.
                  if "adjustment" not in value.lower():
                      continue
                  
                  # Print the match.
                  print ("\n\n" + ("=" * 100))
                  print (f"Node: {node.GetName()} ({node.GetTypeName()}) | Attribute: {pid}\n\nValue:\n\n{value}")
      
                  # For good measure, select the node and break the loop.
                  (doc.SetActiveObject(node, c4d.SELECTION_NEW) 
                   if isinstance(node, c4d.BaseObject) else 
                   doc.SetActiveTag(node, c4d.SELECTION_NEW))
                  break
              c4d.EventAdd()
      
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: TimeLine DopeSheet not update

      Hey, good news, this is fixed in the upcoming hotfix (2026.3.2).

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: Programmatically created Redshift Lights in an Object Plugin converts lights into Null-Objekts without an function after making Objekt-Plugin editable

      Hey @ThomasB,

      so, I had a look and figured it out. When you diff the two objects from my file, there are quite few differences (as I was not 100% faithful in copying all values), but the culprit is this:
      cef8fb22-88d1-4073-8b29-1e2958c0db14-image.png

      The RS Light uses a standard light to accomplish its VP representation. When you disable the 'Illumination' checkmark, the RS Light GetVirtualObjects will simply return a null object, i.e., the behavior you experienced. I filed a ticket for this (ITEM#651586 Inconsistent Redshift Light 'Make Editable' behavior), as I would consider this so unintuitive that it borders on a bug. But this is outside of the SDK/Cinema 4D domain, so I cannot just fix it myself.

      In Python, there is currently no way to fix this on your side. The message MSG_CURRENTSTATE_END is not wrapped for Python (with it you can manipulate the output of a "Make Editable" action before it is inserted). The other route, the build flags on HierarchyHelp are also not possible in Python either, because HierarchyHelp is also not wrapped for Python.

      But I just changed the latter route, and in the next major update you will be able to react to build flags. There will also be a new code example named py-objectdata_buildflags_2027 which showcases this feature. You can then react to your object being made editable, built for rendering, built for export, etc. and react by changing its output.

      In your case you would then probably not disable the illumination when your object is being made editable. So, for now, you either have to live with the behavior, or turn on "preview > illumination".

      Cheers,
      Ferdinand

      Result

      The output, i.e., diff, with the offending line highlighted.
      d0a8e14c-d913-4990-9ec5-29e4a5150cf3-image.png

      Code

      This assumes my scene from above with everything deleted but the two objects.

      import c4d
      import mxutils
      import difflib
      
      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.
          """
          data: list[str] = [
              mxutils.GetParameterTreeString(node) 
              for node in mxutils.IterateTree(doc.GetFirstObject(), True)
          ]
      
          # Diff the first two strings in the list and print the result to the console.
          if len(data) < 2:
              return c4d.gui.MessageDialog("Not enough data to perform diff.")
          
          a: str = data[0]
          b: str = data[1]
      
          diff: list[str] = list(difflib.unified_diff(a.splitlines(), b.splitlines(), lineterm=''))
          if not diff:
              print("No differences found.")
          
          print("\n".join(diff))
          print("\n\n" + "=" * 80 + "\n\n")
          print(a)
          print("\n" + "-" * 80 + "\n")
          print(b)
          
          c4d.EventAdd()
          
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Programmatically created Redshift Lights in an Object Plugin converts lights into Null-Objekts without an function after making Objekt-Plugin editable

      Thanks, I will have a look. This all is starting to make sense now. We rewrote the RS Light and Camera objects a while ago. And at least the RS Camera object is not absolutely non-trivial to operate. This either sounds like that you have to jump through specific hoops or there is a bug in the importer code, that transforms pre 2024 Light and Camera objects (I think this was when we revamped them).

      It might take me a week or so to figure this out. When this is very urgent for you, I would recommend fixing the scene(s) by manually recreating the lights. You could probably also automate this by just selectively copying data (which is likely going to be the solution I am going to provide, unless I come to the conclusion that this must be fixed on our side, as for example the importer being faulty).

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Programmatically created Redshift Lights in an Object Plugin converts lights into Null-Objekts without an function after making Objekt-Plugin editable

      Hey @ThomasB,

      I do not think that this is an API issue. I do not meant with that that I will not help you, as this still happens in the context of a plugin, I say this simply to classify the issue.

      When I try to manually open your build_1.c4d file, it loads in 2026.2.0 but when I then press C on that light in question, I get exactly the output you report. And I cannot really explain why as this is just a normal rectangle shape area light (well, I sort of can, see below 😄 ).

      26b330be-2ffb-4079-94bd-e35821b41c29-image.png

      The plot however thickens when I try to open the file with 2026.3.1:

      3f42e837-826c-4145-87ae-9436d31c51f2-image.png

      This file seems to be somehow corrupted. After some poking around I manged to create this build_2.c4d which loads in 2026.3. But the light still did not work correctly. I then created a new RS light instance and copied over the whole data container via Get/SetData, which again resulted in a broken light. Only when I manually recreated the light by manually copying values, I ended up with a valid light. Which then also revealed that this light should have a much different light representation in the VP.

      Your light:

      8bb73cb1-8cf6-4851-9001-83dff031795e-image.png

      The manual copy:

      31f028b3-6054-4078-b9c0-82f04b49b7a9-image.png

      I.e., you seem to have there a fundamentally corrupted scene and especially light objects. The fact that I could reproduce this with a fresh light instance and a Get/SetData copy indicates this the data container of the light is corrupted. We could now start comparing the data containers of your light and the manually created one, to dive deeper, but I think I stop here for now. Did you programmatically modify this scene? Because your plugin only seems to load it. The question is now if you have more scenes like this. Otherwise I would just use my fixed scene (which also contains the fixed light) and move one.

      Cheers,
      Ferdinand

      PS: Okay, now I see it, the actual scene in much more complex.

      When you want to dig deeper yourself, a great tool to debug this would be mxutils.GetContainerTreeString. You can just dump both containers and then either visually compare them or use a diff tool. Or you use mxutils.GetParameterTreeString directly on the light objects.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Sub materials links and undo

      @DronKozy

      Yeah, I got the general direction, that you are probably implementing a material mixer type of material/shader. When you implement a full render engine binding, you should go the event notification route, as I am sure you can then easily handle the (slight) complexity that comes with them.

      Regarding the architecture, in principle you can do with whatever you want. As Maxon employee I would of course recommend to use our Nodes API (what you call 'native'). The SDK contains both code examples and documentation about this. But this biggest flaw of the Nodes API is probably that it is not entirely non-trivial. But implementing a full node editor also requires a relatively high level of expertise about our APIs, although in different areas.

      Currently Redshift, Vray, Arnold, and CentiLeo use the Nodes API and all other render engines (Corona, Octane, Cycles, etc.) use either a completely custom system or the old Xpresso Nodes API.

      Cheers,
      Ferdinand

      PS: When you are developing a render engine binding, you might want to consider our Maxon Registered Developer Program.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Programmatically created Redshift Lights in an Object Plugin converts lights into Null-Objekts without an function after making Objekt-Plugin editable

      Hello @ThomasB,

      I am sorry that you had a bad experience in the RS forum. Can you show me the actual code where you add the RS light object in code? Because I see no Orslight in your snippet? Or when you load an asset, share that asset? When I do a quick test with a Python generator object, this works fine for me.

      Cheers,
      Ferdinand

      PS: The plugin link you shared only contains an encrypted plugin (pypv) which not only makes it harder for me to get to the source code, I also cannot run such plugin on company hardware before I cracked it and can reasonably say that it does not contain dangerous code. Could you please share an unencrypted version?

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Sub materials links and undo

      Hello @DronKozy,

      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

      I am afraid there is no really good way to do what you want to do. Without example code, I also have to somewhat guess what is going on. As lined out in our support procedures, we recommend to always accompany questions with compileable code.

      I it is a bit fuzzy to me what you are doing, please tell me when I got it wrong. You have a BaseMaterial implementation which itself has LINK parameters which link to other materials. You now want to update MATPREVIEW parameter manually when your dependencies update.

      Tracking Changes

      Cinema generally follows an opt-out and not an opt-in update/message model. E.g., all objects get their GetVirtualObjects method called on each scene update, and it is then up to the object to decide if they actually want to update or just keep using their existing cache.

      The evaluation of materials happens at render time, so there is no direct function which is called or message which is sent to your plugin when something in the scene is being updated. The messages you are using somewhat go into that direction, but you misunderstood a bit their purpose. MSG_UPDATE is sent when an external entity wants to inform a node that it modified dependent data and that the node should update its internal data (outside of the general scene state rebuild). It is not a message that is being sent each time the node has been updated. I could for example get a point object, modify its points, and then not send that message and that would be totally valid code. The object would only update once Cinema 4D reevaluates the object on its own, but sometimes this is desirable. For user interactions, Cinema 4D will usually send this message, but its purpose is still a bit different from what you think it is. The same goes for MSG_CHANGE.

      The way to track changes in Cinema 4D, is the dirty system found on C4Datom. Cinema will usually automatically entail updates when the dirtiness of something changed, e.g., when a user changed the parameters of a node. The problem is that for LINK parameters (and also shader links), changes to the linked node do not count towards the data dirtiness of the node. Or in other words, Cinema does not consider a node A to be changed when the node B changed to which A links. For something like objects this is not really a problem due to the out-out update model, for material previews this means you can get event starved. The video below demonstrates the effect.

      Well, how do I solve this?

      What you do - a material that links other materials and in some shape or form drives its own material preview updates - is simply not intended. So, we have to get a bit creative.

      Piggybacking on other messages

      Just hook into a message that is being sent all the time. Objects and tags for example receive MSG_GETREALCAMERADATA and MSG_GETCUSTOMICON_SETTINGS each time a scene update ran. But I think you will not get these messages in a BaseMaterial. As I said, there is a good chance that you get event starved in a material. I would put a print statement into you message function, printing out the message ID, and then just start interacting with the scene, adding a cube, changing your linked material. To see if anything sticks to the wall, if there is a message ID you can piggy back on.

      Event Notifications

      ⚠ Event notifications are private for a reason. You can easily crash Cinema 4D with them when misused. The type of event notifications we use here, message notifications, are however relatively harmless.

      An alternative could be cinema::AddEventNotification. With event notifications, you can hook into the message stream of other nodes among other things. So, the idea would be:

      1. In your MyMaterialData::Message, listen for MSG_DESCRIPTION_POSTSETPARAMETER, to catch the moment when the user sets a new linked material (which will count as an update for your material).
      2. Then call myMaterialNode->AddEventNotification(materialThatHasBeenSet, NOTIFY_EVENT::MESSAGE, NOTIFY_EVENT_FLAG::NONE, nullptr);
      3. In your MyMaterialData::Message, listen for MSG_NOTIFY_EVENT and then do something like this:
      if (type == MSG_NOTIFY_EVENT && data)
      {
          const NotifyEventData* const notifyData = (NotifyEventData*)data;
          if (notifyData->eventid == NOTIFY_EVENT::MESSAGE && notifyData->event_data)
          {
              NotifyEventMsg* messageData = (NotifyEventMsg*)notifyData->event_data;
      
              // Now we are basically inside the ::Message function of the linked material #materialThatHasBeenSet. We could 
              // for example react to a parameter change.
              if (messageData->msg_id == MSG_DESCRIPTION_POSTSETPARAMETER)
              {
                  // Do something when the linked material changed
              }
          }
      }
      

      All in all, this is not a trivial problem to solve. When you run into more issues, please provide a minimal reproducible example code, so we can help you better.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: GetWorkplaneMatrix is broken in Cinema 4D 2026.3

      This will be fixed in the next upcoming hotfix.

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: GetWorkplaneMatrix is broken in Cinema 4D 2026.3

      Hey @peter_horvath,

      thanks for the files, I will keep you posted here.

      Cheers,
      Ferdinand

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: UV Peeler - accessible?

      I don't know what your script does and if it needs fixing for 2026.3. What I said is that we usually put a lot of effort into keeping our ABIs and APIs stable, as exemplified with the 2026.3.1 hotfix where we fixed an issue with the UV tag interface that made it incompatible with old code.

      2026.3 contained the UV manager overhaul as its main feature. We tried not to break any glass there but I cannot rule it out. when you find a regression in our APIs, you can also always report it. when the break was unintentional we usually provide a fix (in critical cases even a hotfix) or at least a workaround.

      We also usually do not remove systems. For example, the old content system and its "Content Browser" is still in our APIs and Cinema, although it long has been replaced by the Asset Browser and API. Can I guarantee that the UVPeeler will not be removed soon? No, we sometimes have to remove things. But it is quite rare.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: GetWorkplaneMatrix is broken in Cinema 4D 2026.3

      Hey Peter,

      both your uploads failed.

      Cheers,
      Ferdinand

      posted in Bugs
      ferdinandF
      ferdinand