Rhino Compute Service (Windows)

This guide covers all the necessary tools required to get started with the Rhino Compute Service in Csharp

By the end of this guide, you should have all the tools installed necessary for using the Rhino Compute Service with C# in Windows.

This guide presumes you have an:

note: It is recommended that you install the Typical installation.


Setting up a Compute Project in Visual Studio

There are a few tools which are essential to communicate with the Compute server. These include:

  • Rhino3dmIO.Desktop - The Dotnet wrapper for OpenNurbs which contains the functions to read and write Rhino Geometry Objects. This is available as a NuGet package.
  • NewtonSoft.JSON - The very popular JSON library. Compute communicates using a JSON format in the body of a REST POST. This is available as a NuGet Package. This package is called directly by RHinoCompute.cs.
  • RhinoCompute.cs - This is a work in progress package which is meant to add classes available in RhinoCommon, but not available through Rhino3dmIO. RhinoCompute makes calls into RhinoCompute for these functions.

Here are step by step instructions to setting up a basic project to use Compute:

File New

  1. If you have not done so already, launch Visual Studio (for the purposes of this guide, we are using Visual Studio 2017 Community Edition and C#).
  2. Navigate to File > New > ProjectFile New Project
  3. A New Project wizard should appear. In the left column, find the Installed > Visual C# section. In the central list, select the Console App (.NET Framework) template… New Project
  4. For the purposes of this Guide, we will name our demo plugin TestCompute. At the bottom of the window, fill in the Name field. Browse and select a location for this plugin on your disk…
  5. Check Create directory for solution. Note: This is optional depending on how you want to structure your projects.
  6. Click the OK button. Note: You don’t have to add the project to source control for this demo.
  7. For the purposes of this guide, we will accept the defaults and click Finish
  8. A new solution called HelloRhinoCommon should open…

Steps to install the NuGet packages

  1. Right-click your project file in Solution Explorer and select Manage NuGet Packages ….
  2. On the left side of the dialog expand the Online option and select nuget.org.
  3. In the top right search box type “Rhino3dmIO” and click on a Rhino3dmIO.Desktop option (there are 3: Rhino3dmIO.Desktop (Windows/macOS), Rhino3dmIO.iOS and Rhino3dmIO.Android) and click on the Install button.
  4. Continue by typing in the search box type “Newtonsoft.JSON” and click on a NewtonSoft.JSON option and click on the Install button.
  5. Close the Manage NuGet Packages dialog. The Nuget packages are installed and ready to use.

Changes that were made:

  • The Rhino3dmIO.Desktop and NewtonSoft.JSON NuGet packages are installed in your project.
  • The project references the Rhino3dmIO.Desktop and Newtonsoft.JSON assembly.

Include RhinoCompute.cs in the project

RhinoCompute.cs is a the package which adds the methods to call into the compute server. It is organized similar to RhinoCommon calls.

  1. Download the RhinoCompute.cs source file from compute.rhino3d.com into the project folder.
  2. Using the Project pulldown > Add Existing Item…
  3. Select the RhinoCompute.cs source file to add it to the project.

Changes that were made:

The first use of Compute

As a place to start, a simple console app shows the standard workflow using Compute. This is the simplest example of using Rhino3DMio locally to read, write and create Rhino geometry. Then use compute to handle a function that does not exist in Rhino3DMio.

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Rhino.Compute;

namespace TestCompute
{
    class Program
    {
        static void Main(string[] args)
        {
            ComputeServer.ApiToken = "scottd@mcneel.com";

            // Uses standard Rhino3dmIO methods locally to create a sphere.
            var sphere = new Rhino.Geometry.Sphere(Rhino.Geometry.Point3d.Origin, 12);
            var sphereAsBrep = sphere.ToBrep();

            // the following function calls compute.rhino3d.com to get access to something not
            // available in Rhino3dmIO. In this case send a Brep to Compute and get a Mesh back.
            var meshes = MeshCompute.CreateFromBrep(sphereAsBrep);

            // Use regular Rhino3dmIO local calls to count the vertices in the mesh.
            Console.WriteLine($"Got {meshes.Length} meshes");
            for (int i = 0; i < meshes.Length; i++)
            {
                Console.WriteLine($"  {i + 1} mesh has {meshes[i].Vertices.Count} vertices");
            }

            Console.WriteLine("press any key to exit");
            Console.ReadKey();
        }
    }
}

This example above first creates a sphere using Rhino3DMio locally. Then request Compute to mesh that BREP to Compute, the BREP sphere is meshed. Compute then returns the mesh. Finally using the local Rhino3DMio package to walk through the mesh, the vertices are counted.

Line Description
5 Include the Rhino.Compute Assembly from the RhinoCompute.cs Package.
13 All calls to the Compute Server must be accompanied by a token. At this time your email is correct token to use. This is sent with each RhinoCompute.cs Posts.
21 Here the OpenNurbs Brep sphere is sent to Compute to convert to a mesh. The Mesh is returned as a OpenNurbs Mesh.

Seeing the results of Compute

Many times the intersection of objects may need to be calculated. Intersections are not in OpenNurbs, but are part of Compute. Here is a tutorial on creating two circles, finding their intersection, then converting the circles to an SVG image to display.

using System.Collections.Generic;
using System.Text;
using Rhino.Geometry;

namespace CircleIntersection
{
    class Program
    {
        static void Main(string[] args)
        {
            Rhino.Compute.ComputeServer.ApiToken = "circleintersectionsample@mcneel.com";

            // create a couple Circles using a local copy of Rhino3dmIo
            var c1 = new Circle(new Point3d(0, 0, 0), 100);
            var c2 = new Circle(new Point3d(30, 30, 0), 70);

            // call compute to perform a curve boolean operation
            var intersectionCurves = Rhino.Compute.CurveCompute.CreateBooleanIntersection(c1.ToNurbsCurve(), c2.ToNurbsCurve());
            Mesh[] intersectionMeshes = null;
            if (intersectionCurves != null)
            {
                // use local Rhino3dmIo to create a Brep from the curves
                var brep = Brep.CreateTrimmedPlane(c1.Plane, intersectionCurves);

                // call compute to mesh the Brep
                intersectionMeshes = Rhino.Compute.MeshCompute.CreateFromBrep(brep, MeshingParameters.FastRenderMesh);
            }

            // just some helper routines to create an SVG file of the results so we can see what was generated
            string path = "circle_intersection.svg";
            WriteSvgFile(path, c1, c2, intersectionMeshes);
            System.Diagnostics.Process.Start(path);
        }


        /// <summary>
        /// Very basic SVG file creation routine to make an SVG the displays two circles and a mesh.
        /// Kept simple just for this sample; there are much better SVG toolkits available that could
        /// be used for general purpose routines.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="circle1"></param>
        /// <param name="circle2"></param>
        /// <param name="intersectionMeshes"></param>
        static void WriteSvgFile(string path, Circle circle1, Circle circle2, Mesh[] intersectionMeshes)
        {
            var doc = new System.Xml.XmlDocument();
            doc.AppendChild(doc.CreateXmlDeclaration("1.0", "UTF-8", "no"));
            doc.XmlResolver = null;
            doc.AppendChild(doc.CreateDocumentType("svg", "-//W3C//DTD SVG 1.1//EN", "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd", null));
            var svgroot = doc.CreateElement("svg");

            var bbox = circle1.BoundingBox;
            bbox.Union(circle2.BoundingBox);
            var length = (bbox.Max - bbox.Min).Length;
            bbox.Inflate(length * 0.05);

            AppendAttribute(doc, svgroot, "viewBox", $"{bbox.Min.X} {bbox.Min.Y} {bbox.Max.X - bbox.Min.X} {bbox.Max.Y - bbox.Min.Y}");
            AppendAttribute(doc, svgroot, "version", "1.1");
            AppendAttribute(doc, svgroot, "xmlns", "http://www.w3.org/2000/svg");
            doc.AppendChild(svgroot);

            if (intersectionMeshes != null)
            {
                foreach (var mesh in intersectionMeshes)
                {
                    foreach (var face in mesh.Faces)
                    {
                        var elem = doc.CreateElement("polygon");
                        var attrib = doc.CreateAttribute("fill");
                        attrib.Value = "#008000"; // green
                        elem.Attributes.Append(attrib);
                        attrib = doc.CreateAttribute("stroke");
                        attrib.Value = "#008000"; // green
                        elem.Attributes.Append(attrib);
                        attrib = doc.CreateAttribute("points");
                        StringBuilder sb = new StringBuilder();
                        var points = new List<Rhino.Geometry.Point3d>();
                        points.Add(mesh.Vertices[face.A]);
                        points.Add(mesh.Vertices[face.B]);
                        points.Add(mesh.Vertices[face.C]);
                        if (face.IsQuad)
                            points.Add(mesh.Vertices[face.D]);
                        for (int i = 0; i < points.Count; i++)
                        {
                            if (i > 0)
                                sb.Append(", ");
                            sb.Append($"{points[i].X}, {points[i].Y}");
                        }
                        attrib.Value = sb.ToString().Trim();
                        elem.Attributes.Append(attrib);
                        svgroot.AppendChild(elem);
                    }
                }
            }

            svgroot.AppendChild(SvgCircle(circle1, doc));
            svgroot.AppendChild(SvgCircle(circle2, doc));

            doc.Save(path);
        }

        static void AppendAttribute(System.Xml.XmlDocument doc, System.Xml.XmlElement element, string name, string value)
        {
            var attr = doc.CreateAttribute(name);
            attr.Value = value;
            element.Attributes.Append(attr);
        }

        static System.Xml.XmlElement SvgCircle(Rhino.Geometry.Circle c, System.Xml.XmlDocument doc)
        {
            var elem = doc.CreateElement("circle");
            var attrib = doc.CreateAttribute("cx");
            attrib.Value = $"{c.Center.X}";
            elem.Attributes.Append(attrib);
            attrib = doc.CreateAttribute("cy");
            attrib.Value = $"{c.Center.Y}";
            elem.Attributes.Append(attrib);
            attrib = doc.CreateAttribute("r");
            attrib.Value = $"{c.Radius}";
            elem.Attributes.Append(attrib);
            attrib = doc.CreateAttribute("fill-opacity");
            attrib.Value = "0";
            elem.Attributes.Append(attrib);
            attrib = doc.CreateAttribute("stroke");
            attrib.Value = "#000000";
            elem.Attributes.Append(attrib);
            return elem;
        }
    }
}

Next Steps

Congratulations! You have the tools to use Rhino Compute server. Now what?

  1. See a list of the 2400+ API calls available for compute.rhino3d.com.
  2. Download the Compute Samples repo from GitHub.
  3. The libraries are still very new and changing rapidly. Give them a try or get involved. Ask any questions or share what you are working on the Compute Discussion Forum

Footnotes