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.
This part of the component is very similar to the Simple Mathematics topic. 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); } ...
The second parameter is of type Param_Line and it works very similar to the Param_Circle type discussed above.
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); } ...
The SolveInstance() implementation for this component is responsible for the following steps:
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:
... 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:
... Grasshopper.Kernel.Data.GH_Circle circle = null; if (!DA.GetData(0, circle)) { return; } ...
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));
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);