Creating inputs dynamically based on the value of the inputs

Hey! I’m trying to create a gh component in C# with a dynamic set of inputs:

  1. Generate a set of inputs dynamically based on the component “initial” state. This was done using IGH_VariableParameterComponent and Params.RegisterInputParam(ParamObject).

  2. Based on the inputs connected to the component, generate a new set of inputs. Basically what I want to achieve is that whenever a new variable is connected to the component, then a function that clears and generates the new set of inputs is called. Also, when a variable that is already connected to the component is modified, a new set of inputs is generated again. I’ve tried adding an event handler to each of the ParamObject ObjectChanged events but It was only fired when a variable was connected with the component, and not when it was modified…

Is it possible to capture an event when a variable connected to the component is modified? If so, how can I do this?

Thank you very much for your help! Cheer

Hello

A variable change will trigger a new solution.
However it is impossible to add or remove input or output parameters during a solution.
What you will have to do is to schedule a new solution with a callback method.
In that method, modify the inputs and outputs, expire the component and toggle a private bool variable so the component actually knows he needs to skip that process on the next solution.

See :

1 Like

Thank you for your help. Indeed that solution works for when I have all the inputs connected to the component. However, I was also looking to update the inputs when a variable is changed.
For instance, in this case, I made a component to calculate the area of either a circle or a rectangle. If the boolean is_square is set to true, then display the inputs for a rectangle (width and height) and compute the area of a rectangle. If is_square is set to false, then show the inputs for a circle (diameter) and calculate the area of a circle:

1
2

Is this behaviour possible?

Thank you again :slight_smile:

Sure, however you need to make all input parameters Optional (except the toggle maybe ?) so that the component runs even if some inputs aren’t specified.

Unfortunately I only know which parameters are optional at runtime… These input parameters are also created dynamically, so instead of “SampleAreaCalculation” it can be any other unrelated calculation with another set of input parameters.

Also, I get a set of inputs by sending a request to a server with the current state of the component. For example, in the case of “SampleAreaCalculation”, If I send a request containing the input parameter is_square=True, then the response will contain the width and height (and is_square) input parameters.

So basically, I would like to:

  1. Send a request to my server each time an input variable is modified or connected to the component.
  2. If the number or names of the variables from the response are different from the component ones, then update the inputs of the component.

You need some code structure like this I think.

The fact that you don’t know how many inputs are “required” doesn’t prevent you from setting those inputs to Optional, so that the component runs as soon as it has the minimum information required to run (in your case, seems to be the first input).

in the component class :
private bool isRunning = false;

in SolveInstance() :

if(!isRunning)
{
//get your request result here

}

//check if the inputs are valid. 

if(areInputValid)
{
//proceed to calculation and SetData

if(data exists)
{
isRunning = false
}
else
{
AddRuntimeMessage("Input parameter XXX failed to collect data");
return;
}
}
else
{
//schedule a new solution and do nothing else

}



in the callback :
add or remove input parameters
then call a VariableParameterMaintenance() method.
then set isRunning to true
then ExpireSolution(true);

in VariableParameterMaintenance():
set names, component message, optional and data access.
Param[i].Optional = true; //set inputs optional
Also call this method in RegisterInputParams.

You may also need to ensure that the boolean input is only recieving ONE value. Otherwise your parameter live modification is going to cause problems.
You can override BeforeSolveInstance and check the VolatileData only has one (and exactly one) element.

1 Like

Stepping back a little:

Perhaps you might consider two separate components, one for the square and one for the circle. Then, add a third component which decides which one to call based on the input received and feed the area into the next component in the GH flow?

Thank you again magicteddy, I almost got it working by setting every input parameter to optional. This way, the solution is re-calculated on every connection/modification of an input parameter.

However, there is just one small thing missing: I would like the component Input parameters to be updated right after I connect a variable to the component. For intance, in the “SampleAreaCalculation”, if I connect a boolean toogle set to False, to the is_square input variable, then the inputs of the component should immediately change. Because I’m using OnPingDocument().ScheduleSolution(0, callBack), the inputs of the component are only updated after I add another element to my grasshopper (like a panel, etc), or make any changes in the component.

Is there any way to force an update of the components inputs? Thank you again for your help.

Cheers!

Are you expiring the component at the end of the callback method ?

this.ExpireSolution(true);

Thank you, the problem is that I need the component to be able to “figure out” what are the inputs given its current state, otherwise I would need to create a new component for each possible state of the component, which is only known at runtime.

Yeah, like this:

public void callBack(GH_Document doc)
{
    // Update the inputs
    Params.Clear();
    AddInputParameters(currentUi);
    AddOutputParameter();
    Params.OnParametersChanged();
    
    this.ExpireSolution(true);
}

Then, callBack is scheduled in SolveInstance (when a certain condition is met) with:
OnPingDocument().ScheduleSolution(0, callBack);

The inputs dont change until I make any action in grasshopper (e.g. adding other components, connection stuff, etc)

You could try to expire the component layout as well… But I don’t recall doing that.

Let me check my code, I did the same thing for a component (updating outputs on the fly - should be the same).

What does your AddInputParameters method look like ?
There should be a call to RegisterInputParam in there, I recall having problems if removing it.

The only difference I can spot is that I use Instances.ActiveCanvas.Document to get the document instead of OnPingDocument(), I don’t know if that makes a difference.

My RegisterInputParam and RegisterOutputParam is empty because all my input parameters are generated dynamically. Basically, my component first asks what “calculation” I want, and then depending on the calculation, it populates the component with the calculation’s input variables.

These inputs are added in an unrelated function by calling Params.RegisterInputParam(IGH_Param param) on each param object (part of Grasshopper.Kernel.Parameters).

edit: nevermind about the edited bit :stuck_out_tongue: . Using Instances.ActiveCanvas.Document also seems not to make a difference.

edit2: video of the problem:

Basically, the component’s inputs only changed when I changed the bool variable a 2nd time, or, added another gh variable.

This is working for me.
The fact that your permanent input is at the bottom may be problematic…

TestInput.cs (5.3 KB)

3 Likes

Still didn’t manage it to work :frowning:

In my case, all of my inputs are set to “optional=True” since I don’t know which inputs are dependent (optional) on others or not. Maybe it’s because of this…

Hey again, I tried adding the following insideSolveInstance and it now works as I expected

protected async override void SolveInstance(IGH_DataAccess DA)
{
  ...blah blah blah

  if (condition)
  {
    Params.Clear();
    AddInputParameters(currentUi);
    AddOutputParameter();
    Params.OnParametersChanged();

    this.ExpireSolution(true);
  }
  else
  {
    ... actually calculate the results
  }
}

However I have a feeling this is not exactly best practices and will give me an headache in the future :sweat_smile:

Actually this

could be the issue in your case…
This is out of my knowledge unfortunately

Yeah, this was definitely the tricky part. Basically I was most likely getting out-of-date parameters because SolveInstance is probably not meant to be ran asynchronously :stuck_out_tongue: