Serializing non-Rhino classes to a Rhino File


#1

Is it possible to serialize my own classes when saving a rhino document?

Previously I have used user data to save rhino data (point3ds, curves, surfaces etc.) which mirrors my classes - by knowing what I put in I can reconstruct my classes when I get that user data out.
However now my classes are more complex, so I am saving them to my own file types using binary serialization. Is it possible to save my classes to the rhino file, and is there a best practice for doing this?

If it’s not possible to save my classes into the rhino document, presumably I could write something with OpenNURBS to save the rhino/3dm data to my own file types?


Binary archive writer
(Dale Fugier) #2

Yes, you can save your own class data in 3DM files. You can save them on objects as object user data or in the document as document user data. Plug-ins do this all the time.

As far as best practices, I’m sure you will get a lot of opinions. Personally I like to create object that serialize themselves. For example, if I were to create my own point class, I might add Read() and Write() that looked like this:

bool Read(Rhino.FileIO.BinaryArchiveReader archive)
{
  bool rc = false;
  if (null != archive)
  {
    int major, minor;
    archive.Read3dmChunkVersion(out major, out minor);
    if (1 == major && 0 == minor) // version 1.0 of our point
    {
      m_x = archive.ReadDouble();
      m_y = archive.ReadDouble();
      m_z = archive.ReadDouble();
      rc = archive.ReadErrorOccured;
    }
  }
  return rc;
}

bool Write(Rhino.FileIO.BinaryArchiveWriter archive)
{
  bool rc = false;
  if (null != archive)
  {
    archive.Write3dmChunkVersion(1, 0); // version 1.0 of our point
    archive.WriteDouble(m_x);
    archive.WriteDouble(m_y);
    archive.WriteDouble(m_z);
    rc = archive.WriteErrorOccured;
  }
  return rc;
}

Also, Microsoft put a lot off effort into adding serialization support to .NET classes. For more information on this, I’d start here:

http://msdn.microsoft.com/en-us/library/7ay27kt9(v=vs.100).aspx

Basically, you can generate an XML definition of an object, which you could write to a 3DM file as text or as a binary stream.


#3

Thanks for your reply - apologies I’ve taken some time to respond.

Your method of self-serializing objects looks quite elegant, however, I think it might be difficult to implement now as my classes are quite large. Regarding the XML route, I’ll hunt around the MSDN stuff for tips, but do you have any samples of writing to the 3DM file as text or as a binary stream?


(Dale Fugier) #4

Actually, this model works quite good for large classes. The key to make each custom object capable of serializing itself. Trust me, Rhino has some complicated classes, and they all serialize in this manner.

I don’t have any samples to point you at. but the Internet is full of them. Let me know if you cannot find what you need.


#5

Thanks again. I guess difficult to implement wasn’t quite what I meant, more that it will be time consuming to write the functions for all my classes. Had I done that as I was building the classes however, it does sound like that would have been the best solution.

Re: Binary Serialization, I used the example at the bottom of the following article last time to create Serialize & DeSerialize functions for my classes, saving them with a custom file extension, which took very little code.

http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.binary.binaryformatter(v=vs.110).aspx

What I’m struggling to see is how to use the FileStream correctly to serialize classes to the .3dm file of the current model. I’ve sort of got it to work with a dodgy use of the Filestream… by using FileMode.Append, I appended my byte stream to the end of the .3dm file, and could read it back out again.

I encountered some issues with this:

  • The read function needs to know where to start, which means passing a long to FileStream.Position for the reader
  • I can’t modify the rhino file when it’s open (so I copied it and add the data, mostly as a workaround to test what would happen by doing it this way)
  • If I save some custom data in the above way, then edit the file by say, drawing a line, rhino can’t save the backup file, giving error code 0, presumably because I’ve messed up the location of stuff in the file

I’m fairly confident it’s not a good way to do it and can foresee quite a few problems with it. I’m presuming this is mostly a .NET problem, e.g. I’m not missing something in the SDK that will help with this?


(Dale Fugier) #6

You don’t want to use a FileStream because you want to serialize to the 3DM file. Use a MemoryStream. Then, you can archive the bytes using RhinoCommon’s BinaryArchiveWriter and BinaryArchiveReader objects.

I haven’t tested this, but it might work:

[Serializable]
class StuartData
{
  /// <summary>
  /// Members
  /// </summary>
  public double X { get; set; }
  public double Y { get; set; }
  public double Z { get; set; }

  /// <summary>
  /// Constructor
  /// </summary>
  public StuartData()
  {
  }

  /// <summary>
  /// Constructor
  /// </summary>
  public StuartData(StuartData src)
  {
    this.X = src.X;
    this.Y = src.Y;
    this.Z = src.Z;
  }

  /// <summary>
  /// Create
  /// </summary>
  public void Create(StuartData src)
  {
    this.X = src.X;
    this.Y = src.Y;
    this.Z = src.Z;
  }

  /// <summary>
  /// Write to binary archive
  /// </summary>
  public bool Write(BinaryArchiveWriter archive)
  {
    bool rc = false;
    if (null != archive)
    {
      try
      {
        // Write chunk version
        archive.Write3dmChunkVersion(1, 0);

        // Write 'this' object
        IFormatter formatter = new BinaryFormatter();
        MemoryStream stream = new MemoryStream();
        formatter.Serialize(stream, this);
        stream.Seek(0, 0);
        byte[] bytes = stream.ToArray();
        archive.WriteByteArray(bytes);
        stream.Close();

        // Verify writing
        rc = archive.WriteErrorOccured;
      }
      catch
      {
        // TODO
      }
    }
    return rc;
  }

  /// <summary>
  /// Read from binary archive
  /// </summary>
  public bool Read(BinaryArchiveReader archive)
  {
    bool rc = false;
    if (null != archive)
    {
      // Read and verify chunk version
      int major, minor;
      archive.Read3dmChunkVersion(out major, out minor);
      if (1 == major && 0 == minor)
      {
        try
        {
          // Read this object
          byte[] bytes = archive.ReadByteArray();
          MemoryStream stream = new MemoryStream(bytes);
          IFormatter formatter = new BinaryFormatter();
          StuartData data = formatter.Deserialize(stream) as StuartData;
          this.Create(data);
            
          // Verify reading
          rc = archive.ReadErrorOccured;
        }
        catch
        {
          // TODO
        }
      }
    }
    return rc;
  }
}

Storing Data Between Commands, Document User Data VS Shared Instances
#7

Brilliant, will give that a go.
Kicking myself now because I entertained that as a way of doing it and didn’t pursue it!


#8

Yep, that works, thanks very much!

Only amendment I had to make was that the functions read or write should return true if rc is false, rather than returning the value of rc, or rhino brings up a message box with unspecified read or write error occured, and I used a different version-ing. Hope that helps anyone that might find this in the future.


#9

I am trying to use this method to serialize my plugin state. Its sort of working but I had a few questions. I am using the second example above. If I have two objects (different classes), should I create a read/write method in each(similar to first example)? Then I call these individually from the overridden methods in the Plugin? The two classes in my case are a model and a viewmodel. I am hoping to deserialize my UI state. If that doesn’t work I will have to try to rebuild the UI from the model.

I guess my actual question is wrt to the BinaryArchiveReader archive object that is getting passed, does each read and write method just “magically” pull the relevant data from this? How does it not overwrite the other object?


#10

On second thought, my ViewModel contains a reference to the Model, so I am going to try to let it serialize via that…

edit: So where is the typical place to “draw the line” for serialization. I quickly started running into system classes I am using that aren’t able to be serialized, basically anything to do with the WPF or controls. So I can’t serialize my views, but probably just the viewmodels? I sort of expected that, but I just want to make sure I am not missing anything.


(Dale Fugier) #11

The traditional way of doing this is to override:

PlugIn.ShouldCallWriteDocument
PlugIn.WriteDocument
PlugIn.ReadDocument

Here is a simple example:
https://github.com/dalefugier/SampleCsUserData/blob/master/SampleCsUserDataPlugIn.cs

The example above is a bit more complicated - the developer had a fairly unique requriement…


#12

Ah yes,
I am following your example on github pretty closely. Its working pretty well so far, just have to write some code to regenerate all of the views from the de-serialized viewmodels.


#13

Ok,
Still going pretty well, just brute force serializing almost everything and rebuilding what I can’t. It seems that Rhino.Collections.ArchivableDictionary is not marked as Serializable?

Do I need to mark this as non-serializable and then recreate the dictionary from the geometry? Basically I copy the UserDictionary from the geometry into my class at some point. Or is there another way to tell it to store this info?

edit: I decided to just convert to system dicts anytime I read in archivable dicts, as at that point I don’t need the association to the geometry anymore.

The plugin is fully serializing now… that was surprisingly easy.


#14

I cam across another hopefully easy question. I see there is an event RhinoDoc.EndOpenDocument…I thought this would be a good place to call my method which rebuilds my UI from the deserialized data. It works fine, but I noticed that the RhinoDoc at this point(note this was also an issue with args on PlugIn.ReadDocument) has its Name property set to “”. Its like it hasn’t quite opened the document enough to get the Name? When I reopen my file I need to do some things based on the name(say for example the file was renamed, or moved).

Is there a specific point in the loading process where I can safely get the RhinoDoc.Name? For example when I call my Command, it is there. But my plan was to call the command on startup, triggered by an event…but so far these events seem too early to get the Name.


(Dale Fugier) #15

When your EndOpenDocument handler is called, set a flag on your plug-in indicating such. Then in an RhinoApp.Idle event handler, check your plug-in’s flag and then do whatever is necessary. Don’t forget to clear the flag on the out…

– Dale


#16

Thanks Dale,
I think that approach is working well now. Seems better to usually just flag things to the on idle event and then clean them up.

Wes


(Dale Fugier) #17

Just curious, what are you working on?


#18

its a plugin for simulating CNC/robotic tools in rhino. Its been through several major rewrites over the last 6 years, from rhinoscript, to python, to C#. Its now a full wpf gui and pretty full featured, but serialization is going to be a huge help(we actually had this under python, with pickle, but there was no serialization of rhinocommon through pickle…so C# wins there again). And so far seems to be the easiest feature ever added…honestly I am pretty impressed with how easy it is to do this in the framework.


#19

Hi @dale, @menno

As a follow-up from this discussion - I have a question.
I need to write and read to .3dm file more serialized classes.

What approach should be better:

  • use separate memory stream and archive.WriteByteArray(bytes) for every class or
  • serialise each class into separate stream and combine streams and after use archive.WriteByteArray(bytes) only once?

Thanks in advance,
Dmitriy


(Menno Deij - van Rijswijk) #20

Both will work I think, but the first option is less work and less error-prone.