Timeline commands ran via Python script does not have same result
-
Hi!
This is probably not worthy to be called a developing question, but I think this might be the best place to ask, just please beware that I have zero knowledge in Python or scripting for C4D
I'm trying to create a simple workflow script that rewinds the timeline to the start and then steps one frame forward, useful when doing particle sims for example. I then want to assign this script to be triggered by a keyboard shortcut so that these actions can be executed with a single button press instead of two.
Following basic tutorials I got it somewhat working, but the strange thing is that it yields slightly different results in comparison to if I execute the commands manually. It seems the particle sim is not fully reset if ran via the script, but I have no clue to why that might be. I'm hoping there is an obvious simple thing I have missed, does anyone know?
from typing import Optional import c4d doc: c4d.documents.BaseDocument # The active document op: Optional[c4d.BaseObject] # The active object, None if unselected def main() -> None: # Called when the plugin is selected by the user. Similar to CommandData.Execute. c4d.CallCommand(12501) # Go to Start c4d.EventAdd() c4d.CallCommand(12414) # Go to Next Frame c4d.EventAdd() """ def state(): # Defines the state of the command in a menu. Similar to CommandData.GetState. return c4d.CMD_ENABLED """ if __name__ == '__main__': main()
-
Hello @Daguna , welcome to the Plugin Café forum and the Cinema 4D development community, it is great to have you with us!
Getting Started
Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.
- Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
- Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
- Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.
It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: Asking Questions.
About your First Question
It is definitely worth calling a developer question.
First of all, script execution is a blocking operation and the result of the
c4d.EventAdd()
(docs) will be postponed. This means, that there's no reason to call it more than once.Second of all, you get different results because you need to update the simulation after you have changed the current timeline position. You can easily do this with the ExecutePasses() function. (However, I think running
c4d.EventAdd(c4d.EVENT_ANIMATE)
once instead of EventAdd() + ExecutePasses() would actually be sufficient here (docs))The third thing I would recommend is to calculate the time manually, as in this case you'd first calculate the position and then jump there in one turn, rather than jumping first to the very beginning and then jump one frame further. This is easily done using GetMinTime() function to get the time of the first frame in the document, adding the time that corresponds to one frame of the document (this is calculated based on document's fps setting: GetFps()), and then jump directly to this time using SetTime() function.
Please find the final script below.
By the way, there's an example basedocument_animate_r13.py that shows how to run an animation from within the script manager, however, for just a single frame jump the script below is just enough.
Cheers,
IliaThe script:
import c4d doc: c4d.documents.BaseDocument # The currently active document. def main() -> None: # Get the minimal time of the document (can potentially be different from 0) baseTimeMinimal = doc.GetMinTime() # Calculate time of the first frame: minimal time + 1 / FPS baseTimeFirstFrame = baseTimeMinimal + c4d.BaseTime(1, doc.GetFps()) # Set time of the first frame to the document doc.SetTime(baseTimeFirstFrame) # Ask document to recalculate expressions and caches (for the simulation to update) doc.ExecutePasses(None, False, True, True, c4d.BUILDFLAGS_NONE) # Ask cinema to update all the changes c4d.EventAdd() if __name__ == '__main__': main()
-
@i_mazlov Thank you very much for taking the time out to help me with this! Please excuse the late reply, I had neglected to activate email notifications
I have now tried the solution you provided but it gives the same result unfortunately.
Maybe it should be mentioned that I'm working with X-particles mostly, and perhaps something needs to be triggered for it aswell?
I'm thinking there must be a way to get exactly what these two manual buttons presses do into one script, since it's all code underneath anyhow, right?
Thanks again!
-
Hi @Daguna,
I think @i_mazlov misunderstand your purpose here, simulation run depend on time and need rest the time to zero, you want -1 here, you can just replace the "+" with "-", the frame will be set to -1 rather than 1.
Cheers~
DunHouimport c4d doc: c4d.documents.BaseDocument # The currently active document. def main() -> None: # Get the minimal time of the document (can potentially be different from 0) baseTimeMinimal = doc.GetMinTime() # Calculate time of the first frame: minimal time - 1 / FPS baseTimeFirstFrame = baseTimeMinimal - c4d.BaseTime(1, doc.GetFps()) # Set time of the first frame to the document doc.SetTime(baseTimeFirstFrame) # Ask document to recalculate expressions and caches (for the simulation to update) doc.ExecutePasses(None, False, True, True, c4d.BUILDFLAGS_NONE) # Ask cinema to update all the changes c4d.EventAdd() if __name__ == '__main__': main()
-
Hi @Daguna ,
Sorry to bad answer, I misread your target, see a new one here:
from typing import Optional import c4d doc: c4d.documents.BaseDocument # The active document op: Optional[c4d.BaseObject] # The active object, None if unselected def main(): # Retrieves BaseTime of frame 5, 20 start = 0 end = 1 # Loops through the frames for frame in range(start, end + 1): # Changes the time of the document doc.SetTime(c4d.BaseTime(frame, doc.GetFps())) # Updates timeline c4d.GeSyncMessage(c4d.EVMSG_TIMECHANGED) # Redraws the viewport and regenerate the cache object c4d.DrawViews(c4d.DRAWFLAGS_ONLY_ACTIVE_VIEW | c4d.DRAWFLAGS_NO_THREAD | c4d.DRAWFLAGS_STATICBREAK) # Pushes an update event to Cinema 4D c4d.EventAdd(c4d.EVENT_ANIMATE) if __name__ == '__main__': main()
-
Endless thanks @Dunhou, your last version works perfectly!
Final thing, I realized it needs an if statement to check if the playhead is playing forwards, and if so stop it. Command 12412 seems to be a "toggle" value, and I have tried to figure it out myself but without success.. Any chance you can help me with this last piece of the puzzle aswell?
My current attempt, that always seems to trigger:
if c4d.CallCommand(12412) == True: c4d.CallCommand(12412) # Play Forwards return
-
Hi @Daguna ,
You can check if the timeline is running with c4d.CheckIsRunning(). Just add a condition in before the mian().
Cheers~
DunHouif __name__ == '__main__': if c4d.CheckIsRunning(c4d.CHECKISRUNNING_ANIMATIONRUNNING): c4d.CallCommand(12412) # Play Forwards main()
-
Marvelous @Dunhou, thanks yet again!!
Here's the final script in case someone else has use for it:
from typing import Optional import c4d doc: c4d.documents.BaseDocument # The active document op: Optional[c4d.BaseObject] # The active object, None if unselected def main(): # Retrieves BaseTime of frame 5, 20 start = 0 end = 1 if c4d.CheckIsRunning(c4d.CHECKISRUNNING_ANIMATIONRUNNING) == True: c4d.CallCommand(12412) # Play Forwards # Loops through the frames for frame in range(start, end + 1): # Changes the time of the document doc.SetTime(c4d.BaseTime(frame, doc.GetFps())) # Updates timeline c4d.GeSyncMessage(c4d.EVMSG_TIMECHANGED) # Redraws the viewport and regenerate the cache object c4d.DrawViews(c4d.DRAWFLAGS_ONLY_ACTIVE_VIEW | c4d.DRAWFLAGS_NO_THREAD | c4d.DRAWFLAGS_STATICBREAK) # Pushes an update event to Cinema 4D c4d.EventAdd(c4d.EVENT_ANIMATE) if __name__ == '__main__': main()