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