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

    "History" xpresso node python analogue?

    Cinema 4D SDK
    3
    12
    1.5k
    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.
    • intenditoreI
      intenditore
      last edited by Manuel

      Hi
      I need to retrieve a value of a variable/object parameter from a few frames back.
      I thought of collecting all the values to a global list to get it from there by frame number, but it seems to be stupidly memory-greedy and which if worse, if frame is skipped the value won't be stored. Seems too wrong.
      But what is the right way to get the value from a particular frame? Like a "History" xpresso node or valueAtTime() AE function?

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

        Hi,

        animations are stored with the type c4d.CTrack in Cinema 4D. Everything that is a c4d.BaseList2D holds ctracks for all its animated properties. You can get the value of a ctrack at a specific time with c4d.Ctrack.GetValue(). So to do what you want, you could do this:

        # -*- coding: utf-8 -*-
        
        """ A script to be run from the Cinema script manager. Expects an object 
        selection to be present, where the selected object has animated properties.
        """
        
        import c4d
        
        def get_node_property_at_basetime(node, descid, t):
            """Returns the value of an animated property at a specific time.
        
            Args:
                node (c4d.BaseList2D): The node which holds the animated property.
                descid (int, tuple(int), c4d.DescID): A descriptor for the property
                 we want to retrieve.
                t (c4d.BaseTime): The document time at which we want to retrieve the 
                 value of the animated property.
        
            Returns:
                tuple(bool, any): 
                    0 - If the property descid is animated or not. 
                    1 - Either the value at the time t or the current value 
                     if the first value is False.
        
            """
        
            # Some meaningful input value validation goes here.
        
            track = node.FindCTrack(descid)
            if track is None:
                return False, node[descid]
            else:
                return True, track.GetValue(doc, t)
        
        def main():
            """
            """
            # op is predefined as the selected node in a script. op being None means
            # that there is no selection.
            if op is None:
                return
        
            # a time value in seconds.
            t = c4d.BaseTime(0.)
        
            # we could loop through the description of a node and check all
            # properties.
            for bc, descid, _ in op.GetDescription(0):
                desc_name = bc[c4d.DESC_NAME]
                is_animated, value = get_node_property_at_basetime(op, descid, t)
                if is_animated:
                    print "op has an animated property with the name:", desc_name
                    print "its value at t=0 was:", value
        
            # or just invoke it with a known symbol:
            descid = c4d.ID_BASEOBJECT_REL_POSITION
            is_animated, value = get_node_property_at_basetime(op, descid, t)
            if is_animated:
                print "op has a position animation, its value a t=0 was:", value
            else:
                print "op has no position animation, the current value is:", value
        
        if __name__=='__main__':
            main()
        

        Cheers,
        zipit

        MAXON SDK Specialist
        developers.maxon.net

        intenditoreI 1 Reply Last reply Reply Quote 1
        • ManuelM
          Manuel
          last edited by

          Hi,

          For your next threads, please help us keeping things organised and clean. I know it's not your priority but it really simplify our work here.

          • Q&A New Functionality.
          • How to Post Questions especially the tagging part.

          I've added the tags and marked this thread as a question so when you considered it as solved, please change the state 🙂

          This is some kind of problem that cinema 4D have. Depending on what you want to retrieve as information you can be stucked.

          Let's say you want to have a position of an object at frame X.
          Dynamics need the information of frame X-1 to calculate the frame X. So when you read the timeline, you can store information from the past, but not the futur ones.
          If you have a track you can retrieve the value of that track an any point in time.
          If the object is moving based on the time of the document, you can change the time document and ExecutePasses to update the scene at that time and retrieve the information you need.

          You can store your data in a global variable, of course if it's consuming too much memory you have to bake to a file.

          Solution can also be different if you are on a script or a tag, a plugin, a node etc.

          what are you trying to achieve ? Maybe just adding a "bake" function could be one solution.

          Cheers,
          Manuel

          MAXON SDK Specialist

          MAXON Registered Developer

          1 Reply Last reply Reply Quote 1
          • intenditoreI
            intenditore @ferdinand
            last edited by

            @zipit it's looking great, thank you for such a snippet. But the thing is there may be no keyframes on the object, but I must retrieve the changes in it's properties anyway..

            @m_magalhaes said in "History" xpresso node python analogue?:

            For your next threads, please help us keeping things organised and clean

            Ok, sorry. I've been on the old Cafe, but it's the first time after the move, I will try to follow new guides

            @m_magalhaes said in "History" xpresso node python analogue?:

            what are you trying to achieve ?

            Actually, I need to calculate the velocityes of the animated parameters of the object from the deformer (ObjectData plugin, not Python deformer) and trigger an action when it overcomes a threshold. So I need to check the values at least a frame back from the current time

            1 Reply Last reply Reply Quote 0
            • ManuelM
              Manuel
              last edited by

              hi,

              in that case, you should try to change the document time, ExecutePasses to update the scene and retrieve the information. It could be enough.

              If you are inside a deformer you should probably need to clone the document and work on the cloned. But, if the animation is based on some random, that could maybe change your animation. (something you have to keep in mind somewhere)

              Cheers,
              Manuel

              MAXON SDK Specialist

              MAXON Registered Developer

              intenditoreI 1 Reply Last reply Reply Quote 0
              • intenditoreI
                intenditore @Manuel
                last edited by

                @m_magalhaes said in "History" xpresso node python analogue?:

                you should try to change the document time

                But how would it affect the speed/scene handling? I'm afraid it will be too cycle/memory hungry.
                Actually, I see Delay and Jiggle use some way of retrieving the time-dependant values, and actually the result of their work is incorrect if some frames are dropped.
                Can't you give a clue please, how is it done in Jiggle? I start to suspect it simply stores previous frame value as a global variable and rewrites it every frame, no matter if some are dropped. Right guess?

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

                  Here is an example which shows different approaches. Memory consumption should not be your biggest issue.

                  Cheers,
                  zipit

                  # -*- coding: utf-8 -*-
                  
                  """ This script expects you to have an object selected which has a position
                  animation. Also the active document is excpected to be at least 1s long and
                  the current time should be larger than .1 seconds.
                  """
                  
                  import c4d
                  
                  def main():
                      """
                      """
                      if op is None:
                          return
                  
                      # the time the active document is currently at
                      current_time = doc.GetTime()
                  
                      # print out the position at time of the selected object
                      print "current postion:", op[c4d.ID_BASEOBJECT_REL_POSITION]
                      print "current time:", current_time.Get()
                  
                      # construct a time value that was 100ms before the current time,
                      # but does not go below frame 0 (assuming that is what you want)
                      lookback_time = c4d.BaseTime(max(0., current_time.Get() - .1))
                  
                      # set the document time to the time you want to poll
                      doc.SetTime(lookback_time)
                      # let the cinema update the document, you should lookup that method
                      # and tailor the call to your needs. This will
                      doc.ExecutePasses(None,
                                        animation=True,
                                        expressions=True,
                                        caches=True,
                                        flags=c4d.BUILDFLAGS_NONE)
                  
                      # print out the position at time of the selected object
                      print "lookback postion:", op[c4d.ID_BASEOBJECT_REL_POSITION]
                      print "lookback time:", lookback_time.Get()
                  
                      # set the document back to the original time
                      doc.SetTime(current_time)
                      doc.ExecutePasses(None,
                                        animation=True,
                                        expressions=True,
                                        caches=True,
                                        flags=c4d.BUILDFLAGS_NONE)
                  
                      # Everything should be where it has been before.
                      print "postion after lookback:", op[c4d.ID_BASEOBJECT_REL_POSITION]
                  
                      # But this all could mess with the current state of your document.
                      # To circumvent this, you could just clone the document and do the
                      # lookups in the clone.
                  
                      lookup_doc = doc.GetClone(0)
                      lookup_doc.SetTime(lookback_time)
                  
                      lookup_doc.ExecutePasses(None,
                                               animation=True,
                                               expressions=True,
                                               caches=True,
                                               flags=c4d.BUILDFLAGS_NONE)
                  
                      obj = lookup_doc.GetActiveObject()
                      pos = obj[c4d.ID_BASEOBJECT_REL_POSITION]
                      print "pos of the selected object in the lookup doc:", pos
                  
                      # Make life a bit easier for Python's GC
                      lookup_doc.Flush()
                  
                      # But if you have some sort of seld-dependent animation in your document,
                      # particles for example, this will not work, just as timeline scrubbing
                      # won't work for these animations.
                  
                      # The only solution then is to cache your data:
                  
                      cache_doc = doc.GetClone()
                      obj = cache_doc.GetActiveObject()
                  
                      # let's say we want to cache the first second of the document with
                      # a stride of 100ms.
                      min_time = 0.
                      max_time = 1.
                      stride = .1
                      steps = int((max_time - min_time) / stride) + 1
                  
                      cache = {}
                  
                      for i in range(steps):
                          t = i * stride
                          bt = c4d.BaseTime(t)
                          cache_doc.SetTime(bt)
                          cache_doc.ExecutePasses(None,
                                                  animation=True,
                                                  expressions=True,
                                                  caches=True,
                                                  flags=c4d.BUILDFLAGS_NONE)
                          # For some data types, like for example SplineData you will have
                          # to clone the data, as we otherwise will only store a reference to
                          # the data, i.e. the same object over and over again.
                          cache[t] = obj[c4d.ID_BASEOBJECT_REL_POSITION]
                  
                      cache_doc.Flush()
                  
                      print "cached data for 0 <= t <= 1 of the position animation:", cache
                  
                  if __name__ == '__main__':
                      main()
                  
                  

                  MAXON SDK Specialist
                  developers.maxon.net

                  1 Reply Last reply Reply Quote 1
                  • ManuelM
                    Manuel
                    last edited by

                    @intenditore said in "History" xpresso node python analogue?:

                    Can't you give a clue please, how is it done in Jiggle?

                    it store the actual value of the point in a "particle" kind of data type.
                    When it update, it check the length of that stored point with the actual one and act accordingly.
                    That's why the jiggle deformer works even if you play the animation backwards. But the result is not the same when you play foward.

                    Cheers,
                    Manuel

                    MAXON SDK Specialist

                    MAXON Registered Developer

                    1 Reply Last reply Reply Quote 1
                    • intenditoreI
                      intenditore
                      last edited by

                      Wow, seems it's the best dev support forum I ever visited 😃
                      Thank you much! Seems I get the idea

                      @zipit said in "History" xpresso node python analogue?:

                      Here is an example which shows different approaches

                      Many thanks! Where do you get those snippets from?

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

                        @intenditore said in "History" xpresso node python analogue?:

                        Many thanks! Where do you get those snippets from?

                        I wrote them 😉

                        Cheers,
                        zipit

                        MAXON SDK Specialist
                        developers.maxon.net

                        intenditoreI 1 Reply Last reply Reply Quote 1
                        • intenditoreI
                          intenditore @ferdinand
                          last edited by

                          @zipit oh! That's a huge effort! I do appreciate it 🙂

                          1 Reply Last reply Reply Quote 0
                          • ManuelM
                            Manuel
                            last edited by

                            hi,

                            can we considered this thread as solved ? (at least we answered your question)
                            Without further feedback i'll set it to solved tomorrow.

                            Cheers,
                            Manuel

                            MAXON SDK Specialist

                            MAXON Registered Developer

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