SOLVED Grabbing camera position/rotation from Takes

Hi,
I spent some time yesterday trying to develop a Python script, which travels through takes, grabbing camera position (which is changed every time by aligning to spline tag), creating a null object and copying camera Matrix to null. The part I'm struggling with is grabbing the camera position. I either got the original camera position, before it's been modified by aligning to spline property or value 0.
Below is my code and also attached scene, which I've been using for tests.
Am I missing something obvious?

import c4d
from c4d import documents

list_of_takes = []
parent_name = "360_cams"

def GetCategory(doc):
    takeData = doc.GetTakeData()
    if takeData is None:
        return

    mainTake = takeData.GetMainTake()
    take = mainTake.GetDown()

    while take is not None:
        list_of_takes.append((take))
        take = take.GetNext()

def CreateParentObject4360CamExport(doc):
    cam_export_parent = doc.SearchObject(parent_name)
    if cam_export_parent is not None:
        cam_export_parent.Remove()

    c4d.CallCommand(5140)  # Null
    cam_export_parent = doc.SearchObject("Null");
    c4d.CallCommand(1019940)  # Reset PSR
    if cam_export_parent != None:
        cam_export_parent.SetName(parent_name)

    return cam_export_parent

def CreateCopyOfTheCamera(currentCam,currentTake, doc,take_data):
    c4d.CallCommand(5103) # Camera
    camTemp = doc.SearchObject("Camera")
    c4d.CallCommand(1019940)  # Reset PSR
    if camTemp != None:
        camTemp.SetName(currentTake.GetName())


    print("current cam : "+ currentCam.GetName() + " and position " + str(currentCam.GetMg()))
    camTemp.SetMg(currentCam.GetMg())
    c4d.EventAdd()
    return camTemp

# Main function
def main():
    doc = documents.GetActiveDocument()
    takeData = doc.GetTakeData()
    if takeData is None:
        raise RuntimeError("No take data in the document.")
    cam_export_parent = CreateParentObject4360CamExport(doc)
    main_take = takeData.GetMainTake()
    GetCategory(doc)
    for child_take in list_of_takes:
        takeLoaded = takeData.SetCurrentTake(child_take)
        if takeLoaded == True:
            currentCam = child_take.GetCamera(takeData)
            cam2NullClone = CreateCopyOfTheCamera(currentCam,child_take,doc,takeData)
            cam2NullClone.InsertUnder(cam_export_parent)
            c4d.EventAdd()

# Execute main()
if __name__=='__main__':
    c4d.CallCommand(13957) # Clear Console
    main()

Link to c4d file

Thank you, Ferdinand.

It looks like the only part I missed in my code was that extra line

doc.ExecutePasses(bt=None, animation=True, expressions=True, caches=True, flags=c4d.BUILDFLAGS_NONE)

After updating the code on my side - everything seems to be working fine.
Thank you again.
Best regards,
Tomasz

Hello @Futurium,

Thank you for reaching out to us and please excuse the short delay.

I am struggling a bit with understanding what you want to do exactly, but I assume your major problem is that the objects copied by your script always have the transform the object had when the script was invoked and not the transform it should have according to the activated take. This is caused by you not letting Cinema 4D update when you invoke takeData.SetCurrentTake(child_take). You must call BaseDocument.ExecutePasses on the document containing the activated take when you want more complex scene information to be updated immediately, e.g, the "Align To Spline" tag which is driving the camera in your rig.

There are also other problems with your code ranging from the overuse of CallCommand (you should avoid using it), over only looking at top-level takes, to a code structure that at least I do not fully understand. I have provided a code snippet which does what I think you want to be done at the end of this posting.

Cheers,
Ferdinand

The result:
cam_takes.gif

The code:

"""Copies the object named '360 Camera 2' for each take state in the document into a null object.
"""

import c4d
import typing

def AssertType(item: any, t: typing.Type, label: str) -> None:
    """Asserts that #item is of type #t. When the assertion fails, an assertion error will be 
    raised with #label referring to #item.
    """
    if not isinstance(item, t):
        raise AssertionError(f"Expected {t} for '{label}'. Received: {type(item)}")

def GetDocumentTakes(doc: c4d.documents.BaseDocument) -> c4d.modules.takesystem.BaseTake:
    """Yields all non main-take takes in #doc.

    Doing it like this is necessary because your variant only yields takes that are directly parented
    to the main take. Which works for the file provided by you but would fail on something like this:

        MainTake
            Take.0
            Take.1
                Take.2
            Take.3

    As your variant would then only return [Take.0, Take.1, Take.3].
    """
    AssertType(doc, c4d.documents.BaseDocument, "doc")

    takeData = doc.GetTakeData()
    mainTake = takeData.GetMainTake()

    def iterate(node):
        """Walks #node depth first.
        """
        if node is None:
            return

        while node:
            yield node
            for descendant in iterate(node.GetDown()):
                yield descendant
            node = node.GetNext()

    for take in iterate(mainTake.GetDown()):
        yield take


def main(doc: c4d.documents.BaseDocument):
    """Copies the object named '360 Camera 2' for each take state in the document into a null object.

    The copying is done manually without C4DAtom.GetCLone(), following somewhat what you did.
    """
    # Get the take data of the document.
    takeData = doc.GetTakeData()

    # Get the camera target object.
    camera = doc.SearchObject("360 Camera 2")

    # A null object to parent the new cameras to.
    null = c4d.BaseObject(c4d.Onull)
    if null is None:
        raise MemoryError("Could not allocate null object.")
    doc.InsertObject(null)

    # Iterate over all takes in the document.
    for take in GetDocumentTakes(doc):
        # Set the currently yielded take as the active take.
        if not takeData.SetCurrentTake(take):
            raise RuntimeError("Could not activate take.")
        # Execute the passes on the document so that the take can take effect. You could pass 
        # only #True for #expressions for your scene, since you only want to evaluate "align-to-spline" 
        # tag, but I set here more stuff to #True in case you want to do more.
        doc.ExecutePasses(bt=None, animation=True, expressions=True, 
                          caches=True, flags=c4d.BUILDFLAGS_NONE)

        # I assume you want to copy here the #camera object without its tags, but preserve the
        # name, transform, and parameter values of the object (otherwise you could just use 
        # C4DAtom.GetCLone()). 

        # We could also hard-code the camera object type with c4d.Ocamera instead of #camera.
        # But with GetType() it will just copy whatever object type is #camera. When you set #camera 
        # to a null object, how you implicitly did in your script, this still work.
        cameraCopy = c4d.BaseObject(camera.GetType())
        if cameraCopy is None:
            raise MemoryError("Could not allocate camera object.")

        # Copy the name and transform of the camera.
        cameraCopy.SetName(f"{camera.GetName()}({take.GetName()})")
        cameraCopy.SetMg(camera.GetMg())

        # Copy over the parameters of the camera (focus, zoom, etc). We can do this since we can
        # be sure that they are of identical type.
        cameraCopy.SetData(camera.GetData())

        # Insert the new camera copy.
        cameraCopy.InsertUnder(null)

    # Set the active take (back) to the main take.
    takeData.SetCurrentTake(takeData.GetMainTake())

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

if __name__ == "__main__":
    main(doc)

Thank you, Ferdinand.

It looks like the only part I missed in my code was that extra line

doc.ExecutePasses(bt=None, animation=True, expressions=True, caches=True, flags=c4d.BUILDFLAGS_NONE)

After updating the code on my side - everything seems to be working fine.
Thank you again.
Best regards,
Tomasz

Hello @Futurium,

without any further questions and other postings, we will consider this topic as solved and flag it as such by Friday, 17/06/2022.

Thank you for your understanding,
Ferdinand