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,281
    • Groups 2

    Posts

    Recent Best Controversial
    • RE: Alembic Export to Unreal - RootUVs?

      Hey @mp-grafix,

      I am not really an expert on this subject, but the Alembic format does not provide a "root uv" concept as you somewhat imply. There are zero hits for these terms in the Alembic docs. groom_root_uv is a custom Alembic attribute that is supported (and defined) by Unreal (link). This link also explains how to compute this a bit ambiguous term. One might think a root uv would be the uv coordinate on some scalp mesh, but it is actually just the spherical projection of a hair root (that is at least how it is explained in the Unreal link). Unreal will also autogenerate this attribute for your when it does not exist.

      The UV computation of our hair library can be found in c4d.modules.hair.HairLibrary.GetPolyPointST but this is per polygon. You can look at this example for how to use it on a whole mesh. But as far as I understand the Unreal docs, that is not what Unreal wants from you.

      The official Alembic repo also contains the Python bindings, PyAlembic, with them you could add any metadata you want after Cinema exported the file. Hurdle number one is that the Alembic repo does not seem to provide binaries, i.e., they expect you to compile this yourself, which might be a hurdle to high for some users. Hurdle number two is to match the exact format Unreal expects. That is best solved in Unreal forums.

      I am not very much into Unreal, but does our Cineware Unreal binding not solve this? I would say you have much better chances to be heard there, rather than us conforming with our Alembic export to the very specific custom attribute format Unreal expects.

      Cheers,
      Ferdinand

      edit: Our Cineware for Unreal developer is currently on vaction until the middle of August. When you bump this topic then again, I am happy to ask him what his take on this is.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Cache Proxy Tag

      I am afraid there is not really good way to do this. You could expose the tags as BaseLink's in the parameters of your object, so that users can drag them, but that is also clunky and a also a bit 'yikes' to expose the cache internals like that.

      Unless the object and selections are very complex, I would simply go by creating the selections in the cache, and then either documenting them in the docs, using short names such as C1 and C2 for cap one and cap two, or alternatively display a little static text at bottom of your node in the attribute manager which lists these names. This also worked a long time for Cinema itself like this, before we got seven years ago or so the proxy concept.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Cache Proxy Tag

      Hey @Dunhou,

      No, you cannot create such tags from the public C++ or Python API. You can resolve existing tags via MSG_GETREALTAGDATA to their real underlying tag, but you cannot create new ones. At least we do not expose the internal interfaces we use for this.

      You can print the data container of a proxy tag, and will find something like this:

      Root (None , id = -1):
      ├── 2003 (DTYPE_REAL): 0.5
      ├── 2002 (DTYPE_LONG): 0
      ├── 2001 (CUSTOMDATATYPE_FIELDLIST): <c4d.FieldList object at 0x000002B6CF99C8C0>
      ├── 1011 (DTYPE_LONG): 0
      ├── 1000 (DTYPE_LONG): 5673
      ├── 1050449 (DTYPE_SUBCONTAINER , id = -1):
      │   ├── 1 (DTYPE_LONG): 4001
      │   └── 3 (DTYPE_LONG): 4101
      └── 1041671 (DTYPE_VECTOR): Vector(1, 0.9, 0.4)
      

      Where 1050449 is the ID for Tcacheproxytag and this sub-container then contains two fields which represent these two IDs:

      e50d1391-9aae-4765-adfd-9792028c8859-image.png

      We seem to have removed the old BaseLink approach we used before. And the code for resolving the 'name' (which as you can see is actually DTYPE_LONG) is not super trivial. If I would pull out this information for you and others, I would also have to maintain it, which you can see by the deprecation of the base-link, would be ongoing work. When you can make it work with the hinted at information, go for it. But this is a private interface for now, and I cannot reveal its details.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Adding an Icon to a GeDialog Menu Group

      Hey @mogh,

      It depends a bit on what you mean with 'this'. Loading menu resources still does not seem to work in Python. I just tried, and while loading for example the main menu resource in your plugin just works fine, I cannot make it work with a locally defined plugin resource. Below you can see me loading M_EDITOR, i.e., the main menu resource, into the py-cmd_gui_resources_2024 example.

      9f49bdf7-33b1-4ba7-9114-52365c51e3f7-image.png

      That is probably what I was referring to a bit fuzzily with 'I am sorry to inform you, that this is currently not possible. Because the procedures used by Cinema internally cannot be reflected to Python at the moment.' six years ago. I would have to sit down and debug this piece by piece to see what is going wrong with loading locally defined menu resources (or if I just made a mistake in the resource files). Resources in Cinema 4D are a bit different from plugin resources, and menu resources have never been publicly documented or demonstrated, neither in C++ nor in Python. So, this being broken is not impossible.

      What you can do right now, is define a string menu item with an icon. But you will not be able to use this to create submenus with an icon as the original question was about. You can only create a string menu item with an icon, but not a submenu with an icon.

      Cheers,
      Ferdinand

      548c045e-dde1-40f1-8507-aaf26363a986-image.png

      
      import c4d
      
      class IconMenuDialog(c4d.gui.GeDialog):
          """
          """
          def CreateLayout(self) -> bool:
              """Called by Cinema 4D when the GUI of the dialog is being built.
              """
              self.GroupBorderSpace(5, 5, 5, 5)
      
              # Add a menu called "Items".
              self.MenuSubBegin("Items")
              # And a submenu called "Objects".
              self.MenuSubBegin("Objects")
              # Here we use string menu items with the &i icon embed code. This only works for items and
              # not for submenus, therefore resources are currently the only way to define icons for submenus.
              self.MenuAddString(1000, f"Item 1&i{c4d.Ocube}&")
              self.MenuAddString(1001, f"Item 2&i{c4d.Osphere}&")
              self.MenuSubEnd()
      
              # Same thing, but here we use commands instead of string menu items. This will automatically 
              # add the icon and label of the command to the menu item. When invoked, this will execute also 
              # the command without us having to implement this ourself.
              self.MenuSubBegin("Splines")
              self.MenuAddCommand(c4d.Osplinecircle)
              self.MenuAddCommand(c4d.Osplinerectangle)
              self.MenuSubEnd()
      
              self.MenuSubEnd()
             
              return True
      
      if __name__ == "__main__":
          dialog: IconMenuDialog = IconMenuDialog()
          dialog.Open(c4d.DLG_TYPE_ASYNC, defaultw=200, defaulth=100)
      
      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,

      my bad, I did miss that. The reason why you are still having problems is that in the scene data you load from your asset file, has generator and scene building flags set on its nodes that are visible in the object manager. When you then put them into the cache of your object, this leads to buggy/unintended behavior, as these flags should not appear in caches.

      Cheers,
      Ferdinand

      Result

      The initial state:
      7e7b61ce-1255-487b-a969-27469b392a5c-image.png

      Collapsing your generator, it now correctly preserves the nested generators:
      1c9f0097-2ee8-4fad-a0d5-744c91588140-image.png

      Collapsing once more the RS Light:
      c6c4e92c-81fe-4a64-935b-a7be307e9a8c-image.png

      Code

      This builds on the cleaned up code example I posted above. But effectively you only need ResetFlags and call it on your cache before you return it. But you should also remove the reference (self.container) to your own cache in your code example from your code, as this could cause some serious issues.

          def ResetFlags(self, node: c4d.BaseObject) -> None:
              """Resets selected flags in the local hierarchy of the given node.
              """
              def ResetFlags(node: c4d.BaseObject) -> None:
                  node.DelBit(c4d.BIT_CONTROLOBJECT)
                  node.DelBit(c4d.BIT_EDITOBJECT)
      
              ResetFlags(node)
              for child in mxutils.IterateTree(node, True):
                  ResetFlags(child)
      
              return node
      
          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:
                  raise c4d.BaseObject(c4d.Onull)
              
              payload: c4d.BaseObject | None = self.GetAssetPayload()
              if payload:
                  payload.InsertUnder(null)
      
              #  --- DEBUG ---
      
              # Some debug code I wrote to figure out what is going on here. I then quickly figured out 
              # that the parameters were okay, but that this light always malfunctioned. And that returning 
              # a fresh light worked fine. So, the culprit must be outside of the data container of the 
              # light but still inside the light object. I.e., BIT or NBIT flags, which then directly 
              # made me think of the BIT_CONTROLOBJECT and BIT_EDITOBJECT flags.
              #
              # WARNING: The code below is highly illegal code, as we insert objects into the active 
              # document from a non-main thread. This is just for debugging purposes, as I did not mind 
              # Cinema potentially crashing, and should never be done in production code.
              #
              # for child in mxutils.IterateTree(null, True):
              #     if child.GetType() == c4d.Orslight:
              #         print("-" * 100)
              #         print(mxutils.GetParameterTreeString(child))
              #         doc: c4d.documents.BaseDocument | None = c4d.documents.GetActiveDocument()
              #         if doc:
              #             doc.InsertObject(clone)
              #             c4d.EventAdd()
      
              #  --- FIX ---
      
              # Now we reset the build flags of all objects in the hierarchy. Not doing this caused 'Make 
              # Editable' to incorrectly collapse the cache of this object, going much deeper than it 
              # should. When you have this setup, i.e., a generator within the cache of another generator:
              #
              #   SomeGenerator (Obase)
              #   └── Cache
              #       └── Null (Onull)
              #           └── CubeGenerator (Obase) // Generator that is inside the cache of SomeGenerator
              #               └── Cache
              #                   └── Null (Onull)
              #                       └── PolygonObject (Opolygon)
              #
              # then the collapsed cache of SomeGenerator should be (important is the type in parentheses, 
              # not the name in front) as follows:
              #
              #   SomeGenerator (Onull)
              #   └── CubeGenerator (Obase)         // the uncollapsed CubeGenerator generator which was 
              #                                     // before inside the cache of SomeGenerator
              #
              # And not this.
              #
              #   SomeGenerator (Onull)
              #   └── CubeGenerator (Onull)
              #       └── PolygonObject (Opolygon)
              #
              # But the former is exactly what happened when we do not reset the build flags of all objects 
              # in the hierarchy. On top of that comes some bug / side effect of when you do this nested
              # collapsing that it seems to 'forget' half of the cache, 'PolygonObject (Opolygon)' in the
              # example above, or the converted standard light from the RS light in your case.
              self.ResetFlags(null)
      
              return self.ResetFlags(null)
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Problem with Undo when using COLOR field in a description resource

      I tried with a Cube where the only way to change colour is to use the display colour in the basic tab. At first sight this doesn't cause the problem, but in fact it does.

      Yes, that was what I meant. Thanks for confirming. The issue likely already has vanished in the current alpha, because there I could not reproduce it. But in 2026.3.2 I can. What is a bit odd is that no one seems to have touched the relevant code in the last months. But that is something for the GUI team to figure out.

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: Custom node-locked licensing: Bypassing checks for Team Renderer, Clients, Server safely?

      Hey @ThomasB,

      while I understand the direction, your question is a bit ambiguous. What would you consider 'reliable and secure against spoofing'? This very much a question of perspective. Everything can be faked and altered with enough knowledge and determination when the data is local. The functions you mention are based on our licensing API (and are also a bit outdated, you should use the newer endpoints). This API is used by Maxon products itself to verify licenses and is considered secure enough for that purpose. However, nothing prevents a user from patching the Cinema binary so that these functions return whatever the user wants. But that is of course expert knowledge domain.

      I would recommend using the relative new Licensing Manual which also explains how to identify the current product and how to find out how many licenses of a given type are available in a user account. In the video I also touch the subject of hardening for plugin licenses, the TLDR is: Do not over-do it, it is not worth the effort. Everything can be cracked.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • 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