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

    Accessing the Mixamo Control Rig auto-adjustment logic

    Scheduled Pinned Locked Moved Cinema 4D SDK
    pythonwindows2025
    2 Posts 2 Posters 47 Views 1 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • L Offline
      leah.hayes
      last edited by

      Hello Maxon ,

      I am currently studying and developing a Python plugin for Cinema 4D that improves character animation workflows around the Character Object.

      While researching, I noticed that the Mixamo Control Rig template performs an automatic adjustment: when a Mixamo skeleton is in the scene and I switch the Character Object to the Adjust tab, all components snap precisely onto the Mixamo joints. When this happens, the Python console prints:

      <c4d.BaseTag object called Annotation/Annotation with ID 1030659 ...>
      Auto-Adjustment Complete

      I would like to achieve the same automatic snapping behavior for the Advanced Biped template. My questions:

      1. Where is the Python code of the built-in character templates (Mixamo Control Rig, Advanced Biped) stored? I could not find template .c4d files in the installation folder or in the preferences library/character folder.
      2. 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?
      3. Is there a public API entry point to trigger or implement this kind of auto-adjustment for other templates?

      I am using Cinema 4D 2025.3.1 on Windows. Any pointers to documentation, Cineversity material, or the right developer forum thread would be greatly appreciated.
      #Character #Rig
      Thank you for your time,

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF Offline
        ferdinand @leah.hayes
        last edited by ferdinand

        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()
        

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 0
        • First post
          Last post