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.
    • M
      Mats
      last edited by

      Hi.

      I am trying to make a little script that will parent constraint first selected object to second selected object (only selecting 2 objects)

      The code so far is this:

      import c4d
      from c4d import gui
      
      objList = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER | c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
      
      
      tag = c4d.BaseTag(c4d.Tcaconstraint)
      objList[0].InsertTag(tag)
      tag()[c4d.ID_CA_CONSTRAINT_TAG_PARENT] = True
      tag[30001] = objList[1]
      c4d.EventAdd()
      

      The issue however is a slight offsett in the position of the child object when running this script.
      I tested doing it manualy - apply tag, activate parent, select object as target. When doing it manualy i get no offsett on the objects they all stay in palce as intended. BUT!
      The local offsett get populated with values. While when running the script these values instead of getting applied in the Local offset of the tag, it gets populated in the freeze transform in the coordinates on the object.....

      So the question is, am i missing something in this script to prevent it from nuddging?

      • Cinema 4D 2023
      • Windows 10 64x
      1 Reply Last reply Reply Quote 0
      • 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