Placing Motion Sources at Timeline Markers with Python - r19



  • Hello, there!

    I have four custom Motion Sources named "a","b","c","d" and several markers named the same way spread all over the Timeline.

    My Python script is supposed to look at each marker's name and place the corresponding Motion Source match at its time - the Motion Clip layers were also previously created so that different Motion Sources may overlap.

    The problem is I can't find a way to go through the Motion Sources list and compare names, let alone insert them. The Script Log shows nothing when I drag'em manually to Layers in the Timeline.

    I've searched everywhere and all I have found is stuff about CTrack, but I guess that's not the right path.

    I'm beginning to think the solution is somewhere hidden in DescID / DescLevel and the Motion tag, stuff that I have never needed to mess with.

    Could anybody help, please?

    Thanks in advance,

    Leo



  • The struggle goes on...

    As long as a Motion Layer is selected, it is possible to add a Motion Clip via c4d.CallCommand(465001176, 465001176).

    Motion Clips themselves show up with some interesting parameters in the AM. Among them:

    Source: [c4d.ID_MT_CLIP_SOURCE]
    Start: [c4d.ID_MT_CLIP_VIEWSTART]
    End: [c4d.ID_MT_CLIP_VIEWEND]

    How can I get access to those parameters from Python? How can I get access to the Motion Layers?

    Any pointers?


  • Global Moderator

    Hi Leo, first of all, welcome in the plugincafe community!

    I will ask you a little more time to get an answer about it.

    Cheers,
    Maxime.



  • Thanks, Maxime! Cheers!!!


  • Global Moderator

    I'm really sorry for the delay, we are still working on it (hopefully we will get it resolved sooner or later)

    But in any case, don't worry we didn't forget you!
    Cheers,
    Maxime.



  • @m_adam Ok, thanks!


  • Global Moderator

    Hi, @Leo_Saramago!

    First of all, I would like to present my apologies, for the time we ask to solve your issue. Moreover, I would like to point you, to our Q&A functionality in order to use as best as we can the new features offered by the forum.
    I have setup your first post as a question and put tags to the topic. 😉

    With that's said, Motion Layer and Motion Clip are stored in some particular branch of the Motion Tag and there is currently no way to directly access Motion Clip or Motion Layer so you have to do it manually.
    To know exactly how a scene is structured you can use the C++ example activeobject.cpp to help you.
    Here it's an example of how to access to motion clip named "a,b,c or d" and move them to the 20th frame.

    import c4d
    
    def getListHeadFromBranches(op, branchName):
        branches = op.GetBranchInfo()
        for branch in branches:
            if branch["name"] == branchName:
                return branch["head"]
        return
    
    # Main function
    def main():
        obj = op
        if not obj: return
        
        # Get the motion Tag (all the data are stored in it)
        tag = obj.GetTag(465003000)
        if not tag: return
        
        # Get the motion system list head from the motion tag
        motionSystemListHead = getListHeadFromBranches(tag, "Motion System")
        if not motionSystemListHead: return
    
        # Get Motion layers
        motionLayers = list()
        motionLayer = motionSystemListHead.GetDown()
        while motionLayer:
            motionLayers.append(motionLayer)
            motionLayer = motionLayer.GetNext()
    
        if not motionLayers:
            return
    
        # Get Motion clip from the Motion layer
        motionClips = list()
        motionClipsNameAllowed = ["a", "b", "c", "d"]
        for motionLayer in motionLayers:
            # Get the motion Layer list head from the Layer object
            motionLayerListHead = getListHeadFromBranches(motionLayer, "Motion Layer")
            if not motionLayerListHead: return
            
            motionClip = motionLayerListHead.GetDown()
            if not motionClip: continue
            
            name = motionClip.GetName()
            displayedName = motionClip[c4d.ID_MT_CLIP_SOURCE].GetName() # Name displayed in the timeline in the rectangle of the motion clip
            
            if name in motionClipsNameAllowed:
                motionClips.append(motionClip)
                
        # Move all our motions clips
        for motionClip in motionClips:
            duration = motionClip[c4d.ID_MT_CLIP_VIEWEND] - motionClip[c4d.ID_MT_CLIP_VIEWSTART]
            startFrame = c4d.BaseTime(20, doc.GetFps())
            motionClip[c4d.ID_MT_CLIP_VIEWSTART] = startFrame
            motionClip[c4d.ID_MT_CLIP_VIEWEND] = startFrame + duration
            
        c4d.EventAdd()
    
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    But take care when modifying motionClip, and the value you enter. Since you have to modify the basecontainer directly, there is no check done and you can screw up c4d, so be sure values you enter are correct! :)
    Moreover about marker you can find example about how to use them in this example.

    Hope it makes sense if you need help, or you have any questions please let me know!
    Again, all my apologies for the delay.
    Cheers,
    Maxime!



  • Hey, thanks for your reply! There's no need for apologies, Maxime, you've kept in touch and I understand it takes time to figure things out, especially in C4D with its broad range of resources. Your software is a solid robust beast.

    Enough of that... lol!

    I have one question right away:

    When you say "you have to do it manually", you mean I have to drag Motion Sources to Motion Layers so that they become Motion Clips before I run the script?

    If so, this could be a problem because I may have dozens of repetitions for each "a", "b", "c" or "d" depending on the project I'm working on... unless I could create Motion Clip copies dynamically inside a loop. Is that possible?


  • Global Moderator

    Hi @leo_saramago, first of all, you can edit your post. To do so click on the 3 little boxes in the bottom left of your post.

    If I understand correctly.

    • All Motion Sources are loaded.
    • All Motion Clips are created. But not set at the correct time + not linked to the correct Motion Source. But they are named as the Motion Source, isn't?

    And to be sure we get the same terminology and we understand the same thing.

    • A Motion Layer is a container for a Motion Clip.
      • A Motion Clip can't exist without a Motion Layer.
    • A Motion Clip only refers to Motion Source, this is not a container for Motion Source.
      • A Motion Source can exist without any Motion Clip and they can be accessed with the following code
    def main():
        root = doc.GetNLARoot()
        obj = root.GetDown()
        while obj:
            print obj
            obj = obj.GetNext()
    

    With my previous script, you have to select the object, which holds the motion tag.
    Then I assumed Motion Source where already set, but with the previous code snippet, you are able to iterate Motion Source and then add them to your previously created Motion Clip. And then move the Motion Clip to the correct frame.

    If I misunderstand please let me know, and maybe try to summarize what the initial state and the desired final state.
    Cheers,
    Maxime!



  • Hi! Yes, I knew editing was possible, but I also thought purging posts was possible, and I messed up. Lesson learned!

    The only terminology mistake from me was "Motion Clips being containers for Motion Sources". I think we'll get to the same page this time, now that I understand things a little better. Here's what I have in mind:

    • all Markers exist;
    • all Motion Sources exist;
    • all Motion Layers exist, one for each Motion Source, because multiple Motion Clips can overlap several times;
    • No Motion Clips;
    • I would select the object with the Motion System tag before running the script;
    • The script would start Motion Layers iteration;
    • Inside Motion Layers iteration, it would start a markers iteration;
    • This iteration would check if the current marker matches the current Motion Layer's name;
    • If so, it would create a Motion Clip dynamically, maybe via c4d.CallCommand(465001176, 465001176), place it at the current marker in the current Motion Layer, then link the Motion Clip to the proper Motion Source with [c4d.ID_MT_CLIP_SOURCE].
    • Then it would set both [c4d.ID_MT_CLIP_VIEWSTART] and [c4d.ID_MT_CLIP_VIEWEND];
    • move on to the next marker and start a new iteration - until there are no more markers;
    • move on to the next Motion Layer and start a new iteration - until there are no more Motion Layers;

    it's no big deal if I have to manually select each Motion Layer and run the script again. I'm trying to create this tool because having to drag dozens of Motion Sources to the Timeline Markers in every project sounds like a waste of time.

    I've read somewhere I'm supposed to avoid using CallCommand, it's just that I don't know if there's a method to create a Motion Clip on the fly.

    I've just thought of something else: after creating a Motion Clip with CallCommand, I'd still need to find it - and it has to be the right one, before any attributes get modified. How would I make sure it's the one the script had just created, and not another one from a previous iteration? Would it always be the last on a stack? Would I have to store them in some sort of list?

    Thanks again!


  • Global Moderator

    Hi @Leo_Saramago, thanks a lot for your patience.
    As you can see the Motion System is not very well exposed in the API.
    But normally with the following script, it should do what you want to. (MotionLayer are named as same as the MotionSource and as the marker in the document). If you want to see the setup and the script in action

    import c4d
    
    MT_TAG = 465003000 # ID for a Motion Tag
    MT_LAYER = 465003001 # ID for a MotionLayer object
    MT_CLIP = 465003002 # ID for a MotionClip object
    MT_SOURCESTART = 465003056 # BaseContainer ID for Start time of a Motion Source
    MT_SOURCEEND = 465003057 # BaseContainer ID for End time of a Motion Source
    
    # Get the List head of the given branch name
    def getListHeadFromBranches(op, branchName):
        branches = op.GetBranchInfo()
        for branch in branches:
            if branch["name"] == branchName:
                return branch["head"]
        return
    
    # Get a list of all motion Layer stored in a motion Tag
    def GetMotionLayersFromMotionTag(tag):
        if not tag or not tag.CheckType(MT_TAG):
            return
    
        # Get the motion system list head from the motion tag
        motionSystemListHead = getListHeadFromBranches(tag, "Motion System")
        if not motionSystemListHead: return
    
        # Get Motion layers
        motionLayers = list()
        motionLayer = motionSystemListHead.GetDown()
        while motionLayer:
            motionLayers.append(motionLayer)
            motionLayer = motionLayer.GetNext()
    
        return motionLayers
    
    # Function to create a Motion Clip and return it
    def CreateMotionClip(motionLayer):
        if not motionLayer or not motionLayer.CheckType(MT_LAYER):
            return
        
        # Get the motion Layer list head from the Layer object
        motionLayerListHead = getListHeadFromBranches(motionLayer, "Motion Layer")
        if not motionLayerListHead: return
        
        motionClip = c4d.BaseObject(MT_CLIP)
        if not motionClip: return
    
        motionLayer.GetDocument().AddUndo(c4d.UNDOTYPE_NEW, motionClip)
        motionClip.InsertUnderLast(motionLayerListHead)
        return motionClip
    
    # Get all MotionSources in a list
    def GetAllMotionSources(doc):
        if not doc: return
        
        root = doc.GetNLARoot()
        obj = root.GetDown()
        
        motionSources = list()
        while obj:
            motionSources.append(obj)
            obj = obj.GetNext()
    
        return motionSources
    
    # Get all markers
    def GetAllMarkers(doc):
        markers = list()
        marker = c4d.documents.GetFirstMarker(doc)
        
        while marker:
            markers.append(marker)
            marker = marker.GetNext() #Since a marker is a BaseList2D we can use GetNext for iterate
            
        return markers
    
    # Get a baselist 2d by his name from a given list of BaseList2D (marker, obj(MotionSource, MotionLayer etc...), tag) 
    def GetBaseList2DByName(listOfBaseList2D, name):
        if not listOfBaseList2D or not name: return
        
        for bl in listOfBaseList2D:
            if not isinstance(bl, c4d.BaseList2D): continue
            
            if bl.GetName() == name:
                return bl
            
        return
    
    # Main function
    def main():
        # Get selected object
        obj = op
        if not obj: return
    
        # Get the motion Tag (all the data are stored in it)
        tag = obj.GetTag(MT_TAG)
        if not tag: return
    
        # Get all Motion Sources from the document
        motionSources = GetAllMotionSources(tag.GetDocument())
        if not motionSources: return
    
        # Get All Markers from the document
        markers = GetAllMarkers(tag.GetDocument())
        if not markers: return
    
        # Get Motion layers from the tag
        motionLayers = GetMotionLayersFromMotionTag(tag)
        if not motionLayers:
            return
    
        doc.StartUndo()
        # Iterate over all motion Layers
        for layer in motionLayers:
            # Get the motionSource which match the motionLayer name
            motionSource = GetBaseList2DByName(motionSources, layer.GetName())
            if not motionSource: continue
            
            # Get the marker which match the motionLayer name
            marker = GetBaseList2DByName(markers, layer.GetName())
            if not marker: continue
            
            # Create a new motionClip
            motionClip = CreateMotionClip(layer)
            if not motionClip: continue
            
            # Define start Frame and End Frame (if the lenght is define in the marker we use this lenght, otherwise we use the lenght of the motion source)
            start = marker[c4d.TLMARKER_TIME]
            end = marker[c4d.TLMARKER_TIME] + marker[c4d.TLMARKER_LENGTH] if  marker[c4d.TLMARKER_LENGTH] != c4d.BaseTime(0) else marker[c4d.TLMARKER_TIME] + (motionSource[MT_SOURCEEND] - motionSource[MT_SOURCESTART])
            
            # Define our parameter
            doc.AddUndo(c4d.UNDOTYPE_CHANGE, motionClip)
            motionClip[c4d.ID_MT_CLIP_SOURCE] = motionSource
            motionClip[c4d.ID_MT_CLIP_START] = start
            motionClip[c4d.ID_MT_CLIP_END] = end
            
        doc.EndUndo()
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    If you don't understand something in the code, please feel free to ask me any information.
    Again I'm sorry for the huge delay we asked for answers to your first questions in the community, I hope the next one will go faster.
    Cheers,
    Maxime.



  • Hi! Almost there, almost there...

    I replicated your setup and simply pasted the code above. I haven't analysed it, yet.

    It works, but only the first marker for each MotionLayer gets a MotionClip. Please, try adding more markers, something that repeats like "A" "A" "B" "A" "C" "D" "A". There's no need to worry about MotionClips superimposing in the same MotionLayer because the semantics behind those markers guarantee there will never be a case where the MotionClips repeat in such a short span of time.

    I'm sorry I can't reveal more about the nature of those semantics, it's not supposed to go public, but I promise I'll send you an .mp4 showcasing the amazing results this script helps come true.


  • Global Moderator

    Hi @Leo_Saramago don't worry, but please keep in mind we can help you only for problems about our API, and the SDK (like how to create motion clip? How to iterate over all the markers? How to iterate over all the motion layer).
    Normally with the code, I posted previously you get everything you need in order to make the desired change.

    With that's said, since the topic gets a lot of delays here is one of the possible solutions to do what you want.

    import c4d
    
    MT_TAG = 465003000 # ID for a Motion Tag
    MT_LAYER = 465003001 # ID for a MotionLayer object
    MT_CLIP = 465003002 # ID for a MotionClip object
    MT_SOURCESTART = 465003056 # BaseContainer ID for Start time of a Motion Source
    MT_SOURCEEND = 465003057 # BaseContainer ID for End time of a Motion Source
    
    # Get the List head of the given branch name
    def getListHeadFromBranches(op, branchName):
        branches = op.GetBranchInfo()
        for branch in branches:
            if branch["name"] == branchName:
                return branch["head"]
        return
    
    # Get a list of all motion Layer stored in a motion Tag
    def GetMotionLayersFromMotionTag(tag):
        if not tag or not tag.CheckType(MT_TAG):
            return
    
        # Get the motion system list head from the motion tag
        motionSystemListHead = getListHeadFromBranches(tag, "Motion System")
        if not motionSystemListHead: return
    
        # Get Motion layers
        motionLayers = list()
        motionLayer = motionSystemListHead.GetDown()
        while motionLayer:
            motionLayers.append(motionLayer)
            motionLayer = motionLayer.GetNext()
    
        return motionLayers
    
    # Function to create a Motion Clip and return it
    def CreateMotionClip(motionLayer):
        if not motionLayer or not motionLayer.CheckType(MT_LAYER):
            return
        
        # Get the motion Layer list head from the Layer object
        motionLayerListHead = getListHeadFromBranches(motionLayer, "Motion Layer")
        if not motionLayerListHead: return
        
        motionClip = c4d.BaseObject(MT_CLIP)
        if not motionClip: return
    
        motionLayer.GetDocument().AddUndo(c4d.UNDOTYPE_NEW, motionClip)
        motionClip.InsertUnderLast(motionLayerListHead)
        return motionClip
    
    # Get all MotionSources in a list
    def GetAllMotionSources(doc):
        if not doc: return
        
        root = doc.GetNLARoot()
        obj = root.GetDown()
        
        motionSources = list()
        while obj:
            motionSources.append(obj)
            obj = obj.GetNext()
    
        return motionSources
    
    # Get all markers
    def GetAllMarkers(doc):
        markers = list()
        marker = c4d.documents.GetFirstMarker(doc)
        
        while marker:
            markers.append(marker)
            marker = marker.GetNext() #Since a marker is a BaseList2D we can use GetNext for iterate
            
        return markers
    
    # Get a baselist 2d by his name from a given list of BaseList2D (marker, obj(MotionSource, MotionLayer etc...), tag) 
    def GetBaseList2DByName(listOfBaseList2D, name, remove=False):
        if not listOfBaseList2D or not name: return
        
        for bl in listOfBaseList2D:
            if not isinstance(bl, c4d.BaseList2D): continue
            
            if bl.GetName() == name:
                if remove: listOfBaseList2D.remove(bl)
                return bl
            
        return
    
    # Main function
    def main():
        # Get selected object
        obj = op
        if not obj: return
    
        # Get the motion Tag (all the data are stored in it)
        tag = obj.GetTag(MT_TAG)
        if not tag: return
    
        # Get all Motion Sources from the document
        motionSources = GetAllMotionSources(tag.GetDocument())
        if not motionSources: return
    
        # Get All Markers from the document
        markers = GetAllMarkers(tag.GetDocument())
        if not markers: return
    
        # Get Motion layers from the tag
        motionLayers = GetMotionLayersFromMotionTag(tag)
        if not motionLayers:
            return
    
        doc.StartUndo()
        # Iterate over all motion Layers
        for layer in motionLayers:
            # Get the marker which match the motionLayer name
            marker = GetBaseList2DByName(markers, layer.GetName(), remove=True)
            if not marker: continue
            
            while marker:
                # Get the motionSource which match the motionLayer name
                motionSource = GetBaseList2DByName(motionSources, layer.GetName())
                if not motionSource: continue
                
                # Create a new motionClip
                motionClip = CreateMotionClip(layer)
                if not motionClip: continue
                
                # Define start Frame and End Frame (if the lenght is define in the marker we use this lenght, otherwise we use the lenght of the motion source)
                start = marker[c4d.TLMARKER_TIME]
                end = marker[c4d.TLMARKER_TIME] + marker[c4d.TLMARKER_LENGTH] if  marker[c4d.TLMARKER_LENGTH] != c4d.BaseTime(0) else marker[c4d.TLMARKER_TIME] + (motionSource[MT_SOURCEEND] - motionSource[MT_SOURCESTART])
                
                # Define our parameter
                doc.AddUndo(c4d.UNDOTYPE_CHANGE, motionClip)
                motionClip[c4d.ID_MT_CLIP_SOURCE] = motionSource
                motionClip[c4d.ID_MT_CLIP_START] = start
                motionClip[c4d.ID_MT_CLIP_END] = end
                marker = GetBaseList2DByName(markers, layer.GetName(), remove=True)
            
        doc.EndUndo()
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    Cheers,
    Maxime.



  • Ok, I'll let you off the hook. I feel like I can pick from where the code is now and move on. I'm gonna set it to SOLVED.

    Thanks a lot for your time!!!