Difficulty with cloners and and lifetime of objects



  • Hi @mastergog,

    you can add your own unique IDs, but for that you have to register your own namespace so to speak, a plugin id fed into appid. What I was proposing was using the already existing IDs in the namespace MAXON_CREATOR_ID. The advantage here is that Cinema will create them for you ;) Here is a little script which might give you some context.

    Cheers,
    Ferdinand

    """Create a bunch of objects and run the script, it will always spit out the 
    same uuid for the nodes. If you run the script twice in quick succession, it 
    will probably also spit out the same uuid for the caches. But when you invoke
    a rebuild of a cache (by changing the parameter of a parametric object for 
    example), the uuid of the cache of that object will change.
    """
    
    import c4d
    
    def get_uuid(node):
        """
        """
        # A simplified view on caches, we just assume here the top level node
        # of GetCache to be "our cache".
        cache = node.GetCache()
    
        # Get the unique ids stored under the namespace MAXON_CREATOR_ID for
        # the object and its cache.
        uuid_0 = bytes(node.FindUniqueID(c4d.MAXON_CREATOR_ID))
        if cache is not None:
            uuid_1 = bytes(cache.FindUniqueID(c4d.MAXON_CREATOR_ID))
        
        print (node)
        # These ids are just CRC-16 codes, so there is nothing really to see
        # here, but lets convert them to hex so that we can read them more
        # easily.
    
        # This is MAXON_CREATOR_ID for the node itself, it is always there and it
        # should be always the same even across reallocation boundaries, including
        # reloading a document. 
        print ("node uuid:", uuid_0.hex())
    
        # This is the cache of the node, it will also contain an MAXON_CREATOR_ID,
        # but it will NOT always be the same, since caches can change in "shape 
        # and form", so there is no way to maintain this continuity.
        # I assume this is what you are trying to do, or at least a good part of
        # it. This cannot be done, because you are basically asking here the 
        # caches to be static.
        if cache is not None:
            print ("cache uuid:", uuid_1.hex())
    
        # The only way I could think of would be some sort of hierarchy dependent
        # hashing for the nodes of a cache, i.e. determine the identity of a node
        # by its hierarchical relation to the cache root. But this would not be
        # really a solution either, because it would also assume caches to be
        # hierarchically static, which they are not in many cases.
        
    
    def main():
        """Will go over all top level object nodes and print their unique ids.
        """
        node = op
        while node:
            get_uuid(node)
            node = node.GetNext()
    
    if __name__=='__main__':
        main()
    


  • @zipit yes thank you for the script. What I need seems to be exactly the Node uuid.

    But I'm struggling with the C++ equivalent. I have this signature: Bool FindUniqueID(Int32 appid, const Char*& mem, Int& bytes ) const and I use it like so :

            const Char* value = nullptr;
    	Int size = sizeof(Int32);
    	if (object->FindUniqueID(MAXON_CREATOR_ID, value, size)) {
    		/// do stuff with value
    	}
    

    I was expecting *(Int32*)value to be my unique ID but it is always the same. for all objects in the scene.
    This is not the case your Python script, each node has its own ID, so I guess I'm using this wrong ?

    Regards,
    Georgi.



  • Hi @mastergog,

    first of all, size is an output, not an input. Secondly, MAXON_CREATOR_ID is based on GeMarker which takes 16 bytes of memory. 8 bytes are the mac address of the machine, 4 bytes are a timestamp and the last 4 bytes are kind of a secret sauce. MAXON_CREATOR_ID just casts the memory block of a GeMarker into a maxon::Char array. So you could do something like this when looking at the data associated with MAXON_CREATOR_ID in C++, assuming you are going here for a human readable form:

    // For https://plugincafe.maxon.net/topic/13090
    static maxon::Result<void> pc13090(BaseDocument* doc)
    {
    	iferr_scope;
    	// Get the first object in the scene.
    	const BaseObject* node = doc->GetFirstObject();
    	if (node == nullptr) {
    		ApplicationOutput("No objects found in the scene.");
    		return maxon::OK;
    	}
    
    	// Get its unique id stored under MAXON_CREATOR_ID and bring it into a human readable form.
    	const Char* value = nullptr;
    	Int size = 0;
    	if (node->FindUniqueID(MAXON_CREATOR_ID, value, size)) {
    		maxon::BaseArray<maxon::Char> uuid_representation;
    		for (int i = 0; i < size; i++)
    		{
    			uuid_representation.Append(value[i]) iferr_return;
    		}
    		ApplicationOutput("First object's MAXON_CREATOR_ID.UID: '@'", uuid_representation);
    	}
    	return maxon::OK;
    }
    

    Cheers,
    Ferdinand



  • Hey @zipit happy holidays and thank you for the reply, I understand how this id works now.

    However this ID seems to not be persistent for cloned objects. If I put my object in a cloner in instance mode grid array 3,1, 3 dimensions GetVirtualObjects(BaseObject* object...) would get called 9 times with 9 different objects. There are 9 different UUIDs and this is ok.

    But then every subsequent call to GetVirtualObjects(BaseObject* object...), after I change some setting or simply move the object, there will be a different set of 9 UUIDs, so I can't really rely on them anymore. So with the cloner this reallocation boundary changes the UUIDs.

    Is there a way around it?

    EDIT: If I go in edit mode and change the settings of each instance in the cloner separately then the UUIDs seem to be persistent, but that's just in edit mode.



  • Hi @mastergog,

    you are right, assuming you mean with cloned objects generators with caches. If you have a look at my first example in Python, you will see that I did cover this subject there and why it is conceptually not possible to do this. I also roughly lined out a workaround, hashing the objects in a cache by their hierarchical relations, which is in the end not really a workaround due to said conceptual limitations.

    Cinema relies heavily on procedural geometry, you can track the hooks/stand-ins that represent that procedural something, but not what it is inside, because it is by definition polymorphic (in the sense of that that a cache can change its "shape"). You can establish a persistent link between two scene graphs at a given snapshot by simply expanding all caches in Cinema, but you cannot establish a persistent linkage down into the caches for an arbitrary time span.

    Cheers and happy holidays,
    Ferdinand



  • @zipit said in Difficulty with cloners and and lifetime of objects:

    # This is the cache of the node, it will also contain an MAXON_CREATOR_ID,
    # but it will NOT always be the same, since caches can change in "shape 
    # and form", so there is no way to maintain this continuity.
    # I assume this is what you are trying to do, or at least a good part of
    # it. This cannot be done, because you are basically asking here the 
    # caches to be static.
    if cache is not None:
        print ("cache uuid:", uuid_1.hex())
    

    you are right, assuming you mean with cloned objects generators with caches

    Ooooh maybe I misunderstood your point slightly.
    My 9 objects in the cloner would always be different because they are part of the cache of the Cloner object.
    If they stand on their own it is a different story, and the UUIds are persistent.

    Hope i'm getting it right now ?



  • Hi @mastergog,

    @mastergog said in Difficulty with cloners and and lifetime of objects:

    Ooooh maybe I misunderstood your point slightly.
    My 9 objects in the cloner would always be different because they are part of the cache of the Cloner object.
    If they stand on their own it is a different story, and the UUIds are persistent.

    Hope i'm getting it right now ?

    yes, that is what I meant, my choice of words was probably not the best with "shape or form". Imagine a cache that has this form

    root
        A
        B
        C
    

    there is no mechanism in place which would make sure that the cache has always this "form", on the next update it could be

    root
        B
        A
        C
    

    What if there is an object added? What happens to nodes where the parent is being removed and so on. The purpose of a cache is to be rebuild. Each time this happens the old cache is our of the window. And even if Cinema would try to, there is no closed logic to copy over ids from the old to the new cache, i.e. to make ids on cache objects persistent.

    Cheers,
    Ferdinand



  • @zipit yes ok seems I got this now, after completely disregarding the fact that object is part of the Cloner's cache.

    So for my original problem of creating just X amount of materials for X amount of clones, that won't be doable. I'll just have to create as many as new objects are created and then just clean them all up at some point.

    For the clean up mechanism, I have this dummy object which links to my original object and its material. The original object also links to the dummy object. The dummy objects are inserted in the a scene hook and at some point I iterate them and check if they link to nullptr object to determine whether the original object is still part of the document and whether to release the associated materials.

    Do you think there's easier way to achieve this?



  • Hi @mastergog,

    For the clean up mechanism, I have this dummy object which links to my original object and its material. The original object also links to the dummy object. The dummy objects are inserted in the a scene hook and at some point I iterate them and check if they link to nullptr object to determine whether the original object is still part of the document and whether to release the associated materials.

    Depends on what you would define as easier, but you could create a TagData plugin whose node is hidden and then 'mark' all objects in a scene with these hidden tags. When an object is being deleted, and with it your hidden tag, NodeData::Free() is being called, allowing you to react to the event. This would probably be a bit more efficient than your method, but the problem is that you have the dangling sword of node reallocation over your head. So when a node is reestablished you would also would have to make sure if there already is a material/node counterpart on your side of things.

    It would be probably not that easy, would have to try that myself. If performance is not an issue for you, I would stick with your working solution.

    Cheers,
    Ferdinand



  • All right, thank you for all the help.

    I'll close this now.