Simple Component (VB) |
This article contains an exhaustive, step by step explanation of how to build your first component in VB.NET. It will skip over any complicated issues (such as mathematics, geometry and data handling) in order to reduce the totality of new concepts. You will however need to have a good understanding of basic OOP concepts such as classes, types and inheritance. If you do not understand these DotNET essentials, we recommend you start with some other reading material first.
All you need to do in order to define a new Component is to inherit from the GH_Component base class in Grasshopper. Assuming you've set up your project correctly, create a new, blank class in your project:
At this point a new file should be created (MyFirstComponent.vb) with the following content:
Public Class MyFirstComponent End Class
Imports Grasshopper.Kernel Public Class MyFirstComponent End Class
Imports Grasshopper.Kernel Public Class MyFirstComponent Inherits GH_Component End Class
Imports Grasshopper.Kernel Public Class MyFirstComponent Inherits GH_Component Protected Overrides Sub RegisterInputParams(ByVal pManager As GH_Component.GH_InputParamManager) 'Don't worry about this just yet! End Sub Protected Overrides Sub RegisterOutputParams(ByVal pManager As GH_Component.GH_OutputParamManager) 'We'll get to it soon enough. End Sub Protected Overrides Sub SolveInstance(ByVal DA As IGH_DataAccess) 'I know it all looks scary. End Sub Public Overrides ReadOnly Property ComponentGuid() As System.Guid Get 'But we'll deal with it one item at a time. End Get End Property End Class
As we've seen in the previous section, Visual Studio will populate the MyFirstComponent class with a collection of properties and subroutines that we need to implement. There is however another subroutine that requires our attention that is missing. This is the constructor. The constructor is a special subroutine inside each class which gets called when the class is instantiated (or "constructed"). This can happen only once (we feeble humans can only be born once as well after all) and it necessarily happens before anything else is allowed to happen. The GH_Component base class has a constructor which is not empty, so we have to call that constructor from within our constructor and supply it with all the information it needs. Let's play ball. Add the following code near the top of the MyFirstComponent class:
Imports Grasshopper.Kernel Public Class MyFirstComponent Inherits GH_Component Public Sub New() MyBase.New("MyFirst", "MFC", "My first component", "Extra", "Simple") End Sub ...
Parameter | Purpose |
---|---|
name | The name of our component. The name is what appears on tooltips and Panel dropdowns. |
abbreviation | The abbreviation of our component. The abbreviation is what is written on the component once it appears on the Canvas. |
description | A description of our component. The description is used on tooltips to provide users with a more detailed idea about what this component is for. |
category | The tab category for the component. The category equals the name of the Tab onto which the Component will appear. If a non-existing category is supplied, a new Tab will be added to the Grasshopper GUI. |
subCategory | The panel category for the component. The sub-category equals the name of the Panel onto which the Component will appear. If a non-existing sub-category is supplied, a new Panel will be added to the Category Tab. |
Every type of object inside a Grasshopper document must have a Guid associated with it. When a Grasshopper file (*.gh or *.ghx) is written these Guids are used as markers, so it becomes clear what portions of the file belong to which object. When the file is read back in, that marker is compared against the list of all cached components and if a match is found the appropriate component is asked to please go and deserialize itself. (You thought I was going to say something else didn't you?). When no matching component can be found it is assumed that whoever wrote the file had access to certain components that are not available locally, and that portion of the file is dutifully skipped.
So, long story short, we need to invent a Guid (Globally Unique IDentifier) that will positively and unerringly indicate this component. You can generate spanking new Guids using an Online Guid Generator or Microsofts popular guidgen.exe. Never re-use a Guid and never edit one by hand. Always generate a proper one using an official tool.
Once you have a new Guid standing by, modify the ComponentGuid property to return it:
... Public Overrides ReadOnly Property ComponentGuid() As System.Guid Get 'Don't copy this GUID, make a new one Return New Guid("419c3a3a-cc48-4717-8cef-5f5647a5ecfc") End Get End Property ...
Components have unique input and output parameters which are most often fixed. We are ignoring those rare cases where a component either has no inputs or no outputs, or where there is a variable number of parameters. There are two subroutines that allow you to define (or "register") these parameters. These routines are called from within the base class constructor and they are only called once. Let's have a look at the default implementation that Visual Studio generated again:
... Protected Overrides Sub RegisterInputParams(ByVal pManager As GH_Component.GH_InputParamManager) End Sub Protected Overrides Sub RegisterOutputParams(ByVal pManager As GH_Component.GH_OutputParamManager) End Sub ...
In this example we'll only create two parameters (one input, one output) and they will both be of type String.
... Protected Overrides Sub RegisterInputParams(ByVal pManager As Grasshopper.Kernel.GH_Component.GH_InputParamManager) pManager.AddTextParameter("String", "S", "String to reverse") End Sub Protected Overrides Sub RegisterOutputParams(ByVal pManager As Grasshopper.Kernel.GH_Component.GH_OutputParamManager) pManager.AddTextParameter("Reverse", "R", "Reversed string") End Sub ...
Our new component sure looks perky and expensive, but it doesn't do anything useful yet (am I the only one who's reminded of Paris Hilton?). We still need to write the contents of the SolveInstance subroutine, which is where all the action takes place. The SolveInstance() function is called upon whenever the component needs to handle input data. In this particular example, if we plug a list of twelve Strings into the [S] parameter, SolveInstance() will be called twelve times.
As you may already have guessed, the component we're writing will reverse a given textual string from [S] and output the result to [R]. Since we're operating on individual items of data (the default behaviour), all we need to do inside the SolveInstance() function is retrieve the current String from [S], reverse it and assign it to [R]. Now, String reversal is not a function that is directly available in the framework String type, so we need to actually do some thinking:
... Protected Overrides Sub SolveInstance(ByVal DA As Grasshopper.Kernel.IGH_DataAccess) 'Declare a variable for the input String Dim data As String = Nothing 'Use the DA object to retrieve the data inside the first input parameter. 'If the retieval fails (for example if there is no data) we need to abort. If (Not DA.GetData(0, data)) Then Return 'If the retrieved data is Nothing, we need to abort. 'We're also going to abort on a zero-length String. If (data Is Nothing) Then Return If (data.Length = 0) Then Return 'Convert the String to a character array. Dim chars As Char() = data.ToCharArray() 'Reverse the array of character. Array.Reverse(chars) 'Use the DA object to assign a new String to the first output parameter. DA.SetData(0, New String(chars)) End Sub ...