Thinking Particles, Mograph Cache
-
On 08/10/2013 at 15:34, xxxxxxxx wrote:
I am having a challenge with Thinking Particles, Mograph Cache, and Python: All the code runs without errors and sets the correct keys, but I must be missing a key idea somewhere about thinking particles—probably in the sequence of what has to happen first, second, third, and so on. Any comments would be greatly appreciated.
To picture the scene, I have many towers in whose tops lights fly around like fireflies. These lights turn on and off at different frames for different durations, in either the same or in different towers.
I am using the Volume Emitter from the Content Browser—two for each tower, an upper and lower. I assign a unique particle group for the two emitters for each tower. I use one cloner for each tower, set to object mode to generate the lights, using the particle group assigned to the individual tower. From an xml database, I read in a series of values that tell when to turn on the particles from a given frame to another frame and for which tower. I set the emitters' type to Shot, keyframing the On parameter to True on one frame and then False on the next, and keyframe the Life parameter for the duration between frames. Then I bake the particles using the Mograph Cache Tag. Lastly, I delete the On parameter tracks of the emitters and set the On value to False, expecting that the cloner will use the cache and the emitter is no longer necessary.
The code works for a single tower, but not for two. With different settings, I can get one or the other tower working. For the tower that doesn't work, it seems that the particles were never turned on for that tower (the cache is empty, even though, as the script runs, I can see the cache window appear, showing the system is baking the clones). Or, with other settings, I've gotten odd results such as having too many particles appearing in the towers, or having some cached To and From frame areas with no data.
One key seems that I need to reset the timeline to 0, which I do in the Python code, but maybe someone can explain why this would be so and, if doing so resets the master particle system, does it do so asynchronously (which may account for the odd results) or synchronously, pausing the rest of the script until it recalculates the particles. And when is it "safe" to do this?Another key seems to be the state of the objects when the script begins. Was the particle system turned off or on when it started? Was there already a track? Were the cache tags empty? So I wrote a set up script to control the state.
On one occasion, it worked perfectly, but when I reset the starting values to re-run the program, it stopped working and I am guessing in the dark about what is required.Here is the relevant code for the setup:
def SetStartValues(tower) : topEmitter=GetChildByName(tower, "Glow_Dot_Emitter_Top") #set a unique random seed CreateKey(topEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(30)), random.randint(1,1000),0) bottomEmitter=GetChildByName(tower, "Glow_Dot_Emitter_Bottom") #set a unique random seed CreateKey(bottomEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(30)), random.randint(1,1000),0) #find the "on" track and delete it onTrack = topEmitter.FindCTrack(c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1))) if onTrack: onTrack.Remove() onTrack = bottomEmitter.FindCTrack(c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1))) if onTrack: onTrack.Remove() #turn the emitter on without a keyframe topEmitter[c4d.ID_USERDATA,1] = True bottomEmitter[c4d.ID_USERDATA,1] = True #Clear the cache glowDotCloner = GetChildByName(tower, "Glow Dots Cloner") cacheTag = glowDotCloner.GetFirstTag() cacheTag[c4d.MGCACHETAG_ACTIVE] = True cacheTag[c4d.MGCACHETAG_BAKESEQUENCE_ACTIVE] = True cacheTag[c4d.MGCACHETAG_LOOP] = False c4d.CallButton(cacheTag,c4d.MGCACHETAG_CLEARCACHE)
Here is the relevant code for setting the values:
def main() : #set up the databases of settings SetUpScene() #set up the default values at frame zero #import the xml databases and convert them into list objects masterFramesList = GetMasterXMLList() setsList = GetSetsXMLList() colorsList = GetColorsXMLList() dynamicsList = GetDynamicsXMLList() for frame in masterFramesList: towerSet = [] #get the set of towers if (frame.attrib["namedSet"] is not "") : found = False for name in setsList: if (name.attrib['SetName'] == frame.attrib["namedSet"]) : towerSet = CreateTowerSet(name.attrib["towers"]) print "Tower set " + frame.attrib["namedSet"] + " has been chosen for frame " + frame.attrib["beginFrame"] found = True break if (found == False) : print ("*******Set name "+frame.attrib["namedSet"]+" could not be found in setsList.") return else: towerSet = CreateTowerSet(frame.attrib["set"]) for tower in towerSet: #get the actual objects on the tower and set up the variables towerObj = doc.SearchObject(tower) #each tower has its own particle group lightName = GetLightName(frame.attrib["musicalLayer"]) lightObjName = lightName + " Obj" spotlightName = GetSpotlightName(frame.attrib["musicalLayer"]) spotlightShaderName = GetSpotlightShaderName(frame.attrib["musicalLayer"]) rampUpFrame = int(frame.attrib["beginFrame"])-9 startFrame = int(frame.attrib["beginFrame"]) endFrame = int(frame.attrib["endFrame"]) rampDownFrame = int(frame.attrib["endFrame"])+9 topEmitter = None bottomEmitter = None glowDotCloner = None cacheTag = None if (towerObj) : #get the different objects that need settings fireflyLight = GetChildByName(towerObj, "Glow_Dot_Light") #the light object for the cloner topEmitter = GetChildByName(towerObj, "Glow_Dot_Emitter_Top") #each tower has two emitters: both are assigned to a single particle group representing that tower bottomEmitter = GetChildByName(towerObj, "Glow_Dot_Emitter_Bottom") #different options to trigger the particles. Do they need to be turned on? #CreateKey(topEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), True,0) #CreateKey(bottomEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), True,0) #now set the fireflies, only if the layer is full (or maybe top, as well) if (frame.attrib["musicalLayer"] == "full") : particleLife = rampDownFrame-(rampUpFrame-1) #calculate the particle life CreateKey(fireflyLight,c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), 0.00,rampUpFrame) CreateKey(fireflyLight,c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), 1.84,startFrame) CreateKey(fireflyLight,c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), 1.84,endFrame) CreateKey(fireflyLight,c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), 0.00,rampDownFrame) #CreateKey(topEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), True,0) #to set up particles at the beginning? #CreateKey(topEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), False,1) #to set up particles at the beginning? #set a random seed CreateKey(topEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(30)), random.randint(1,1000),rampUpFrame) #turn on and off the emitter, which is set to Shot CreateKey(topEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), True, rampUpFrame) CreateKey(topEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), False,rampUpFrame + 1) CreateKey(topEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(6)), particleLife, rampUpFrame) #life of the particles #CreateKey(bottomEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), True,0) #to set up particles #CreateKey(bottomEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), False,1) #to set up particles #set a random seed CreateKey(bottomEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(30)), random.randint(1,1000),rampUpFrame) #turn on and off the emitter, which is set to Shot CreateKey(bottomEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), True, rampUpFrame) CreateKey(bottomEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), False,rampUpFrame + 1) CreateKey(bottomEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(6)), particleLife, rampUpFrame) #life of the particles print "Set the top and bottom emitters for frame " + str(startFrame) GoToFrame(0) #possibly to reset the particles? GoToFrame(1) GoToFrame(0) #Bake particles glowDotCloner = GetChildByName(towerObj, "Glow Dots Cloner") #each tower has one cloner cacheTag = glowDotCloner.GetFirstTag() cacheTag[c4d.MGCACHETAG_ACTIVE] = True cacheTag[c4d.MGCACHETAG_BAKESEQUENCE_ACTIVE] = True cacheTag[c4d.MGCACHETAG_LOOP] = False cacheTag[c4d.MGCACHETAG_BAKEFROM] = c4d.BaseTime(rampUpFrame,doc.GetFps()) cacheTag[c4d.MGCACHETAG_BAKETO] = c4d.BaseTime(rampDownFrame,doc.GetFps()) #CreateKey(cacheTag, c4d.DescID(c4d.DescLevel(c4d.MGCACHETAG_BAKEFROM)), c4d.BaseTime(rampUpFrame,doc.GetFps()), rampUpFrame) #other options to see what is needed #CreateKey(cacheTag, c4d.DescID(c4d.DescLevel(c4d.MGCACHETAG_BAKETO)), c4d.BaseTime(rampDownFrame,doc.GetFps()), rampUpFrame) c4d.CallButton(cacheTag,c4d.MGCACHETAG_BAKESEQUENCE) print "Baked sequence on " + towerObj.GetName() + " for frame "+ str(startFrame) #clear emitter tracks and turn off at this time as an option? #CreateKey(topEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), False,rampUpFrame) #CreateKey(topEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), False,rampUpFrame+1) #CreateKey(bottomEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), False,rampUpFrame) #CreateKey(bottomEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), False,rampUpFrame+1) #c4d.EventAdd(c4d.EVENT_FORCEREDRAW) #Now that the cloner cache tag is filled, remove the tracks. Flush keys as an alternative to make a difference? onTrack = topEmitter.FindCTrack(c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1))) if onTrack: onTrack.Remove() onTrack = bottomEmitter.FindCTrack(c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1))) if onTrack: onTrack.Remove() #CreateKey(topEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), False,0) #set key while keeping track as an alternative? #CreateKey(bottomEmitter, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)), False,0) topEmitter[c4d.ID_USERDATA,1] = False #set emitter to off bottomEmitter[c4d.ID_USERDATA,1] = False c4d.EventAdd(c4d.EVENT_FORCEREDRAW) if __name__=='__main__': main()
Thanks for your comments.
-
On 08/10/2013 at 17:56, xxxxxxxx wrote:
My first guess would be a threading problem. Have you tried to add something like that ?
for tower in towerSet: # ... wall of code ... c4d.CallButton(cacheTag,c4d.MGCACHETAG_BAKESEQUENCE) time.sleep(10)
The example does assume that the baking process does take about 10 seconds per tower. And yes that approach on handling threading problems is illegal in more than 26 countries
-
On 16/10/2013 at 12:53, xxxxxxxx wrote:
Thanks for your response. I waded into Threads and couldn't quite understand how to use the functions for what I needed to accomplish, but I solved the problem simply by removing the cache part of the script into a separate script and then running it separately, after all the other settings in the original script were made.
But for my own edification: Is the goal of the thread functions to work like a traffic cop, holding up parts of the script while another one runs, so that one can wait on another?