Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Force cache rebuild in python

    Cinema 4D SDK
    r19 r20 python
    3
    5
    1.0k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • rsodreR
      rsodre
      last edited by rsodre

      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?

      1 Reply Last reply Reply Quote 0
      • r_giganteR
        r_gigante
        last edited by

        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

        rsodreR 1 Reply Last reply Reply Quote 1
        • rsodreR
          rsodre @r_gigante
          last edited by

          @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()
          
          1 Reply Last reply Reply Quote 0
          • M
            m_adam
            last edited by m_adam

            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.

            MAXON SDK Specialist

            Development Blog, MAXON Registered Developer

            rsodreR 1 Reply Last reply Reply Quote 0
            • rsodreR
              rsodre @m_adam
              last edited by

              @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.

              1 Reply Last reply Reply Quote 0
              • First post
                Last post