Dynamic VertexMap



  • Hello PluginCafe,

    I wanted to know whether it is possible to change the data count of a vertex map dynamically without always killing the tag and recreating it? I have a mesh that gets subdivided dynamically and I need the vertex map to always fit the mesh vertex count but I can't have the user to always re-link the vertex map on every change.

    Best Regards,
    Florian



  • Hi,

    this should not be necessary, since vertex maps are being automatically resized to match the point count of their hosting object. Could you provide some code or an example on where you did encounter a different behavior? Below you will find a script, that shows you how to access the high level data of a vertex map.

    import c4d
    
    
    def main():
        """
        """
        # Ensure that the current selection is a polygon object.
        if not isinstance(op, c4d.PolygonObject):
            return
    
        # Get the first vertex map of the object.
        vertextmaps = [tag for tag in op.GetTags()
                       if tag.CheckType(c4d.Tvertexmap)]
        if not vertextmaps:
            return
        vmap = vertextmaps[0]
    
        # Vertex maps are instances of c4d.VariableTag. VariableTag.GetDataCount()
        # returns the number of data elements of the instance, e.g. the number of
        # weights for a vertex map.
        print "vertex map data count before subdivision:", vmap.GetDataCount()
    
        # Subdivide the object once
        bc = c4d.BaseContainer()
        bc[c4d.MDATA_SUBDIVIDE_SUB] = 1
        res = c4d.utils.SendModelingCommand(
            command=c4d.MCOMMAND_SUBDIVIDE,
            list=[op],
            mode=c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
            bc=bc,
            doc=doc)
        # The vertex map has been automatically resized.
        print "vertex map data count after subdivision:", vmap.GetDataCount()
    
        # A list of weights with the length of the size of the vertex map, where
        # even IDs (i.e. the point IDs of the polygon object) are a weight of 1
        # and odd elements are a weight of 0.
        weights = [float(n % 2 == 0) for n in range(vmap.GetDataCount())]
    
        # Write the new weight data into our vertex map.
        vmap.SetAllHighlevelData(weights)
        # Update Cinema
        c4d.EventAdd()
    
    
    # Execute main()
    if __name__ == '__main__':
        main()
    
    

    Cheers
    zipit



  • Hi @neon please make use of the Q&A Functionality.

    Regarding your question I would ask you for more information regarding your context, are you in a CommandData, ObjectData?
    Normally as @zipit said the vertex map is automatically resized when obj.Message(c4d.MESSAGE_UPDATE) is called (which is normally done for each modeling operation).

    So maybe you simply need this as "subdivided dynamically" is very obscure and could mean a lot of things.

    Cheers,
    Maxime.



  • Hello,
    thanks m_adam and zipit for your replies.
    I am working in an ObjectData Plugin.
    I am loading a mesh into my ObjectData (in Init) and need to add modifiers/subdivision in GetVirtualObjects.
    After Zipits answer I noticed why it didn't work properly and that is that I added the tag to my ObjectData instead of the loaded mesh, the reason for that is that I would need the user to see the vertexmap but I can't expose the mesh.

    I have now changed it so the vertexmap is created on my internal mesh, and that scales just fine when subdividing, so in that regard my problem is solved.

    But I don't know if I am now able to expose the tag, and only the tag, somehow to the user.
    My Plugin uses the vertexmap internally and I want the user to be able to use it for materials etc.

    Is there any way?
    Thanks for your help so far!
    Best Regards,
    Florian



  • Hi @neon sorry for the delay, unfortunately, Cinema 4D is not designed this way.
    If you think about it, there is no tag that works by hierarchy inheritance except the selection tag. Which work by naming a selection. So it's a very special case.

    The main issue is Cinema 4D will, in any case, reset the data you define in the vertex map to the number of polygons. (and in this case, 0 since it's a generator).

    So here a workaround, but it's very experimental and can be buggy. So please don't use it in production as he does some really cross-thread things that are not allowed and that could crash Cinema 4D. But at least it can give you some ideas.
    pc_11963.c4d

    And the python code of the python generator.

    import c4d
    
    
    def Bl2DIterator(obj):
        """
        Iterates over any BaseList2D list
        """
        
        while obj:
            yield obj
            for opChild in Bl2DIterator(obj.GetDown()):
                yield opChild
            obj = obj.GetNext()
    
    def FindAllVetexShaderFromObj(obj, targetVertexMap):
        """
        Retrieve from each material assigned to `obj` all vertexMap shader
        where `targetVertexMap` is used.
        """
        allVertexShader = []
        
        for tag in obj.GetTags():
            if not tag.IsInstanceOf(c4d.Ttexture):
                continue
            
            mat = tag[c4d.TEXTURETAG_MATERIAL]
            if mat is None:
                continue
            
            for shader in Bl2DIterator(mat.GetFirstShader()):
                if shader.IsInstanceOf(c4d.Xvertexmap):
                    vMap = shader[c4d.SLA_DIRTY_VMAP_OBJECT]
                    if vMap is not None and vMap == targetVertexMap:
                        allVertexShader.append(shader)
        
        return allVertexShader
    
    def main():
        # Retrieve a copy of linked obj
        polygonObj = op[c4d.ID_USERDATA,1]
        if not isinstance(polygonObj, c4d.PolygonObject):
            return
        polygonObj = polygonObj.GetClone()
        polygonObj.SetMl(op.GetMg())
    
        # Retrieve vertex map from the copied object
        vMap = polygonObj.GetTag(c4d.Tvertexmap)
        if vMap is None:
            return
    
        # Subdivide the opbject
        bc = c4d.BaseContainer()
        bc[c4d.MDATA_SUBDIVIDE_SUB] = 1
        res = c4d.utils.SendModelingCommand(
            command=c4d.MCOMMAND_SUBDIVIDE,
            list=[polygonObj],
            mode=c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
            bc=bc,
            doc=polygonObj.GetDocument())
    
        # Assign random values
        weights = [float(n % 4 == 0) for n in range(vMap.GetDataCount())]
        vMap.SetAllHighlevelData(weights)
    
        # Retrieve existing vertexMap and shader from the applied material that use this vertex map
        shaderUsingOldVertexMap = []
        tMapOld = op.GetTag(c4d.Tvertexmap)
        if tMapOld is not None:
            shaderUsingOldVertexMap += FindAllVetexShaderFromObj(op, tMapOld)
            tMapOld.Remove()
    
        # Creates a new tag with the correct number of data and disable it so C4D don't overwrite it
        # Cross-thread Operation, that could crash Cinema 4D
        vMapGenerator = op.MakeVariableTag(c4d.Tvertexmap, polygonObj.GetPointCount())
        vMapGenerator[c4d.EXPRESSION_ENABLE] = False
    
        # Copy the data from the vertex map within the generator to the one exposed to the user
        vMapGenerator.SetAllHighlevelData(vMap.GetAllHighlevelData())
        
        # Remap all the vertex shader to use the new vertex map.
        for shader in shaderUsingOldVertexMap:
            shader[c4d.SLA_DIRTY_VMAP_OBJECT] = vMapGenerator
        
        return polygonObj
    

    Cheers,
    Maxime.



  • Hello Maxime,
    thanks for your answer!

    Its too bad that I can't do it without some hacky way, but thanks for your code snipped and example.

    I'll mark this thread as solved, so thanks!

    Best Regards,
    Florian


Log in to reply