Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    C4D Parent constraint python

    Cinema 4D SDK
    python
    4
    14
    2.8k
    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.
    • B
      bentraje
      last edited by bentraje

      @Mats

      Have you tried setting the maintain offset for a safe measure?
      tag[c4d.ID_CA_CONSTRAINT_TAG_PSR_MAINTAIN] = True

      M 1 Reply Last reply Reply Quote 0
      • M
        Mats @bentraje
        last edited by Mats

        @bentraje Good tip, but doesnt work sadly, still offsetting 😞

        Python_Parentconstraint_C.gif

        iplaiI 1 Reply Last reply Reply Quote 0
        • iplaiI
          iplai @Mats
          last edited by

          @mats

          Maybe you should call the Set Inital State button of the tag and set the local offset vector as the target's negative vector.
          Here's the code:

          import c4d
          
          objList = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER | c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
          tag = c4d.BaseTag(c4d.Tcaconstraint)
          obj1, obj2 = objList[:2]
          obj1.InsertTag(tag)
          c4d.CallButton(tag, c4d.ID_CA_CONSTRAINT_TAG_SET_INITIAL_STATE) # SET INITIAL STATE
          tag[c4d.ID_CA_CONSTRAINT_TAG_PARENT] = True
          tag[30009, 1000] = obj2[c4d.ID_BASEOBJECT_REL_POSITION] * -1  # Local Offset P
          tag[30001] = obj2  # Target
          c4d.EventAdd()
          
          

          C4DJSON -- a useful module to load an dump c4d objects in python dict format (similar to standard python json module).
          Examples on Notion.

          M 1 Reply Last reply Reply Quote 1
          • M
            Mats @iplai
            last edited by

            @iplai Thank you.
            That seems to work. I assume c4d is doing this under the hood when we do the parent constraint manualy.
            This is probably something one need to consider regarding other constraint aswell i assume.

            Appreciate the comments in the solution code.

            ferdinandF 2 Replies Last reply Reply Quote 0
            • ferdinandF
              ferdinand @Mats
              last edited by

              Hey @mats,

              Thank you for reaching out to us and please excuse the radio silence from our side. We were quite busy with the 2023.1 release in the last week, but I will have a look at your problem tomorrow.

              Cheers,
              Ferdinand

              MAXON SDK Specialist
              developers.maxon.net

              1 Reply Last reply Reply Quote 0
              • ferdinandF
                ferdinand @Mats
                last edited by ferdinand

                Hey @mats,

                I ran your code, and I cannot reproduce your problem. The line tag( [c4d.ID_CA_CONSTRAINT_TAG_PARENT] = True is not really wrong but makes not too much sense as you are calling there C4DAtom.__call__, the line should be tag[c4d.ID_CA_CONSTRAINT_TAG_PARENT] = True.

                I also added undo steps to your script, but it was working without them for me too. Maybe I am misunderstanding what you mean with 'When doing it manually, I get no offset on the objects, they all stay in place as intended.', but for me there is no 'offset'.

                I ran the code on 2023.0.0 and 2023.1.0 to the same effect, find my code below. If the problem does persist for you, I will have to ask you to explain what you would consider wrong with the result.

                Cheers,
                Ferdinand

                The result:
                constrain_cubes.gif

                The code:

                """Constraints the two currently selected objects with a parent-child constraint.
                """
                
                import c4d
                import typing
                
                doc: c4d.documents.BaseDocument  # The active document
                op: typing.Optional[c4d.BaseObject]  # The active object, None if unselected
                
                def main() -> None:
                    """
                    """
                    # Get the object selection in order and bail when not exactly two objects are selected.
                    selection: list[c4d.BaseObject] = doc.GetActiveObjects(
                        c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER | c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
                    if len(selection) != 2:
                        raise RuntimeError("Please select exactly two objects.")
                    
                    # Unpack the selection into a parent and child variable, rename the two objects, and add undos
                    # for the operation.
                    parent, child = selection
                    doc.StartUndo()
                    doc.AddUndo(c4d.UNDOTYPE_CHANGE, parent)
                    doc.AddUndo(c4d.UNDOTYPE_CHANGE, child)
                    parent.SetName(f"{parent.GetName()} (Parent)")
                    child.SetName(f"{child.GetName()} (Child)")
                
                    # Create the tag.
                    tag: c4d.BaseTag = child.MakeTag(c4d.Tcaconstraint)
                    if tag is None:
                        raise MemoryError("Could not allocate tag.")
                
                    # Add an undo for the tag being added (must be done after the operation) and add another undo
                    # for the tag being modified (not really necessary here, but good form).
                    doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, tag)
                    doc.AddUndo(c4d.UNDOTYPE_CHANGE, tag)
                
                    # Constraint #child to #parent.
                    tag[c4d.ID_CA_CONSTRAINT_TAG_PARENT] = True
                    tag[30001] = parent
                    doc.EndUndo()
                    c4d.EventAdd()
                
                if __name__ == '__main__':
                    main()
                
                

                MAXON SDK Specialist
                developers.maxon.net

                1 Reply Last reply Reply Quote 0
                • iplaiI
                  iplai
                  last edited by iplai

                  @ferdinand @Mats
                  I compared the two pieces of code and found the key diffence is the lines of creating the Constraint Tag:
                  When you using MakeTag, c4d will do some internal operations I guess:

                  tag: c4d.BaseTag = child.MakeTag(c4d.Tcaconstraint)
                  

                  While using BaseTag constructor, then InsertTag, no internal operations are done:

                  tag = c4d.BaseTag(c4d.Tcaconstraint)
                  child.InsertTag(tag)
                  

                  In my experience, for the most cases of tags, both of them work properly, apart from the above exception.

                  C4DJSON -- a useful module to load an dump c4d objects in python dict format (similar to standard python json module).
                  Examples on Notion.

                  ferdinandF 1 Reply Last reply Reply Quote 0
                  • ferdinandF
                    ferdinand @iplai
                    last edited by ferdinand

                    Hey @iplai,

                    That seems unlikely because all the method does, is free you from the burden of having to insert the tag yourself.
                    Screenshot 2022-11-11 at 09.58.10.png

                    and when I change the relevant portion of my script to this,

                    tag: c4d.BaseTag = c4d.BaseTag(c4d.Tcaconstraint)
                    if tag is None:
                        raise MemoryError("Could not allocate tag.")
                    child.InsertTag(tag)
                    

                    the outcome is still the same. As I said, there is no real substantial difference between my code and the one from @Mats, I just rewrote the script to be cleaner.

                    If anything could cause issues here, then it is the missing context guard of @Mats script. The context guard is not irrelevant, and you must use it, as it prevents the code from being executed outside of the __main__ context. But I do not really see how this should come into play here, since the Script Manager is executing the whole module.

                    Even when I use the code from @Mats, everything is working fine for me.

                    Cheers,
                    Ferdinand

                    MAXON SDK Specialist
                    developers.maxon.net

                    iplaiI 1 Reply Last reply Reply Quote 0
                    • iplaiI
                      iplai @ferdinand
                      last edited by iplai

                      @ferdinand
                      I verified the scene again, the two methods do the same thing indeed. Forgive my mistakes last post.
                      The problem is when the parent is not at original position, after apply the script, the offset arises.
                      The selection order might also affect the result. I'm trying to figure it out.

                      C4DJSON -- a useful module to load an dump c4d objects in python dict format (similar to standard python json module).
                      Examples on Notion.

                      ferdinandF 1 Reply Last reply Reply Quote 0
                      • ferdinandF
                        ferdinand @iplai
                        last edited by ferdinand

                        Hey @iplai,

                        Thank you for your help. Yes, that is indeed it. The constraint tag seems to be writing the inverse offset of the parent global transform into the constraint offset when the parent is set manually.

                        46036ad7-8f70-465f-b517-27b41b88a191-image.png
                        615a79ac-c08a-42e8-a1fa-b089e0bf141f-image.png

                        You can emulate this by adding this line to the script:

                        data: dict[str, c4d.DescID] = {"id": c4d.DescID(c4d.ID_CA_CONSTRAINT_TAG_PARENT_LOCALTRANFORM_UPDATEBUTTON)}
                        tag.Message(c4d.MSG_DESCRIPTION_COMMAND, data)
                        

                        Find the full script below.

                        edit:

                        Cheers,
                        Ferdinand

                        The full script:

                        import c4d
                        import typing
                        
                        doc: c4d.documents.BaseDocument  # The active document
                        op: typing.Optional[c4d.BaseObject]  # The active object, None if unselected
                        
                        def main() -> None:
                            """
                            """
                            # Get the object selection in order and bail when not exactly two objects are selected.
                            selection: list[c4d.BaseObject] = doc.GetActiveObjects(
                                c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER | c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
                            if len(selection) < 2:
                                raise RuntimeError("Please select at least two objects.")
                            
                            # Unpack the selection into a parent and child variable, rename the two objects, and add undos
                            # for the operation.
                            parent, child = selection[:2]
                            doc.StartUndo()
                            doc.AddUndo(c4d.UNDOTYPE_CHANGE, parent)
                            doc.AddUndo(c4d.UNDOTYPE_CHANGE, child)
                            parent.SetName(f"{parent.GetName()} (Parent)")
                            child.SetName(f"{child.GetName()} (Child)")
                        
                            # Create the tag.
                            tag: c4d.BaseTag = child.MakeTag(c4d.Tcaconstraint)
                            if tag is None:
                                raise MemoryError("Could not allocate tag.")
                        
                            # Add an undo for the tag being added (must be done after the operation) and add another undo
                            # for the tag being modified (not really necessary here, but good form).
                            doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, tag)
                            doc.AddUndo(c4d.UNDOTYPE_CHANGE, tag)
                        
                            # Constraint #child to #parent and write the inverse offset of #parent as
                            # the constraint offset.
                            tag[c4d.ID_CA_CONSTRAINT_TAG_PARENT] = True
                            tag[30001] = parent
                            data: dict[str, c4d.DescID] = {"id": c4d.DescID(c4d.ID_CA_CONSTRAINT_TAG_PARENT_LOCALTRANFORM_UPDATEBUTTON)}
                            tag.Message(c4d.MSG_DESCRIPTION_COMMAND, data)
                            
                            doc.EndUndo()
                            c4d.EventAdd()
                        
                        if __name__ == '__main__':
                            main()
                        

                        MAXON SDK Specialist
                        developers.maxon.net

                        iplaiI 1 Reply Last reply Reply Quote 0
                        • iplaiI
                          iplai @ferdinand
                          last edited by iplai

                          @ferdinand
                          The Constrain Tag may not only influence position, but also rotation, scale. I think the set inital state button should be called as mentioned in my first reply:

                          c4d.CallButton(tag, c4d.ID_CA_CONSTRAINT_TAG_SET_INITIAL_STATE) # SET INITIAL STATE
                          
                          tag[30009,1000] = -parent.GetRelPos() #?
                          tag[30009,1001] = -parent.GetRelScale() #?
                          tag[30009,1002] = -parent.GetRelRot() #?
                          

                          C4DJSON -- a useful module to load an dump c4d objects in python dict format (similar to standard python json module).
                          Examples on Notion.

                          ferdinandF 1 Reply Last reply Reply Quote 0
                          • ferdinandF
                            ferdinand @iplai
                            last edited by ferdinand

                            Hey @iplai,

                            to cover the case where either both the parent and child or only one of them is embedded in a hierarchy, i.e., where the local matrix is unequal to the global matrix, or when you generally want to address the other transforms as scale or rotation, and do not want to do the math yourself, it is best to send a description message for ID_CA_CONSTRAINT_TAG_PARENT_LOCALTRANFORM_UPDATEBUTTON to the tag, as this is was effectively triggers the method computing these values. This will then cover all cases:

                            e729e764-f5a5-4479-a76b-84c21868ca51-image.png

                            I have updated my answer above.

                            Cheers,
                            Ferdinand

                            MAXON SDK Specialist
                            developers.maxon.net

                            iplaiI 1 Reply Last reply Reply Quote 0
                            • iplaiI
                              iplai @ferdinand
                              last edited by iplai

                              @ferdinand
                              Thanks, you are the best. This is exactly the internal operation I'm finding.

                              C4DJSON -- a useful module to load an dump c4d objects in python dict format (similar to standard python json module).
                              Examples on Notion.

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