Overview
So far the example components have all operated on individual data items. This is known as One-In-One-Out. But what if you want to operate on more than one item at a time; One-In-Many-Out, Many-In-One-Out or Many-In-Many-Out? This requires that input or output parameters have a non-standard Grasshopper.Kernel.GH_ParamAccess flag.
List Parameters
Input and Output parameters that are part of Grasshopper components have an access flag that affects how the component treats data stored in these parameters. Take for example the Polyline component. It creates a single polyline object from a collection of corner-points. This is a Many-In-One-Out kind of logic. The Divide component creates a whole bunch of division points from a single curve. This is an example of One-In-Many-Out. The Cull components remove certain items from lists, this is an example of Many-In-Many-Out.
Most components treat their inputs and outputs as parameters that provide individual instances of data, rather than related collections of data. This is indicated by all parameters having an GH_ParamAccess.item access flag. You can however assign different access flags to parameters. Preferably this flag should be assigned only once, namely in the RegisterInputParams or RegisterOutputParams method overrides. It is legal to modify an access flag as long as a solution is not currently in progress, but it is not recommended.
In this guide, we’ll be writing a component that removes (culls) the bottom-most N objects in a collection of geometric shapes. As inputs we’ll need a collection of geometry and an integer indicating how many objects the user wants to remove, and as output we’ll provide the same collection of geometry, but with the bottom-most objects missing. This is therefore a Many-In-Many-Out case.
The Component code (without the RegisterInputParams
, RegisterOutputParams
and SolveInstance
methods) may look like this:
public class Component_CullByElevation : GH_Component
{
public Component_CullByElevation()
: base("Cull Elevation", "CullZ", "Cull objects by relative elevation", "Sets", "Sequence")
{ }
public override Kernel.GH_Exposure Exposure
{
get { return GH_Exposure.primary | GH_Exposure.obscure; }
}
public override System.Guid ComponentGuid
{
get { return new Guid("{A8FF9CBA-0837-4cd6-9198-0D17325D3F8F}"); }
}
//...additional component code will go here..
}
Public Class Component_CullByElevation
Inherits GH_Component
Public Sub New()
MyBase.New("Cull Elevation", "CullZ", "Cull objects by relative elevation", "Sets", "Sequence")
End Sub
Protected Overrides ReadOnly Property Icon() As System.Drawing.Bitmap
Get
Return My.Resources.TheIconNameForThisComponent
End Get
End Property
Public Overrides ReadOnly Property Exposure() As Kernel.GH_Exposure
Get
Return GH_Exposure.primary Or GH_Exposure.obscure
End Get
End Property
Public Overrides ReadOnly Property ComponentGuid() As System.Guid
Get
Return New Guid("{A8FF9CBA-0837-4cd6-9198-0D17325D3F8F}")
End Get
End Property
'...further example code will come here...
End Class
We’ll need to register the parameters as well, and we’ll use the overloaded methods to immediately assign the correct parameter access flags…
protected override void RegisterInputParams(Kernel.GH_Component.GH_InputParamManager pManager)
{
pManager.AddGeometryParameter("Geometry", "G", "Geometry to cull", GH_ParamAccess.list);
pManager.AddIntegerParameter("Count", "C", "Number of objects to cull", GH_ParamAccess.item, 1);
}
protected override void RegisterOutputParams(Kernel.GH_Component.GH_OutputParamManager pManager)
{
pManager.AddGeometryParameter("Geometry", "G", "Culled geometry", GH_ParamAccess.list);
}
Protected Overrides Sub RegisterInputParams(ByVal pManager As Kernel.GH_Component.GH_InputParamManager)
'list access is non-standard, so we need to specifically assign it.
pManager.AddGeometryParameter("Geometry", "G", "Geometry to cull", GH_ParamAccess.list)
pManager.AddIntegerParameter("Count", "C", "Number of objects to cull", GH_ParamAccess.item, 1)
End Sub
Protected Overrides Sub RegisterOutputParams(ByVal pManager As Kernel.GH_Component.GH_OutputParamManager)
'Again, we need to specify list access as that is not default.
pManager.AddGeometryParameter("Geometry", "G", "Culled geometry", GH_ParamAccess.list)
End Sub
Input parameters rigorously enforce their access. You are only allowed to retrieve individual items from inputs that have the GH_ParamAccess.item flag set. Lists can only be retrieved when the access is set to GH_ParamAccess.list
and data trees can only be gotten from a GH_ParamAccess.tree
parameter. Failure to do so will result in an error message at runtime. Output parameter are more flexible, but this is only because output access was added only in Grasshopper 0.9.0001 and strict enforcement would result in SDK breakage with previous versions.
Solving Routine
The SolveInstance
implementation is basically identical to previous examples, the only difference is the way the component interacts with the parameters…
protected override void SolveInstance(Kernel.IGH_DataAccess DA)
{
//Declare a new List(Of T) to hold your data.
//This list must exist and should probably be empty.
List<IGH_GeometricGoo> geometry = new List<IGH_GeometricGoo>();
Int32 count = 0;
//Retrieve the whole list using Da.GetDataList().
if ((!DA.GetDataList(0, geometry)))
return;
if ((!DA.GetData(1, count)))
return;
//Validate inputs.
if ((count < 0))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Count must be a positive integer");
return;
}
//The number of objects to cull is larger than or
//equal to the total number of objects. I.e. cull them all.
if ((geometry.Count <= count))
return;
//Iteratively remove the lowest object from the list.
for (Int32 N = 1; N <= count; N++)
{
double lowestElevation = double.MaxValue;
Int32 lowestIndex = -1;
//Iterate over all remaining geometry and find the lowest one.
for (Int32 i = 0; i <= geometry.Count - 1; i++)
{
if ((geometry[i] == null))
continue;
BoundingBox bbox = geometry[i].Boundingbox;
if ((!bbox.IsValid))
continue;
double localElevation = bbox.Min.Z;
if ((localElevation < lowestElevation))
{
lowestElevation = localElevation;
lowestIndex = i;
}
}
//Delete the lowest object.
geometry.RemoveAt(lowestIndex);
}
//Assign the remaining geometry
//(even if it is only a single item!)
//using the DA.SetDataList() method.
DA.SetDataList(0, geometry);
}
Protected Overrides Sub SolveInstance(ByVal DA As Kernel.IGH_DataAccess)
'Declare a new List(Of T) to hold your data.
'This list cannot be a null reference.
Dim geometry As New List(Of IGH_GeometricGoo)
Dim count As Int32 = 0
'Retrieve the whole list using DA.GetDataList().
If (Not DA.GetDataList(0, geometry)) Then Return
If (Not DA.GetData(1, count)) Then Return
'Validate inputs.
If (count < 0) Then
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Count must be a positive integer")
Return
End If
'The number of objects to cull is larger than or
'equal to the total number of objects. I.e. cull them all.
If (geometry.Count <= count) Then Return
'Iteratively remove the lowest object from the list.
For N As Int32 = 1 To count
Dim lowestElevation As Double = Double.MaxValue
Dim lowestIndex As Int32 = -1
'Iterate over all remaining geometry and find the lowest one.
For i As Int32 = 0 To geometry.Count - 1
If (geometry(i) Is Nothing) Then Continue For
Dim bbox As BoundingBox = geometry(i).Boundingbox
If (Not bbox.IsValid) Then Continue For
Dim localElevation As Double = bbox.Min.Z
If (localElevation < lowestElevation) Then
lowestElevation = localElevation
lowestIndex = i
End If
Next
'Delete the lowest object.
If (lowestIndex >= 0) Then geometry.RemoveAt(lowestIndex)
Next
'Assign the remaining geometry using the DA.SetDataList() method.
DA.SetDataList(0, geometry)
End Sub