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
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Getting an Object to Follow Another Regardless of Hierarchy

    Cinema 4D SDK
    sdk python
    4
    12
    1.4k
    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.
    • ManuelM
      Manuel
      last edited by Manuel

      hi,

      The offset is a vector between the two objects. This offset can be calculated by subtracting the two matrix of the objects and retrieve the offset argument of the Matrix.
      You have to store this vector as a local's coordinates of the target. (target's inverse matrix * vector)
      Now you have that, you just have to pick your object's matrix and set the offset parameter to your stored offset.
      If you only want to update the X axis, just write the X axis it's simple, just update matrix.off.x

      In the tag i've added a button and the target's field. This could go with something like this.

      import c4d
      #Welcome to the world of Python
      
      storedData = 123456 # baseContainer
      offsetInitializedID = 1000 # Bool
      offsetID = 1001 #Vector
      matrixLockID = 1002 #Matrix
      
      
      
      
      def WriteOffset(obj, vec, init = True):
          # write the offset in the BaseContainer
          global storedData
          data = obj.GetDataInstance()
          if data is None:
              return False
          bc = data.GetContainer(storedData)
          if bc is None:
              bc = c4d.BaseContainer()
          bc.SetBool(offsetInitializedID, init)
          bc.SetVector(offsetID, vec)
          bc.SetMatrix(matrixLockID,obj.GetMg())
          data.SetContainer(storedData, bc)
          return True
      
      
      def ReadOffset(obj):
          # Read the offset from the BaseContainer
          global storedData
          data = obj.GetDataInstance()
          if data is None:
              return False, c4d.Vector(0), c4d.Matrix()
          bc = data.GetContainer(storedData)
          return  bc.GetBool(offsetInitializedID), bc.GetVector(offsetID), bc.GetMatrix(matrixLockID)
      
      
      def message(id, data):
          if id == c4d.MSG_DESCRIPTION_COMMAND:
              if data['id'].GetDepth() > 1 and  data['id'][1].id == 3:
                  # get the target's matrix
                  target = op[c4d.ID_USERDATA,1]
                  obj = op.GetObject()
                  if target is None or obj is None:
                      return
                  #Get the object's matrix'
                  tmg = target.GetMg()
                  objmg = obj.GetMg()
                  diffMatrix = objmg -  tmg
                  offset = diffMatrix.off
                  if not WriteOffset(obj, offset):
                      raise ValueError ("couldn't write value in the basecontaier")
      
      
      def main():
          # Retrieve the object
      
          # option 0 all param
          # option 1 only rotation
          # option 2 only offset
          # option 3 only X axis
          # optino 4 even if B is child of A only X
          option  = 4
      
          obj = op.GetObject()
          objMg = obj.GetMg()
          offsetActive, offset, oldMatrix  =  ReadOffset(obj)
          
          if offsetActive:
              target = op[c4d.ID_USERDATA,1]
              tmg = target.GetMg()
      
              if option  == 0:
                  # Get the global coordinate of the offset.
                  tmg.off = tmg * offset
      
              elif option == 1:
                  # Only add the offset to the target's matrix
                  tmg.off = objMg.off
      
              elif option == 2:
                  # Build the new matrix based on the object's matrix and the global coordinate of the offset
                  objMg.off = tmg * offset
                  tmg = objMg
      
              elif option == 3:
                  objMg.off = c4d.Vector(tmg.off.x +  offset.x, objMg.off.y, objMg.off.z)
                  tmg = objMg
                  
              elif option == 4:
                  oldMatrix.off = c4d.Vector(tmg.off.x +  offset.x, oldMatrix.off.y, oldMatrix.off.z)
                  tmg = oldMatrix
      
              # remove the scale by normalizing the
              tmg.Normalize()
              obj.SetMg(tmg)
      
      
      
               
      

      Cheers,
      Manuel

      MAXON SDK Specialist

      MAXON Registered Developer

      ? 1 Reply Last reply Reply Quote 0
      • ?
        A Former User @Manuel
        last edited by A Former User

        @m_magalhaes Manuel, I am SO grateful for your help, thank you. 🌻 I've spent a lot of time so far and was feeling very frustrated. Thank you!

        I have a couple of questions about your code if I may:

        1. As mentioned in the original post, I'm starting with a single, local space dimension value. I try and change tmg's position X value then change it to global space, as in the example below, (posX). This code works when the objects are siblings or children of other objects, but when the Object is parented to the Target, the Object rotates and scales (even with the Option set to 2). Can you please teach me how to change a single value like this that will work anywhere in the hierarchy in your code?
                posX = curve.GetValue(newTime, fps) # 'curve' is the CCurve for the relative Pos.x CTrack
                tmg = target.GetMg()
                vec = c4d.Vector(posX, tmg.off.y, tmg.off.z)
                parentMat = target.GetUpMg() #get Parent's matrix
                tmg.off = vec * parentMat #convert position vector from Curve to Global Matrix
        

        Scenario.png

        1. # option 1 only rotation
          This didn't only affect rotation when I set the option to 1. The position & scale were also affected. The code is adding the offset with tmg.off += offset[1], but would adding the target's rotation involve MatrixToHPB?

        Thanks so much, again!

        1 Reply Last reply Reply Quote 0
        • ManuelM
          Manuel
          last edited by Manuel

          hi,

          I've modified my code a bit, just to remove that bug and clean it a bit. I've also added the posibility to "freeze" the object except the X axis even if B is a child of A (and you are moving A)

          The scale is store in the vectors of the matrix itsellf (v1, v2, v3) the length of the vector is the scale in that particular axis.

          Have a look at matrix fundamentals

          so if you want to set the scale back to 1, you have to just set all the vector lenght to 1. In other words, you have to normalized those vector, or in a matrix you just normalize the matrix itself.

          If you want to retrieve or keep the scale, you can retrieve the scale of on object's matrix with GetScale and apply with Scale (same link)

          Don't over think how matrix works, just focus on how you can use them.

          .off is the displacement information
          .v1 is the x axis information about the rotation (where the vector is pointing to) and the scale (how long the vector is, it's magnitude)
          .v2 is the y axis
          .v3 is the z axis.

          the identity matrix (no displacement, no rotation, scale 1) can be created with c4d.Matrix()
          With that if you only want to add the displacement information just add your information in .off
          etc etc.

          Just be careful that the axis v1, v2, v3 must be orthogonal

          Cheers,
          Manuel

          MAXON SDK Specialist

          MAXON Registered Developer

          ? 1 Reply Last reply Reply Quote 0
          • ?
            A Former User @Manuel
            last edited by A Former User

            @m_magalhaes Hi Manuel, thank you, again, for your help with the code and further explanations.

            I've been reading Matrix Fundamentals a lot during this process and still have a lot to learn.

            I tried out the modified code and it works for position & rotation, but discovered some unexpected shearing behavior when animating the Target's scale with Option 4 when the objects are parent/child.
            hpIk7nE.gif
            Project File

            Am I doing something wrong or is this a bug?

            Thank you.

            1 Reply Last reply Reply Quote 0
            • ManuelM
              Manuel
              last edited by

              hi,

              i finally asked the dev if they got a better idea than myself.

              It's reproducible with our constrain tag, that's the combinaison of rotation and scale of the parent that apply to the child.

              I'm afraid it's a bit of a limitation in our system and this particular case.

              Let's see.

              Cheers,
              Manuel

              MAXON SDK Specialist

              MAXON Registered Developer

              ? 2 Replies Last reply Reply Quote 0
              • ?
                A Former User @Manuel
                last edited by A Former User

                @m_magalhaes Hi Manuel, it feels like your idea is very close. Thank you for taking the extra steps.

                1 Reply Last reply Reply Quote 0
                • CairynC
                  Cairyn
                  last edited by

                  Sorry for barging in, but as the same Q was asked on two more forums, I stumbled over it.

                  I couldn't find the actual error in the code, but after reducing the whole thing to the crucial lines, I am fairly convinced that it is a bug. I created a completely new scene (R21) to exclude any strangenesses from the original file, and it's still easily reproductible:

                  Shear.jpg

                  The script here is very simple:

                  import c4d
                  
                  def main():
                      obj = op.GetObject()
                      parent = obj.GetUp()
                      parentMat = parent.GetMg()
                      obj.SetMl(~parentMat)
                  

                  It does nothing but assign the child the inverted parent matrix as local matrix. Logically, this should make the child stay constant and in place, regardless of whatever changes you perform to the parent.

                  It works very well as long as you only rotate or translate the parent; as soon as you add some scaling, you get the result above. The child's matrix starts shearing, and the coordinate system is no longer orthogonal (which isn't even supported by C4D natively).

                  I can only conclude that the matrix inversion has a bug and is not handling scaled matrices correctly (as strange as it sounds - such a basic error should show up in every second scene...).

                  1 Reply Last reply Reply Quote 0
                  • a_blockA
                    a_block
                    last edited by

                    I have been following this topic in all three (!) forums since beginning and I'm really looking forward to the outcome of this topic.

                    I'd call myself quite a Matrix mutant, so just ignore me, if my question is completely off topic or bogus in this context. My expectation would have been the same as @Cairyn's.
                    Yet, there has always been this last sentence on the Matrix fundamentals page, which reads pretty suspicious to me.

                    [Local Matrix] = MatrixMove(op.GetFrozenPos()) * HPBToMatrix(op.GetFrozenRot()) * MatrixMove(op.GetRelPos()) * HPBToMatrix(op.GetRelRot()) * MatrixScale(op.GetFrozenScale) * MatrixScale(op.GetRelScale())

                    Please note that the both scales are applied at the very beginning, so the local matrix is not the same as [Frozen Matrix] * [Relative Matrix]. This is necessary in order to guarantee that your local matrix always stays a rectangular system; distorted sy

                    I'm not questioning the correctness of this sentence. I'm just wondering if it may be related to this issue? And if one would maybe need to separate the scale from the equation and calculate it in correct order?

                    1 Reply Last reply Reply Quote 0
                    • ?
                      A Former User @Manuel
                      last edited by

                      @m_magalhaes Hi Manuel, I just wanted to check and see if the developers had any update on this issue? Thank you!

                      1 Reply Last reply Reply Quote 0
                      • ManuelM
                        Manuel
                        last edited by

                        hi,

                        sorry for the late reply, but other task and vacations came in : /
                        I don't have any solution for that particular case. (that's also why the same happen with our tag)

                        Cheers,
                        Manuel

                        MAXON SDK Specialist

                        MAXON Registered Developer

                        ? 1 Reply Last reply Reply Quote 0
                        • ?
                          A Former User @Manuel
                          last edited by

                          @m_magalhaes That's okay. Thank you for letting me know!

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