SOLVED Global Matrix - Trying to copy data from Mixamo Control Rig with Python

Hello, there!

I would like to copy the animation I have on a Mixamo Control Rig to a different character. By different, I mean only the hierarchies and naming convention don't match.

I'm having trouble with copying data from Mixamo Control Rig's _bind joint chain. Most curves and values can't be applied directly because the joints inside the Mixamo Rig have frozen values.

That's why I'm going for GetMg() and SetMg(). I've tried looping within a frame range, but GetMg() always returns the same vectors. I even tried c4d.CallCommand(12414) inside a loop, but no luck. Any pointers?

Thanks for your time,

Leo

@Leo_Saramago I forgot to mention I have baked the animation in the _bind chain.

For testing purposes: I created a cube and gave it some keyframe animation, then baked it. Console shows the same values for every frame when I execute this simplified version of my original python code:

import c4d

def main():
    
    c4d.CallCommand(12501) #Moves Playhead to Start
    obj = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER) #Remember: Select Object in OM
    
    for i in range(10):
        mg = obj[0].GetMg()
        print (mg.off)
        c4d.CallCommand(12414) #Moves Playhead to Next Frame

# Execute main()
if __name__=='__main__':
    main()

You have to update the document each frame with ExecutePasses()
In your for loop, simply add doc.ExecutePasses(None, True, True, False, c4d.BUILDFLAGS_NONE) as the first method call. (Adjust parameters to your needs.)

Hi @Leo_Saramago,

thank you for reaching out to us. As already pointed out by @mp5gosu (thank you for that), you can execute the passes on the document when you you want to evaluate a new state.

But we have also an example on GitHub that is much closer to your specific scenario. Besides the more straight forward approach of setting the BaseTime of a document directly, it also shows you how to update the scene state via various messages. Below you will find a slightly modified version which does the same as your script, i.e., printing the global offset of an object.

Cheers,
Ferdinand

"""Example for animating a document.

Based on cinema4d_py_sdk_extended example:
    scripts/04_3d_concepts/scene_elements/scene_management/basedocument_animate_r13.py

As discussed in:
    plugincafe.maxon.net/topic/13228
"""
import c4d


def main():
    # Saves current time
    ctime = doc.GetTime()

    # Retrieves BaseTime of frame 5, 20
    start = 5
    end = 20

    # Loops through the frames
    for frame in range(start, end + 1):
        
        # Sets the Status Bar
        c4d.StatusSetBar(100.0 * float(frame - start) / float(end - start))

        # Changes the time of the document
        doc.SetTime(c4d.BaseTime(frame, doc.GetFps()))

        # Updates timeline
        c4d.GeSyncMessage(c4d.EVMSG_TIMECHANGED)

        # Redraws the viewport and regenerate the cache object
        c4d.DrawViews(c4d.DRAWFLAGS_ONLY_ACTIVE_VIEW | c4d.DRAWFLAGS_NO_THREAD | c4d.DRAWFLAGS_STATICBREAK)

        # Print the current document frame and the offset of the currently selected object.
        offset = op.GetMg().off if op else None
        print(f"Frame: {frame}, offset: {offset}")

    # Sets the time back to the original time.
    doc.SetTime(ctime)

    # Pushes an update event to Cinema 4D
    c4d.EventAdd(c4d.EVENT_ANIMATE)

    # Clears the Status Bar
    c4d.StatusClear()


if __name__ == "__main__":
    main()

@mp5gosu and @ferdinand Thank you very much. Really helpful! Sorry for the delay. I'll perform a couple of tests and get back to you before closing this one. Soon, I promise.

Ok, now that I'm sure I've grabbed the proper data via GetMg(), can I apply SetMg() to each target joint and keyframe all properties at once for each frame? Or do I have to go thru each CTrack curve of my target joint and use SetMg() over and over again?

Thanks, these answers have been very helpful!

@Leo_Saramago This photoshop montage illustrates my point:
Keyframing_right_after_SetMg.png
On the left, you can see the curves for my source object. The curves on the right belong to my target object, and they came to be after applying SetMg(mg) in every frame inside a loop. This is the result I'm actually going for, except for the unwanted keyframes concerning Scale. Nothing that I couldn't put up with, I mean, deleting those keyframes wouldn't be trouble at all, it's just that MAXON SDK Team recommends avoiding CallCommands - in this case, I used c4d.CallCommand(12410) #Record Active Objects. As I had mentioned before, the source object has frozen values, hence those curves being different.
Keyframing_right_after_SetMg-Viewport.png
As you can see above, both objects have the same animation going on in Global Space. I just want to learn how to achieve this same result the proper way.

Thanks for your time,

Leo

Hi @Leo_Saramago you would need to create keyframes manually, for the Position, Scale, and Rotation of each object.

Find an example of how to create a keyframe in ctrack_create_keys.py.

And I also recommend you to read DescIds and Animation

Hope this help, cheers,
Maxime.

@m_adam Hi! Thanks for the reply.

None of those would work because the source's curves are not to be copied as they are. I mean, the values of each keyframe in each curve are not necessarily the same because the source has frozen values.

Based on those links you've provided, the only option would be to forget about GetMg()/SetMeg() methods. I would have to retrieve all frozen values from source objects, and use them to offset each key value in every curve. And I'd have to do the same to target objects - because they could also have frozen values. Then, depending on how both source and target objects were nested, I'd have to figure out their overall offsets.

Is this train of thought correct? If so, what's the DescID that would retrieve values stored in "freeze"?

Once again, thanks!

Hi @Leo_Saramago,

sorry, I totally overlooked your reply. Thanks @m_adam for jumping in. You are right, your point of entry for keyframing is not Get/SetMg/Ml() since nodes have no matrix parameter which could be keyframed. Our matrix manual contains however the conversion formulas (at the very bottom). The BaseObject parameters are documented in the BaseObject ressources. The attribute for frozen rotations is for example c4d.ID_BASEOBJECT_FROZEN_ROTATION.

I also would not say that the Maxon SDK team does not recommend using CallCommand. At least I would not go that far. It is a convenience tool that lacks sometimes the finer control to do more complex things. It is just a very simple procedural entry point into our API - which is sometimes misconceived as something more powerful. In these cases you have to use our object oriented API.

Reading your postings, I am still not quite sure what you want to do. If you want to animate some frozen value for multiple objects in a scene, you will have to iterate over all objects, find the ones you want to modify and then write some CKey into their CTrack\CCurve. We have a whole chapter on animation features in our Python SDK Script examples. Without any code from your side, it is hard to give you more substantial advice.

Cheers,
Ferdinand

@ferdinand Hi! Thanks for clarifying things some more. I guess I was looking for frozen values in the wrong place. Browser search wouldn't give me any results.

The problem with writing CKey into CTrack\CCurve in my original scenario is that I have to pass the values of the keys whenever I apply the methods, and I don't have those values. I have the sources', yes, but not the targets'. The source is a Mixamo Control Rig - frozen values in almost every _bind joint.

That's why I thought of GetMg()/SetMg() iterating over the joint chains within the frame range. They work, but then I have to use CallCommand to set keyframes and do some clean up afterwards. No big deal, really.

So, just to wrap this up, is there a way to set keyframes, other than CallCommand, that doesn't require CKey values?

Thanks for your time,

Leo

Hi @Leo_Saramago,

while I agree that using transforms/matrices is more convenient than vectors for position, scale and rotation, it frankly do not quite get why you cannot use the latter since matrices are exactly the same, just written in a more convenient form.

There are however multiple convenience methods attached to BaseDocument, .AnimateObject(), .AutoKey(), .Record() and .RecordKey(), with which you can animate nodes without having to deal with the more atomic animation types directly. I have attached a very simple example at the end for the .Record() method, in the hopes that this reflects what you are trying to do.

Cheers,
Ferdinand

"""Little example for one of the animation convenience methods of 
BaseDocuement.

Select an object and run the script, it will create a short animation for it.

As discussed in:
    plugincafe.maxon.net/topic/13228/
"""

import c4d
import math

def main():
    """
    """
    # Get out when there is no object selected. op is predefined as the 
    # primary selected object in a script module.
    if op is None:
        raise ValueError("Please select an object.")

    # Set a frozen rotation for that object.
    op[c4d.ID_BASEOBJECT_FROZEN_ROTATION] = c4d.Vector(0, 0, math.pi)
    # Set a frozen translation for that object.
    op[c4d.ID_BASEOBJECT_FROZEN_POSITION] = c4d.Vector(50., 0, 0)

    # Take ten steps.
    for t in range(10):

        # Create a BaseTime in 1/10th of a second intervals from our step count.
        bt = c4d.BaseTime(t * .1)
        # Set the document to that time. doc is like op a predefined script
        # module attribute, pointing to the currently active document.
        doc.SetTime(bt)

        # Set the rotation of our object.
        rotation = t * .1 * math.pi
        op[c4d.ID_BASEOBJECT_REL_ROTATION] = c4d.Vector(rotation, 0, 0)
        
        # You can also make use of SetMg() here if you want to, this however
        # will not respect the frozen values, or only in a way that is probably
        # not what you want. So if you set a frozen offset of (100, 0, 0)
        # for example and then write an offset of (0, 0, 0) into the object
        # via SetMg(), the object will then have the relative position of
        # (-100, 0, 0) in the coordinate manger, because (0, 0, 0) in world
        # coordinates is (-100, 0, 0) in frozen coordinates. Keyframing with
        # SetMg() will however work fine.
        
        # mg = c4d.utils.MatrixRotZ(rotation)
        # mg.off = c4d.Vector(t * 10, 0, 0)
        # op.SetMg(mg)

        # Record the active object(s) in the document. Additional convenience
        # methods for animating stuff are BaseDocument.AnimateObject(), 
        # .AutoKey(), and .RecordKey(). Se documentation for details.
        doc.Record()

    # Push an update event to Cinema 4D, so that our editor is getting updated.
    c4d.EventAdd()


if __name__=='__main__':
    main()```