Placing Motion Sources at Timeline Markers with Python - r19
-
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!
-
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?
-
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!
-
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.
-
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!!!