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

    Placing Motion Sources at Timeline Markers with Python - r19

    Cinema 4D SDK
    python
    2
    14
    3.4k
    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.
    • Leo_SaramagoL
      Leo_Saramago
      last edited by m_adam

      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 idea dictates everything."

      by David Lynch

      1 Reply Last reply Reply Quote 0
      • Leo_SaramagoL
        Leo_Saramago
        last edited by Leo_Saramago

        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?

        "The idea dictates everything."

        by David Lynch

        1 Reply Last reply Reply Quote 0
        • M
          m_adam
          last edited by

          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.

          MAXON SDK Specialist

          Development Blog, MAXON Registered Developer

          1 Reply Last reply Reply Quote 0
          • Leo_SaramagoL
            Leo_Saramago
            last edited by

            Thanks, Maxime! Cheers!!!

            "The idea dictates everything."

            by David Lynch

            1 Reply Last reply Reply Quote 0
            • M
              m_adam
              last edited by

              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.

              MAXON SDK Specialist

              Development Blog, MAXON Registered Developer

              Leo_SaramagoL 1 Reply Last reply Reply Quote 0
              • Leo_SaramagoL
                Leo_Saramago @m_adam
                last edited by

                @m_adam Ok, thanks!

                "The idea dictates everything."

                by David Lynch

                1 Reply Last reply Reply Quote 0
                • M
                  m_adam
                  last edited by m_adam

                  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!

                  MAXON SDK Specialist

                  Development Blog, MAXON Registered Developer

                  1 Reply Last reply Reply Quote 2
                  • Leo_SaramagoL
                    Leo_Saramago
                    last edited by Leo_Saramago

                    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?

                    "The idea dictates everything."

                    by David Lynch

                    1 Reply Last reply Reply Quote 0
                    • M
                      m_adam
                      last edited by

                      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!

                      MAXON SDK Specialist

                      Development Blog, MAXON Registered Developer

                      1 Reply Last reply Reply Quote 2
                      • Leo_SaramagoL
                        Leo_Saramago
                        last edited by Leo_Saramago

                        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!

                        "The idea dictates everything."

                        by David Lynch

                        1 Reply Last reply Reply Quote 0
                        • M
                          m_adam
                          last edited by

                          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.

                          MAXON SDK Specialist

                          Development Blog, MAXON Registered Developer

                          1 Reply Last reply Reply Quote 2
                          • Leo_SaramagoL
                            Leo_Saramago
                            last edited by Leo_Saramago

                            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.

                            "The idea dictates everything."

                            by David Lynch

                            1 Reply Last reply Reply Quote 0
                            • M
                              m_adam
                              last edited by m_adam

                              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.

                              MAXON SDK Specialist

                              Development Blog, MAXON Registered Developer

                              1 Reply Last reply Reply Quote 2
                              • Leo_SaramagoL
                                Leo_Saramago
                                last edited by

                                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!!!

                                "The idea dictates everything."

                                by David Lynch

                                1 Reply Last reply Reply Quote 0
                                • First post
                                  Last post