Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware 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

    Unpacking Animation Data Wrapped by the Motion System (NLA)

    Cinema 4D SDK
    2023 python
    2
    5
    1.2k
    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.
    • K
      kng_ito
      last edited by ferdinand

      Hi,

      I tried to access to keyframes that are attached to layers of Motion System tag, but it seems not possible.
      Those methods used to get keyframe such as GetTracks() and GetKeyCount() only return values for Default Layer of the Motion System tag.
      Is there any way to get correct information of keyframes on Motion Manager layers? Thank you.

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @kng_ito
        last edited by

        Hey @kng_ito,

        Thank you for reaching out to us. Could you please provide an example scene file and the code you have written?

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

        K 1 Reply Last reply Reply Quote 0
        • K
          kng_ito @ferdinand
          last edited by

          Hi @ferdinand,

          The scene file:
          GetKeyframesInMotionSystemLeyers.c4d

          The Motion System tag on the cube object has 2 Layers (Layer 0 and Layer 1).
          Layer 0 has 2 keyframes in Position . X track.
          Layer 1 has 3 keyframes in Position . Y track.

          After selecting the Cube object, run the following script.

          import c4d
          
          def main():
              # Print the name of track and the number of keyframes for each track
              tracks = op.GetCTracks()
              for track in tracks:
                  curve = track.GetCurve()
                  key_count = curve.GetKeyCount()
                  print(track.GetName())
                  print(key_count)
          
          if __name__ == '__main__':
              main()
          

          The result:

          Position . X
          1
          Position . Y
          1
          
          ferdinandF 1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand @kng_ito
            last edited by ferdinand

            Hey @kng_ito,

            Thank you for your reply. I still must do some interpretation here, and your initial core question,

            Is there any way to get correct information of key frames on Motion Manager layers?

            seems to have been slightly misleading. As you do not seem to want to retrieve the key frames for the layers in your Motion System (e.g., an animation for their strength value), but the key frames of the animation the layer is wrapping. I have changed the title of the topic to better reflect that.

            You cannot just iterate over the key frames of the object for that, as they are wrapped by the motion layers. What you see in the in the Timeline Manager is just smoke and mirrors, or more specifically the data from the motion sources. This is evident by the fact that switching the active layer will also change the displayed tracks and curves.

            motion_keyframes.gif

            Your Cube object is actually just carrying two tracks with one frame each, which serve as dummies to be dynamically filled by the Motion System when the document is animated. So, this output of yours was correct:

            Position . X: 1 Key
            Position . Y: 1 Key
            

            Branching and the Motion System

            The Motion System is more or less undocumented, even in the C++ API, with no dedicated interfaces for it being publicly provided. But you can access the data you want which is stored in the branches of the Motion System tag. I am not going to expand on the concepts of branches here in all detail, but you can think of them as alternative hierarchy paths. A BaseObject carries a tag branch for all its tags, a document an object branch for all its object, etc. So, instead of just a pure up, down, previous, and next tree, there are things attached to nodes which then contain a hierarchy of their own, the branches.

            The data you are looking for is stored in the c4d.NLAbase branches of the Motion System tag controlling the animation.

            To find out more about the concept of branches, you can read these two answers of mine on Plugin Café and read the C++ Docs Manual for GeListHead.

            1. Simple Branch Traversal (Python): Demonstrates how to traverse a node and all its descendants in all branches and printing them in a makeshift tree. The output shown below has been created with the code from this example.
            2. Complex Branch Traversal with Branch Content Mapping (Python): Demonstrates basically the same, only that the code provided here also predicts where it must branch into to find certain kinds of nodes.
            3. GeListHead Manual: GeListHead and GeListNode::GetBranchInfo implement the branching logic. This is the C++ Manual for that, but the manual is of questionable quality as it fails to explain the core concept and instead dabbles in technical details.

            Accessing the Keyframes of Animations Wrapped by a Motion System

            A Motion System tag has an c4d.NLAbase == 5349 branch called 'Motion System'. It contains all the animation layers managed by the system as BaseList2D instances. These are the nodes which are represented by the layer items with sliders in the tag. Each of these layers also has a c4d.NLAbase branch called Animation Layer which holds the 'Motion Sources' for the animation wrapped by the layer. In your case the cube objects on which the original animation has been created. Here you will find the key-frames which are wrapped by the animation layer. To clarify the somewhat wordy explanation, here is a cropped branching tree for the Motion System tag in your scene.

            <c4d.BaseTag object called Motion System/Motion System ...          // The Motion System tag.
              Branching into 'Motion System (5349):'                            // Its NLA branch. 
                <c4d.GeListHead>
                  Direct Children:                                              // The nodes inside this branch
                    <c4d.BaseList2D object called Layer 0/Motion Layer>         // The first animation layer.
                      Branching into 'Animation Layer (5349):'                  // Its NLA branch. 
                        <c4d.GeListHead>
                          Direct Children:                                      // The nodes inside this branch.
                            <c4d.BaseObject object called Cube/Cube>            // A node holding animation data.
                              Branching into 'Tracks (5350):'                   // Here starts the animation data.
                                ...
                    <c4d.BaseList2D object called Layer 1/Motion Layer>         // The second layer.
                      Branching into 'Animation Layer (5349):'                  // The same game again ...
                        <c4d.GeListHead>
                          Direct Children: 
                            <c4d.BaseObject object called Cube/Cube>
                              Branching into 'Tracks (5350):'
                                ...
            

            When we put this information together, we can reach into the Motion System data of a Motion System tag to retrieve the nodes which hold the original information, two Ocube objects in your example scene. It is not possible to retrieve the same data from tracks of the Ocube object hosting the Motion System tag, as it is simply not stored there. It is the nodes stored deep inside the tag which drive the animation of the node hosting the animation (and its direct hierarchical descendants).

            Find an example which puts all these things together below.

            Cheers,
            Ferdinand

            Result:

            The full node graph for <c4d.BaseTag object called Motion System/Motion System with ID 465003000 at 3006362080256>:
            ----------------------------------------------------------------------------------------------------
            
            <c4d.BaseTag object called Motion System/Motion System with ID 465003000 at 3006362080256>
              Branching into 'Motion System (5349):'
                <c4d.GeListHead object at 0x000002BBF9250DC0>
                  Direct Children:'
                    <c4d.BaseList2D object called Layer 0/Motion Layer with ID 465003001 at 3006362071744>
                      Branching into 'Animation Layer (5349):'
                        <c4d.GeListHead object at 0x000002BBF924AF40>
                          Direct Children:'
                            <c4d.BaseObject object called Cube/Cube with ID 5159 at 3006362212608>
                              Branching into 'Tracks (5350):'
                                <c4d.GeListHead object at 0x000002BBF926CAC0>
                                  Direct Children:'
                                    <c4d.CTrack object called Position . X/Track with ID 5350 at 3006362078784>
                                      Branching into 'Sequences (5351):'
                                        <c4d.GeListHead object at 0x000002BBF9244140>
                                          Direct Children:'
                                            <c4d.CCurve object called  with ID 5351 at 3006362070592>
                    <c4d.BaseList2D object called Layer 1/Motion Layer with ID 465003001 at 3006362062912>
                      Branching into 'Animation Layer (5349):'
                        <c4d.GeListHead object at 0x000002BBF927CE80>
                          Direct Children:'
                            <c4d.BaseObject object called Cube/Cube with ID 5159 at 3006362173952>
                              Branching into 'Tracks (5350):'
                                <c4d.GeListHead object at 0x000002BBF9273840>
                                  Direct Children:'
                                    <c4d.CTrack object called Position . Y/Track with ID 5350 at 3006362111168>
                                      Branching into 'Sequences (5351):'
                                        <c4d.GeListHead object at 0x000002BBF927E700>
                                          Direct Children:'
                                            <c4d.CCurve object called  with ID 5351 at 3006362199424>
            <c4d.BaseTag object called Phong/Phong with ID 5612 at 3006362101184>
            ----------------------------------------------------------------------------------------------------
            
            Traversing the NLA branches of <c4d.BaseTag object called Motion System/Motion System with ID 465003000 at 3006362080256> for animation data:
            
            <c4d.BaseList2D object called Layer 0/Motion Layer with ID 465003001 at 3006362235840>
            <c4d.BaseObject object called Cube/Cube with ID 5159 at 3006362091456>
            	Position . X
            		Key 0: Value: 0.0, Data: None
            		Key 1: Value: 100.0, Data: None
            <c4d.BaseList2D object called Layer 1/Motion Layer with ID 465003001 at 3006362238144>
            <c4d.BaseObject object called Cube/Cube with ID 5159 at 3006362083840>
            	Position . Y
            		Key 0: Value: 0.0, Data: None
            		Key 1: Value: 34.0, Data: None
            		Key 2: Value: 122.0, Data: None
            

            Code:

            """Demonstrates how to unpack animation data stored inside a Motion System tag.
            
            To run this Script Manager script, one must select an object which holds a Motion System tag. It 
            will print out all keyframes which are wrapped by the animation layers of the tag. The animation
            data data is stored in the #NLAbase branch relations of the tag. 
            
            See https://developers.maxon.net/forum/topic/14298 for a more complete overview of the subject.
            
            Note:
                This example uses two symbols exposed with `2023.0.0`. To run the script in prior versions, one
                must replace them with their integer values or attach attributes of these names and values to
                the c4d module.
            
                c4d.Tmotionsystem: int = 465003000
                c4d.NLAbase: int = 5349
            """
            __version__ = "2023.0.0"
            
            import c4d
            import typing
            
            op: typing.Optional[c4d.BaseObject]
            
            def PrintRelatedNodes(node: c4d.GeListNode, indent: int=0):
                """Traverses all node relations of #node and prints them out in a tree-like manner.
            
                This functions is only used to demonstrate the structure of a motion tag. See 
                https://plugincafe.maxon.nethttps://developers.maxon.net/forum/topic/14056/copy-layershader-to-a-new-material/6 for the original
                and more densely commented code.
                """
                def RepresentNode(node: c4d.GeListNode) -> None:
                    """Represents a node over its string representation and branches.
                    """
                    print(f"{'  ' * indent}{node}")
                    if node.GetBranchInfo() == None:
                        return
            
                    for item in node.GetBranchInfo():
                        branchHead = item["head"]
                        name = item["name"]
                        tid = item["id"]
            
                        if branchHead.GetDown() == None:
                            continue
                        
                        print (f"{'  ' * (indent + 1)}Branching into '{name} ({tid}):'")
                        PrintRelatedNodes(branchHead, indent + 2)
            
                if not isinstance(node, c4d.GeListNode):
                    return
            
                if indent == 0:
                    print (f"{'-' * 100}\n")
            
                while node:
                    RepresentNode(node)
                    if node.GetDown():
                        print (f"{'  ' * (indent + 1)}Direct Children:'")
                        PrintRelatedNodes(node.GetDown(), indent + 2)
            
                    node = node.GetNext()
            
                if indent == 0:
                    print (f"{'-' * 100}\n")
            
            # --- Actual code starts here ----------------------------------------------------------------------
            
            # A type alias for a generator which yields BaseList2D instances.
            BaseList2DGenerator: typing.Type = typing.Generator[c4d.BaseList2D, None, None]
            
            def GetMotionData(node: c4d.BaseList2D) -> BaseList2DGenerator:
                """Yields the relevant nodes of a motion system stored in #node by unpacking the branched data.
                """
                def branch(node: c4d.GeListNode, branchTypes: list[int]) -> BaseList2DGenerator:
                    """Yields the branches of #node whose ID is contained in #branchTypes.
            
                    Yielded will not be the branch head but the first node in it. Branches with a matching type
                    but no nodes will be ignored.
                    """
                    branchCollection: typing.Optional[list[dict]] = node.GetBranchInfo()
                    if branchCollection is None:
                        return
            
                    for branch in branchCollection:
                        if branch["head"] and branch["id"] in branchTypes and branch["head"].GetDown():
                            yield branch["head"].GetDown()
            
                def iter(node: c4d.GeListNode, branchTypes: list[int]) -> BaseList2DGenerator:
                    """Yields the descendants of #node, including its branches and the branches of descendants.
                    
                    Traversed will only be branches whose ID is contained in #branchTypes.
                    """
                    if not node:
                        return
                    
                    while (node):
                        yield node
                        for branchNode in branch(node, branchTypes):
                            for child in iter(branchNode, branchTypes):
                                yield child
            
                        for child in node.GetChildren():
                            for item in iter(child, branchTypes):
                                yield item
                        node = node.GetNext()
            
                # Iterate over all descendants of #node, branching into all branches that are of type NLAbase.
                # Both motion systems and animation layers live under this branch ID.
                motionTypes: list[int] = [c4d.NLAbase]
                for head in branch(node, motionTypes):
                    for node in iter(head, motionTypes):
                        yield node
            
            def main() -> None:
                """Runs the example.
                """
                # Check if there is a selected object and that it has a Motion System tag.
                if not op:
                    print ("Please select an object.")
                    return
                
                tag: c4d.BaseTag = op.GetTag(c4d.Tmotionsystem)
                if not isinstance(tag, c4d.BaseTag):
                    print ("Please select an object that has a motion system tag.")
                    return
            
                # To understand what is going on here, we should look at the node data stored in the tag, i.e.,
                # unpack its branches. The tag stores an object for each animation layer it holds under the
                # animation layer branches of its motion system branch.
                print ("The full node graph for {tag}:")
                PrintRelatedNodes(tag)
            
                # Traverse #tag for the key frames contained within it. When you would have animated the layer
                # strength of the Animation layers, this would also be printed here. Adjust to your liking to
                # retrieve only the data you need.
                print ("Traversing the NLA branches of {tag} for animation data:")
                for node in GetMotionData(tag):
                    print (f"Node in an NLA branch: {node}")
            
                    # Iterate over the tracks and curves of the NLA node.
                    for track in node.GetCTracks():
                        print (f"\t{track.GetName()}")
                        curve: c4d.CCurve = track.GetCurve()
            
                        for kid in range(curve.GetKeyCount()):
                            key: c4d.CKey = curve.GetKey(kid)
                            print(f"Key {kid}: Value: {key.GetValue()}, Data: {key.GetGeData()}")
            
            
            if __name__ == '__main__':
                main()
            

            MAXON SDK Specialist
            developers.maxon.net

            K 1 Reply Last reply Reply Quote 0
            • K
              kng_ito @ferdinand
              last edited by

              Hi @ferdinand,

              Thank you for the detailed explanation and the code.
              It works perfectly and the problem is solved!

              1 Reply Last reply Reply Quote 0
              • ferdinandF ferdinand referenced this topic on
              • ferdinandF ferdinand referenced this topic on
              • ferdinandF ferdinand referenced this topic on
              • First post
                Last post