Internalize custom parameter + copy / paste

unhandled

#1

Hi,

  • I’ve searched the forums and haven’t found an answer that works…

  • I have a custom data type Foo which does stuff and has member variables.

  • I’ve created a GH_Foo object, which inherits GH_Goo<Foo>, IGH_PreviewData, and GH_ISerializable.

  • This GH_Foo overrides the public override bool Write(GH_IWriter writer) and public override bool Read(GH_IReader reader) to serialize it’s value Foo object.

  • I have then created a FooParameter which inherits GH_PersistentParam<GH_Foo>.

All of this works great and lets me pass Foo around the canvas and through various components, and also make a FooParameter component.

However, when copy / pasting across definitions, or when saving / loading, the internalized data is lost, and I’m left with an empty parameter. Something either isn’t serializing right, or I’m missing a trick when it comes to internalizing the data in the parameter.

Following the answer in this, all should be well if I’ve overridden the Read / Write functions in GH_Foo, however it appears that all is not, in fact, well.

Am I missing something obvious or are there other functions that need to be overridden or other interfaces that need interfacing?


(Dale Fugier) #2

@tom_svilans - I’ve moved this to the Grasshopper Developer category, where it should get more attention…


#3

I think heriting from GH_Goo / GH_PersistentParam should be enough, not aware that you’d need to implement further interfaces.

Maybe you should post some code snippets, e.g. what happens inside the Read/Write methods.


(David Rutten) #4

Yeah some code would be good. The main question for starters is whether the serialisation works if you put your GH_Foo inside a generic parameter.

The reliable (de)serialisation of data was a bit of an afterthought in the GH1 api design and was never properly implemented. There’s a lot of hoops you have to jump through to make it work.


#5

No problemo, thanks for the feedback @DavidRutten and @dsonntag. Here is the code for Read and Write:

        public override bool Write(GH_IWriter writer)
        {
            byte[] rawGuide = GH_Convert.CommonObjectToByteArray(Value.Centreline);
            writer.SetByteArray("guide", 0, rawGuide );

            writer.SetInt32("num_frames", Value.Frames.Count);

            for (int i = 0; i < Value.Frames.Count; ++i)
            {
                Plane p = Value.Frames[i].Item2;

                writer.SetPlane("frames", i, new GH_IO.Types.GH_Plane(
                    p.OriginX, p.OriginY, p.OriginZ, p.XAxis.X, p.XAxis.Y, p.XAxis.Z, p.YAxis.X, p.YAxis.Y, p.YAxis.Z));
            }

            writer.SetInt32("lcx", Value.Data.NumWidth);
            writer.SetInt32("lcy", Value.Data.NumHeight);
            writer.SetDouble("lsx", Value.Data.LamWidth);
            writer.SetDouble("lsy", Value.Data.LamHeight);
            writer.SetInt32("interpolation", (int)Value.Data.InterpolationType);
            writer.SetInt32("samples", Value.Data.Samples);

            return base.Write(writer);
        }

        public override bool Read(GH_IReader reader)
        {
            byte[] rawGuide = reader.GetByteArray("guide");
            Curve guide = GH_Convert.ByteArrayToCommonObject<Curve>(rawGuide);

            int N = reader.GetInt32("num_frames");
            Plane[] frames = new Plane[N];

            for (int i = 0; i < N; ++i)
            {
                var gp = reader.GetPlane("frames", i);
                frames[i] = new Plane(
                    new Point3d(
                        gp.Origin.x,
                        gp.Origin.y,
                        gp.Origin.z),
                    new Vector3d(
                        gp.XAxis.x,
                        gp.XAxis.y,
                        gp.XAxis.z),
                    new Vector3d(
                        gp.YAxis.x,
                        gp.YAxis.y,
                        gp.YAxis.z)
                        );
            }

            int lcx = reader.GetInt32("lcx");
            int lcy = reader.GetInt32("lcy");
            double lsx = reader.GetDouble("lsx");
            double lsy = reader.GetDouble("lsy");
            int interpolation = reader.GetInt32("interpolation");
            int samples = reader.GetInt32("samples");

            GlulamData data = new GlulamData(lcx, lcy, lsx, lsy, samples);
            data.InterpolationType = (GlulamData.Interpolation)interpolation;

            Value = Glulam.CreateGlulam(guide, frames, data);

            return base.Read(reader);
        }
    }

EDIT: Also, when copy / pasting the parameter to the same file, it ends up being a load of nulls. Same problem, I imagine…


(David Rutten) #6

Copy, paste, undo and redo are all basically relying on the same code as write and read. So yes, it’s the same problem.


(David Rutten) #8

Here’s a very basic implementation of a data type, a goo wrapper and a parameter that works:

using System;
using System.Collections.Generic;
using GH_IO.Serialization;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;

namespace GooIoTest
{
  /// <summary>
  /// A simple immutable data type with no functionality.
  /// </summary>
  public sealed class Foo
  {
    public Foo(int integer, string text)
    {
      Integer = integer;
      Text = text;
    }

    public int Integer { get; }
    public string Text { get; }

    public override string ToString()
    {
      return string.Format("[{0}] \"{1}\"", Integer, Text);
    }
  }

  /// <summary>
  /// An IGH_Goo wrapper around Foo.
  /// </summary>
  public sealed class FooGoo : GH_Goo<Foo>
  {
    #region constructors
    public FooGoo()
     : this(null)
    { }
    public FooGoo(Foo foo)
    {
      Value = foo;
    }

    public override IGH_Goo Duplicate()
    {
      // It's okay to share the same Foo instance since Foo is immutable.
      return new FooGoo(Value);
    }
    #endregion

    #region properties
    public override string ToString()
    {
      if (Value == null) return "No foo";
      return Value.ToString();
    }

    public override string TypeName => "Foo";
    public override string TypeDescription => "Pointless foo data";
    public override bool IsValid
    {
      get
      {
        if (Value == null) return false;
        if (Value.Integer < 0) return false;
        if (Value.Text == null) return false;
        return true;
      }
    }
    public override string IsValidWhyNot
    {
      get
      {
        if (Value == null) return "No data";
        if (Value.Integer < 0) return "Negative integer data";
        if (Value.Text == null) return "No text data";
        return string.Empty;
      }
    }

    public override bool CastFrom(object source)
    {
      if (source == null) return false;
      if (source is int integer)
      {
        Value = new Foo(integer, string.Empty);
        return true;
      }
      if (source is GH_Integer ghInteger)
      {
        Value = new Foo(ghInteger.Value, string.Empty);
        return true;
      }
      if (source is string text)
      {
        Value = new Foo(0, text);
        return true;
      }
      if (source is GH_String ghText)
      {
        Value = new Foo(0, ghText.Value);
        return true;
      }
      return false;
    }
    public override bool CastTo<TQ>(ref TQ target)
    {
      if (Value == null)
        return false;

      if (typeof(TQ) == typeof(int))
      {
        target = (TQ)(object)Value.Integer;
        return true;
      }
      if (typeof(TQ) == typeof(GH_Integer))
      {
        target = (TQ)(object)new GH_Integer(Value.Integer);
        return true;
      }

      if (typeof(TQ) == typeof(double))
      {
        target = (TQ)(object)Value.Integer;
        return true;
      }
      if (typeof(TQ) == typeof(GH_Number))
      {
        target = (TQ)(object)new GH_Number(Value.Integer);
        return true;
      }

      if (typeof(TQ) == typeof(string))
      {
        target = (TQ)(object)Value.Text;
        return true;
      }
      if (typeof(TQ) == typeof(GH_String))
      {
        target = (TQ)(object)new GH_String(Value.Text);
        return true;
      }

      return false;
    }
    #endregion

    #region (de)serialisation
    private const string IoIntegerKey = "Integer";
    private const string IoTextKey = "Text";
    public override bool Write(GH_IWriter writer)
    {
      if (Value != null)
      {
        writer.SetInt32(IoIntegerKey, Value.Integer);
        if (Value.Text != null)
          writer.SetString(IoTextKey, Value.Text);
      }
      return true;
    }
    public override bool Read(GH_IReader reader)
    {
      if (!reader.ItemExists(IoIntegerKey))
      {
        Value = null;
        return true;
      }

      int integer = reader.GetInt32(IoIntegerKey);
      string text = null;

      if (reader.ItemExists(IoTextKey))
        text = reader.GetString(IoTextKey);

      Value = new Foo(integer, text);

      return true;
    }
    #endregion
  }

  public sealed class FooParameter : GH_PersistentParam<FooGoo>
  {
    public FooParameter()
    : this("Foo", "Foo", "A collection of Foo data", "Foo", "Bar")
    { }
    public FooParameter(GH_InstanceDescription tag) : base(tag) { }
    public FooParameter(string name, string nickname, string description, string category, string subcategory)
      : base(name, nickname, description, category, subcategory) { }

    public override Guid ComponentGuid => new Guid("{606C9679-C36C-48B1-A547-22B68EE8A0A1}");
    protected override GH_GetterResult Prompt_Singular(ref FooGoo value)
    {
      return GH_GetterResult.cancel;
    }
    protected override GH_GetterResult Prompt_Plural(ref List<FooGoo> values)
    {
      return GH_GetterResult.cancel;
    }
  }
}

#9

Thanks very much for this! I will give it a try.


#10

So… copied your example, still didn’t work.

Then realized that in Write() I was calling writer.SetByteArray(string key, int index, byte[] bArray) whereas in Read() I was calling reader.GetByteArray(string key), the difference being the int index in the writer.

Removed this, and now it seems to serialize / deserialize correctly. Another case of code blindness :see_no_evil:

Apologies for wasting your time and thanks for your help!