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.

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
.
- 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.
- 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.
- 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://plugincafe.maxon.net/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.net/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()