Object Position w/o Updating Viewport
-
On 07/08/2015 at 11:58, xxxxxxxx wrote:
Hi,
I'm still pretty green with both C4D and Python, but I'm attempting to build a simple velocity/acceleration script that I could store in a Tag. I have a mathematical approximation for the velocity/accel based on two/three points of position, respectively. My hang-up is trying to retrieve an object's position when it is not driven by keys or curves directly (i.e. if it is driven by Xpresso or contained in an animated Null).
It may just be a gap in my understanding, but I've only been able to successfully retrieve an object's position at different points in time by moving the playhead and updating the viewport. So my question is: _ Is it possible to _ get the position of a non-self-animated object without having to scrub the timeline?
Here's a sample of what I am currently doing (moving the playhead via Python, and updating timeline. I've gotten the flags/method from the posts of others, so please let me know if I can do this better!)
dvFlag1 = c4d.DRAWFLAGS_ONLY_ACTIVE_VIEW #Only redraw the active view dvFlag2 = c4d.DRAWFLAGS_NO_THREAD #Synchronous call dvFlag3 = c4d.DRAWFLAGS_NO_REDUCTION #Ignore the redraw limit dvFlag4 = c4d.DRAWFLAGS_STATICBREAK #Used to poll ESC key for NO_THREAD pos = [] pos.append(animObj.GetRelPos()) #Record the object's current position doc.SetTime(c4d.BaseTime(prevFrame, FPS)) #Move to the defined prevFrame frame value c4d.DrawViews(dvFlag1|dvFlag2|dvFlag3|dvFlag4) #Redraw the veiwport with flags defined above c4d.GeSyncMessage(c4d.EVMSG_TIMECHANGED) #SyncMessage to C4D to update timeline pos.append(animObj.GetRelPos()) #Record the object's relative position doc.SetTime(c4d.BaseTime(nextFrame, FPS)) #Move to the defined nextFrame frame value c4d.DrawViews(dvFlag1|dvFlag2|dvFlag3|dvFlag4) #Redraw the veiwport with flags defined above c4d.GeSyncMessage(c4d.EVMSG_TIMECHANGED) #SyncMessage to C4D to update timeline pos.append(animObj.GetRelPos()) #Record the object's relative position doc.SetTime(c4d.BaseTime(cFrame, FPS)) #Move playhead back to current frame
In a perfect world, the values would continuously update in some User Data fields on the tag as I preview an animation--but I'd obviously run into issues if I want to both play-back the animation AND need to have Python jump back and forth to retrieve position values.
Any help or thoughts are greatly appreciated!
Marcus -
On 10/08/2015 at 05:44, xxxxxxxx wrote:
Hello,
indeed, to get the position of an object at a certain time you have to animate the document. To do this I think the most simple way is to call ExecutePasses() after you set the time. When you just want to access the position then there is no reason to call DrawViews() or GeSyncMessage().
How exactly do you want to use that script, what do you mean with "store in a Tag"?
Best wishes,
Sebastian -
On 10/08/2015 at 06:31, xxxxxxxx wrote:
Thanks for the reply Sebastian.
I use C4D to animate concept designs of machinery, and it would be extremely helpful to have a real-time feedback of an object's velocity and acceleration in order to assess the real-world physical feasibility of creating these motions. (I actually was asking a few months back about understanding the equations representing the C4D f-curves in order to perform vel/accel derivations, but am trying a new approach)
Ultimately I want to use this script to read position values of an object (at 3 different times: previous frame, current frame, and next frame), and use these to calculate the velocity/acceleration. I can get it to work when not previewing the animation, but was wondering if it was possible to do it as the animation is playing--but that is where I run into issues since it didn't seem I could play the animation AND update it at different times in order to read the position. I won't have a chance to try this for a bit, but can I use SetTime() with ExecutePasses() while the animation is playing to do this?
By "store in a Tag," I just meant that I was planning to put this code in a Python Tag on a Null to serve as a motion calculator tool of sorts that I could keep with my .c4d projects. Currently the Tag has user data fields that are populated by the calculations performed in the Python code. My thought was to either have the code read the Tag's object as the object of interest, or just have a link field. But having it as a Tag made sense to me when trying to develop for the real-time update approach
Hope that makes sense!
--Marcus -
On 11/08/2015 at 00:33, xxxxxxxx wrote:
Hello,
please remember that Cinema is no real time application framework or simulation tool. So there may be limits to what you can do.
When the animation is playing the most simple thing to do would be to store the position of the object in the last frame and compare it to the position in the current frame. This ways you would get a velocity (but only during animation, like the "Position velocity" of the Xpresso Object node). The Python Tag is evaluated not just on every frame but every time the document is calculated, so also when you edit something in the scene. This means you would have to check if the frame has changed.
Another approach could be to cache all the data you need by calculating the desired values for each frame of the scene and storing them. Then you could display the desired result for the current frame.
Best wishes,
Sebastian -
On 11/08/2015 at 04:50, xxxxxxxx wrote:
Thanks again for looking into it, Sebastian. I realize it's a different application than that for which C4D is intended--we use it for sales and marketing as well, so it's a broad spectrum! As an engineer by training, my role is to bridge the gap between conceptual animation and hard engineering numbers.
But, it's good to know the limitations! I'll try to post an update once I get a chance to be back working on this--the cache idea occurred to me as well... just need to sit down and think about how I'd want to do it. (Other projects have come up in the meantime)
--MV
-
On 20/08/2015 at 06:51, xxxxxxxx wrote:
Okay, so I've been able to return to this for a bit, and have my code such that it will approximate the instantaneous velocity and acceleration of an object based on its change in position over the course of 3 frames. It's set up to accomodate my looping/cyclical animations running from 0-359 frames (thinking of frames as if they were degrees) which makes frame 360 the same as frame 0.
At this point, I think I've found the ideal real-time motion feedback is not possible within C4D's framework. The next best thing would be to have a "Motion Calculator" object with user data fields on it, such that when I click a button "Calculate Motion" my script runs once. When I've tried to implement it in a Python tag or a Python generator, I get: RuntimeError: maximum recursion depth exceeded.
Is it possible to restrict the Python Tag or Python Generator's refresh rate so it only runs the script once?
Here's what I've been working with, in part:FPS = doc.GetFps() #Get the FPS of the document obj = doc.GetActiveObject() #Grab the selected object if not obj: #If no object is selected print "No object selected" #Print message in the console fFrame = doc.GetMinTime().GetFrame(FPS) #Get the first frame "FFRAME" lFrame = doc.GetMaxTime().GetFrame(FPS) #Get the last frame "LFRAME" def RetrievePosData() : cTime = doc.GetTime() cFrame = cTime.GetFrame(FPS) #Get the current frame "CFRAME" if cFrame == 0: #If current frame = 0, prevFrame loops to end pTime = c4d.BaseTime(lFrame,FPS) nTime = c4d.BaseTime(cFrame+1,FPS) elif cFrame == lFrame: #If current frame = end, nextFrame loops to begining pTime = c4d.BaseTime(cFrame-1,FPS) nTime = c4d.BaseTime(fFrame,FPS) else: #Otherwise, prev/next Frame are just 1-index from current pTime = c4d.BaseTime(cFrame-1,FPS) nTime = c4d.BaseTime(cFrame+1,FPS) doc.SetTime(pTime) doc.ExecutePasses(None,True,True,True,c4d.BUILDFLAGS_0) pPos = obj.GetRelPos() doc.SetTime(nTime) doc.ExecutePasses(None,True,True,True,c4d.BUILDFLAGS_0) nPos = obj.GetRelPos() doc.SetTime(cTime) doc.ExecutePasses(None,True,True,True,c4d.BUILDFLAGS_0) cPos = obj.GetRelPos() pos = [pPos, cPos, nPos] #Store PREVIOUS-Position, CURRENT-Position, and NEXT-Position return pos def CalcPVA(pos) : """ APPROXIMATION OF FIRST AND SECOND DERIVATIVE Position --> f(t) Velocity --> f'(t) ~= [f(t+h)-f(t-h)]/2h Acceleration --> f"(t) ~= [f(t+h)-2f(t)+f(t-h)]/h^2 """ pPos = pos[0] #Set the previous position value = f(t-h) cPos = pos[1] #Set the current position value = f(t) nPos = pos[2] #Set the next position value = f(t+h) vel = (nPos - pPos)/2 #Approx velocity --> in/frame (VECTOR) accel = (nPos-2*cPos+pPos) #Approx acceleration --> in/frame^2 (Vector) return vel, accel def main() : if obj: (pos) = RetrievePosData() (vel, accel) = CalcPVA(pos) print "Velocity is %3.3f in/frame" %vel.GetLength() print "Acceleration is %3.3f in/frame^2" %(accel, gs) if __name__=='__main__': main()
-
On 21/08/2015 at 00:06, xxxxxxxx wrote:
Hello,
a generator or a tag are executed every time the scene is calculated. This is when a new frame is started or when anything in the scene changed, for example the position of the current camera. So it is intentional that generators and tags are executed very often when you edit the scene interactively.
You could store the current frame in your script and compare that stored frame every time when the script is executed with the new given frame. Then you could executed you code only when the frame changed. If you do not limit the execution of your code you run into infinite recursions, as you describe.
Best wishes,
Sebastian -
On 21/08/2015 at 04:51, xxxxxxxx wrote:
Sebastian,
I'm not sure I understand how one could "store" values in the script, since I assumed all the variables are cleared each time the script is run. I worked with Matlab prior to any experience with Python, and we always set it up to "clear all" variables between runs--I just assumed Python would default to be the same.
I guess limiting the execution of the code is exactly what I was hoping to do, while still retaining the convenience of using a generator/tag as the container for the code and user data.
What might the structure of the code look like as you are describing it? As stated above, I'm not seeing how I would approach "storing" values between executions of the code, and would that idea still work with the python generator/tag?
I'll just note that the one method I had been trying (but still was running into infinite recursions) was to have a Boolean check box
op[c4d.ID_USERDATA,33]
that would turn itself off immediately after the main body of code is run... but it still wants to evaluate the bit within the if statement despite my efforts to restrict it. Do you know why I am still running into the infinite recursion even with that Boolean in place?
if op[c4d.ID_USERDATA,33] == True: print pTime.GetFrame(FPS), cTime.GetFrame(FPS), nTime.GetFrame(FPS) if obj: doc.SetTime(pTime) doc.ExecutePasses(None,True,True,True,c4d.BUILDFLAGS_INTERNALRENDERER) pPos = obj.GetRelPos() doc.SetTime(nTime) doc.ExecutePasses(None,True,True,True,c4d.BUILDFLAGS_INTERNALRENDERER) nPos = obj.GetRelPos() doc.SetTime(cTime) doc.ExecutePasses(None,True,True,True,c4d.BUILDFLAGS_INTERNALRENDERER) cPos = obj.GetRelPos() op[c4d.ID_USERDATA,33] = False
As a side-note, what exactly is the purpose of the BuildFlags? I just picked one that seemed to make sense, but otherwise it was a shot in the dark.
Thanks!
Marcus -
On 21/08/2015 at 08:38, xxxxxxxx wrote:
Hello,
using a user data parameter would have been my suggestion to store data. You should be able to set and get proper values of a user data parameter. Have you tried to store the current frame this way?
The build flags define the context of the operation. Some functions may work differently when the current rendering process is done for internal rendering (in the viewport) or external rendering (in the picture viewer etc.).
Best wishes,
Sebastian -
On 21/08/2015 at 09:28, xxxxxxxx wrote:
I had not tried storing the current time in a user data field, but wouldn't that amount to the same thing as assigning the value to a variable in the code? I may just be misunderstanding what you are describing, but it sounds to me like storing it in user data would look like this:
cTime = doc.GetTime() cFrame = cTime.Get()*FPS op[c4d.ID_USERDATA,34] = cTime.GetFrame(FPS)
But then to get the stored value, am I not just doing the reverse operation of:
cTime = op[c4d.ID_USERDATA,34]
Which ( I think...) would leave me in the same boat in which I started. Again, sorry if I've missed the mark on your intended approach. But I'm also still confused why I'm falling into an infinite recursion with this bit of code:
cTime = doc.GetTime() #Current time cFrame = cTime.Get()*FPS #Current frame pTime = c4d.BaseTime(cFrame-1,FPS) #Previous Frame's time nTime = c4d.BaseTime(cFrame+1,FPS) #Next Frame's time doc.SetTime(pTime) doc.ExecutePasses(None,True,True,True,c4d.BUILDFLAGS_INTERNALRENDERER) pPos = obj.GetRelPos() doc.SetTime(nTime) doc.ExecutePasses(None,True,True,True,c4d.BUILDFLAGS_INTERNALRENDERER) nPos = obj.GetRelPos() doc.SetTime(cTime) doc.ExecutePasses(None,True,True,True,c4d.BUILDFLAGS_INTERNALRENDERER) cPos = obj.GetRelPos()
I had thought that if the playhead is not moving, this code should run with no problem. From the script manager it does (and works like a charm!). But once it's in a generator/tag, Cinema locks up (When it's not giving me the infinite recursion error).
-
On 23/08/2015 at 23:48, xxxxxxxx wrote:
Hello,
ExecutePasses() executs the whole document with everything in it, including all tags. This means you Python tag calls ExecutePasses() which in return calls your Python tag wich again calls ExcecutePasses() etc.
A script in the script manager is not part of the document. So calling ExecutePasses() will not execute that script again.
Best wishes,
Sebastian -
On 24/08/2015 at 05:48, xxxxxxxx wrote:
Ah... okay; so if I'm understanding correctly (in my case) the tag runs, hits the first ExectuePasses() command, at which point the process starts over, such that it never actually hits the Boolean command in order to quit? Thus you fall into the infinite recursion I guess. Funny thing is that sometimes I get the infinite recursion error, but sometimes C4D just locks up.
I'm going to go back to my original method of updating the view-port to see if I can't get that to work with my Boolean switch in the UserData... will let you know if I make any progress.
Thanks again, Sebastian!
--MV