How to properly update and reset deform cache.



  • I am writing some code that basically loops through a list of controller handles And for each handle it basically moves it one unit via SetRelPos(c4d.Vector(0,0,-1)).

    These controls are driving a spline which drives a spline deformer.

    So in my code I first store all the points of my mesh as a default state to compare against. Then when I move the handle I do getDeformCache to get the deformed state of the verts and then basically calculate a delta between the deformed state and the original state. Then I set the handle back so its original state again

    I was running into strange issues where the deform cache was not Updating and so I was wondering what sort of updates or refreshes must i do in order to properly get the deformed states and reset them? Here's some example code of what I am doing

    obj=op
    #Store the initial points
    origPoints=[(x*obj.GetMg()).z for x in obj.GetAllPoints()]
    
    #Set the controller's position
    null.SetRelPos(c4d.Vector(0,0,-1))
    
    #Get the Deform Cache and get the points from the deformed cache
    deformed=obj.GetDeformCache()
    deformedPoints=[(y*deformed.GetMg()).z for y in deformed.GetAllPoints()]
    
    deltas=[abs(x-origPoints[id]) for id, x in enumerate(deformedPoints)]
    
    
    


  • Hi,

    welcome to the forum. Unfortunately your first post is a bit ambiguous, since you do not clearly state what you are doing.

    1. What language are you using? I assume Python?
    2. What is your code? A bunch of scripts and scripting nodes or are these all plugins?
    3. You should provide some minified code, as there are more ambiguities (I won't list them all, but the important point is how exactly your two parts, the spline vertex control stuff and the other part, are interlinked).

    But with some guess work the mostly likely answer to your question is:

    When you have some kind of ObjectData node, its caches will not be rebuild until Cinema executes the cache building pass on the document that node is attached to. If you cannot to wait for the document it is attached to update the node automatically, you could copy the node to a temporary document (and all other nodes that are relevant to its caches) and execute the cache pass on that document via BaseDocument.ExecutePasses. But generally speaking this is a rather unfavourable approach. You should design whatever you are doing in such a way that you do not have to do that.

    Cheers,
    zipit



  • Hey there,

    Yes sorry I wrote my original question from my phone.

    I am using python. This is just a simple script. It is meant to compare the difference in point position from deformed to non deformed object, take that delta, and convert it to a vertex map(for now).

    Because I am moving the object only 1 unit, in theory the deltas should never exceed 1.0. However, I cannot seem to get the delta's right. The vertex map is often just coming in blank. Or the values are so small.

    Testing this on a cube, and walking through it manually, the starting Pose is at -100 for a point, then when I move the null it becomes -100.538, yet the delta is saying it's 0.0. but others are some rediculously small number.

    So I'm confused why this is, and how to get correct data, because if I did a current state to object on the mesh and instead of using the deformer compared the two meshes, it would be correct.



  • Hi,

    as I said, you have to build the caches if you want to modify some kind of deformer and then access its results. As an example:

    """Run this in the script manager.
    """
    
    import c4d
    
    # Caches are evaluated in passes, just instantiating or modifying
    # something does not mean that the caches are being build.
    
    # A parametric sphere object.
    sphere = c4d.BaseList2D(c4d.Osphere)
    # There are no caches yet.
    print "The cache of our sphere has not been build yet:", sphere.GetCache()
    
    # We have to build the caches manually if we want to access them in this
    # script. We will be using this function.
    def build_caches(op):
        """Builds the caches for a node and returns a clone that contains the caches.
        
        Args:
            op (c4d.BaseObject): The node to build the caches for.
        
        Returns:
            c4d.BaseObject: The clone with the caches.
        """
        # Nodes must not be attached to more than one document, so we
        # need to clone our node first. 
        clone = op.GetClone(c4d.COPYFLAGS_NONE)
        # Create a temporary document and execute the cache pass with
        # our clone attached to the temporary document.
        doc = c4d.documents.BaseDocument()
        doc.InsertObject(clone)
        doc.ExecutePasses(bt=None, animation=False, expressions=False, 
                          caches=True, flags=c4d.BUILDFLAGS_NONE)
        # Remove the clone and return it.
        clone.Remove()
        return clone
    
    # Now we can build the caches for our sphere.
    clone = build_caches(sphere)
    print "The static cache for the parametric sphere:", clone.GetCache() 
    
    # For simplicity sake we will take the static cache of our sphere 
    # (otherwise the deform cache would be attached to the static cache
    # of the sphere) for further examples.
    sphere = clone.GetCache()
    
    # We now create a deformer and attach it to our sphere.
    deformer = c4d.BaseList2D(c4d.Obulge)
    deformer[c4d.DEFORMOBJECT_STRENGTH] = .33
    deformer.InsertUnder(sphere)
    # But just like before, the caches have not been (re)build yet. So we 
    # build them again. This will also apply when you would just modify
    # the strength of an existing deformer.
    clone = build_caches(sphere)
    # Our freshly build deform cache:
    print "And its deform cache:", clone.GetDeformCache()
    
    # Now we have fully build caches for the current state of our object.
    # Computing the deltas is relatively easy now.
    
    def compute_delta_map(node):
        """Computes the normalized delta between the static and deformed cache of
        a polygon object.
        
        The normalized deltas are written to a vertex map and attached to the
        passed node.
        
        Args:
            node (c4d.PolygonObject): The node to compute the delta for.
        
        Returns:
            c4d.PolygonObject: The passed node with the deltas vertex map attached.
        """
        if not isinstance(node, c4d.PolygonObject):
            return TypeError(node)
        if not isinstance(node.GetDeformCache(), c4d.PolygonObject):
            return AttributeError("No deform cache present.")
    
        # Calculate the normalized deltas.
        static_points = node.GetAllPoints()
        deform_points = node.GetDeformCache().GetAllPoints()
        deltas = [(b-a).GetLengthSquared() 
                  for a, b in zip(static_points, deform_points)]
        max_delta = max(deltas)
        weights = [d / max_delta for d in deltas]
        # Create the vertex map.
        tag = c4d.VariableTag(c4d.Tvertexmap, node.GetPointCount())
        tag.SetAllHighlevelData(weights)
        node.InsertTag(tag)
        return node
    
    # Computes the deltas between the static and deformed cache.
    node = compute_delta_map(clone)
    # Insert the object.
    doc.InsertObject(node)
    c4d.EventAdd()
    

    Cheers,
    zipit



  • Thanks for the reply and the code. I don't fully understand what is happening. It is still doing the odd thing of sometimes working and sometimes not.

    In your file you make an object and then build the cache yourself. But what happens if I have a scene already, and am not creating the stuff?

    I have this file as a simplified test: https://www.dropbox.com/s/yafk8a0syry52sc/test_0001.c4d?dl=0

    And I am running this code(Make sure to select the cube before running it):

    import c4d
    from c4d import gui
    # Welcome to the world of Python
    
    
    # Script state in the menu or the command palette
    # Return True or c4d.CMD_ENABLED to enable, False or 0 to disable
    # Alternatively return c4d.CMD_ENABLED|c4d.CMD_VALUE to enable and check/mark
    #def state():
    #    return True
    
    # Main function
    def build_cache(op):
        """Builds the caches for a node and returns a clone that contains the caches.
    
        Args:
            op (c4d.BaseObject): The node to build the caches for.
    
        Returns:
            c4d.BaseObject: The clone with the caches.
        """
        # Nodes must not be attached to more than one document, so we
        # need to clone our node first.
        clone = op.GetClone(c4d.COPYFLAGS_NONE)
        # Create a temporary document and execute the cache pass with
        # our clone attached to the temporary document.
        doc = c4d.documents.BaseDocument()
        doc.InsertObject(clone)
        doc.ExecutePasses(bt=None, animation=False, expressions=False,
                          caches=True, flags=c4d.BUILDFLAGS_NONE)
        # Remove the clone and return it.
        clone.Remove()
        return clone
    
    def main():
        obj=op
        origPoints=[x.z for x in obj.GetAllPoints()]
        print origPoints
        null=doc.SearchObject("mod_0")
        null.SetRelPos(c4d.Vector(0,0,-1.0))
    
        clone=build_cache(obj)
        deformed=clone.GetDeformCache()
        deformedPoints=[x.z for x in clone.GetAllPoints()]
        print deformedPoints
        deltas=[abs(x-origPoints[id]) for id, x in enumerate(deformedPoints)]
    
        print deltas
        vMap=obj.MakeVariableTag(c4d.Tvertexmap, obj.GetPointCount())
        obj.InsertTag(vMap)
        vMap.SetName(null.GetName())
        vMap.SetAllHighlevelData(deltas)
    
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    But it is still not working. It basically prints the same sets of points which results in a zero delta. I'm sorry if I am being dense I just don't fully understand what's happening with the caches and when they happen and when I need to fire new ones etc. Long term, this script would be cycling through 12 or so controllers(ie, move 1 unit, get the deformed cache, reset back to 0, reset the cache, repeat per controller), so I want to just get an understanding of what's happening and how to set things properly.



  • Hi,

    as also stated earlier, you have to copy over everything into the temporary document that is relevant for the cache building of your node. If your node's cache is dependent on the the document time, you will have to also set the document time. If your node's cache is influenced by some expressions (tags), that you will also have to execute the expressions pass on the document. In my example copying over only the node works, because everything relevant is parented to the node.

    Cheers,
    zipit



  • I'm sorry, I'm still not following exactly.

    In my test file, I have a cube, a spline deformer, some nulls, and an Xpresso tag. So what all do I need to cache? Each one of those things? only the Cube but with the Expression flag set to True? This is kind of mind blowing how hard this is. I was hoping it would be as simple as an EventAdd or a CoreMessage being sent to refresh everything and pull in the DeformCache. I don't even need this to be via cached data, per se. I just need to be able to get the result of a mesh as a result of moving something or changing a deformer. So I just assumed I had to be working on the dformer cache. Maybe I can use the sendModelingCommand to Current State to Object the thing and get the mesh that way and remove it, I dunno.

    I just cannot seem to wrap my head around what you are saying. I'm sorry if I'm being super dense.



  • Hi @BretBays, this is not clear to us but you are executing the script from the script manager?
    If so then why not directly animate the current document to one frame +1. The basedocument_read_animated_mesh.py example may help you in this regards.

    Note that you are moving your object in local space (SetRelPos) while reading the object in global space, this may also introduce some inconsistencies.

    Cheers,
    Maxime.



  • @m_adam said in How to properly update and reset deform cache.:

    Hi @BretBays, this is not clear to us but you are executing the script from the script manager?
    If so then why not directly animate the current document to one frame +1. The basedocument_read_animated_mesh.py example may help you in this regards.

    Note that you are moving your object in local space (SetRelPos) while reading the object in global space, this may also introduce some inconsistencies.

    Cheers,
    Maxime.

    Hey Maxime,

    Yes, I am running this from the script manager currently. The plan could be this would be a script run from a button click(Like in the mixamo template). I tried what you said, but it still is not working properly.

    Here's what I was trying :

    import c4d
    from c4d import gui
    # Welcome to the world of Python
    
    
    # Script state in the menu or the command palette
    # Return True or c4d.CMD_ENABLED to enable, False or 0 to disable
    # Alternatively return c4d.CMD_ENABLED|c4d.CMD_VALUE to enable and check/mark
    #def state():
    #    return True
    
    def main():
        obj=op
        origPoints=[x.z for x in obj.GetAllPoints()]
        print origPoints
        null=doc.SearchObject("mod_0")
        null.SetRelPos(c4d.Vector(0,0,-1.0))
    
        #-----START FROM The Basedocument animate document example
        doc.SetTime(c4d.BaseTime(1, doc.GetFps()))
    
        # Updates timeline
        c4d.GeSyncMessage(c4d.EVMSG_TIMECHANGED)
    
        # Redraws the viewport and regenerate the cache object
        c4d.DrawViews(c4d.DRAWFLAGS_ONLY_ACTIVE_VIEW | c4d.DRAWFLAGS_NO_THREAD | c4d.DRAWFLAGS_NO_REDUCTION | c4d.DRAWFLAGS_STATICBREAK)
        #-----END The Basedocument animate document example
    
        deformed=obj.GetDeformCache()
        deformedPoints=[x.z for x in obj.GetAllPoints()]
        print deformedPoints
        deltas=[abs(x-origPoints[id]) for id, x in enumerate(deformedPoints)]
    
        print deltas
        vMap=obj.MakeVariableTag(c4d.Tvertexmap, obj.GetPointCount())
        obj.InsertTag(vMap)
        vMap.SetName(null.GetName())
        vMap.SetAllHighlevelData(deltas)
    
        doc.SetTime(c4d.BaseTime(0, doc.GetFps()))
    
        # Updates timeline
        c4d.GeSyncMessage(c4d.EVMSG_TIMECHANGED)
    
        # Redraws the viewport and regenerate the cache object
        c4d.DrawViews(c4d.DRAWFLAGS_ONLY_ACTIVE_VIEW | c4d.DRAWFLAGS_NO_THREAD | c4d.DRAWFLAGS_NO_REDUCTION | c4d.DRAWFLAGS_STATICBREAK)
        doc.ExecutePasses(bt=None, animation=True, expressions=True, caches=True, flags=c4d.BUILDFLAGS_NONE)
    
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    I've tried with forcing a full redraw. Nothing One thing I did notice is the file I linked above didn't have the modifying spline set for the spline deformer so that wasn't helping things, I'm sure so try this file(https://www.dropbox.com/s/yafk8a0syry52sc/test_0001.c4d?dl=0). But still that isn't working either. I am at a loss. I am doing something dumb.



  • Any one have any added input with how to handle some of this? I plan on trying to ensure that the curve, the nulls, deformer, and mesh are all copied into the temp doc as mentioned before to create the cache. But if that doesn't work, any other ideas?



  • First of all, I'm sorry I simply forget your topic, so thanks for bumping it again.

    Regarding your question, I'm really not sure about what you want to achieve. So maybe try to explain what you want to achieve with simple step by step.

    Anyway, I will try to answers your question about how to retrieve the delta from a non-deformed geometry to a deformed geometry.
    Keep in mind that all points position retrieved by GetAllPoints() are local to the current object matrix, so doing a SetRelPos, on the object will most likely not affect the point position returned since it moves the whole object matrix.
    So here a quick example that properly retrieves the delta from a deformed object and its normal version.

    import c4d
    
    def main():
        # Get the current world position
        allPtCurrentFrame = op.GetDeformCache().GetAllPoints()
        mgCurrentFrame = op.GetDeformCache().GetMg()
        
        # Move the mod +10 in Z in world position
        mod = doc.SearchObject('mod_1')
        modMat = mod.GetMg()
        modMat.off += c4d.Vector(0, 0, 10)
        mod.SetMg(modMat)
        
        # change the frame
        doc.SetTime(c4d.BaseTime(1, doc.GetFps()))
        buildflag = c4d.BUILDFLAGS_NONE if c4d.GetC4DVersion() > 20000 else c4d.BUILDFLAGS_0
        doc.ExecutePasses(None, True, True, True, buildflag)
        
        # Get the next world position
        allPtNextFrame = op.GetDeformCache().GetAllPoints()
        mgNextFrame = op.GetDeformCache().GetMg()
    
        # Calculate the difference in both local space and global space
        diffLocalSpace = [allPtNextFrame[ptID] - x for ptID, x in enumerate(allPtCurrentFrame)]
        diffGlobalSpace = [(allPtNextFrame[ptID] * mgNextFrame) - (x * mgCurrentFrame) for ptID, x in enumerate(allPtCurrentFrame)]
        
        # For each axes create a Vertex map
        componentNames = ["x", "y", "z"]
        for componentId, componentName in enumerate(componentNames):
            # First find the maximum delta for the current component
            maxLocalSpace  = 1
            maxGlobalSpace = 1
            for ptLocal in diffLocalSpace:
                maxLocalSpace = max(maxLocalSpace, ptLocal[componentId])
                
            for ptGlobal in diffGlobalSpace:
                maxGlobalSpace = max(maxGlobalSpace, ptGlobal[componentId])
                
            # Normalize all of them from 0 to 1
            normalizedLocal  = [pt[componentId] / float(maxLocalSpace)  for pt in diffLocalSpace]
            normalizedGlobal = [pt[componentId] / float(maxGlobalSpace) for pt in diffGlobalSpace]
                
            # Create Tag for local
            vMapLocal = op.MakeVariableTag(c4d.Tvertexmap, op.GetPointCount())
            op.InsertTag(vMapLocal)
            vMapLocal.SetName("{0}_local".format(componentName))
            vMapLocal.SetAllHighlevelData(normalizedLocal)
        
            # Create Tag for Global
            vMapGlobal = op.MakeVariableTag(c4d.Tvertexmap, op.GetPointCount())
            op.InsertTag(vMapGlobal)
            vMapGlobal.SetName("{0}_global".format(componentName))
            vMapGlobal.SetAllHighlevelData(normalizedGlobal)
            
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    Cheers,
    Maxime.


Log in to reply