Spline dynamics



  • Hi,

    I'm trying to get points position of the spline with softbody dynamics applied to it from within generator object.

    c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_CURRENTSTATETOOBJECT, list=[some_obj], mode=c4d.MODELINGCOMMANDMODE_ALL, doc=doc)
    

    is returning non-dynamics positions.
    The same is happen when you'll call "Current state to object" — dynamics would be just cancelled.

    How that might be done? I just want to dynamically obtain current spline deformation.

    Thanks!



  • Hi @baca,

    thank you for reaching out to us. A softbody simulation state of a PointObjecti s reflected in its cache. For a SplineObject this cache will be a LineObject which you have to convert to a SplineObject to properly insert into a document. But in the process of answering your question I did discover some wonkines going on with softbody simulations, splines and vertex weights. This issue is unrelated to the SDK and you cannot do anything about it. I have created a bug-report for it. You will find a very simple example illustrating the general approach at the end of this posting.

    Cheers,
    Ferdinand

    """Illustrates how to get the simulation state of a point object softbody
    simulation.
    
    Discussed in:
        https://plugincafe.maxon.net/topic/13159/spline-dynamics
    """
    
    import c4d
    from c4d import gui
    
    def main():
        """Entry point.
        """
        # Get out when the active object is not a SplineObject.
        if not isinstance(op, c4d.SplineObject):
            return
    
        # In the following we will get the cache of a spline object and reinsert
        # it into the document. The cache will be a LineObject which is normally
        # not meant to be inserted into a document, as it is the underlying
        # discretization of a SplineObject. But for visualization purposes this
        # should be enough. 
    
        # Currently soft-body animations for splines with a weightmap will ignore
        # deformers completely. For polygon objects you will find in the 
        # DeformChache() the simulated and deformed state of the object.
        cache = op.GetDeformCache()
        if cache is not None:
            # We insert a clone of the cache back into the document. We have to
            # clone it for reasons of ownership.
            doc.InsertObject(cache.GetClone(0))
            return
    
        # Get the simulated but not deformed (by deformers) cache of the object. 
        # This does work, but there is some wonkiness going on with splines and
        # softbodies in general. But this has nothing to do with the SDK. 
        cache = op.GetCache()
        if cache is not None:
            # We insert a clone of the cache back into the document. We have to
            # clone it for reasons of ownership.
            doc.InsertObject(cache.GetClone(0))
    
    
    if __name__=='__main__':
        main()
    


  • Hi @ferdinand,

    Thank you, appreciate your reply.

    So if Spline provided as an input then GetCache() returns LineObject which is great.

    Can you please highlight how to deal with Splines under the Cloner object?
    Cloner's deform cache in that case returns null with nondeformed splines:
    reds are with dynamics, white are from cache

    Test generator scene: py-dynamics-cache.c4d



  • Hi @baca,

    your solution does not work because you are not reaching deep enough into the cache of the object. You have to actually grab the LineObject data. In case of generators like a Cloner that will be a more complex structure. The complexity of Cinema 4D's caches has been discussed recently here.

    There is also the basedocument_read_animated_mesh.py github example which covers the topic of cache evaluation. Below you will find a version of your file which makes use of a part of that example to do what you want to do. But there are other hurdles to overcome, for details see attached file.

    Cheers,
    Ferdinand

    the file: py-dynamics-cache.c4d
    the code:

    """Example for retrieving the simulation caches of generators.
    
    As discussed in:
        https://plugincafe.maxon.net/topic/13159
    
    Related Python SDK examples:
        basedocument_read_animated_mesh_r16.py
    """
    
    import c4d
    
    
    def CacheIterator(op, types=None):
        """An iterator for the elements of a BaseObject cache.
    
        Handles both "normal" and deformed caches and has the capability to 
        filter by node type.
    
        Args:
            op (c4d.BaseObject): The node to walk the cache for.
            types (Union[list, tuple, None], optional): A collection of type IDs
            from one of which a yielded node has to be derived from. Will yield 
            all node types if None. Defaults to None.
    
        Yields:
            c4d.BaseObject: A cache element of op.
    
        Raises:
            TypeError: On argument type violations.
        """
        if not isinstance(op, c4d.BaseObject):
            msg = "Expected a BaseObject or derived class, got: {0}"
            raise TypeError(msg.format(op.__class__.__name__))
    
        if not isinstance(types, (tuple, list, type(None))):
            msg = "Expected a tuple, list or None, got: {0}"
            raise TypeError(msg.format(types.__class__.__name__))
    
        # Try to retrieve 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 CacheIterator(temp, types):
                yield obj
    
        # Try to retrieve the cache of the Object
        temp = op.GetCache()
        if temp is not None:
            # If there is a cache iterates 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 CacheIterator(temp, types):
                yield obj
    
        # If op is not a generator / modifier
        if not op.GetBit(c4d.BIT_CONTROLOBJECT):
            # Yield op if it is derived from one of the passed type symbols.
            if types is None or any([op.IsInstanceOf(t) for t in types]):
                yield op
    
        # Then finally iterates over the child of the current object to retrieve
        # 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 CacheIterator(temp, types):
                yield obj
            temp = temp.GetNext()
    
    
    def main():
        """Entry point.
        """
        # Get your user data.
        source = op[c4d.ID_USERDATA, 1]
        if source is None:
            return
    
        # A null to parent all the deformed splines to.
        null = c4d.BaseList2D(c4d.Onull)
    
        # Iterate over the cache of the object to get all the line objects
        # contained in the cache.
        for line in CacheIterator(source, types=[c4d.Oline]):
                    
            # We should really not return cloned LineObjects, as this was just a
            # simplification I made, but instead create new splines for them.
    
            # Compute the local transform for the new object.
            ml = ~source.GetMg() * line.GetMg()
            # Get all the points from the line object.
            points = line.GetAllPoints()
            # Create a new linear spline, copy all point data over and attach it 
            # to the null.
            spline = c4d.SplineObject(pcnt=len(points),
                                      type=c4d.SPLINETYPE_LINEAR)
            spline.SetAllPoints(points)
    
            if op[c4d.ID_USERDATA, 2]:
                spline = line.GetClone(0)
    
            spline.InsertUnder(null)
            spline.SetMl(ml)
    
            # This solution does not handle properly LineObject instances with 
            # multiple segments. This also affects the ability to evaluate if
            # the original spline was a closed spline. Both information can be
            # found in the Tsegment tag hosted by the cache generating 
            # SplineObject. For that you would have modify the CacheIterator
            # function to also return the cache generator node for each 
            # LineObject cache.
            # 
            # Or when you are sure that you only have 1-segmented splines and
            # want them to be always open/closed, you can of course also just
            # hard code your desired result.
    
        return null
    


  • @ferdinand thanks a lot, seems clear.
    More or less...



  • Hi @baca,

    if something remains unclear, please do not hesitate to ask. I know I was a bit cloudy here:

            # This solution does not handle properly LineObject instances with 
            # multiple segments. This also affects the ability to evaluate if
            # the original spline was a closed spline. Both information can be
            # found in the Tsegment tag hosted by the cache generating 
            # SplineObject. For that you would have modify the CacheIterator
            # function to also return the cache generator node for each 
            # LineObject cache.
            # 
            # Or when you are sure that you only have 1-segmented splines and
            # want them to be always open/closed, you can of course also just
            # hard code your desired result.
    

    but I cannot design the full functionality for you, since this fall out of the scope of support. You have to design the conditions of where you want this to work yourself. When you have then questions on how to do that in code, we will be more than happy to help you.

    I would however ask you to open a new thread if you for example struggle with the concrete design of how get everything out of a Cloner object cache you need, since this would be then a new topic.

    Cheers,
    Ferdinand



  • @ferdinand you were clear and example is pretty good, I got the idea.
    No need to write code for me ;)

    But it's not trivial overall, I just need to spend some time on this.

    Thanks again.