Write object to HyperFile



  • On 23/01/2013 at 09:30, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:   R14 
    Platform:   Windows  ;   
    Language(s) :     C++  ;

    ---------
    Suppose I have a BaseObject as a private member of my ObjectData class. How would I
    go about writing it into the HyperFile, and reading it back again in NodeData::Write(),
    NodeData::Read()? There is no method WriteAtom() or similar in the HyperFile class. 😢

    The reason I need this for, is that the BaseObject my ObjectData class stores privately can not
    be recomputed from scratch and must therefore be cached, written and read from the scene file!

    Thank you,
    Niklas



  • On 23/01/2013 at 10:02, xxxxxxxx wrote:

    Won't read/write data fit your needs ? GeData has a constructor with a BaseList2D
    as parameter, so i think it is also meant to contain a whole object.



  • On 23/01/2013 at 11:05, xxxxxxxx wrote:

    Thanks for the input, Ferdinand. This is a good idea of yours. I will do some tests and report. :)

    Best,
    Niklas



  • On 24/01/2013 at 06:43, xxxxxxxx wrote:

    Hello Ferdinand,

    unfortunately, my fears have been confirmed that the GeData only stores a link/reference to the
    BaseList2D object, not the object itself. So using WriteGeData() does not work at all. Thanks anyway!

    I am thinking about trying to replicate Cinema's  saving procedure for the object, if no built-in way
    is available. Am I missing something on the list that needs to be done written/read from the
    HyperFile?

    • Plugin ID
    • Container
    • Invoke NodeData::Write() / Read()

    Something like this maybe?

    /**
     * Retrieve the virtual method table of a NodeData plugin.
     */
    #define RetrieveTable(x, t, m) ((x)->*((t* )C4DOS.Bl->RetrieveTableX((x), 0))->m)
      
    /**
     * Write a BaseList2D plugin to a HyperFile.
     */
    Bool WriteBaseList2D(BaseList2D* node, HyperFile* hf) {
        NodeData* nd = node->GetNodeData();
        if (nd == NULL) return NULL;
      
        BaseContainer* container = node->GetDataInstance();
        if (!hf->WriteLong(node->GetType())) return FALSE;
        if (!hf->WriteContainer(*container)) return FALSE;
        if (!RetrieveTable(nd, NODEPLUGIN, Write)(node, hf)) return FALSE;
      
        return TRUE;
    }
    

    Seems legit, but how can I do the reading? Can I simply allocate a BaseList2D with the 
    plugin ID and call the NodeData's Read() method again?

    Also, I've just seen that C4DAtom has a Read() and Write() method. But I need to allocate an
    instance before I can call it's Read() method, so how am I supposed to Read() a C4DAtom
    when using it's Write() method?

    How can I obtain the disklevel of a plugin? This value is required for C4DAtom::Read() and
    NodeData::Read(), so either method I use, I need to obtain this value from somewhere.

    Thanks,
    Niklas



  • On 24/01/2013 at 06:56, xxxxxxxx wrote:

    I was able to retrieve the disklevel like this:

    /**
     * Retrieve a value from the NodeDatas' table.
     */
    #define GetNodeAttribute(x, t, m) (((t* )C4DOS.Bl->RetrieveTableX((x), 0))->m)
      
    NodeData* nd = node->GetNodeData();
    LONG level = GetNodeAttribute(nd, NODEPLUGIN, disklevel);
    


  • On 24/01/2013 at 07:24, xxxxxxxx wrote:

    hm,

    i cannot say so much really helpful, but nevertheless a few thoughts.

    Originally posted by xxxxxxxx

    Seems legit, but how can I do the reading? Can I simply allocate a 
    BaseList2D with the plugin ID and call the NodeData's Read() method again?

    hm, note sure how far this is related but i have once tried to store a baselist2d(renderdata) 
    outside of the c4d file in this way and i lost all children (the videopost). i could read the children, 
    but i could not write them back. not sure if it was my crappy coding, but i ended up rebuilding my renderdata from scratch.

    Originally posted by xxxxxxxx

    How can I obtain the disklevel of a plugin? This value is required for 
    C4DAtom::Read() and NodeData::Read(), so either method I use, I need to obtain this value
    from somewhere.

    at least for plugins the disklevel is just the revison, so you basically ignore it if you do not
    plan multiple versions with upwards comp - assume its default 0.

    the whole approach seems at least to me like trying to fit square into circle. why don't 
    you use a customdata type to store your custom data, when you are already in cpp.
    a rather cheap way could also be to write you custom data into an invisible variable tag 
    on your mainplugin instance just before your object is freed/stored and when it is alloctaed 
    agian you could read the data back into your baselist2d. all this depends of couse on the 
    nature of your data.



  • On 24/01/2013 at 07:33, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    at least for plugins the disklevel is just the revison, so you basically ignore it if you do not
    plan multiple versions with upwards comp - assume its default 0.

    I need the disklevel of the object to write, not the disklevel of my own plugin. If my plugin reads
    in an object with another disklevel and I set it to 0 where it would actually support the new data
    from a higher disklevel, this would be incorrect (or inconvenient) behavior. However, I already
    figured out how to retrieve it, see my previous post. :)

    Originally posted by xxxxxxxx

    the whole approach seems at least to me like trying to fit square into circle. why don't 
    you use a customdata type to store your custom data, when you are already in cpp.
    a rather cheap way could also be to write you custom data into an invisible variable tag 
    on your mainplugin instance just before your object is freed/stored and when it is alloctaed 
    agian you could read the data back into your baselist2d. all this depends of couse on the 
    nature of your data.

    I do not just have some data, I have actual BaseObject instances that can be "packed" into my
    plugin. I don't know how I should represent this as a CustomDataType. And there, I would also
    need to write the objects to the HyperFile.

    Anyway, I just figured out how to do it. I'm not very happy with it, however. It would be nice
    to know how to use the C4DAtom::Read() method correctly rather than using my own code,
    because it would guaruntee everything is done correctly. I can imagine that I have forgotten
    something important.

    /\*\*
     \* Retrieve a virtual method from the NodeDatas' table.
     \*/
    #define GetNodeMethod(x, t, m) ((x)->\*((t\*)C4DOS.Bl->RetrieveTableX((x), 0))->m)
    
    /\*\*
     \* Retrieve a value from the NodeDatas' table.
     \*/
    #define GetNodeAttribute(x, t, m) (((t\*)C4DOS.Bl->RetrieveTableX((x), 0))->m)
    
    /\*\*
     \* Write a BaseList2D plugin to a HyperFile.
     \*/
    Bool WriteBaseList2D(BaseList2D\* node, HyperFile\* hf) {
        NodeData\* nd = node->GetNodeData();
        if (nd == NULL) return NULL;
    
        BaseContainer\* container = node->GetDataInstance();
        if (!hf->WriteLong(node->GetType())) return FALSE;
        if (!hf->WriteContainer(\*container)) return FALSE;
        if (!GetNodeMethod(nd, NODEPLUGIN, Write)(node, hf)) return FALSE;
    
        return TRUE;
    }
    
    /\*\*
     \* Read a BaseList2D plugin from a HyperFile.
     \*/
    Bool ReadBaseList2D(BaseList2D\*& node, HyperFile\* hf) {
        LONG pluginId;
        BaseContainer container;
        if (!hf->ReadLong(&pluginId)) return FALSE;
        if (!hf->ReadContainer(&container, FALSE)) return FALSE;
    
        /\* Allocate a new BaseList2D instance. \*/
        node = BaseList2D::Alloc(pluginId);
        if (node == NULL) return FALSE;
        NodeData\* nd = node->GetNodeData();
        if (nd == NULL) {
            BaseList2D::Free(node);
            return FALSE;
        }
    
        LONG disklevel = GetNodeAttribute(nd, NODEPLUGIN, disklevel);
        if (!GetNodeMethod(nd, NODEPLUGIN, Read)(node, hf, disklevel)) return FALSE;
    
        node->SetData(container);
        return TRUE;
    }
    

    Best,
    Niklas



  • On 24/01/2013 at 07:38, xxxxxxxx wrote:

    I just realized that this method does also not include the children of the object. I would
    need to go on recursively for the children of the object to be written. Tags are not included as well.

    How can I use C4DAtom::Read() correctly? An example would be very nice.
    *poking the MAXON support*
    🙂

    Thanks,
    Niklas



  • On 24/01/2013 at 09:21, xxxxxxxx wrote:

    Hi Niklas,

    Have you considered inserting the BaseObject in the document?

    C4DAtom's Write()/Read() and WriteObject()/ReadObject() methods are actually rather private and as stated in the docs, not recommended for plugins.
    The developers recommend to save the objects as C4D documents rather than hyperfiles using a dummy document and SaveDocument()/LoadDocument().



  • On 24/01/2013 at 09:31, xxxxxxxx wrote:

    Hello Yannick,
    thanks for your answer.

    I was already considering creating a "fake" document, insert all my private objects and save
    this document to the HyperFile. I could've sworn there was a fucntion to save a document to
    a HyperFile, but I can't find it anymore (so I guess, there isn't). Possibly I'm just missing something
    obvious?

    Using the SaveDocument()/LoadDocument() functions, I'd need to write my objects to a seperate
    file. This would be very inconvenient IMO. I'd like them to be written to the HyperFile passed on
    my ObjectData's Write(). (the same for reading of course)

    Thank you,
    -Niklas



  • On 24/01/2013 at 10:01, xxxxxxxx wrote:

    PS: Just tried this, and it doesn't work. Even looks unnatural, because the ID is provided twice,
    but was worth a try. ;-)

    Bool ReadBaseList2D(BaseList2D*& node, HyperFile* hf) {
        LONG pluginId;
        if (!hf->ReadLong(&pluginId)) return FALSE;

    /* Allocate a new BaseList2D instance. */
        node = BaseList2D::Alloc(pluginId);
        if (node == NULL) return FALSE;
        NodeData* nd = node->GetNodeData();
        if (nd == NULL) {
            BaseList2D::Free(node);
            return FALSE;
        }

    LONG disklevel = GetNodeAttribute(nd, NODEPLUGIN, disklevel);
        if (!node->Read(hf, pluginId, disklevel)) {    // ! returns FALSE
            GePrint("C4DAtom::Read() failed.");
            return FALSE;
        }

    return TRUE;
    }

    I've used C4DAtom::Write() in the write procedure of course.

    PS: Using ReadObject()/WriteObject() instead doesn't work either. I get a MessageDialog
    with "Incorrect File Structure" message.



  • On 25/01/2013 at 11:10, xxxxxxxx wrote:

    Hi Niklas,

    You can call SaveDocument()/LoadDocument() to write/read a document from a memory file.
    Drawback of this solution is the the byte sequences saved by HyperFile::WriteMemory() will not be platform independent.

    Here's some code:

    Bool MyObjectData::Write(GeListNode *node, HyperFile *hf)
    {
        Filename file;
        AutoAlloc<MemoryFileStruct> mfs;
      
        // Set the memory file sruct to store the data saved
        file.SetMemoryWriteMode(mfs);
        
        // Save document to the file in memory
        Bool res = SaveDocument(doc, file, SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST, FORMAT_C4DEXPORT);
        if (res)
        {
            void *data = NULL;
            VLONG size;
      
            // Get the memory data that was written to the file in memory
            mfs->GetData(data, size);
            
            // Write the memory data to the object's hyperfile
            hf->WriteMemory(data, size);
        }
      
    	return TRUE;
    }
      
    Bool MyObjectData::Read(GeListNode *node, HyperFile *hf, LONG level)
    {
        void *data = NULL;
        VLONG size;
      
        // Read the file stored in memory
        Bool res = hf->ReadMemory(&data, &size);
        if (res)
        {
            Filename file;
      
            // Set the file to read from the memory buffer
            file.SetMemoryReadMode(data, size);
      
            // Load the document from the memory file
            doc = LoadDocument(file, SCENEFILTER_OBJECTS, NULL);
      
            // Free the data! (See note in the docs at HyperFile::ReadMemory())
            GeFree(data);
      
            // Kill the document!
            KillDocument(doc);
        }
      
    	return TRUE;
    }
    


  • On 27/01/2013 at 12:01, xxxxxxxx wrote:

    Hello Yannick,

    thank you very much for the example. I did not know we can save a document to memory this

    way. Using this method works perfectly!

    Best,
    Niklas


Log in to reply