We’ll discuss how to deal with different access levels of input data and invalid Structs vs. invalid and null Classes.
Overview
This component will perform a simple Circle-Line Split operation. We’ll retrieve a single Circle and a single Line input, make sure the data is valid, project the line onto the circle plane, determine whether or not the Split operation is valid and then output the two arcs on either side of the slicing line.
Prerequisites
We will not be dealing with any of the basics of component development. Please make sure you have read the Your First Component, Simple Component, and Simple Mathematics Component guides 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.
Input Parameters
This part of the component is very similar to the Simple Mathematics Component, except this time there will be no default values.
...
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
pManager.AddCircleParameter("Circle", "C", "The circle to slice", GH_ParamAccess.item);
pManager.AddLineParameter("Line", "L", "Slicing line", GH_ParamAccess.item);
}
...
...
Protected Overrides Sub RegisterInputParams(ByVal pManager As GH_Component.GH_InputParamManager)
pManager.AddCircleParameter("Circle", "C", "The circle to slice", GH_ParamAccess.item)
pManager.AddLineParameter("Line", "L", "Slicing line", GH_ParamAccess.item)
End Sub
...
The first parameter is of type Param_Circle
and the data it contains will consist solely of GH_Circle
. GH_Circle
is a class that wraps the Rhino.Geometry.Circle
structure. It provides methods that allow Grasshopper to incorporate RhinoCommon circles into the default GUI. These methods include Archiving, Previewing, Baking and Casting (Converting) functions. However, when accessing data inside a Param_Circle
parameter, you are not limited to the GH_Circle
type, as we shall see.
The second parameter is of type Param_Line
and it works very similar to the Param_Circle
type discussed above.
Output Parameters
Our component will output two arcs on success, or nulls on failure.
...
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
{
pManager.AddArcParameter("Arc A", "A", "First Split result.", GH_ParamAccess.item);
pManager.AddArcParameter("Arc B", "B", "Second Split result.", GH_ParamAccess.item);
}
...
...
Protected Overrides Sub RegisterOutputParams(ByVal pManager As GH_Component.GH_OutputParamManager)
pManager.AddArcParameter("Arc A", "A", "First Split result.", GH_ParamAccess.item)
pManager.AddArcParameter("Arc B", "B", "Second Split result.", GH_ParamAccess.item)
End Sub
...
Solve Instance
The SolveInstance()
implementation for this component is responsible for the following steps:
- Declare placeholder variables for the input data.
- Retrieve input data.
- Test input data for validity.
- Project line segment onto circle plane.
- Test projected segment for validity.
- Solve intersections for circle and projected segment.
- Abort on insufficient intersections.
- Create the slice arcs.
- Assign arcs to the output parameters.
Steps 2 and 9 however can be approached from different directions. First I’ll show you the recommended approach and then we’ll have a look at the alternatives:
...
protected override void SolveInstance(IGH_DataAccess DA)
{
// 1. Declare placeholder variables and assign initial invalid data.
// This way, if the input parameters fail to supply valid data, we know when to abort.
Rhino.Geometry.Circle circle = Rhino.Geometry.Circle.Unset;
Rhino.Geometry.Line line = Rhino.Geometry.Line.Unset;
// 2. Retrieve input data.
if (!DA.GetData(0, ref circle)) { return; }
if (!DA.GetData(1, ref line)) { return; }
// 3. Abort on invalid inputs.
if (!circle.IsValid) { return; }
if (!line.IsValid) { return; }
// 4. Project line segment onto circle plane.
line.Transform(Rhino.Geometry.Transform.PlanarProjection(circle.Plane));
// 5. Test projected segment for validity.
if (line.Length < Rhino.RhinoMath.ZeroTolerance)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Line could not be projected onto the Circle plane");
return;
}
// 6. Solve intersections and 7. Abort if there are less than two intersections.
double t1;
double t2;
Rhino.Geometry.Point3d p1;
Rhino.Geometry.Point3d p2;
switch (Rhino.Geometry.Intersect.Intersection.LineCircle(line, circle, out t1, out p1, out t2, out p2))
{
case Rhino.Geometry.Intersect.LineCircleIntersection.None:
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "No intersections were found");
return;
case Rhino.Geometry.Intersect.LineCircleIntersection.Single:
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Only a single intersection was found");
return;
}
// 8. Create slicing arcs.
double ct;
circle.ClosestParameter(p1, out ct);
Rhino.Geometry.Vector3d tan = circle.TangentAt(ct);
// 9. Assign output arcs.
DA.SetData(0, new Rhino.Geometry.Arc(p1, tan, p2));
DA.SetData(1, new Rhino.Geometry.Arc(p1, -tan, p2));
}
...
...
Protected Overrides Sub SolveInstance(ByVal DA As Grasshopper.Kernel.IGH_DataAccess)
'1. Declare placeholder variables and assign initial invalid data.
' This way, if the input parameters fail to supply valid data, we know when to abort.
Dim circle As Rhino.Geometry.Circle = Rhino.Geometry.Circle.Unset
Dim line As Rhino.Geometry.Line = Rhino.Geometry.Line.Unset
'2. Retrieve input data.
If (Not DA.GetData(0, circle)) Then Return
If (Not DA.GetData(1, line)) Then Return
'3. Abort on invalid inputs.
If (Not circle.IsValid) Then Return
If (Not line.IsValid) Then Return
'4. Project line segment onto circle plane.
line.Transform(Rhino.Geometry.Transform.PlanarProjection(circle.Plane))
'5. Test projected segment for validity.
If (line.Length < Rhino.RhinoMath.ZeroTolerance) Then
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Line could not be projected onto the Circle plane")
Return
End If
'6. Solve intersections and 7. Abort if there are less than two intersections.
Dim t1 As Double
Dim p1 As Rhino.Geometry.Point3d
Dim t2 As Double
Dim p2 As Rhino.Geometry.Point3d
Select Case Rhino.Geometry.Intersect.Intersection.LineCircle(line, circle, t1, p1, t2, p2)
Case Rhino.Geometry.Intersect.LineCircleIntersection.None
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "No intersections were found")
Return
Case Rhino.Geometry.Intersect.LineCircleIntersection.Single
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Only a single intersection was found")
Return
End Select
'8. Create slicing arcs.
Dim ct As Double
circle.ClosestParameter(p1, ct)
Dim tan As Rhino.Geometry.Vector3d = circle.TangentAt(ct)
'9. Assign output arcs.
DA.SetData(0, New Rhino.Geometry.Arc(p1, tan, p2))
DA.SetData(1, New Rhino.Geometry.Arc(p1, -tan, p2))
End Sub
...
As I mentioned before, the first input parameter is of type Param_Circle
and it contains data of type GH_Circle
. But when we’re accessing the parameter via the DA.GetData(0, circle)
method, we’re using Rhino.Geometry.Circle
instead of GH_Circle
. The DA.GetData()
method is capable of converting data from the intrinsic parameter type into requested types, provided the conversion makes sense. GH_Circle
to Rhino.Geometry.Circle
is a perfectly valid conversion, GH_Circle
to Rhino.Geometry.Transform
would not be.
The Grasshopper Component SDK has been designed on the premise that the bulk of all components that operate on data only care about the data itself, not how it is wrapped up inside the Grasshopper data structures. The conversion routines that translate GH_Circle
data into Rhino.Geometry.Circle
data (and obviously also GH_Number
into System.Double
, and GH_Colour
into System.Drawing.Color
etc.), have been highly optimised and should be used in almost all circumstances.
If however you feel the need to get access to the GH_Circle
data directly, you can retrieve that instance in the same fashion:
...
Grasshopper.Kernel.Data.GH_Circle circle = null;
if (!DA.GetData(0, circle)) { return; }
...
...
Dim circle As Grasshopper.Kernel.Data.GH_Circle = Nothing
If (Not DA.GetData(0, circle)) Then Return
...
Note that a single instance of GH_Circle
may be shared among any number of parameters in Grasshopper, and thus changing one will change data everywhere. If you request Rhino.Geometry.Circle
, System.Double
or System.Drawing.Color
instead of GH_Circle
, GH_Number
or GH_Colour
you won’t have to worry about this pitfall since you will always be given an object that has been disassociated from the (potentially shared) wrapper type.
Similarly, you are allowed to store output data in different formats as well. Instead of…
DA.SetData(0, new Rhino.Geometry.Arc(p1, tan, p2));
DA.SetData(0, New Rhino.Geometry.Arc(p1, tan, p2))
…you could also provide an instance of Grasshopper.Kernel.Types.GH_Arc
:
Grasshopper.Kernel.Types.GH_Arc gh_arc = new Grasshopper.Kernel.Types.GH_Arc(new Rhino.Geometry.Arc(p1, tan, p2));
DA.SetData(0, gh_arc);
Dim gh_arc As New Grasshopper.Kernel.Types.GH_Arc(New Rhino.Geometry.Arc(p1, tan, p2))
DA.SetData(0, gh_arc)