Solved bc lost stored mesh

my generator plugin takes the first mesh child object and modify it. than it stores the current values into a bc and return the mesh to the scene unfortunally i can't get acces to my cache mesh via bc via

clone = op[res.REDUCER_CACHE].GetData(1005)

full gvo :

    def GetVirtualObjects(self, op, hh):

        """
        :param op:
        :param hh:
        :return:mesh


        """

        """
                setup
        """

        if not op[c4d.ID_BASEOBJECT_GENERATOR_FLAG]: return None

        doc = op.GetDocument()

        srcObj = op.GetDown()

        if not srcObj:
            return None

        while srcObj:

            mesh = self.ModelCurrentStateToOBject(doc, [srcObj])[0]
            if isinstance(mesh,c4d.PolygonObject):
                op.GetAndCheckHierarchyClone(hh, srcObj, c4d.HIERARCHYCLONEFLAGS_0, True)
                break

            srcObj = srcObj.GetNext()




        mesh = self.HelperFieldToVertexmap(op,mesh)

        firstFieldCache = sum(self.HelperGetVertexMapData(mesh))


        clone = op[res.REDUCER_CACHE].GetData(1005)

        if clone and self.CacheCheck(op,firstFieldCache):
            return clone


        mesh,avgLvlDistList = self.Reducer(doc, op, mesh)


        """
                cache
        """

        bc = c4d.BaseContainer()

        bc.SetData(1001, self.HelperGetOptions(op))
        bc.SetData(1002, firstFieldCache)

        if doc != None:
            bc.SetData(1004, doc.GetTime().GetFrame(doc.GetFps()))

        cMesh = mesh.GetClone()

        bc.SetData(1005, cMesh)

        op[res.REDUCER_CACHE] = bc

        return mesh

Hi,

your data is not lost. At least not in the way you think it is. The statement bc.SetData(1005, cMesh) in your code will store a BaseLink to cMesh in the container at the given ID, not the node itself. When you later on poll that ID, without having ensured that cMesh is not being freed/garbage collected, that BaseLink will return None, due to the fact that the object it is pointing to does not exist anymore.

While BaseContainer is a versatile data type that can store basically anything, it is severely crippled by the fact that we do not have access to GeData in Python and we are therefor at the mercy of the Python bindings and how they interpret data passed to SetData. Because of that there is unfortunately no way to store a BaseObject in a BaseContainer in Python. At least I am not aware of a way.

If it is just some vertex and polygon data you want to store, you could serialise them manually into a BaseContainer. The other option would be to attach the data to your NodeData instance and overwrite the IO-methods to serialise your data into a HyperFile when the documented is being loaded, closed, etc.

Cheers,
zipit

MAXON SDK Specialist
developers.maxon.net

This is the GVO from another plugin which works flawless. The only difference is that i didn't use an input object.

    def GetVirtualObjects(self, op, hh):

        if not op[c4d.ID_BASEOBJECT_GENERATOR_FLAG]: return None

        if op[res.GRIDDER_OUTPUT] == 0:

            if self.CacheCheck(op, op.GetDocument()) is True:

                preReturn = op[res.GRIDDER_CACHE].GetData(1005)

                return preReturn

            return self.Gridder(op)

        return None
        meshCache = op.GetClone()

        bc = c4d.BaseContainer()

        bc.SetData(1001, self.HelperGetOptions(op))
        bc.SetData(1002, self.CacheShader(op, doc)[0])
        bc.SetData(1003, self.CacheField(op, doc))

        if doc != None:
            bc.SetData(1004, doc.GetTime().GetFrame(doc.GetFps()))

        bc.SetData(1005, meshCache)

        op[res.GRIDDER_CACHE] = bc

        c4d.StatusClear()

On the other hand: I have no idea how to serialize a polygon object with uvs, vertex maps and phongtag. Also seems HyperFile a little bit over the top to store something temporary. MemoryFileStruct() could work but I need to store the byteseq as well...

GetAndCheckHierarchyClone works but it has the disadvantage of refreshing during viewport changes, so I need to use my own dirtycheck.

I think I am doing something fundamental wrong there

Hi,

full disclaimer: I did not read your code very thoroughly, because it is often pointless with these snippets, as most of it makes little sense without the whole context. I am arguing from a purely formal stand point and have little to no clue what you are trying to do on a bigger semantic scope. So my advice should be taken with a grain of salt, there might be easier solutions.

Initially your statement that this "works flawless" in another plugin had me a bit worried that I told you nonsense, but after a quick test I highly doubt that that other code "works flawless". I expanded my quick test into some narrative code which should illustrate the problem. You will find it at the end of the post.

For the serialising part: If you want to do it manually, you just have to decompose you object (in a pythonics sense) into data types that can be stored in a BaseContainer. There are no inherently wrong ways to do this. But I only mentioned these things because I did not know what you were trying to do. When you are not interested in making whatever you are trying to cache to be persistent with a document state (i.e. load/save/copy persistent), you should just attach whatever data you want to store to some Python object of your choice. As already mentioned, the NodeData instance of your plugin would be a good candidate. Here is some mock/pseudo code to illustrate what I mean:

def GVO(self, node, **kwargs):
    """
    """
    nid = node.FindUniqueID(c4d.MAXON_CREATOR_ID)
    if nid in self.some_cache:
        cache = self.some_cache[nid]
        # do something
    else:
        cache = node.GetClone(0)
        self.some_cache[nid] = cache
        # do something else

Cheers,
zipit

Test code:

import c4d
import gc


def test_container_entry(bc, cid):
    """Pretty prints the data stored at and if it is a BaseLink element for
    a given element id of a BaseContainer.

    Args:
        bc (c4d.BaseContainer): The container to test.
        cid (int): The element id.
    """
    is_baselink = bc.GetType(cid) == c4d.DA_ALIASLINK
    print "Data for id {}: {}".format(cid, bc[cid])
    print "Element at id {} is a BaseLink: {}".format(cid, is_baselink)
    print "-" * 100


def some_scope_context(node):
    """Demonstrates your problem.

    This will create a only locally bound clone of a node and try to "insert"
    it into a BaseContainer (it will actually only create a link).

    Args:
        node (c4d.C4DAtom): Something to cache/clone.

    Returns:
        c4d.BaseContainer: The "cache" container, which is not really a cache
        at all.
    """
    # The container.
    bc = c4d.BaseContainer()
    # A node that is only bound to the local scope, this would be the
    # cMesh symbol in your code. If we would pass in node directly, this
    # all would not happen.
    clone = node.GetClone(0)
    # These are IMHO equivalent in Python due to the fact that we have
    # no control over how SetData interprets the passed data, i.e. just
    # like with __setitem__, we are at the mercy of the Python bindings.
    bc[1000] = clone
    bc.SetData(1001, clone)

    # This will print out that there are some BaseObjects at the given IDs
    # and that these elements are BaseLinks. I.e. it behaves like you expect
    # the container to behave (minus the element type part).
    print "In scope context:"
    print "=" * 100
    test_container_entry(bc, 1000)
    test_container_entry(bc, 1001)
    # We push the container out of the scope. The node 'clone' will be 
    # freed after the interpreter steps to the next instruction.
    return bc


def main():
    """Entry point.
    """
    # Some dummy node we want to cache.
    node = c4d.BaseList2D(c4d.Onull)
    # Now we pass our node into another context, this would be GVO in your
    # code. It returns a BaseContainer which is meant to hold our "cache",
    # which is not really a cache, because it only links to a cache.
    bc = some_scope_context(node)
    # This line should not be necessary due to the fact that we just left
    # a scope when exiting 'some_scope_context', but Python's garbage
    # collector is a nasty piece of work, so this is only here to make sure
    # to get the point of this example across and the node "clone" freed.
    gc.collect()

    # When we now poll our returned container it is "empty" (returns None)
    # due to the fact that the cloned node is not alive anymore. 
    # It died with the scope context of the function 'some_scope_context'.
    print("After scope context:")
    print "=" * 100
    test_container_entry(bc, 1000)
    test_container_entry(bc, 1001)


if __name__ == "__main__":
    main()

Test output:

In scope context:
====================================================================================================
Data for id 1000: <c4d.BaseObject object called 'Null/Null' with ID 5140 at 0x0000021CADCAFA70>
Element at id 1000 is a BaseLink: True
----------------------------------------------------------------------------------------------------
Data for id 1001: <c4d.BaseObject object called 'Null/Null' with ID 5140 at 0x0000021CADCAFA70>
Element at id 1001 is a BaseLink: True
----------------------------------------------------------------------------------------------------
After scope context:
====================================================================================================
Data for id 1000: None
Element at id 1000 is a BaseLink: True
----------------------------------------------------------------------------------------------------
Data for id 1001: None
Element at id 1001 is a BaseLink: True
----------------------------------------------------------------------------------------------------
[Finished in 7.2s]

MAXON SDK Specialist
developers.maxon.net

hi,

as @zipit said, you are storing a baselink in the basecontainer, not the object itself.

Your GVO should return None only in case of Memory Error as it's noted on the documentation

That said, i don't understand what you are trying to do and why.

it stores the current values into a bc
witch values do you want to store ? Or is it the current state of the object you want to store ?

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

After taking a deep dive into the documentation again i figure out that im thinking way to complicated. I use the Cache provided by cinema and write a custom check around it. This avoid using a cached mesh in a baseContainer and also removing a cinema4d breaking memoryleak in my plugin i just found.

So thank you.