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 multiple ExecutePassesto 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 inside ModifyObject(). 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.


Log in to reply