Problems of copying position keyframs to another obj



  • There are two box in the scene.I want to copy position keyframs of one box to another. My way of doing that is copy the position to the target box frame by frame.The problem is by using "doc.SetTime" or "SetDocumentTime(doc, time)",although the time is changed ,but the position of the source box remain the value when i execute the script and never update. So the result is that the target box get a lot of same frames.
    Is there anyway to solve the problems??
    test.c4d

    Here's my code,the source is box1 and the target is box2

    import c4d
    smallbox=doc.SearchObject("box1")
    bigbox=doc.SearchObject("box2")
    
    for frame in range(60):
        doc.SetTime(c4d.BaseTime(frame,30))
        bigbox.SetMg(smallbox.GetMg())
        c4d.CallCommand(12410) # record active obj
    
    c4d.EventAdd(c4d.EVENT_ANIMATE)
            
    


  • Hi, @q1w2ertyu first of all welcome in the plugincafe community.

    As a reminder, I would point you to How to Post Questions and Q&A Functionality, I've set up your topic correctly, don't worry since it's the first topic no issue at all but try to do it for the next ones.

    So while your approach to animate the whole document is valid, it's a bit too much for you need and will probably take some time to compute on heavy scenes.

    In Cinema 4D Keyframe are represented as Ckey which is hosted on Ccurve which is hosted on a CTrack.
    All three elements inherit from BaseList2D so that means they also inherit from C4DAtom so that means to have a copy of one of these objects you can directly call GetClone.

    So if you want to copy only the position based on the keyframe of an object you can simply do the next things.

    """
    Copyright: MAXON Computer GmbH
    Author: Manuel Magalhaes
    
    Description:
        - Copies the position, rotation and animation Tracks (so all Keyframes) from obj1 to obj2.
    
    Notes:
        - If obj2 already have some animation data for its position, rotation, and animation these data will be lost.
    
    Class/method highlighted:
        - BaseObject.GetCTracks()
        - CTracks.GetDescriptionID()
        - CTracks.FindCTrack()
        - C4DAtom.GetClone()
        - CTracks.InsertTrackSorted()
        - BaseDocument.AnimateObject()
    
    Compatible:
        - Win / Mac
        - R14, R15, R16, R17, R18, R19, R20
    """
    import c4d
    
    
    def main():
        # Retrieves the object called obj1 from the active document.
        animatedBox = doc.SearchObject("obj1")
        if animatedBox is None:
            raise RuntimeError("Failed to retrieves obj1 in document.")
    
        # Retrieves the object called obj2 from the active document.
        fixedBox = doc.SearchObject("obj2")
        if fixedBox is None:
            raise RuntimeError("Failed to retrieves obj2 in document.")
    
        # Retrieves all the CTrack of obj1. CTracks contains all keyframes information of a parameter.
        tracks = animatedBox.GetCTracks()
        if not tracks:
            raise ValueError("Failed to retrieved animated tracks information for obj1.")
    
        # Defines a list that will contains the ID of parameters we want to copies.
        # Such ID can be found by drag-and-drop a parameter into the python console.
        trackListToCopy = [c4d.ID_BASEOBJECT_POSITION, c4d.ID_BASEOBJECT_ROTATION, c4d.ID_BASEOBJECT_SCALE]
    
        # Start the Undo process.
        doc.StartUndo()
    
        # Iterates overs the CTracks of obj1.
        for track in tracks:
            # Retrieves the full parameter ID (DescID) describing a parameter.
            did = track.GetDescriptionID()
    
            # If the Parameter ID of the current CTracks is not on the trackListToCopy we go to the next one.
            if not did[0].id in trackListToCopy:
                continue
    
            # Find if our static object already got an animation track for this parameter ID.
            foundTrack = fixedBox.FindCTrack(did)
            if foundTrack:
                # Removes the track if found.
                doc.AddUndo(c4d.UNDOTYPE_DELETE, foundTrack)
                foundTrack.Remove()
    
            # Copies the initial CTrack in memory. All CCurve and CKey are kept in this CTrack.
            clone = track.GetClone()
    
            # Inserts the copied CTrack to the static object.
            fixedBox.InsertTrackSorted(clone)
            doc.AddUndo(c4d.UNDOTYPE_NEW, clone)
    
        # Ends the Undo Process.
        doc.EndUndo()
    
        # Updates fixedBox Geometry taking in account previously created keyframes
        animateflag = c4d.ANIMATEFLAGS_NONE if c4d.GetC4DVersion() > 20000 else c4d.ANIMATEFLAGS_0
        doc.AnimateObject(fixedBox, doc.GetTime(), animateflag)
    
        # Updates Cinema 4D with All Changes we made
        c4d.EventAdd()
    
    
    if __name__ == "__main__":
        main()
    

    Finally just in case, in your initial script you need a call to BaseDocument.ExecutePasses to generates the new state of the documentation, this will update the object geometry and move the object taking in consideration, keyframe, dynamics, expression and so on (so very overkill for your need).

    Cheers,
    Maxime.



  • Thank you very much, your answer is detailed and the method is really overkill !

    The reason why I don't use Ckey is that I got a lots of objects each one has its own animation. And these objects are in a hierachy, which means the motion of the object at the bottom of the hierachy is different from its reletive motion. I want to copy the ABS motion of the bottom object to another object.

    And I have never found a DescID describing the global position of a object(such as "obj.GetMg().off" ). So I use the silly frame by frame method to copy the global position of an obj.

    I'm a noob in C4D script, maybe the problem is silly, does C4d have a DescID containing the global position data ???



  • Hi, @q1w2ertyu

    Actually, all spaces information of an object in Cinema 4D is stored in local space of this object.

    There is no direct within Cinema 4D to do that you have to think about your own algorithm.

    That means you will need to iterates each key to set them to the correct space.
    So while doing this is already a bit cobblestone since you need to handle correctly the matrix and all issue that can appear since you are working in another space (e.g. gimbal lock).
    You also have to deal with tangent animation in top of that. Which can be also a big issue.

    So is it possible? Yes, but it's pretty hard to have a generic way for doing it and handling all kinds of animations especially if they are not baked each frame.

    Now if you frame are baked for each frame, I think the easiest way would be:

    • for each frame call BaseDocument.AnimateObject, so the animated object will be updated according to Keyframes information.
      • Retrieves the world space matrix with globalMat = obj.GetMg()
      • Transforms this global matrix in local space of the target obj by doing globalMat * ~targetObj.GetMg()
      • Extract informations for this LocalMatrix:
        • pos = localMatrix.off
        • scale = c4d.Vector(localMatrix.v1.GetNormalized(), localMatrix.v2.GetNormalized(), localMatrix.v3.GetNormalized())
        • rotation = c4d.utils.MatrixToHPB(m) You may need to call c4d.utils.GetOptimalAngle to avoid gimbal lock.
      • Creates the CTracks and Ckey accordingly.

    Instead of calling BaseDocument.AnimateObject you can iterate the CTracks of the first object and directly build the local Matrix but you will also need to do this for its parent objects, in order to be able to compute its global matrix so it's better to let AnimateObject do the object and directly retrieve the global matrix.

    I'm afraid I can't help you more here since we can only help you regarding C4D API stuff, not algorithm stuff.

    But if you don't mind to do it the most efficiently possible, I guess your first approach is correct. At least it's the easiest to set up.

    import c4d
    
    
    def main():
        smallbox = doc.SearchObject("obj1")
        bigbox = doc.SearchObject("obj2")
    
        # Iterates from frame 0 to 60
        for frame in range(60):
            # Defiens the document Time
            doc.SetTime(c4d.BaseTime(frame, doc.GetFps()))
    
            # Updates Cache
            c4d.DrawViews(c4d.DRAWFLAGS_FORCEFULLREDRAW)
    
            # Notifies C4D UI the time changed
            c4d.GeSyncMessage(c4d.EVMSG_TIMECHANGED)
    
            # Sets the global matrix of the static object with the global pos of the animated object
            bigbox.SetMg(smallbox.GetMg())
    
            # Press the record active obj button
            c4d.CallCommand(12410)
    
    if __name__ == "__main__":
        main()
    

    Just a little information DrawViews embed a call to ExcutePasses, so ExecutePasses only update the cache, while DrawViews, refresh the cache and the viewport.

    Cheers,
    Maxime.



  • @m_adam Thank you for another detailed answer !


Log in to reply