3ds Max automatically saves all items referenced by a plug-in when saving the plug-in to a scene file. All parameter blocks are expected to be exposed as references and will therefore be saved and loaded automatically. Any additional data that is not explicitly referenced by the plug-in or contained in a parameter block must be saved or loaded manually.
There are two methods of the ReferenceMaker
class which are called by the system when the plug-in's data needs to be loaded or saved. These methods are named ReferenceMaker::Load()
and ReferenceMaker::Save()
. The plug-in implements these methods to load or save any special data it has, that is not managed by the parameter blocks.
When a 3ds Max file is saved, each plug-in object is asked to save its data and is provided with an ISave
pointer. This data can be partitioned into chunks. When an object needs to be saved, 3ds Max creates a chunk for the object. Inside that chunk, another chunk is created to contain reference information which the system writes. The plug-in's ReferenceMaker::Save()
method is then called. Before ReferenceMaker::Save()
is called the file pointer is positioned inside the plug-in's chunk but after the first chunk containing the reference data. The plug-in can then create its own chunks. Each chunk may contain sub-chunks or data, but not both. If you want to put both data and chunks inside another chunk, the data needs to be contained in a separate chunk. To save a block of binary data, the ISave::Write()
method is used that writes a block of bytes to the output stream.
Loading a 3ds Max file is similar. The plug-in is asked to load its data and is given an ILoad
pointer which allows it to read chunks. When a plug-in DLL is asked to create a new instance of its class (using the ClassDesc::Create()
method), a flag is passed to indicate whether the new instance is being created with the intention of loading an object from a disk file. In this way, if the flag is TRUE
, the object doesn't have to perform any initialization because it is guaranteed that it will be asked to read data from a file.
The following three methods of the ClassDesc
class may be used to save data associated with a class in a 3ds Max file. This is appropriate for preference data and other similar information associated with a class. If you want to save data associated with the class then you must override ClassDesc::NeedsToSave()
to return TRUE
and implement the Save()
and Load()
methods. See the topic on Loading and Saving Plug-in Data for more details on the way data is written to and read from 3ds Max files.
ClassDesc::NeedsToSave()
- Returns TRUE
if there is data associated with the class that needs to be saved in the 3ds Max file. If this is so, implement the Save()
and Load()
methods below. If there is no class data to save, return FALSE
.ClassDesc::Save()
- If ClassDesc::NeedsToSave()
returns TRUE
then this method should be implemented to save the data associated with the class.ClassDesc::Load()
- If NeedsToSave()
returns TRUE
then this method should be implemented to load the data associated with the class.There are two classes available for writing and reading hierarchical data structures to a linear stream, such as an AppData
block. These are AppSave
and AppLoad
. Please see those classes for more details.
The following code demonstrates how both ReferenceMaker::Save()
and ReferenceMaker::Load()
may be implemented for a plug-in with two pieces of data it needs to save and restore.
This example writes an array of 10 floating point values and a single DWORD
containing flags. The process of saving is very simple. The output file is already open and ready to be written to. A pointer to the ISave
class has been passed in with which the plug-in can call methods to save data. The plug-in begins by creating a chunk using the ISave::BeginChunk()
method and passing an ID it has defined. This ID can be any USHORT
, as only the plug-in's loading and saving procedures will ever use it. It then writes the data using the ISave::Write()
method. It then closes the chunk using ISave::EndChunk()
. It begins a new chunk and writes the flag data. After ending this chunk it returns IO_OK
to indicate a successful save.
The data is saved in chunks like this so that it may be loaded in a manner which is not order dependent. One could write the flags first and the array second and it would still be read correctly.
#define SAMPLE_DATA_CHUNK 1000
#define SAMPLE_FLAGS_CHUNK 1010
IOResult Sample::Save(ISave* isave)
{
ULONG nb;
isave->BeginChunk(SAMPLE_DATA_CHUNK);
isave->Write(myArray, sizeof(float) * 10, &nb);
isave->EndChunk();
isave->BeginChunk(SAMPLE_FLAGS_CHUNK);
isave->Write(&flags, sizeof(DWORD), &nb);
isave->EndChunk();
return IO_OK;
}
The following code demonstrates the loading process. Again, in preparation for this call the system has opened the file and positioned the file pointer at the plug-in's objects chunk. The code loops, opening chunks and reading the data until ILoad::OpenChunk()
no longer returns IO_OK
. This indicates there are no more chunk at this level and the process is done.
Inside the loop, the code switches on the chunk ID. For each ID it recognizes it reads the data using ILoad::Read()
. The chunk is then closed using ILoad::CloseChunk()
. If Read()
did not return IO_OK
an error occurred and this error code is returned. Otherwise, the loop begins again.
If the loading was successful, IO_OK
is returned to indicate so.
IOResult Sample::Load(ILoad* iload)
{
ULONG nb;
IOResult res;
while (IO_OK==(res=iload->OpenChunk()))
{
switch(iload->CurChunkID())
{
case SAMPLE_DATA_CHUNK:
res=iload->Read(myArray, sizeof(float)*10, &nb);
break;
case SAMPLE_FLAGS_CHUNK:
res=iload->Read(&flags, sizeof(DWORD), &nb);
break;
}
iload->CloseChunk();
if (res!=IO_OK) return res;
}
return IO_OK;
}
The possible return values for IOResult
are:
IO_OK
- The result was acceptable - no errors.IO_END
- This is returned from OpenChunk()
when the end of the chunks at a certain level have been reached. It is used as a signal to terminate the processing of chunks at that level.IO_ERROR
- This is returned if an error occurred. Note that the plug-in should not put up a message box if a read error occurred. It should simply return the error status. This prevents a overabundance of messages from appearing.