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.
    • ?
      A Former User
      last edited by A Former User

      Hello,
      I'd like to drive an object's transformation attribute by another object's transformations, regardless of where they are in the hierarchy. The driven object would follow the driver object if it was a parent, child, or sibling. There are a few caveats (I'm using position as an example, but this could also include individual dimensions of rotation & scale):

      • My project requires that I do this in code (so I cannot use Constraints).
      • I have to start from the Driver's local coordinates
      • I do not want to transform the entire matrix - just one attribute at a time (so something like driven.SetMg(driver.GetMg()) wouldn't work).
      • I have to maintain the difference in position between the two objects.

      I have read the Matrix Fundamentals article, searched this forum, documentation, & online, and tried many things but I haven't found a working solution.

      In this example Python Tag code, the Driven object is jumping between two positions as if there is a loop. I thought this was because the driven.GetMg() used to apply a Matrix to the driver value needed to be a static value, but it snaps to the wrong position and double transforms when I use one in its place (myMat, below):

      import c4d
      
      #myMat = c4d.Matrix()
      #myMat.off = c4d.Vector(500,0,0) #have tried this instead of driven.GetMg()
      
      def main():
          driver = doc.SearchObject("Driver")
          driven = doc.SearchObject("Driven")
          posX = driver[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_X] #starting with local pos X
          parentMat = driver.GetUpMg() #get Parent's matrix
          newPosVec = c4d.Vector(posX,0,0) #creating a Vector for the next step
          newPosVec= newPosVec * parentMat #applying Parent Matrix to get driver's global X
          newPosVec = newPosVec * ~driven.GetMg()
          print "newPosVec.x: %s"%(newPosVec.x)
          driven[c4d.ID_BASEOBJECT_ABS_POSITION,c4d.VECTOR_X] = newPosVec.x
      

      It is giving me a lot of trouble.

      How can I drive an object in one dimension with another object's local coordinates, maintaining the objects' offsets, and regardless of hierarchy?

      Thank you.

      1 Reply Last reply Reply Quote 0
      • 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