Simple Data Types

Overview

It’s a rather complicated topic as data is an integral part of the Grasshopper process and GUI. Grasshopper needs to be able to (de)serialize data, display data in tooltips, convert data to other types of data, prompt the user for persistent data, draw geometry preview data in viewports and bake geometric data. In this topic I’ll only talk about non-geometric data, we’ll get to previews and baking in a later topic.

Practically all native data types in Grasshopper are based either on a .NET Framework type or a RhinoCommon type. For example System.Boolean, System.String, Rhino.Geometry.Point3d and Rhino.Geometry.Brep to name but a few. However, the parameters in Grasshopper don’t directly store Booleans, String, Points and Breps as these types can’t handle themselves in the cauldron that is Grasshopper.

Prerequisites

We will not be dealing with any of the basics of component development in this guide. Please start with the Your First Component guide and the Simple Component guide before starting this one.

Before you start, create a new class that derives from Grasshopper.Kernel.GH_Component, as outlined in the Simple Component guide.

The IGH_Goo interface

In this section I’ll briefly discuss all the methods and properties that are defined in IGH_Goo. What they’re for, who uses them at what time, etc, etc.

All data used in Grasshopper must implement the IGH_Goo interface. IGH_Goo defines the bare minimum of methods and properties for any kind of data before it is allowed to play ball.

IsValid

bool IsValid { get{} }
ReadOnly Property IsValid() As Boolean

Not all data types are valid all the time, and this property allows you to test the current instance of your data. When data is invalid it will often be ignored by components.

TypeName

string TypeName { get{} }
ReadOnly Property TypeName() As String

This property must return a human-readable name for your data type.

TypeDescription

string TypeDescription { get{} }
ReadOnly Property TypeDescription() As String

This property must return a human-readable description of your data type.

Duplicate

IGH_Goo Duplicate()
Function Duplicate() As IGH_Goo

This function must return an exact duplicate of the data item. Data is typically shared amongst multiple Grasshopper parameters, so before data is changed, it first needs to copy itself. When data only contains ValueTypes and Primitives, making a copy of an instance is usually quite easy.

ToString

string ToString()
Function ToString() As String

This function is called whenever Grasshopper needs a human-readable version of your data. It is this function that populates the data panel in parameter tooltips. You don’t need to create a String that is parsable, it only needs to be somewhat informative.

EmitProxy

IGH_GooProxy EmitProxy()
Function EmitProxy() As IGH_GooProxy

Data proxies are used in the Data Collection Manager. You can ignore this function (i.e. Return Nothing) without crippling your data type.

CastTo

bool CastTo<T>(out T target)
Function CastTo(Of T)(ByRef target As T) As Boolean

Data Casting is a core feature of Grasshopper data. It basically allows data types defined in Grasshopper add-ons to become an integral part of Grasshopper. Lets assume that we have a Component that operates on data type [A]. But instead of playing nice, we provide data of type [B]. Two conversion (casting) attempts will be made in order to change [B] into [A]. If [B] implements IGH_Goo, then it is asked if it knows how to convert itself into an instance of [A]. Failing that, if [A] implements IGH_Goo, it is asked whether or not it knows how to construct itself from an instance of [B].

The CastTo() function is responsible for step 1. The CastTo() method is a generic method, meaning the types on which it operates are not defined until the method is actually called. This allows the function to operate “intelligently” on data types. It also unfortunately means you have to be “intelligent” when implementing this function.

See the Grasshopper Data Types guide for more information on conversion.

CastFrom

bool CastFrom(object source)
Function CastFrom(ByVal source As Object) As Boolean

The CastFrom() function is responsible for step 2 of data casting. Some kind of data is provided as a source object and if the local Type knows how to “read” the source data it can perform the conversion.

See the Grasshopper Data Types guide for more information on conversion.

ScriptVariable

object ScriptVariable()
Function ScriptVariable() As Object

When data is fed into a VB or C# script component, it is usually stripped of IGH_Goo specific data and methods. The ScriptVariable() method allows a data type to provide a stripped down version of itself for use in a Script component.

The GH_Goo abstract class

Although all data in Grasshopper must implement the IGH_Goo interface, it is not necessary to actually write a type from scratch. It is good practice to inherit from the abstract class GH_Goo<T>, as it takes care of some of the basic functionality. GH_Goo is a generic type (that’s what the “<T>” bit means), where T is the actual type you’re wrapping. GH_Goo<T> has several abstract methods and properties which must be implemented, but a lot of the other methods are already implemented with basic (though usually useless) functionality.

An Example Type

We’ll now create a very simple custom type. This will introduce the basic concept of custom type development, without dealing with any of the baking and previewing logic yet. Our custom type will be a TriState flag, similar to boolean values but with an extra state called “Unknown”. We’ll represent these different states using integers:

  • Negative One -1 = Unknown
  • Zero 0 = False
  • One 1 = True

We’ll start with the general class layout, then drill down into each individual function. Create a new public class called TriStateType and inherit from GH_Goo<Integer>. Be sure to import the Grasshopper Kernel and Kernel.Types namespaces as we’ll need them both:

using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;

namespace MyTypes
{
  public class TriStateType : GH_Goo<int>
  {

  }
}
Imports Grasshopper.Kernel
Imports Grasshopper.Kernel.Types

Namespace MyTypes
  Public Class TriStateType
    Inherits GH_Goo(Of Integer)

  End Class
End Namespace

Constructors

Unless a constructor is defined, .NET classes always have a default constructor which initializes all the fields of the class to their default values. This constructor does not require any inputs and when you develop custom types it is a good idea to always provide a default constructor. If there is no default constructor, then class instances cannot be created automatically which thwarts certain algorithms in Grasshopper.

In addition to a default constructor I also find it useful to supply so called copy constructors which create a new instance of the type class with a preset value.

// Default Constructor, sets the state to Unknown.
public TriStateType()
{
  this.Value = -1;
}

// Constructor with initial value
public TriStateType(int tristateValue)
{
  this.Value = tristateValue;
}

// Copy Constructor
public TriStateType(TriStateType tristateSource)
{
  this.Value = tristateSource.Value;
}

// Duplication method (technically not a constructor)
public override IGH_Goo Duplicate()
{
  return new TriStateType(this);
}
'' Default Constructor, sets the state to Unknown.
Public Sub New()
  Me.Value = -1
End Sub

'' Constructor with initial value
Public Sub New(ByVal tristateValue As Integer)
  Me.Value = tristateValue
End Sub

'' Copy Constructor
Public Sub New(ByVal tristateSource As TriStateType)
  Value = tristateSource.Value
End Sub

'' Duplication method (technically not a constructor)
Public Overrides Function Duplicate() As Kernel.Types.IGH_Goo
  Return New TriStateType(Me)
End Function

Incidentally, the Value property which we are using to assign integers to our local instance is provided by the GH_Goo<T> base class. GH_Goo<T> defines a protected field of type T called m_value and also a public accessor property called Value which gets or sets the m_value field.

In this particular case, it actually makes sense to override the default Value property implementation, as the number of sensible values we can assign (-1, 0 and +1) is a subset of the total number values available through the Integer data type. It makes no sense to assign -62 for example. We could of course agree that all negative values indicate an “Unknown” state, but we should try to restrict ourselves to only three integer values:

// Override the Value property to strip non-sensical states.
public override int Value
{
  get { return base.Value; }
  set
  {
    if (value < -1) { value = -1; }
    if (value > +1) { value = +1; }
    base.Value = value;
  }
}
'' Override the Value property to strip non-sensical states.
Public Overrides Property Value() As Integer
  Get
    Return MyBase.Value
  End Get
  Set(ByVal value As Integer)
    If (value < -1) Then value = -1
    If (value > +1) Then value = +1
    MyBase.Value = value
  End Set
End Property

Formatters

Formatting data is primarily a User Interface task. Both the data type and the data state need to be presented in human-readable form every now and again. This mostly involves readonly properties as looking at data does not change its state:

// TriState instances are always valid
public override bool IsValid
{
  get { return true; }
}

// Return a string with the name of this Type.
public override string TypeName
{
  get { return "TriState"; }
}

// Return a string describing what this Type is about.
public override string TypeDescription
{
  get { return "A TriState Value (True, False or Unknown)"; }
}

// Return a string representation of the state (value) of this instance.
public override string ToString()
{
  if (this.Value == 0) { return "False"; }
  if (this.Value > 0) { return "True"; }
  return "Unknown";
}
'' TriState instances are always valid
Public Overrides ReadOnly Property IsValid() As Boolean
  Get
    Return True
  End Get
End Property

'' Return a string with the name of this Type.
Public Overrides ReadOnly Property TypeName() As String
  Get
    Return "TriState"
  End Get
End Property

'' Return a string describing what this Type is about.
Public Overrides ReadOnly Property TypeDescription() As String
  Get
    Return "A TriState Value (True, False or Unknown)"
  End Get
End Property

'' Return a string representation of the state (value) of this instance.
Public Overrides Function ToString() As String
  If (Value = 0) Then Return "False"
  If (Value > 0) Then Return "True"
  Return "Unknown"
End Function

Serialization

Some data types can be stored as persistent data. Persistent data must be able to serialize and deserialize itself from a Grasshopper file. Most simple types support this feature (Booleans, Integers, Strings, Colours, Circles, Planes etc.), most complex geometry types cannot be stored as persistent data (Curves, Breps, Meshes). If possible, you should aim to provide robust (de)serialization for your data:

// Serialize this instance to a Grasshopper writer object.
public override bool Write(GH_IO.Serialization.GH_IWriter writer)
{
  writer.SetInt32("tri", this.Value);
  return true;
}

// Deserialize this instance from a Grasshopper reader object.
public override bool Read(GH_IO.Serialization.GH_IReader reader)
{
  this.Value = reader.GetInt32("tri");
  return true;
}
'' Serialize this instance to a Grasshopper writer object.
Public Overrides Function Write(ByVal writer As GH_IO.Serialization.GH_IWriter) As Boolean
  writer.SetInt32("tri", Value)
  Return True
End Function

'' Deserialize this instance from a Grasshopper reader object.
Public Overrides Function Read(ByVal reader As GH_IO.Serialization.GH_IReader) As Boolean
  Value = reader.GetInt32("tri")
  Return True
End Function

Casting

There are three casting methods on IGH_Goo; the CastFrom() and CastTo() methods that facilitate conversions between different types of data and the ScriptVariable() method which creates a safe instance of this data to be used inside untrusted code (such as VB or C# Script components).

// Return the Integer we use to represent the TriState flag.
public override object ScriptVariable()
{
  return this.Value;
}

// This function is called when Grasshopper needs to convert this
// instance of TriStateType into some other type Q.
public override bool CastTo<Q>(ref Q target)
{
  //First, see if Q is similar to the Integer primitive.
  if (typeof(Q).IsAssignableFrom(typeof(int)))
  {
    object ptr = this.Value;
    target = (Q)ptr;
    return true;
  }

  //Then, see if Q is similar to the GH_Integer type.
  if (typeof(Q).IsAssignableFrom(typeof(GH_Integer)))
  {
    object ptr = new GH_Integer(this.Value);
    target = (Q)ptr;
    return true;
  }

  //We could choose to also handle casts to Boolean, GH_Boolean,
  //Double and GH_Number, but this is left as an exercise for the reader.
  return false;
}

// This function is called when Grasshopper needs to convert other data
// into TriStateType.
public override bool CastFrom(object source)
{
  //Abort immediately on bogus data.
  if (source == null) { return false; }

  //Use the Grasshopper Integer converter. By specifying GH_Conversion.Both
  //we will get both exact and fuzzy results. You should always try to use the
  //methods available through GH_Convert as they are extensive and consistent.
  int val;
  if (GH_Convert.ToInt32(source, out val, GH_Conversion.Both))
  {
    this.Value = val;
    return true;
  }

  //If the integer conversion failed, we can still try to parse Strings.
  //If possible, you should ensure that your data type can 'deserialize' itself
  //from the output of the ToString() method.
  string str = null;
  if (GH_Convert.ToString(source, out str, GH_Conversion.Both))
  {
    switch (str.ToUpperInvariant())
    {
      case "FALSE":
      case "F":
      case "NO":
      case "N":
        this.Value = 0;
        return true;

      case "TRUE":
      case "T":
      case "YES":
      case "Y":
        this.Value = +1;
        return true;

      case "UNKNOWN":
      case "UNSET":
      case "MAYBE":
      case "DUNNO":
      case "?":
        this.Value = -1;
        return true;
    }
  }

  //We've exhausted the possible conversions, it seems that source
  //cannot be converted into a TriStateType after all.
  return false;
}
'' Return the Integer we use to represent the TriState flag.
Public Overrides Function ScriptVariable() As Object
  Return Value
End Function

'' This function is called when Grasshopper needs to convert this
'' instance of TriStateType into some other type Q.
Public Overrides Function CastTo(Of Q)(ByRef target As Q) As Boolean
  'First, see if Q is similar to the Integer primitive.
  If (GetType(Q).IsAssignableFrom(GetType(Integer))) Then
    Dim ptr As Object = Value
    target = DirectCast(ptr, Q)
    Return True
  End If

  'Then, see if Q is similar to the GH_Integer type.
  If (GetType(Q).IsAssignableFrom(GetType(GH_Integer))) Then
    Dim int As Object = New GH_Integer(Value)
    target = DirectCast(int, Q)
    Return True
  End If

  'We could choose to also handle casts to Boolean, GH_Boolean,
  'Double and GH_Number, but this is left as an exercise for the reader.
  Return False
End Function

'' This function is called when Grasshopper needs to convert other data
'' into TriStateType.
Public Overrides Function CastFrom(ByVal source As Object) As Boolean
  'Abort immediately on bogus data.
  If (source Is Nothing) Then Return False

  'Use the Grasshopper Integer converter. By specifying GH_Conversion.Both
  'we will get both exact and fuzzy results. You should always try to use the
  'methods available through GH_Convert as they are extensive and consistent.
  Dim int As Integer
  If (GH_Convert.ToInt32(source, int, GH_Conversion.Both)) Then
    Value = int
    Return True
  End If

  'If the integer conversion failed, we can still try to parse Strings.
  'If possible, you should ensure that your data type can 'deserialize' itself
  'from the output of the ToString() method.
  Dim str As String = Nothing
  If (GH_Convert.ToString(source, str, GH_Conversion.Both)) Then
    Select Case str.ToUpperInvariant()
      Case "FALSE", "F", "NO", "N"
        Value = 0
        Return True
      Case "TRUE", "T", "YES", "Y"
        Value = +1
        Return True
      Case "UNKNOWN", "UNSET", "MAYBE", "DUNNO", "?"
        Value = -1
        Return True
    End Select
  End If

  'We've exhausted the possible conversions, it seems that source
  'cannot be converted into a TriStateType after all.
  Return False
End Function