"History" xpresso node python analogue?



  • 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?



  • 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



  • 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.

    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



  • @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



  • 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



  • @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?



  • 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()
    
    


  • @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



  • 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?



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

    Many thanks! Where do you get those snippets from?

    I wrote them ;)

    Cheers,
    zipit



  • @zipit oh! That's a huge effort! I do appreciate it :)



  • 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