Force cache rebuild in python
-
Hello,
I'm creating a generator and adding a modifier from a python script, but before the script ends, I need to access some data from the modifier that is calculated in ModifyObject(), when the generator deform cache is built. But I can't make it actually generate the cache before the end of the script. From the logs, I can see the cache is generated only after the script ends.
Here's some snippets of what I'm trying...
gen = c4d.BaseObject(GENERATOR_ID) doc.InsertObject(gen) mod = c4d.BaseObject(MODIFIER_ID) doc.InsertObject(mod, gen) # This parameter is 50.0 by default len = mod.GetParameter(mod_LengthAttribute, c4d.DESCFLAGS_GET_0) print "mod_LengthAttribute 1 = "+str(len) # flag an update? no effect gen.Message(c4d.MSG_UPDATE); # Animate the object? no effect time = c4d.BaseTime(0) doc.AnimateObject(gen,time,0) # I understand this should rebuild the cache, but no effect doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_0) c4d.EventAdd() #c4d.EventAdd(c4d.EVENT_ANIMATE) # does not build too # Tried this too, no effect #modelingSettings = c4d.BaseContainer() #modelingSettings[c4d.MDATA_CURRENTSTATETOOBJECT_NOGENERATE] = True #res = c4d.utils.SendModelingCommand(c4d.MCOMMAND_CURRENTSTATETOOBJECT, [gen], c4d.MODELINGCOMMANDMODE_ALL, modelingSettings, doc) # Should be seeing something here, but it prints None print "GetDeformCache = " + str(gen.GetDeformCache()) # ModifyObject() changes this to 100.0, but console still displays 50.0 len = mod.GetParameter(mod_LengthAttribute, c4d.DESCFLAGS_GET_0) print "mod_LengthAttribute 2 = "+str(len)
After the script ends, I can see some debug messages from the modifier, and I can see the parameter I'm testing
mod_LengthAttribute
have changed.Is it possible to force my generator to rebuild before the script ends?
Or is there a python command to "wait until next cycle" so the document can update and before it continues? -
Hi @rsodre thanks for reaching out us.
With regard to your issue, first it's relevant to highlight that
EventAdd()
actually push an event in the Cinema queue. Because of the multi-threading system, the event evaluation can't be exactly defined on a temporal level so it's likely that when the next line in the script it's executed the objects' cache have not yet built.Differently it's for
BaseDocument::ExecutePasses()
(see here in the BaseDocument Manual about Animate) where depending on the generators and on the deformer you could end up in calling up multipleExecutePasses
to properly evaluate the scene.Last but not least I suggest you to check for the snippet included the BaseObject::GetCache() to properly access a generator's cache
In the code below I've used it once and the cache is right there
def DoRecursion(op): tp = op.GetDeformCache() if tp is not None: print "\tvalid deformed cache" DoRecursion(tp) else: tp = op.GetCache() if tp is not None: print "valid cache" DoRecursion(tp) else: if not op.GetBit(c4d.BIT_CONTROLOBJECT): if op.IsInstanceOf(c4d.Opolygon): print "\t\t",op tp = op.GetDown() while tp is not None: DoRecursion(tp) tp = tp.GetNext() # Main function def main(): gen = c4d.BaseObject(c4d.Ocube) doc.InsertObject(gen) gen.SetParameter(c4d.PRIM_CUBE_SUBY, 25, c4d.DESCFLAGS_SET_NONE) mod = c4d.BaseObject(c4d.Obend) doc.InsertObject(mod, gen) # set the modifier strength value mod.SetParameter(c4d.DEFORMOBJECT_STRENGTH, c4d.utils.DegToRad(50), c4d.DESCFLAGS_SET_NONE) # evaluate the scene doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_0) # retrieve the cache DoRecursion(gen) # push into the Cinema 4D event queue c4d.EventAdd()
Best, Riccardo
-
@r_gigante I see, your script works fine with me, but I think my modifier take too long to be cached right away when I do the same.
Using some threading I can wait for the cache to build. But instead of sleeping would be better to detect from the object or modifier if they are completely finished. Can I get that?
def MakeObject(): # Generate the object and execute passes def RunAfterBuild(): # process built object def ExecuteAfterSeconds(seconds,callback): time.sleep(seconds) callback() object = MakeObject() t = Thread(target=ExecuteAfterSeconds, args=(1,RunAfterBuild)) t.start()
-
Hi @rsodre, may I ask you in which case you think a modifier is not correctly built? It seems from my research there is no flag/message sent after a cache is built.
But before to ask the confirmation to the development team, I would like to understand your issue since I'm not able to reproduce it.
Here with a pretty intensive cube and generator/deformer, it's working nicely. Do you have a precise example where it does not work?import c4d def DeformedPolygonCacheIterator(op): """ A Python Generator to iterate over all PolygonCache of passed BaseObject :param op: The BaseObject to retrieves all PolygonObject cache. """ if not isinstance(op, c4d.BaseObject): raise TypeError("Expected a BaseObject or derived class got {0}".format(op.__class__.__name__)) # Try to retrieves the deformed cache of the object temp = op.GetDeformCache() if temp is not None: # If there is a deformed cache we iterate over him, a deformed cache can also contain deformed cache # e.g. in case of a nested deformer for obj in DeformedPolygonCacheIterator(temp): yield obj # Try to retrieves the cache of the Object temp = op.GetCache() if temp is not None: # If there is a cache iterate over its, a cache can also contain deformed cache # e.g. an instance, have a cache of its linked object but if this object is deformed, then you have a deformed cache as well for obj in DeformedPolygonCacheIterator(temp): yield obj # If op is not a generator / modifier if not op.GetBit(c4d.BIT_CONTROLOBJECT): # If op is a PolygonObject we return it if op.IsInstanceOf(c4d.Opolygon): yield op # Then finally iterates over the child of the current object to retrieves all objects # e.g. in a cloner set to Instance mode, all clones is a new object. temp = op.GetDown() while temp: for obj in DeformedPolygonCacheIterator(temp): yield obj temp = temp.GetNext() # Main function def main(): # Creates a subdivision surface gen = c4d.BaseObject(1007455) doc.InsertObject(gen) # Creates a cube obj = c4d.BaseObject(c4d.Ocube) obj[c4d.PRIM_CUBE_SUBX] = 100 obj[c4d.PRIM_CUBE_SUBY] = 100 obj[c4d.PRIM_CUBE_SUBZ] = 100 doc.InsertObject(obj, gen) # Creates a bend deformer mod = c4d.BaseObject(c4d.Obend) mod[c4d.DEFORMOBJECT_STRENGTH] = 1.45 doc.InsertObject(mod, obj) # Builds teh cache doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_INTERNALRENDERER) # Iterates the cache of the generator and creates a null for each point position masterNull = c4d.BaseObject(c4d.Onull) for obj in DeformedPolygonCacheIterator(gen): for pt in obj.GetAllPoints(): null = c4d.BaseObject(c4d.Onull) null.SetAbsPos(pt) null.InsertUnder(masterNull) doc.InsertObject(masterNull) c4d.EventAdd() # Execute main() if __name__=='__main__': main()
Cheers,
Maxime. -
@m_adam Thanks Maxime. Your example really shows that the cache must be built independent of how much processing it takes.
Leaves me knowing that there must be something wrong on my modifiers, and indeed there was. I was checking if
bt != nullptr
insideModifyObject()
. I remember I copied this from some example, and it was interrupting the modifier to process. When the script is over, it would fire again with bt filled and finish deforming.