コード例 #1
0
        //weighted random populate point with attractors
        //reference Junichiro Horikawa on Github
        Point3dList Populate(Brep brep, double n, int ite, List <Point3d> attractors, double attractor_r, double attractor_strength)
        {
            var points = new Point3dList();
            int num    = Convert.ToInt32(n);

            if (brep != null)
            {
                var attracts = new Point3dList(attractors);
                var rnd      = new Random();
                var bbox     = brep.GetBoundingBox(true);
                attractor_strength = (-1) * attractor_strength;

                for (int i = 0; i < num; i++)
                {
                    if (points.Count == 0)
                    {
                        var rndpt = CreateRandomPoint(rnd, brep, bbox);
                        points.Add(rndpt);
                    }
                    else
                    {
                        double  fdist = -1;
                        Point3d fpt   = new Point3d();
                        for (int t = 0; t < Math.Max(Math.Min(ite, i), 10); t++)
                        {
                            var nrndpt = CreateRandomPoint(rnd, brep, bbox);
                            var nindex = points.ClosestIndex(nrndpt);
                            var npts   = points[nindex];

                            var ndist = npts.DistanceTo(nrndpt);

                            if (attractor_strength != 0)
                            {
                                var nattractid   = attracts.ClosestIndex(nrndpt);
                                var nattarctpts  = attracts[nattractid];
                                var mindist      = attractor_r;
                                var pow          = attractor_strength;
                                var nattractdist = Math.Pow(Remap(Math.Min(nattarctpts.DistanceTo(nrndpt), mindist), 0, mindist, 0, 1.0), pow);
                                ndist *= nattractdist;
                            }

                            if (fdist < ndist)
                            {
                                fdist = ndist;
                                fpt   = nrndpt;
                            }
                        }
                        points.Add(fpt);
                    }
                }
            }
            return(points);
        }
コード例 #2
0
        public static void TestPolygonSelect()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Editor   ed  = doc.Editor;
            //声明一个Point3d类列表对象,用于存储多段线的顶点
            Point3dList pts = new Point3dList();
            //提示用户选择多段线
            PromptEntityResult per = ed.GetEntity("请选择多段线");

            if (per.Status != PromptStatus.OK)
            {
                return;                               //选择错误,返回
            }
            using (Transaction trans = doc.TransactionManager.StartTransaction())
            {
                //转换为Polyline对象
                Polyline pline = trans.GetObject(per.ObjectId, OpenMode.ForRead) as Polyline;
                if (pline != null)
                {
                    //遍历所选多段线的顶点并添加到Point3d类列表
                    for (int i = 0; i < pline.NumberOfVertices; i++)
                    {
                        Point3d point = pline.GetPoint3dAt(i);
                        pts.Add(point);
                    }
                    //窗口选择,仅选择完全位于多边形区域中的对象
                    PromptSelectionResult psr = ed.SelectWindowPolygon(pts);
                    if (psr.Status == PromptStatus.OK)
                    {
                        Application.ShowAlertDialog("选择集中实体的数量:" + psr.Value.Count.ToString());
                    }
                }
                trans.Commit();
            }
        }
コード例 #3
0
        /// <summary>
        /// This function selects points
        /// </summary>
        /// <param name="message"></param>
        /// <param name="pointer"></param>
        public static void XSelectPoints(string message, out Point3dList pointer)
        {
            // variables are:
            // string message
            // out Point3dList points - the out won't be recognized unless you have Rhino.Collections and the 'out parameters
            // have to be filled in the function.
            // don't need a doc because we won't write anything to the document table, only reading.


            pointer = new Point3dList();

            var go = new GetObject();

            go.GeometryFilter = Rhino.DocObjects.ObjectType.Point;
            go.SetCommandPrompt(message);
            go.GetMultiple(1, 0);

            if (go.CommandResult() != Result.Success)
            {
                return;
            }

            for (var i = 0; i < go.ObjectCount; i++)
            {
                var point = go.Object(i).Point();
                if (null != point)
                {
                    pointer.Add(point.Location);
                }
            }
        }
コード例 #4
0
ファイル: froGHRTree.cs プロジェクト: Co-de-iT/froGH
 public froGHRTree(List <Point3d> points)
 {
     Points = new Point3dList();
     Source = EnumRTreeType.Points;
     Tree   = new RTree();
     for (int i = 0; i < points.Count; i++)
     {
         Points.Add(points[i]);
         Tree.Insert(points[i], i);
     }
 }
コード例 #5
0
ファイル: CreateNetCommand.cs プロジェクト: jhaazpr/rocky
        protected Polyline generateFingerJoint(Line jointLine, double thickness)
        {
            Point3d     currPoint = new Point3d(jointLine.FromX, jointLine.FromY, 0);
            Point3dList points    = new Point3dList();

            points.Add(currPoint);

            double xIncr, yIncr;

            yIncr = thickness;
            xIncr = thickness / 2;

            // An even finger count means the finger will be drawn right of the
            // center line
            int fingerCount     = 0;
            int fingerDirection = -1;

            // Loop invariant: incrementing and placing current point will always
            // result in a point before the stoppingY
            while (currPoint.Y + yIncr <= jointLine.ToY)
            {
                // Multiplier for right finger on even, vice versa for odd
                fingerDirection = fingerCount % 2 == 0 ? 1 : -1;

                currPoint += new Vector3d(xIncr * fingerDirection, 0, 0);
                points.Add(currPoint);
                currPoint += new Vector3d(0, yIncr, 0);
                points.Add(currPoint);
                currPoint += new Vector3d(-xIncr * fingerDirection, 0, 0);
                points.Add(currPoint);

                fingerCount += 1;
            }

            // Finish the last truncated finger if necessary
            if (currPoint.Y < jointLine.ToY)
            {
                fingerDirection = fingerCount % 2 == 0 ? 1 : -1;

                currPoint += new Vector3d(xIncr * fingerDirection, 0, 0);
                points.Add(currPoint);
                currPoint += new Vector3d(0, jointLine.ToY - currPoint.Y, 0);
                points.Add(currPoint);
                currPoint += new Vector3d(-xIncr * fingerDirection, 0, 0);
                points.Add(currPoint);
            }

            return(new Polyline(points));
        }
コード例 #6
0
        /// <summary>
        /// Gets all Greville (Edit) points for this curve.
        /// </summary>
        public Point3dList GrevillePoints()
        {
            double[] gr_ab = GrevilleParameters();
            if (gr_ab == null)
            {
                return(null);
            }

            Point3dList gr_pts = new Point3dList(gr_ab.Length);

            foreach (double t in gr_ab)
            {
                gr_pts.Add(PointAt(t));
            }

            return(gr_pts);
        }
        protected override Result RunCommand(RhinoDoc doc, RunMode mode)
        {
            Random r     = new Random(13);
            var    plcnt = 1000000;
            var    edge  = 10;

            GetNumber gn = new GetNumber();

            gn.SetCommandPrompt("How many pointsies");
            gn.SetDefaultInteger(plcnt);
            gn.SetUpperLimit(500000001, true);
            gn.SetLowerLimit(1, true);
            gn.AcceptNothing(true);

            var gnrc = gn.Get();

            if (gnrc == GetResult.Nothing || gnrc == GetResult.Number)
            {
                var nr = (int)gn.Number();
                if (nr > 500000000)
                {
                    RhinoApp.WriteLine("More than 500.000.000 points");
                    return(Result.Cancel);
                }
                pc = new PointCloud();
                Point3dList p3dlist = new Point3dList(nr);
                List <System.Drawing.Color> collist = new List <System.Drawing.Color>(nr);
                for (int i = 0; i < nr; i++)
                {
                    var d = (double)i;
                    p3dlist.Add(new Point3d(r.NextDouble() * edge, r.NextDouble() * edge, r.NextDouble() * edge));
                    collist.Add(System.Drawing.Color.FromArgb(r.Next(0, 255), r.Next(0, 255), r.Next(0, 255)));
                }
                pc.AddRange(p3dlist, collist);
                cnd.ThePc   = pc;
                cnd.Enabled = true;
            }

            doc.Views.Redraw();


            return(Result.Success);
        }
コード例 #8
0
        /// <summary>
        /// Solves the instance.
        /// </summary>
        /// <param name="DA">Da.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            // Properties
            Mesh   mesh              = null;
            Curve  initialCurve      = null;
            double specifiedDistance = 0.0;
            int    maxCount          = 0;
            double threshold         = 0.0;
            double perpStepSize      = 0.0;
            bool   bothDir           = false;
            double minThreshold      = 0.0;

            // Set the input data
            if (!DA.GetData(0, ref mesh))
            {
                return;
            }
            if (!DA.GetData(1, ref initialCurve))
            {
                return;
            }
            if (!DA.GetData(2, ref bothDir))
            {
                return;
            }
            if (!DA.GetData(3, ref maxCount))
            {
                return;
            }
            if (!DA.GetData(4, ref threshold))
            {
                return;
            }
            if (!DA.GetData(5, ref specifiedDistance))
            {
                return;
            }
            if (!DA.GetData(6, ref perpStepSize))
            {
                return;
            }
            if (!DA.GetData(7, ref minThreshold))
            {
                return;
            }

            // Data validation
            if (maxCount == 0)
            {
                AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Count cannot be 0");
            }
            if (!mesh.IsValid)
            {
                AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Mesh is invalid");
            }

            // Placeholder properties
            DataTree <Curve> pattern       = new DataTree <Curve>();
            Curve            previousCurve = initialCurve;
            List <Curve>     perpGeods     = new List <Curve>();
            List <double>    perpParams    = new List <double>();

            // Start piecewise evolution process
            for (int i = 0; i < maxCount; i++)
            {
                Debug.WriteLine("Iter " + i);
                //  Create placeholder lists
                perpGeods  = new List <Curve>();
                perpParams = new List <double>();

                // Divide curve
                double[] geodParams = previousCurve.DivideByLength(perpStepSize, true);

                if (geodParams == null)
                {
                    AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "No  points found on iter" + i);
                    break;
                }
                if (geodParams.Length <= 2)
                {
                    AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Not enough points found on iter" + i);
                    break;
                }

                // Generate perp geodesics for measurement
                foreach (double t in geodParams)
                {
                    // Get curve tangent vector and mesh normal
                    Point3d  point   = previousCurve.PointAt(t);
                    Vector3d tangent = previousCurve.TangentAt(t);
                    Vector3d normal  = mesh.NormalAt(mesh.ClosestMeshPoint(point, 0.0));

                    // Rotate vector against normals 90 degrees
                    tangent.Rotate(0.5 * Math.PI, normal);

                    // Generate perp geodesic
                    Curve perpGeodesic = MeshGeodesicMethods.getGeodesicCurveOnMesh(mesh, point, tangent, 100).ToNurbsCurve();

                    // Check for success
                    if (perpGeodesic != null && perpGeodesic.GetLength() > specifiedDistance)
                    {
                        // Divide by specified length
                        double perpT = 0.0;
                        perpGeodesic.LengthParameter(specifiedDistance, out perpT);

                        // Add data to lists
                        perpGeods.Add(perpGeodesic);
                        perpParams.Add(perpT);
                    }
                }
                // Clean perp geods of intersections ocurring BEFORE the specified distance
                var result = CleanPerpGeodesics(perpGeods, perpParams, specifiedDistance);
                // Assign clean lists
                perpGeods  = result.perpGeodesics;
                perpParams = result.perpParams;


                // Break if not enough perpGeods remain
                if (perpGeods.Count < 6)
                {
                    AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Not  enough perp geodesics where found for iter " + i);
                    break;
                }

                //Generate the next piecewise geodesic
                List <Curve> iterCurves = GeneratePiecewiseGeodesicCurve(mesh, perpParams, perpGeods, 1000, bothDir, 0, threshold, Vector3d.Unset);

                // Add it to the pattern
                pattern.AddRange(iterCurves, new GH_Path(i));

                // Assign as previous for the next round
                Curve[] joinedResult = Curve.JoinCurves(iterCurves);

                // Error check
                if (joinedResult.Length > 1)
                {
                    AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "More than 1 curve after Join in iter " + i);
                    break;
                }
                //Create points and bisectrix vectors for next round of perp geodesics
                Point3dList ptList = new Point3dList();
                foreach (Curve c in iterCurves)
                {
                    if (!ptList.Contains(c.PointAtStart))
                    {
                        ptList.Add(c.PointAtStart);
                    }
                    if (!ptList.Contains(c.PointAtEnd))
                    {
                        ptList.Add(c.PointAtEnd);
                    }
                }
                Debug.WriteLine("ptList Count: " + ptList.Count);
                // Assign new curve to previous curve
                Curve joinedCurve = joinedResult[0];
                previousCurve = joinedCurve;
            }

            // Assign data to output
            DA.SetDataTree(0, pattern);
            DA.SetDataList(2, perpGeods);
        }
コード例 #9
0
ファイル: xpointpicker.cs プロジェクト: danieldepoe/csharp
        protected override Result RunCommand(RhinoDoc doc, RunMode mode)
        {
            Point3dList points = new Point3dList();

            // Point3dList is from the Collections class, we add a new variable points then initialize it and add
            // empty array

            Result commandres;

            //calls class Point3dList and initializes a new empty array with the name 'points'

            while (true)

            //make an infinite loop.  every time we click on screen we will create a point which will
            // be added to the document and the screen will be refreshed.  it happens fast!!
            {
                var gp = new GetPoint();

                //prompt user
                string prompt;
                prompt = "set point(s) by click";

                gp.SetCommandPrompt(prompt);


                //set up what to do with the result
                var res = gp.Get();

                //get function returns a really cool type of enumeration
                //  https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Input_GetResult.htm

                if (res == GetResult.Point)

                //using Rhino.Input, double equal sign is necessary...

                {
                    doc.Objects.AddPoint(gp.Point());
                    //adds a point object to the document to object table

                    doc.Views.Redraw();
                    //refreshes the views

                    points.Add(gp.Point());
                    // add the point to our points array declared above

                    commandres = Result.Success;
                }

                else if (res == GetResult.Nothing)
                {
                    commandres = Result.Failure;
                    break;
                }
                else
                {
                    commandres = Result.Cancel;
                    break;
                }
            }

            RhinoApp.WriteLine("The user drew {0} point(s) successfully", points.Count.ToString());

            // access the points array and cast it to a string, outside the function so that it will only
            // show how many ponints you've drawn when the function is complete.

            return(Result.Success);
        }
コード例 #10
0
ファイル: Beaver.Terrain.cs プロジェクト: parkjunseok/Beaver
        DataTree<Object> GetStepTree(Mesh terrainMesh, List<double> contourZList, DataTree<Curve> contourTree, List<Brep> brepList)
        {
            DataTree<Object> stepTree = new DataTree<Object>();
            Plane basePlane = Plane.WorldXY;
            basePlane.OriginZ = terrainMesh.GetBoundingBox(true).Min.Z;

            // For each contour-plane
            for (int i = 0; i < contourTree.BranchCount; i++)
            {

                // create higher-Z pt list
                Point3dList winPtList = new Point3dList();

                foreach (Curve contourCrv in contourTree.Branches[i])
                {

                    Plane frm;
                    double t;
                    contourCrv.NormalizedLengthParameter(0.5, out t);
                    contourCrv.FrameAt(t, out frm);
                    frm.Transform(Rhino.Geometry.Transform.PlanarProjection(basePlane));

                    Point3d samplePt0, samplePt1;
                    samplePt0 = frm.Origin;
                    samplePt1 = frm.Origin;
                    samplePt0 += doc.ModelAbsoluteTolerance * frm.YAxis;
                    samplePt1 -= doc.ModelAbsoluteTolerance * frm.YAxis;
                    Ray3d ray0 = new Ray3d(samplePt0, Vector3d.ZAxis);
                    Ray3d ray1 = new Ray3d(samplePt1, Vector3d.ZAxis);
                    double rayParam0 = Rhino.Geometry.Intersect.Intersection.MeshRay(terrainMesh, ray0);
                    double rayParam1 = Rhino.Geometry.Intersect.Intersection.MeshRay(terrainMesh, ray1);

                    winPtList.Add(((rayParam0 > rayParam1) ? samplePt0 : samplePt1));
                }

                // For each splitted region in contour-plane
                foreach (BrepFace brepFace in brepList[i].Faces)
                {
                    Brep testBrep = brepFace.DuplicateFace(false);

                    foreach (Point3d pt in winPtList)
                    {

                        LineCurve testRay = new LineCurve(new Line(pt, Vector3d.ZAxis, 1000));
                        Point3d[] outPts;
                        Curve[] outCrvs;

                        bool ix = Rhino.Geometry.Intersect.Intersection.CurveBrep(testRay, testBrep, doc.ModelAbsoluteTolerance, out outCrvs, out outPts);
                        if (outPts.Length != 0)
                        {
                            stepTree.Add(testBrep, new GH_Path(i));
                            break;
                        }
                    }
                }
            }

            return stepTree;
        }
コード例 #11
0
        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            IGH_GeometricGoo shape    = null;
            int            num        = -1;
            int            ite        = -1;
            List <Point3d> attractors = new List <Point3d>();
            List <double>  radiuses   = new List <double>();
            List <double>  weights    = new List <double>();

            if (!DA.GetData <IGH_GeometricGoo>(0, ref shape))
            {
                return;
            }
            if (!DA.GetData(1, ref num))
            {
                return;
            }
            if (!DA.GetData(2, ref ite))
            {
                return;
            }
            DA.GetDataList(3, attractors);
            DA.GetDataList(4, radiuses);
            DA.GetDataList(5, weights);

            GeometryBase geo = GH_Convert.ToGeometryBase(shape);

            var points   = new Point3dList();
            var attracts = new Point3dList(attractors);
            var rnd      = new Random();

            var bbox = geo.GetBoundingBox(true);

            for (int i = 0; i < num; i++)
            {
                if (points.Count == 0)
                {
                    var rndpt = CreateRandomPoint(rnd, geo, bbox);
                    points.Add(rndpt);
                }
                else
                {
                    double  fdist = -1;
                    Point3d fpos  = new Point3d();
                    for (int t = 0; t < Math.Max(Math.Min(ite, i), 10); t++)
                    {
                        var nrndpt = CreateRandomPoint(rnd, geo, bbox);

                        double nattractdist = 1;
                        for (int n = 0; n < attracts.Count; n++)
                        {
                            var nattract = attracts[n];
                            var rad      = radiuses[Math.Min(n, radiuses.Count - 1)];
                            var pow      = weights[Math.Min(n, radiuses.Count - 1)];

                            var ntdist = Math.Pow(JellyUtility.Remap(Math.Min(nattract.DistanceTo(nrndpt), rad), 0, rad, 0, 1.0), pow);
                            nattractdist *= ntdist;
                        }

                        var nindex = points.ClosestIndex(nrndpt);
                        var npos   = points[nindex];

                        var ndist = npos.DistanceTo(nrndpt) * nattractdist;

                        if (fdist < ndist)
                        {
                            fdist = ndist;
                            fpos  = nrndpt;
                        }
                    }
                    points.Add(fpos);
                }
            }


            DA.SetDataList(0, points);
        }
コード例 #12
0
ファイル: FrameTools.cs プロジェクト: smor/intralattice
        /// <summary>
        /// Removes duplicate/invalid/tiny curves and outputs the cleaned list, a list of unique nodes and a list of node pairs.
        /// </summary>
        public static List<Curve> CleanNetwork(List<Curve> inputStruts, double tol, out Point3dList nodes, out List<IndexPair> nodePairs)
        {
            nodes = new Point3dList();
            nodePairs = new List<IndexPair>();

            var struts = new List<Curve>();

            // Loop over list of struts
            for (int i = 0; i < inputStruts.Count; i++)
            {
                Curve strut = inputStruts[i];
                // Unitize domain
                strut.Domain = new Interval(0, 1);
                // Minimum strut length (if user defined very small tolerance, use 100*rhinotolerance)
                double minLength = Math.Max(tol, 100*RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
                // If strut is invalid, ignore it
                if (strut == null || !strut.IsValid || strut.IsShort(minLength))
                {
                    continue;
                }

                Point3d[] pts = new Point3d[2] { strut.PointAtStart, strut.PointAtEnd };
                List<int> nodeIndices = new List<int>();
                // Loop over end points of strut
                // Check if node is already in nodes list, if so, we find its index instead of creating a new node
                for (int j = 0; j < 2; j++)
                {
                    Point3d pt = pts[j];
                    // Find closest node to current pt
                    int closestIndex = nodes.ClosestIndex(pt); 

                    // If node already exists (within tolerance), set the index
                    if (nodes.Count != 0 && pt.EpsilonEquals(nodes[closestIndex], tol))
                    {
                        nodeIndices.Add(closestIndex);
                    }
                    // If node doesn't exist
                    else
                    {
                        // Update lookup list
                        nodes.Add(pt);
                        nodeIndices.Add(nodes.Count - 1);
                    }
                }

                // We must ignore duplicate struts
                bool isDuplicate = false;
                IndexPair nodePair = new IndexPair(nodeIndices[0], nodeIndices[1]);

                int dupIndex = nodePairs.IndexOf(nodePair);
                // dupIndex equals -1 if nodePair not found, i.e. if it doesn't equal -1, a match was found
                if (nodePairs.Count != 0 && dupIndex != -1)
                {
                    // Check the curve midpoint to make sure it's a duplicate
                    Curve testStrut = struts[dupIndex];
                    Point3d ptA = strut.PointAt(0.5);
                    Point3d ptB = testStrut.PointAt(0.5);
                    if (ptA.EpsilonEquals(ptB, tol)) isDuplicate = true;
                }

                // So we only create the strut if it doesn't exist yet (check nodePairLookup list)
                if (!isDuplicate)
                {
                    // Update the lookup list
                    nodePairs.Add(nodePair);
                    strut.Domain = new Interval(0, 1);
                    struts.Add(strut);
                }
            }

            return struts;
        }
コード例 #13
0
ファイル: penrose.cs プロジェクト: chirs/parametric_systems
    public static DataTree<Polyline> makeTiles(Mesh mesh)
    {
        DataTree < Polyline> Tree = new DataTree<Polyline>();
        for(int i = 0;i < mesh.Faces.Count;i++)
        {
          Vector3d ab = new Vector3d (mesh.Vertices[mesh.Faces[i].B] - mesh.Vertices[mesh.Faces[i].A]);
          Vector3d ac = new Vector3d (mesh.Vertices[mesh.Faces[i].C] - mesh.Vertices[mesh.Faces[i].A]);

          Point3dList pts = new Point3dList();
          pts.Add(mesh.Vertices[mesh.Faces[i].A]);
          pts.Add(mesh.Vertices[mesh.Faces[i].C]);
          pts.Add(mesh.Vertices[mesh.Faces[i].B]);
          Polyline edge = new Polyline(pts);
          Tree.Add(edge);
        }
        return Tree;
    }
コード例 #14
0
        /// <summary>
        /// Removes duplicate/invalid/tiny curves and outputs the cleaned list, a list of unique nodes and a list of node pairs.
        /// </summary>
        public static List <Curve> CleanNetwork(List <Curve> inputStruts, double tol, out Point3dList nodes, out List <IndexPair> nodePairs)
        {
            nodes     = new Point3dList();
            nodePairs = new List <IndexPair>();

            var struts = new List <Curve>();

            // Loop over list of struts
            for (int i = 0; i < inputStruts.Count; i++)
            {
                Curve strut = inputStruts[i];
                // Unitize domain
                strut.Domain = new Interval(0, 1);
                // Minimum strut length (if user defined very small tolerance, use 100*rhinotolerance)
                double minLength = Math.Max(tol, 100 * RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
                // If strut is invalid, ignore it
                if (strut == null || !strut.IsValid || strut.IsShort(minLength))
                {
                    continue;
                }

                Point3d[] pts = new Point3d[2] {
                    strut.PointAtStart, strut.PointAtEnd
                };
                List <int> nodeIndices = new List <int>();
                // Loop over end points of strut
                // Check if node is already in nodes list, if so, we find its index instead of creating a new node
                for (int j = 0; j < 2; j++)
                {
                    Point3d pt = pts[j];
                    // Find closest node to current pt
                    int closestIndex = nodes.ClosestIndex(pt);

                    // If node already exists (within tolerance), set the index
                    if (nodes.Count != 0 && pt.EpsilonEquals(nodes[closestIndex], tol))
                    {
                        nodeIndices.Add(closestIndex);
                    }
                    // If node doesn't exist
                    else
                    {
                        // Update lookup list
                        nodes.Add(pt);
                        nodeIndices.Add(nodes.Count - 1);
                    }
                }

                // We must ignore duplicate struts
                bool      isDuplicate = false;
                IndexPair nodePair    = new IndexPair(nodeIndices[0], nodeIndices[1]);

                int dupIndex = nodePairs.IndexOf(nodePair);
                // dupIndex equals -1 if nodePair not found, i.e. if it doesn't equal -1, a match was found
                if (nodePairs.Count != 0 && dupIndex != -1)
                {
                    // Check the curve midpoint to make sure it's a duplicate
                    Curve   testStrut = struts[dupIndex];
                    Point3d ptA       = strut.PointAt(0.5);
                    Point3d ptB       = testStrut.PointAt(0.5);
                    if (ptA.EpsilonEquals(ptB, tol))
                    {
                        isDuplicate = true;
                    }
                }

                // So we only create the strut if it doesn't exist yet (check nodePairLookup list)
                if (!isDuplicate)
                {
                    // Update the lookup list
                    nodePairs.Add(nodePair);
                    strut.Domain = new Interval(0, 1);
                    struts.Add(strut);
                }
            }

            return(struts);
        }