Using custom GH_Param as Hops output

Hi, I’m now trying to use my class that inherits GH_Param as Hops output.
I defined classes below,

  • “Whisky” class
  • “GHWhisky” class which inherits GH_Goo
  • “Param_Whisky” class which inherits GH_Param
  • “ConstructWhisky” component class which inherits GH_Component

And I cloned Compute.rhino3d Repository, added codes about my classes in all parts that switches behavior depending on passed type in compute.geometry and Hops projects.
As a result, Hops can recognize my Param_Whisky as output and add output port to Hops Component, but returns null.
As I debugged, compute.geometry server seems to return null as Value in VolatileData in Param_Whisky.
Do I have to override some Interfaces or methods or modify other parts in compute.geometry?

Thanks in advance,

    public class Whisky
    {
        public Whisky()
        {
        }

        public Whisky(string name, int distilledYear, int matureYear, string distillerName, string bottlerName)
        {
            Name = name;
            DistilledYear = distilledYear;
            MatureYear = matureYear;
            DistillerName = distillerName;
            BottlerName = bottlerName;
        }

        public string Name { get; set; }
        public int DistilledYear { get; set; }
        public int MatureYear { get; set; }
        public string DistillerName { get; set; }
        public string BottlerName { get; set; }
        public bool IsBottlers => DistillerName == BottlerName;
    }

    public class GHWhisky : GH_Goo<Whisky>
    {
        public GHWhisky(GH_Goo<Whisky> other) : base(other)
        {
            Value = other.Value;
        }

        public GHWhisky()
        {
        }

        public GHWhisky(Whisky internal_data) : base(internal_data)
        {
            Value = internal_data;
        }

        public override bool IsValid => !string.IsNullOrEmpty(Value.Name) && !string.IsNullOrEmpty(Value.DistillerName) && !string.IsNullOrEmpty(Value.BottlerName);

        public override string TypeName => typeof(Whisky).Name;

        public override string TypeDescription => "Whisky class for grasshopper.";

        public override IGH_Goo Duplicate()
        {
            return new GHWhisky { Value = new Whisky { Name = Value.Name.Clone() as string, DistilledYear = Value.DistilledYear, MatureYear = Value.MatureYear, BottlerName = Value.BottlerName.Clone() as string, DistillerName = Value.DistillerName.Clone() as string } };
        }

        public override string ToString()
        {
            return $"{Value.Name} {Value.MatureYear}yo, Bottled by {Value.BottlerName}, Distilled in {Value.DistilledYear} by {Value.DistillerName}";
        }

        public override bool CastFrom(object source)
        {
            switch (source)
            {
                case Whisky w:
                    this.Value = w;
                    return true;
                default:
                    return false;
            }
        }

        public override bool CastTo<Q>(ref Q target)
        {
            switch (target)
            {
                case Whisky w:
                    target = (Q)(object)Value;
                    return true;
                default:
                    return false;
            }
        }

    }

    public class Param_Whisky : GH_Param<GHWhisky>
    {

        public Param_Whisky(string name, string nickname, string description, GH_ParamAccess access) : base(name, nickname, description, "", "", access)
        {
        }
        public Param_Whisky() : base("Whisky", "W", "Whisky class", "HopsTest", "Param", GH_ParamAccess.tree)
        {
        }

        public Param_Whisky(IGH_InstanceDescription tag) : base(tag)
        {
        }

        public Param_Whisky(IGH_InstanceDescription tag, GH_ParamAccess access) : base(tag, access)
        {
        }

        public override Guid ComponentGuid => new Guid("0f143222-e477-4d81-bba2-c6f0dce5b7e6");

    }

    public class ConstructWhisky : GH_Component
    {

        public ConstructWhisky() : base("ConstructWhisky", "CW", "", "HopsTest", "Construction")
        {
        }

        public override Guid ComponentGuid => new Guid("8c394fe0-bee5-4f94-8296-c2b08d71db45");

        protected override void RegisterInputParams(GH_InputParamManager pManager)
        {
            pManager.AddTextParameter("Name", "N", "", GH_ParamAccess.item);
            pManager.AddIntegerParameter("MatureYear", "MY", "", GH_ParamAccess.item);
            pManager.AddIntegerParameter("DiltilledYear", "DY", "", GH_ParamAccess.item);
            pManager.AddTextParameter("DistillerName", "DN", "", GH_ParamAccess.item);
            pManager.AddTextParameter("BottlerName", "BN", "", GH_ParamAccess.item);
            pManager[4].Optional = true;
        }

        protected override void RegisterOutputParams(GH_OutputParamManager pManager)
        {
            pManager.AddParameter(new Param_Whisky("Whisky", "W", "", GH_ParamAccess.item));
        }

        protected override void SolveInstance(IGH_DataAccess DA)
        {
            string name = default;
            int mYear = default;
            int dYear = default;
            string dName = default;
            string bName = default;

            if (!DA.GetData(0, ref name))
                return;
            if (!DA.GetData(1, ref mYear))
                return;
            if (!DA.GetData(2, ref dYear))
                return;
            if (!DA.GetData(3, ref dName))
                return;
            if (!DA.GetData(4, ref bName))
                bName = dName;

            DA.SetData(0, new Whisky(name, dYear, mYear, dName, bName));
        }

    }

Hops and rhino.compute only know how to work with built in types. We don’t currently have support for custom types.

Thanks for reply, does it mean that supporting custom types is impossible even if I modify codes in Hops and rhino.compute?
I’m trying to support by modifying them now.

I think I’m failing serializing custom object in VolatileData to json for ResthopperObject.

@stevebaer
I’ve forgotten mention.

If you’re modifying the code, then I wouldn’t say this is impossible :grinning:. It would also be a great addition to have.

Mentioning @AndyPayne here as he may be able to help as well.

Try right clicking on your hops component and generate a json file. This should show what hops is trying to send to compute and may help show how your data is being serialized to json.

@stevebaer
Thank you very much, I didn’t notice json export function in menu item!
Json exported is below, and it seems to succeed serialize. I guess it maybe fails to deserialize.
I’ll check serialize/deserialize settings in hops and compute.geometry, I’ve not modified it.

[{"ParamName":"Name","InnerTree":{"0":[{"type":"System.String","data":"\"SpringBank\""}]}},{"ParamName":"MatureYear","InnerTree":{"0":[{"type":"System.Int32","data":"12"}]}},{"ParamName":"DistillYear","InnerTree":{"0":[{"type":"System.Int32","data":"2009"}]}},{"ParamName":"DistillerName","InnerTree":{"0":[{"type":"System.String","data":"\"SpringBank\""}]}},{"ParamName":"BottlerName","InnerTree":{"0":[{"type":"System.String","data":"\"SpringBank\""}]}}]

@notkoutaro I think what you’re seeing is the json deserialization of the input getter components (which in your example looks like they’re getting strings and integers, etc.). What you really need to see if the JSON request back which gives you the output value. I recently made a change to the hops component which allows you to export all of the request/responses that hops sends. This will be available in the next release of Hops. At the moment however, it doesn’t look like rhino.compute is returning anything at all (the text panel is empty). I think step one is to figure out why it isn’t returning anything at all.
Here’s my recommendation. I would remove your custom parameter from your reference definition. Instead, I would connect a generic “Data” parameter (Param/Primitive/Data) to the output of the “Construct Whisky” component. Then, save the “Data” parameter into a group and give it an output name like you did before. Hopefully, this will at least output something from the Hops component. If it does, then I would connect your new Whisky parameter to the output of the Hops component to cast the generic data object back into your Whisky data type. Hopefully this make sense. If not, perhaps you can share your files and I’ll do my best to mock something up.

@AndyPayne
Thanks for detailed and helpful advice!!!
I’ll try to use Data Parameter ( or use Text Parameter) to send json and wait for next release.