Click or drag to resize

Simple Geometry (C#)

This article shows how to use some of the simpler geometry types and classes in the RhinoCommon and Grasshopper SDK. We'll discuss how to deal with different access levels of input data and invalid Structs vs. invalid and null Classes. You should have read the My First Component and Simple Mathematics topics before starting this one.

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.

Before you start with this topic, create a new class that derives from Grasshopper.Kernel.GH_Component, as outlined in the My First Component topic.

Input parameters

This part of the component is very similar to the Simple Mathematics topic. Except this time there will be no default values.

C#
...
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);
}
...
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.

C#
...
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);
}
...

SolveInstance

The SolveInstance() implementation for this component is responsible for the following steps:

  1. Declare placeholder variables for the input data.
  2. Retrieve input data.
  3. Test input data for validity.
  4. Project line segment onto circle plane.
  5. Test projected segment for validity.
  6. Solve intersections for circle and projected segment.
  7. Abort on insufficient intersections.
  8. Create the slice arcs.
  9. Assign arcs to the output parameters.

Item 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:

C#
...
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.ClosestParameterTo(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));
}
...

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:

C#
...
Grasshopper.Kernel.Data.GH_Circle circle = null;
if (!DA.GetData(0, circle)) { 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:

C#
DA.SetData(0, new Rhino.Geometry.Arc(p1, tan, p2));
you could also provide an instance of Grasshopper.Kernel.Types.GH_Arc:
C#
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);