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 @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