• Categories
    • Overview
    • News & Information
    • Cinema 4D SDK Support
    • Cineware SDK Support
    • ZBrush 4D SDK Support
    • Bugs
    • General Talk
  • Unread
  • Recent
  • Tags
  • Users
  • Register
  • Login
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
  • Register
  • Login

"History" xpresso node python analogue?

Cinema 4D SDK
3
12
1.2k
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.
  • I
    intenditore
    last edited by Manuel Mar 18, 2020, 8:14 AM Mar 18, 2020, 2:52 AM

    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
    • F
      ferdinand
      last edited by ferdinand Mar 18, 2020, 11:06 AM Mar 18, 2020, 10:59 AM

      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

      I 1 Reply Last reply Mar 18, 2020, 2:51 PM Reply Quote 1
      • M
        Manuel
        last edited by Mar 18, 2020, 11:06 AM

        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
        • I
          intenditore @ferdinand
          last edited by Mar 18, 2020, 2:51 PM

          @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
          • M
            Manuel
            last edited by Mar 18, 2020, 3:46 PM

            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

            I 1 Reply Last reply Mar 18, 2020, 4:05 PM Reply Quote 0
            • I
              intenditore @Manuel
              last edited by Mar 18, 2020, 4:05 PM

              @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
              • F
                ferdinand
                last edited by ferdinand Mar 18, 2020, 4:09 PM Mar 18, 2020, 4:06 PM

                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
                • M
                  Manuel
                  last edited by Mar 18, 2020, 5:39 PM

                  @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
                  • I
                    intenditore
                    last edited by Mar 19, 2020, 12:53 AM

                    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?

                    F 1 Reply Last reply Mar 19, 2020, 4:38 AM Reply Quote 0
                    • F
                      ferdinand @intenditore
                      last edited by Mar 19, 2020, 4:38 AM

                      @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

                      I 1 Reply Last reply Mar 19, 2020, 4:46 AM Reply Quote 1
                      • I
                        intenditore @ferdinand
                        last edited by Mar 19, 2020, 4:46 AM

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

                        1 Reply Last reply Reply Quote 0
                        • M
                          Manuel
                          last edited by Apr 27, 2020, 11:49 AM

                          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