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?