Odd Behavior for Curve.CreateControlPointCurve() or new LineCurve();

Hi All,
I am working on a method using VS2022 and C#, and wish to derive the intersection point on the surface of a Brep using the method:

Intersection.CurveBrep(circularSweepLn, _reVesselShape, .001, out oLapCrvs, out intZPts);

My issue is the creation of a suitable curve from two points. I have used the following methods with similar results. Above are the debug values of the endpoints before and after the conversion.

It appears something is not working, as when I invoke the intersection method, there are intersection points (intZPts) on the Brep surface, but the X coordinates of those points should be in the range of -1.0 to -11.0, not -340.9 to -350.9.

Is there a bug here or am I missing something?

Thanks ahead for your advice.
Dave

You’re going to have to post more code, and possibly even the brep you’re intersecting.

Hi, David, thanks for your interest.

The code design of my project is that Grasshopper components serve to gather parameters. These parameters, once collected, are sent to a separate DLL for processing. To add context, I offer the following details:

A DLL method determines the minimum and maximum radius of an organic shaped Brep, named _reVesselShape. The radius is calculated from a central axial line to the surface of the shape.

A LineCurve is created perpendicular to the Axial Line. This line, named circularSweepLn, rotates around the axial line and intersects with the surface of the Brep, yielding points from which to derive the min/max radius value.

I first created a list of circularSweepLns and then processed the intersect points. This is where I ran into problems and requested help from the forum.

During a long session debugging, I discovered that when looping through the list of circularSweepLn’s, the endpoint coordinates would all change in unison and all the end point coordinates were identical. This despite the fact that, the calculated position of the endpoints was different. This gave me a clue the problem.

In the screen shots from the original post, the end coordinates of circularSweepLn were from one element of a list of 30 LineCurves. My attempt to discover an answer led me to remember that points, lines and probably LineCurves are value based variables, not referenced as with classes. Because the first start and end points were the seed of all the descendent LineCurves, altering the position of the end points affected all the LineCurves created. (Perhaps I am drawing a conclusion that is not based on the C# language.)

Since my original posting, I have revised the offending method. Two principle changes corrected the problem. 1) Though it may not be necessary, I created Curves, not LineCurves. 2) Once the original Curve was created, ie, ‘circularSweepCrv’, I duplicated the original to ‘dupCrv2’ before any transforms were applied. This made every line unique. After transforms, the duplicate Curves were then added to a list. The code of the method CalcMinMaxRadius follows

   public static (double, double, Point3d, Point3d, List<Curve>, List<Point3d>) CalcMinMaxRadius(Brep _reVesselShape) 
        {
            (double, double, Point3d, Point3d) domainsBox = CalcShapeSpecs(_reVesselShape);
            Point3d _startPt = domainsBox.Item3;
            Point3d _endPt = domainsBox.Item4;
 
            int centerLineTicks = 10; //? adjust to 500
            double centerLen = _startPt.DistanceTo(_endPt);
            double centerLineOffset = centerLen / (centerLineTicks);
            Vector3d centerLnVector = new Vector3d(_endPt - _startPt);


            int radialTicks = 10; //? adjust to 360
            double radialRadOffset = 2 * Math.PI / radialTicks;

            double minLen;
            double maxLen;
            Curve[] oLapCrvs;
            Point3d[] intZPts;

            List<Point3d> intFerPts = new List<Point3d>();
            List<double> lenSort = new List<double>();
            List<Curve> _xCurves = new List<Curve>();

            // *** generate Circular Sweep Line ***
            Line line = new Line(_startPt, new Point3d(_startPt.X, 6, _startPt.Z)); 
            Point3d pt = line.PointAtLength(line.Length / 2);
            List <Point3d> endpts = new List<Point3d>() { line.From, pt, line.To }; 
            Curve circularSweepCrv = Curve.CreateControlPointCurve(endpts, 1); 

            // Calculate the Offset Points Along the Center Line
            Point3d tickCL = new Point3d();
            List<Point3d> offsetPts = new List<Point3d>();
            int cLnTicks = centerLineTicks + 1;
            for ( int i = 0; i < cLnTicks; i++)
            {
                double tick = 1.0 / cLnTicks * i;
                tickCL = CalcOffsetPt(_startPt, _endPt, centerLineOffset * i);
                offsetPts.Add(tickCL);
            }

            // Calculate the Radial Sweep Angles About the CenterLine 
            double sweepOffset;
            List<double> sweepOffsets = new List<double>();
            int rTicks = radialTicks + 1;
            for (int i = 0; i < rTicks; i++)
            {
                sweepOffset = radialRadOffset * i;
                sweepOffsets.Add(sweepOffset);
            }

            // TODO: Offset the end of reVesselShapeto the block

            // Start testing at the bottom of the VShape
            for (int i = 0; i < centerLineTicks + 1; i++)
           {
                Curve dupCrv = circularSweepCrv.DuplicateCurve();
                double offx = offsetPts[i].X;
                double offy = offsetPts[i].Y;
                double offz = offsetPts[i].Z;

                dupCrv.Translate(offx , offy , offz );
                
                for(int j = 0; j < radialTicks + 1; j++)
                {
                    Curve dupCrv2 = dupCrv.DuplicateCurve();
                    dupCrv2.Transform(Transform.Rotation(sweepOffsets[j], centerLnVector, offsetPts[i]));
                    var start = dupCrv2.PointAtStart;
                    var end = dupCrv2.PointAtEnd;
                    double len = dupCrv2.GetLength();
                    _xCurves.Add(dupCrv2);
                    Intersection.CurveBrep(dupCrv2, _reVesselShape, .001, out oLapCrvs, out intZPts);

                        for(int k = 0; k < intZPts.Length; k++)
                        {
                            intFerPts.AddRange(intZPts);
                            double intZLength = intZPts[k].DistanceTo(offsetPts[i]);
                            if (intZLength > .75) 
                                  { lenSort.Add(intZLength); } 
                        }
                }
            }
            lenSort.Sort();
            minLen = lenSort[0];
            maxLen = lenSort[lenSort.Count - 1];
            return (minLen, maxLen, _startPt, _endPt, _xCurves, intFerPts);
        }

Below is an illustration of what I hoped to achieve.

Long story for what appears a simple answer.

Thank you, David, for the inquiry.

Dave

It would be a lot faster to generate points on the surface using a UV point grid and measuring the distance from these points to the axis line. Line closest point is much less work than brep/curve intersections, and since your sweep lines are perpendicular to your axis, I think it amounts to exactly the same result.

It’s going to take me a while to parse your code since you didn’t name any of your tuple elements. I’m also not really clear on how to interpret variable names starting with an underbar. How do they semantically differ from ones without?

(I do know there’s an issue with revolution surfaces in Rhino when the revolution profile curve isn’t coplanar with the axis, that has yielded wrong intersection results for me in the past.)

HI, David. Thank you for looking again at the post.

  1. Regarding the use of a U,V point grid methodology, I agree that it is more efficient. However, my investigation of determining distance from the U,V point on a Brep surface to the axial line has been problematic. For organic shapes with convex surfaces and ceases, the calculation of distance using a Rhinocommon method has shown at times to yield undesirable results. In short, a perpendicular distance from the axial line is preferred over a point on the Brep surface back to the axial line. It appears that the Rhinocommon algorithm is not constrained to a right angle when approaching the axial line. My guess is that Rhinocommon uses the normal vector at the surface. I had a similar problem when determining a reasonable attack angle as a CNC cutter approaches the Brep surface.

The issues are further complicated when adjusting for interference on a 4 degree of freedom CNC machine.

The CNC machine can do only one angular adjustment, not two.

  1. I apologize that my code is a mess. “While attempting to write a novel, I have yet to lurn to spel.” Though grossly inconsistent, my notation has to do with scope. ‘_’ variables are method parameters or return values. Lower case variables are created inside the method. Upper case variables are instances of complex objects available from outside the method. Review of the code shows that I don’t follow my own advice. Still learning about consistency…

  2. Thanks for heads up on the issue of revolution surfaces, I have also been pinched by coplanar anomalies when testing different solutions for this project.

All advice is appreciated!

Dave

I’m sorry, but this answer will have to do. My vacation starts tomorrow and I have to go pack.

I wrote a function which I think does what you’re after. It does less than your original one, but does seem to yield the right results (file with single test attached blob.3dm (3.3 MB)).

I’m using LineCurve as the curve type for the intersector.

public static ((double distance, Point3d point) minimum, (double distance, Point3d point) maximum) FindMinMaxRadius(Brep shape, Line axis)
{
  // Let's just pick some sampling values out of a hat.
  var alongSamples = 100;
  var aroundSamples = 100;

  // Boundingbox is used to make sure that the intersection rays are always long enough to poke
  // out of the brep, at least assuming the axis goes through the brep. If the axis is way off
  // in space somewhere, the radiating rays may not in fact hit the shape. This can be made
  // more reliable, but it sounds like it would be good enough for the use here.
  var spanLength = shape.GetBoundingBox(false).Diagonal.Length;

  var minimumDistance = double.PositiveInfinity;
  var maximumDistance = double.NegativeInfinity;
  var minimumLocation = Point3d.Unset;
  var maximumLocation = Point3d.Unset;

  for (int s = 0; s <= alongSamples; s++)
  {
    var pointOnAxis = axis.PointAt((double)s / alongSamples);
    var perpendicular = new Plane(pointOnAxis, axis.Direction);

    for (int a = 0; a < aroundSamples; a++)
    {
      var angle = 2 * System.Math.PI * ((double)a / aroundSamples);
      var rayVector = spanLength * perpendicular.XAxis;
      rayVector.Rotate(angle, axis.Direction);

      var ray = new LineCurve(pointOnAxis, pointOnAxis + rayVector);
      if (Rhino.Geometry.Intersect.Intersection.CurveBrep(ray, shape, 0.001, out _, out var intersectionPoints))
        if (intersectionPoints.Length > 0)
        {
          // Out of all the intersections, find the one nearest the axis.
          // There probably only ever is one intersection, but still.
          var nearestDistance = double.PositiveInfinity;
          var nearestLocation = Point3d.Unset;
          foreach (var intersection in intersectionPoints)
          {
            var localDistance = intersection.DistanceTo(pointOnAxis);
            if (localDistance < nearestDistance)
            {
              nearestDistance = localDistance;
              nearestLocation = intersection;
            }
          }

          // Update the minimum and maximum fields if we've
          // exceeded the currently known results.
          if (nearestDistance < minimumDistance)
          {
            minimumDistance = nearestDistance;
            minimumLocation = nearestLocation;
          }

          if (nearestDistance > maximumDistance)
          {
            maximumDistance = nearestDistance;
            maximumLocation = nearestLocation;
          }
        }
    }
  }

  return ((minimumDistance, minimumLocation), (maximumDistance, maximumLocation));
}

Hi, David:
Just got back to testing your code. The blob.3dm file is Version 8, and not compatible with Version 7. – no big deal. Instead, I tested the example code against my original Brep ReVesselShape.

To compare the respective efficiency of the algorithms, I used the System.Diagnostics Stopwatch class for timing. The duration of the methods CalcMinMaxDiam(ReVesselShape) - mine, and FindMinMaxRadius(RevesselShape, axis) - yours, was pushed out to a console window I cooked up with the help of ChatGBT. With a 100 X 100 sample count, the results follow and indicate your code is more than 100% more efficient.

image

Frankly, I am not surprised or dismayed. I found the example most instructive and a basis for more research and assimilation. This topic began with “Odd Behavior…”, and ends with appreciation. More gems to find!
Thanks,
Dave