Serialising and deserialising a custom type for Grasshopper

Hi,

I’m having trouble serialising and deserialising a custom type that I have made for Grassopper.
It’s specifically the reading that seems to be the issue. When I’m debugging with VS Read never seems to get called. Whereas Write gets called regularly. I can also confirm that everything is being properly written when inspecting the .gh file. I’m just not sure though why Read wouldn’t get called.

Also I’ve chosen to inherit from the IGH_GeometricGoo interface instead of GH_GeometricGoo<T> not sure if that might have anything to do with it.

Below I have a shortened excerpt of the class and specifically the two methods.

public class Manipulator : IGH_GeometricGoo
{

    public bool Read(GH_IReader reader)
    {
        this.Name = reader.GetString("Name");
        this.Manufacturer = (Manufacturer)reader.GetInt32("Manufacturer");

        Plane bPlane = new Plane();
        GH_Convert.ToPlane(reader.GetPlane("RobBasePlane"), ref bPlane, GH_Conversion.Both);
        this.RobBasePlane = bPlane

        return true;

    }
    public bool Write(GH_IWriter writer)
    {
        GH_Plane gH_RobBasePlane = new GH_Plane(this.RobBasePlane);

        writer.SetString("Name", this.Name);
        writer.SetInt32("Manufacturer", (int)this.Manufacturer);
        gH_RobBasePlane.Write(writer.CreateChunk("RobBasePlane"));

    return true;
    }
}

If you want to store custom types within the definition file, you will need to implement your own GH_PersistentParam, otherwise the type would stay run-time.

2 Likes

Hey @gankeyu,

thanks for the feedback. I have now tiered implementing a GH_PersistentParam class. But I still seem to have the same issues, of not even being able to copy data. The issue seems similar to:

but so far I’ve not been able to spot a mis match between my Write and Read methods.

Here is my new Param class:

public class Param_Manipulator : GH_PersistentParam<Manipulator>
{
    public Param_Manipulator()
      : base("Axis Robot", "Axis Robot", "This parampeter will store Axis Robots and their data.", Axis.AxisInfo.Plugin, Axis.AxisInfo.TabCore)
    { }

    public override Guid ComponentGuid => new Guid("17C49BD4-7A54-4471-961A-B5E0E971F7F4");

    protected override Manipulator InstantiateT()
    {
        return Manipulator.Default;
    }
    protected override GH_GetterResult Prompt_Singular(ref Manipulator value)
    {
        Rhino.Input.Custom.GetOption go = new Rhino.Input.Custom.GetOption();
        go.SetCommandPrompt("Set default Robot");
        go.AcceptNothing(true);
        go.AddOption("True");

        switch (go.Get())
        {
            case Rhino.Input.GetResult.Option:
                if (go.Option().EnglishName == "True") { value = Manipulator.Default; }
                return GH_GetterResult.success;

            case Rhino.Input.GetResult.Nothing:
                return GH_GetterResult.accept;

            default:
                return GH_GetterResult.cancel;
        }

        return GH_GetterResult.cancel;
    }
    protected override GH_GetterResult Prompt_Plural(ref List<Manipulator> values)
    {
        return GH_GetterResult.cancel;
    }
    public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
    {
        //Menu_AppendItem(menu, "Set the default value", SetDefaultHandler, SourceCount == 0);
        base.AppendAdditionalMenuItems(menu);
    }
    private void SetDefaultHandler(object sender, EventArgs e)
    {
        PersistentData.Clear();
        PersistentData.Append(Manipulator.Default, new GH_Path(0));
        ExpireSolution(true);
    }
    public override bool Write(GH_IWriter writer)
    {
        PersistentData.Write(writer);
        return base.Write(writer);
    }
    public override bool Read(GH_IReader reader)
    {
        SetPersistentData(Manipulator.Default);            
        return base.Read(reader);
    }
}

I know that I’m currently just setting a default, but that also just returns null as of now.
On the other hand the SetDefaultHandler method works fine.

Though I haven’t carefully checked your code, at first glance you don’t need to override Read & Write

Hi @gankeyu,
I seem to have solved it. Turns out just having a placeholder in the ClearCaches methods is a problem.
After defining that one properly it seems to be working now. Thank you for you help :slight_smile:

1 Like

Hi,

similarly to Manipulator, I have a custom class D.
My goal is to have a GH component where you construct/build an instance of this class, which you get as an output. A second GH component, similar to the GH native component Data, would internalize the object instance. I am attaching the code that generates the first GH component and includes the class D (constructor fields etc.). Below you see the code for the second component. I cannot understand how to (de)serialize class fields like Point, Curve, Brep. Any hint/help in the Write, Read, Prompt_singular, Prompt_Plural methods would be much appreciated. Thanks!
BuildD.cs (3.6 KB)

public class SerializeD : GH_PersistentParam<D>
{
    public SerializeD(string name, string nickname, string description, string category, string subcategory)
        : base(name, nickname, description, category, subcategory) { }

    public SerializeD(GH_InstanceDescription tag) : base(tag) { }

    /// <summary>
    /// Initializes a new instance of the SerializeD class.
    /// </summary>
    public SerializeD()
      : base("Serialize D",
             "SerialD",
             "Description",
             "Category",
             "Subcategory")
    {
    }

    protected override GH_GetterResult Prompt_Singular(ref DGoo value) {
        return GH_GetterResult.cancel;
    }
    protected override GH_GetterResult Prompt_Plural(ref List<DGoo> values) {
        return GH_GetterResult.cancel;
    }

    protected override System.Drawing.Bitmap Icon
    {
        get
        {
            //You can add image files to your project resources and access them like this:
            // return Resources.IconForThisComponent;
            return null;
        }
    }

    public override Guid ComponentGuid
    {
        get { return new Guid("A80F4174-12B3-4CEB-B6C6-908326715BA7"); }
    }
}

public class DGoo : GH_Goo<D> {

    #region CONSTRUCTORS
    public DGoo()
        : this(null) { }
    public DGoo(D d) {
        Value = d;
    }
    #endregion

    #region PROPERTIES
    public override bool IsValid {
        get {
            if (Value.isEmpty) return false;
            return true;
        }
    }
    public override string TypeName => "D Ser";
    public override string TypeDescription => "Serialize D";
    public override IGH_Goo Duplicate() {
        // It's okay to share the same Foo instance since Foo is immutable.
        return new DGoo(Value);
    }
    public override string ToString() {
        if (Value == null) return "No d";
        return Value.ToString();
    }
    public override bool CastFrom(object source) {
        if (source.GetType() == typeof(D)) {
            Value = (D)source;
            return true;
        }

        if (source.GetType() == typeof(DGoo)) {
            Value = ((DGoo)source).Value;
            return true;
        }
        return base.CastFrom(source);
    }
    public override bool CastTo<Q>(ref Q target) {
        Type q = typeof(Q);
        if (typeof(Q) == typeof(D)) {
            target = (Q)(object)Value;
            return true;
        }

        if (typeof(Q) == typeof(DGoo)) {
            target = (Q)(object)this;
            return true;
        }

        return base.CastTo(ref target);
    }
    #endregion

    #region (DE)SERIALIZATION
    private string IoPointKey = "Point";
    private string IoCurvesKey;
    private string IoMeshesKey;

    public override bool Write(GH_IWriter writer) {
        if (!Value.isEmpty) {
            /*                
            Point3d pt = Value.Point;
            byte[] byteArrayP = GH_Convert.CommonObjectToByteArray<GeometryBase>(pt);
            writer.SetByteArray(IoPointKey, byteArrayP);
            */
            
            GH_Point3D gh_Point = new GH_Point3D(Value.Point.X, Value.Point.Y, Value.Point.Z);
            writer.SetPoint3D(IoPointKey, gh_Point);

            if (Value.Curves != null) {
                for (int i = 0; i < Value.Curves.Length; i++) {
                    IoCurvesKey = "Curve_" +  i.ToString();
                    byte[] byteArrayC = GH_Convert.CommonObjectToByteArray(Value.Curves[i]);
                    writer.SetByteArray(IoCurvesKey, byteArrayC);
                }                    
            }
            
            if (Value.Meshes != null) {
                for (int i = 0; i < Value.Meshes.Length; i++) {
                    IoMeshesKey = "Mesh_" + i.ToString();
                    byte[] byteArrayM = GH_Convert.CommonObjectToByteArray(Value.Meshes[i]);
                    writer.SetByteArray(IoMeshesKey, byteArrayM);
                }
            }
        }
        return true;
    }
    public override bool Read(GH_IReader reader) {

        GH_Point3D pt = reader.GetPoint3D(IoPointKey);
        List<Curve> curves = new List<Curve>();
        List<Rhino.Geometry.Mesh> meshes = new List<Rhino.Geometry.Mesh>();

        Value = new D(new Point3d(pt.x, pt.y, pt.z), curves, meshes);
        return true;
    }
    #endregion
}