Dynamic input/ouput C# class in Grasshopper plugins

Hi there,

I want to output optional data in my GH plugin in C#. Do you know how I can do this write “VariableParameterMaintenance” So plus sign appears and I get to choose which I want to output. Sorry for the possible poor explaination. Something like below(example from ClimateStudio Plugin):

You must implement the IGH_VariableParameterComponent interface, and then handle the 5 or so methods that come with that.

Thanks David!

I’ve added the interface:

        public bool CanInsertParameter(GH_ParameterSide side, int index)
        {
            return side == GH_ParameterSide.Output;
        }

        public bool CanRemoveParameter(GH_ParameterSide side, int index)
        {
            return side == GH_ParameterSide.Output;
        }

        public IGH_Param CreateParameter(GH_ParameterSide side, int index)
        {
            throw new NotImplementedException();
        }

        public bool DestroyParameter(GH_ParameterSide side, int index)
        {
            return true;
        }

        public void VariableParameterMaintenance()
        {
            for (var i = 0; i < Params.Output.Count; i++)
            {
                var param = Params.Output[i];
                if (param.NickName == "-")
                {
                    param.Name = $"Data {i + 1}";
                    param.NickName = $"d{i + 1}";
                }
                else
                {
                    param.Name = param.NickName;
                }
                param.Description = $"Output {i + 1}";
                param.Optional = true;
                param.MutableNickName = true;
                //param.Access = GH_ParamAccess.item;
            }
        }

Got some idea from jSwan’s repo, but I was wondering if I could find some sample for this.

How about this:

using System;
using Grasshopper.Kernel;

namespace TextSupport
{
  public sealed class VariableComponentExample : GH_Component, IGH_VariableParameterComponent
  {
    public VariableComponentExample() : base("Variable Outputs", "VarOut", "Variable output example.", "Test", "Test") { }
    public override Guid ComponentGuid => new Guid("{0159A62F-D6AD-4862-AFFF-0792C6DBB949}");

    protected override void RegisterInputParams(GH_InputParamManager pManager)
    {
      pManager.AddTextParameter("Text", "tx", "Text input.", GH_ParamAccess.item);
    }
    protected override void RegisterOutputParams(GH_OutputParamManager pManager)
    {
      // Let's create three outputs by default.
      // Note that I do not set any names here since 
      // VariableParameterMaintenance() will do that.
      pManager.AddTextParameter("", "", "", GH_ParamAccess.item);
      pManager.AddTextParameter("", "", "", GH_ParamAccess.item);
      pManager.AddTextParameter("", "", "", GH_ParamAccess.item);
    }

    protected override void SolveInstance(IGH_DataAccess access)
    {
      string text = null;
      access.GetData(0, ref text);

      if (string.IsNullOrEmpty(text))
        return;

      for (int i = 0; i < Math.Min(text.Length, Params.Output.Count); i++)
        access.SetData(i, text[i]);
    }

    bool IGH_VariableParameterComponent.CanInsertParameter(GH_ParameterSide side, int index)
    {
      return side == GH_ParameterSide.Output;
    }
    bool IGH_VariableParameterComponent.CanRemoveParameter(GH_ParameterSide side, int index)
    {
      return side == GH_ParameterSide.Output;
    }
    IGH_Param IGH_VariableParameterComponent.CreateParameter(GH_ParameterSide side, int index)
    {
      // We must return a parameter of the correct type.
      return new Grasshopper.Kernel.Parameters.Param_String();
    }
    bool IGH_VariableParameterComponent.DestroyParameter(GH_ParameterSide side, int index)
    {
      return true;
    }
    void IGH_VariableParameterComponent.VariableParameterMaintenance()
    {
      // All the heavy lifting happens here.
      // We want to assign certain properties to all the variable outputs.
      // You don't *have* to implement this method, you could also make sure
      // that all properties are set when you return a new parameter in 
      // CreateParameter(side, index), but in this example I'm choosing
      // to do all the maintenance in the method which has the word
      // "maintenance" in its name.
      for (int i = 0; i < Params.Output.Count; i++)
      {
        var param = Params.Output[i];
        param.Access = GH_ParamAccess.item;

        param.MutableNickName = false;
        param.NickName = $"C{i + 1}";
        param.Name = $"Character {i + 1}.";
        param.Description = $"Character at location {i + 1}.";
      }
    }
  }
}
2 Likes

This works beautifully. Thanks David.

Hello, how can I do this in Python???

    def SetUpParam(self, p, name, nickname, description):
        p.Name = name
        p.NickName = nickname
        p.Description = description
        p.Optional = True
    def RegisterInputParams(self, pManager):
        # First parameter
        p = Grasshopper.Kernel.Parameters.Param_GenericObject()
        p.Simplify = True
        # p.PersistentData.ClearData()
        self.SetUpParam(p, "Data1", "D1", "Data1")
        p.Access = Grasshopper.Kernel.GH_ParamAccess.tree
        self.Params.Input.Add(p)

        for i in range(2, 4):
            p = Grasshopper.Kernel.Parameters.Param_GenericObject()
            p.Simplify = True
            p.PersistentData.ClearData()
            self.SetUpParam(p, "Data%d"%i, "D%d"%i, "%d"%i)
            p.Access = Grasshopper.Kernel.GH_ParamAccess.tree
            self.Params.Input.Add(p)

    def RegisterOutputParams(self, pManager):
        p = Grasshopper.Kernel.Parameters.Param_GenericObject()
        self.SetUpParam(p, "Data", "DA", "Data.")
        self.Params.Output.Add(p)

    def SolveInstance(self, DA):
        input_count = len(self.Params.Input)
        input_data_list = []

        for i in range(input_count):
            data_input = self.marshal.GetInput(DA, i)
            input_data_list.append(data_input)

        result = self.RunScript(input_data_list)

        if result is not None:
            self.marshal.SetOutput(result, DA, 0, True)

Short answer: You don’t.

Long answer:

You can get “close” to achieving something similar, but you will run into issues as long as the code changing the parameters is called during SolveInstance.

Changing component parameters during SolveInstance is a big nono (and Grasshopper will make sure to throw angry popups at you). You will have to find a way to call the methods updating the parameters outside of SolveInstance, which is tricky to achieve with ghPython script components.

That said, I did an experiment with exploding JSON values into individual outputs (similar to Jswan plugin) using ghPython before. Might be helpful reference if you want to have a shot at it yourself.

explode_JSON_using_ghPython.gh (4.1 KB)

import Grasshopper
import json

data = json.loads(data)

# Clean existing params
for param in list([p for p in ghenv.Component.Params.Output]):
    ghenv.Component.Params.UnregisterOutputParameter(param)

# Create new params with name based on key in data
for key in data.keys():
    param = Grasshopper.Kernel.Parameters.Param_GenericObject()
    param.NickName = key
    ghenv.Component.Params.RegisterOutputParam(param)

# Call this to finalize param creation and make them collect python variables
ghenv.Component.Params.OnParametersChanged()

# Assign your value
for key, value in data.items():
    exec(key + "=" + "data[key]")

But really - save yourself the pain and just write it as a compiled C# component :wink:.

I’ll try. Thank you very much. I will report the test results here

Hi, when i use dynamic output C# . I have a problem. This is code. How i can fix it .Thanks all.
Test.cs (5.6 KB)