Serialize and deserialize rhino CommonObject

Hi, everyone! Recently I’m trying to use rhino3dm.js to deserialize some objects I serialized in Grasshopper with method call Grasshopper.Kernel.GH_Convert.CommonObjectToByteArray, this method returns array of byte, and I convert it into base64 string via method call System.Convert.ToBase64String, then I stored it to local file.

Now, I want to restore it by using rhino3dm.js in node.js, I found a static method CommonObject.decode, seems like this method will give me what I want, but, It’s not, when I go though rhino3dm source code I found something like this, it looks like decode a json object from method Rhino.Runtime.CommonObject.ToJSON and this method is not available in rhino 6, and I’m stacked.

So, my question is it even possible use CommonObject.decode method from rhino3dm.js to decode object data from method Grasshopper.Kernel.GH_Convert.CommonObjectToByteArray ?

Hi, the answer appears to be: absolutely.

On my compute server, I’m doing something a bit differently. Instead of base64 encoding anything or using GH_Convert, I call a basic:

JsonConvert.SerializeObject(geometry)

In my case, geometry is a GH_Circle going through circleGoo.Value.ToNurbsCurve()

The json comes out something like:

{"version":10000,"archive3dm":70,"opennurbs":-1909687039,"data":"+n8CAP4BAAAAAAAA+/8CABQAAAAAAAAAGRGvXlEL1BG//gAQgwEi8EoaeRf8/wIAxgEAAAAAAAARAwAAAAEAAAADAAAACQAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA8L8AAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAYLURU+yH5PxgtRFT7Ifk/GC1EVPshCUAYLURU+yEJQNIhM3982RJA0iEzf3zZEkAYLURU+yEZQBgtRFT7IRlACQAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/zDt/Zp6g5j/MO39mnqDmPwAAAAAAAAAAzDt/Zp6g5j8AAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAADwP8w7f2aeoOa/zDt/Zp6g5j8AAAAAAAAAAMw7f2aeoOY/AAAAAAAA8L8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D/MO39mnqDmv8w7f2aeoOa/AAAAAAAAAADMO39mnqDmPwAAAAAAAAAAAAAAAAAA8L8AAAAAAAAAAAAAAAAAAPA/zDt/Zp6g5j/MO39mnqDmvwAAAAAAAAAAzDt/Zp6g5j8AAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwC1D6j4/38CgAAAAAAAAAAA"}

Then, from rhino3dm.js on the client, I do:

import rhino3dm from 'rhino3dm'

rhino3dm().then((rh) => {
        /* @ts-expect-error missing type */
        const x = rh.CommonObject.decode(json)
        console.log(x)
})

The json is the same object from above and is NOT stringified.

The console statement then gives me a nice wasm object, and I can work with the geometry:

rhino3dm

For example, x.degree returns 2 and x.isClosed returns true. I’m still figuring out the best way to surface type information from decode(), but these are in the docs.

What sort of issues are you running into? I had the most trouble with getting webpack to play nicely with the wasm shipped with rhino3dm.js

Thanks for reply! But my problem is about data, I have lots of data serialized by GH_Convert.CommonObjectToByteArray is stored in my disk, and I want make these data become something that CommonObject.decode could recognize, and finally become real Geometry while my program reading these data. Thanks anyway.

ah misread. your post came up while debugging my issue and helped me solve it, wanted to share the learning. It looks like it might be worth trying GH_Convert.ByteArrayToCommonObject ?

either way, best of luck!

1 Like

rhino3dm is built to be able to deserialize object that have been serialized using RhinoCommon’s ToJson function
https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_Runtime_CommonObject_ToJSON.htm

1 Like

Thanks :mask:

Yes, I’m aware. But when I am digging this data up, I found something might be useful, I’ll post it later.

Ok, here is my thing for this problem, and the following information doesn’t came up with solution, just for fun.

First of all, let’s make it easer, I draw a single circle and serialize it in grassshopper C# Component with this code:


var bytes = GH_Convert.CommonObjectToByteArray(x);

A = Convert.ToBase64String(bytes);

B = x.ToJSON(new Rhino.FileIO.SerializationOptions());

then, I’ve got two data I’m gonna call a for GH_Convert.CommonObjectToByteArray and b for CommonObject.ToJSON.


// a is base64 string

const a = `Y2BkYGD4DwQgGgR4mIBEWFBGZl6+c35ubn6ejkJYalFxZn6erbmeoaGekaGRpbGepYGBoY6Cc2lOSWlRqm1eamlJUWKOjkJAaVJOZrJ3amVIfnZqnq2pqZGRhWGqpXmyuampqbEBK8gScbDZeu6p+bmpJUWVeo5Fyc6lRWWpLEA59jKIVVyJRckZmWWpxim5nPkFqXl5pUVJxSwpiSWJIEUcHBxMIFcKqDMwGIGc/4Wbh5MZyOAHEbZAS5h+1TMxGEK99Ps/E4MI1Hda+4zPb+G8Irj/N4NAM6PSh2ky5xr/AOV/QuUFGPCCD/a0kYeLN0AoFQdUGgE4i2/uX89/zAa7vMoBiPw1G3R9ErouIb8VJeHq4yxCNauy/B1AIfbaSW/h/3qmBpgcN35PjIIRCAA=`


// b is a object, and obviously b.data is a base64

const b = {
version: 10000,
archive3dm: 70,
opennurbs: -1909687039,
data:
'+n8CADEBAAAAAAAA+/8CABQAAAAAAAAAKr4zz7QJ1BG/+wAQgwEi8JYczoH8/wIA+QAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/AAAAAAAAAIAAAAAAAAAkQAAAAAAAACRAAAAAAAAAAAAAAAAAAAAAAAlz2b+vD8Y8AAAAAAAAJEAAAAAAAAAAAAAAAAAAACTACXPZv68P1jwAAAAAAAAAAAAAAAAAAAAAGC1EVPshGUAAAAAAAAAAAF44VSl6ak9AAwAAAOtCLqH/fwKAAAAAAAAAAAA='

}

Now, I save these base64 data into a binary file, a.bin for data a and b.bin for data b, then hexdump them.

here is a.bin

0000000 63 60 64 60 60 f8 0f 04 20 1a 04 78 98 80 44 58
0000010 50 46 66 5e be 73 7e 6e 6e 7e 9e 8e 42 58 6a 51
0000020 71 66 7e 9e ad b9 9e a1 a1 9e 91 a1 91 a5 b1 9e
0000030 a5 81 81 a1 8e 82 73 69 4e 49 69 51 aa 6d 5e 6a
0000040 69 49 51 62 8e 8e 42 40 69 52 4e 66 b2 77 6a 65
0000050 48 7e 76 6a 9e ad a9 a9 91 91 85 61 aa a5 79 b2
0000060 b9 a9 a9 a9 b1 01 2b c8 12 71 b0 d9 7a ee a9 f9
0000070 b9 a9 25 45 95 7a 8e 45 c9 ce a5 45 65 a9 2c 40
0000080 39 f6 32 88 55 5c 89 45 c9 19 99 65 a9 c6 29 b9
0000090 9c f9 05 a9 79 79 a5 45 49 c5 2c 29 89 25 89 20
00000a0 45 1c 1c 1c 4c 20 57 0a a8 33 30 18 81 9c ff 85
00000b0 9b 87 93 19 c8 e0 07 11 b6 40 4b 98 7e d5 33 31
00000c0 18 42 bd f4 fb 3f 13 83 08 d4 77 5a fb 8c cf 6f
00000d0 e1 bc 22 b8 ff 37 83 40 33 a3 d2 87 69 32 e7 1a
00000e0 ff 00 e5 7f 42 e5 05 18 f0 82 0f f6 b4 91 87 8b
00000f0 37 40 28 15 07 54 1a 01 38 8b 6f ee 5f cf 7f cc
0000100 06 bb bc ca 01 88 fc 35 1b 74 7d 12 ba 2e 21 bf
0000110 15 25 e1 ea e3 2c 42 35 ab b2 fc 1d 40 21 f6 da
0000120 49 6f e1 ff 7a a6 06 98 1c 37 7e 4f 8c 82 11 08
0000130 00

and here is b.bin

0000000 fa 7f 02 00 31 01 00 00 00 00 00 00 fb ff 02 00
0000010 14 00 00 00 00 00 00 00 2a be 33 cf b4 09 d4 11
0000020 bf fb 00 10 83 01 22 f0 96 1c ce 81 fc ff 02 00
0000030 f9 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00
0000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000050 00 00 00 00 00 00 00 f0 3f 00 00 00 00 00 00 00
0000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000070 00 00 00 00 00 00 00 f0 3f 00 00 00 00 00 00 00
0000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000090 00 00 00 00 00 00 00 f0 3f 00 00 00 00 00 00 00
00000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f0
00000b0 3f 00 00 00 00 00 00 00 80 00 00 00 00 00 00 24
00000c0 40 00 00 00 00 00 00 24 40 00 00 00 00 00 00 00
00000d0 00 00 00 00 00 00 00 00 00 09 73 d9 bf af 0f c6
00000e0 3c 00 00 00 00 00 00 24 40 00 00 00 00 00 00 00
00000f0 00 00 00 00 00 00 00 24 c0 09 73 d9 bf af 0f d6
0000100 3c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000110 00 18 2d 44 54 fb 21 19 40 00 00 00 00 00 00 00
0000120 00 5e 38 55 29 7a 6a 4f 40 03 00 00 00 eb 42 2e
0000130 a1 ff 7f 02 80 00 00 00 00 00 00 00 00

as we can see in these hexdump, b.bin contains lots of 00 and a.bin is the other way, so I presume a.bin is a compressed data. So, after few moments digging, I accidentally found out a method to decompresse them:


const zlib = require('zlib')

zlib.inflateRawSync(Buffer.from(a, 'base64'))

after decompress, a.bin is become to this:

0000000 00 01 00 00 00 ff ff ff ff 01 00 00 00 00 00 00
0000010 00 0c 02 00 00 00 56 52 68 69 6e 6f 43 6f 6d 6d
0000020 6f 6e 2c 20 56 65 72 73 69 6f 6e 3d 37 2e 31 31
0000030 2e 32 31 32 39 33 2e 39 30 30 31 2c 20 43 75 6c
0000040 74 75 72 65 3d 6e 65 75 74 72 61 6c 2c 20 50 75
0000050 62 6c 69 63 4b 65 79 54 6f 6b 65 6e 3d 35 35 32
0000060 32 38 31 65 39 37 63 37 35 35 35 33 30 05 01 00
0000070 00 00 17 52 68 69 6e 6f 2e 47 65 6f 6d 65 74 72
0000080 79 2e 41 72 63 43 75 72 76 65 04 00 00 00 07 76
0000090 65 72 73 69 6f 6e 0a 61 72 63 68 69 76 65 33 64
00000a0 6d 09 6f 70 65 6e 6e 75 72 62 73 04 64 61 74 61
00000b0 00 00 00 07 08 08 08 02 02 00 00 00 10 27 00 00
00000c0 32 00 00 00 ff f4 0b 0c 09 03 00 00 00 0f 03 00
00000d0 00 00 3d 01 00 00 02 fa 7f 02 00 31 01 00 00 00
00000e0 00 00 00 fb ff 02 00 14 00 00 00 00 00 00 00 2a
00000f0 be 33 cf b4 09 d4 11 bf fb 00 10 83 01 22 f0 96
0000100 1c ce 81 fc ff 02 00 f9 00 00 00 00 00 00 00 10
0000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f0 3f
0000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f0 3f
0000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f0 3f
0000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000180 00 00 00 00 00 00 f0 3f 00 00 00 00 00 00 00 80
0000190 00 00 00 00 00 00 24 40 00 00 00 00 00 00 24 40
00001a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00001b0 09 73 d9 bf af 0f c6 3c 00 00 00 00 00 00 24 40
00001c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 24 c0
00001d0 09 73 d9 bf af 0f d6 3c 00 00 00 00 00 00 00 00
00001e0 00 00 00 00 00 00 00 00 18 2d 44 54 fb 21 19 40
00001f0 00 00 00 00 00 00 00 00 5e 38 55 29 7a 6a 4f 40
0000200 03 00 00 00 eb 42 2e a1 ff 7f 02 80 00 00 00 00
0000210 00 00 00 00 0b 00 00 00 00 00 00 00 00 00 00 00
0000220 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0
*
0000420 00 00 00 00 00 00 00 00
0000428

now we can see part of decompressd a.bin is almost the same as b.bin start with postion 00000d0 at fa 7f 02 00 31 01 ....

wow! this is something, at least a.bin is organised binary data

this is part one of this post, I’ll post part two later.

part 2

Since a.bin is a organised binary data, I’m trying to figure out what dose these data means, and I find a hexviewer, to dig more infomation about it, and I see this:

turns out the begining of these bytes represent the infomation of assambly and the following bytes represent the class of data inside, and I see these keywords version archive3dm opennurbsdata, these keywords is the key of json from CommonObject.ToJSON method. aha, now I see, there is a small changce to transform bytes from GH_Convert.CommonObjectToByteArray to a json which CommonObject.decode can recognize.

To create a bytes adapter to translate these bytes, I found some useful resource to understand these bytes, thanks to a friend of mine told me this bytes structure looks like BinaryFormatter from System.Runtime.Serialization.Formatters.Binary namespace, so I take a look about BinaryFormatter Class.

Also I found a post about how-to-analyse-contents-of-binary-serialization-stream, and I follow these instruction read a.bin byte by byte, wow, it works.

End of story.


PS: So far, it’s not done yet, now I’m in the middle of something, I’ve got no time for these bytes

1 Like