"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 ac4d.BaseList2D
holds ctracks for all its animated properties. You can get the value of a ctrack at a specific time withc4d.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.
- 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 -
@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