Divide Close Curve(Polyline) By List Of Area

Divide Close Curve(Polyline) By List Of Area
Hi, I wrote c# code to split a close curve (multiple lines) based on area list
My Component has several options:
divide 2 part to Target Area = 0
divide in Equal Area = 1
divide by list of Area = 2
Explanation that this code can divide a closed curve in the direction of the y axis from top to bottom based on target areas
*i share my Code *
If anyone can optimize this code or have a better suggestion to make it faster, please help
to share
divide Area new -----.gh (37.2 KB)


@Mahdiyar
@PeterFotiadis
@DanielPiker
@laurent_delrieu
@Dani_Abalde

Indeed there’s a thing or two that require some attention more. But although I have various C#'s for that matter … they work differently so I can’t cut and paste Methods.

well but may you suggest different c# solotiuon ?

I’ll examine the thing in depth and I’ll be back soon (but all my similar things are in the practice that reopens April 18).

In general one suggested ability (more pragmatinc for facades and the likes - if you are after that kind of stuff) more would be using BrepFaces. Like:

Or maybe something random. Like:


1 Like

I improved my script but it’s a bit slow!

If you have the new code we can take a look

Hello
for me a simple way is to move the cutter from the beginning to the end with a specified number of steps. For each step calculate the area of the intersection.
image

here my script that use my library of Nautilus (LDLIB.dll). The library is using Clipper.
But this could be done with
https://developer.rhino3d.com/api/rhinocommon/rhino.geometry.curve/createbooleanintersection

divide Area LD.gh (8.7 KB)

Also all the calculation could be done with parallel calculations. So it take 0.2 s for 1000 calculations

 private void RunScript(Curve cutter, Curve cutted, ref object dys, ref object areas)
  {


    BoundingBox bb_cutted = cutted.GetBoundingBox(true);
    BoundingBox bb_cutter = cutter.GetBoundingBox(true);

    double maxYtranslation = bb_cutter.Center.Y - bb_cutter.Diagonal.Y / 2 - (bb_cutted.Center.Y - bb_cutted.Diagonal.Y / 2 ) * 1.2;
    int nSteps = 1000;
    double tolerance = 0.01;

    //Mouvement in -Y
    double[] lst_dy = new double[nSteps];
    //Area of each intersection
    double[] lst_area = new double[nSteps];

    Polyline pl_cutted;
    ClipperLib.Polyline3D.ConvertCurveToPolyline(cutted, tolerance, out pl_cutted);

    Parallel.For(0, nSteps, i =>
      {
      double dy = maxYtranslation * (double) i / (double) (nSteps - 1);
      Vector3d vY = new Vector3d(0, -dy, 0);
      lst_dy[i] = dy;
      Curve cutterTranslated = cutter.DuplicateCurve();
      cutterTranslated.Transform(Transform.Translation(vY));

      Polyline pl_cutter;
      ClipperLib.Polyline3D.ConvertCurveToPolyline(cutterTranslated, tolerance, out pl_cutter);
      List<Polyline> pls = ClipperLib.Polyline3D.Boolean(ClipperLib.ClipType.ctIntersection, pl_cutter, pl_cutted, Plane.WorldXY, tolerance, true);

      double area = 0;
      foreach (Polyline pl in pls)
      {
        area += LDLIB.PrintTools.PolylineArea(pl, Plane.WorldXY);
      }

      lst_area[i] = area;
      }
      );

    dys = lst_dy;
    areas = lst_area;

  }
}

it is a kind of brute force
you can use more optimized tool like this one that was used recently by @DanielPiker
the function f(x) is the area depending on the movement in Y.

1 Like

Here’s my root finding example Laurent refers to:

1 Like

Not c#, but this solves in under half second.
Fully translating the same algorithm in c# should make it faster…


divide Area new ----- interpolation1.gh (45.4 KB)

Similar concept to what already said by others, Curve-Curve boolean intersection + area calc done inside a c# in parallel as Laurent said.

Pseudo-geometric solution, pseudo brute force.
Intersection with the “intergral graph”, which have kinks where the section curve intercepts a discontinuity/corner on the area curve to split.

1 Like

Yes this one,
with this method and Clipper, searching for 100 areas takes 11 ms.


Not finished code, but the idea is there.
divide Area LD v2.gh (8.6 KB)
Take the code of Daniel and use RhinoCommon tool and it must work.

2 Likes

@laurent_delrieu
Thanks, but some error in the component



i use Nautilus 0.8
2023-04-07

It will work with Nautilus 0.9. But if I have some time I will certainly do an autonomous component.

1 Like

10 minutes rework
Here a solution with only Rhinocommon and no plugin
The inputs must be closed curves. But it is possible to have a cutter not using a closed curve and close it in the code. It is also possible to the other things inside.

I reuse 2 codes
Daniel Piker tool

And John D. Cook one

divide Area LD v3.gh (15.6 KB)

 private void RunScript(Curve cutter, Curve cutted, double area, ref object vector)
  {
    //Laurent Delrieu 21/04/2023
    double tolerance = 0.001;
    sCC = new sliceClosedCurve(cutter, cutted, tolerance);
    double dy = RootFinding.Brent(new FunctionOfOneVariable(f), 0, sCC.maxYtranslation, tolerance, area);

    //Mouvement of cutter in order to have the area
    vector = new Vector3d(0, -dy, 0);

  }

  // <Custom additional code> 
  static sliceClosedCurve sCC;

  /// <summary>
  /// Daniel Piker tool transformed
  ///https://discourse.mcneel.com/t/sweep-plane-until-lower-section-has-a-certain-volume/128708/36
  /// </summary>
  public class sliceClosedCurve
  {
    Curve cutter;
    Curve cutted;
    public double maxYtranslation;
    double tolerance;


    public  sliceClosedCurve(Curve c_cutter, Curve c_cutted, double tol)
    {
      cutter = c_cutter.DuplicateCurve();
      cutted = c_cutted.DuplicateCurve();
      tolerance = tol;

      BoundingBox bb_cutted = cutted.GetBoundingBox(true);
      BoundingBox bb_cutter = cutter.GetBoundingBox(true);

      maxYtranslation = bb_cutter.Center.Y - bb_cutter.Diagonal.Y / 2 - (bb_cutted.Center.Y - bb_cutted.Diagonal.Y / 2 ) * 1.2;
    }
    public double getArea(double dy)
    {
      Vector3d vY = new Vector3d(0, -dy, 0);
      Curve cutterTranslated = cutter.DuplicateCurve();
      cutterTranslated.Transform(Transform.Translation(vY));


      Curve[] tab_curves = Curve.CreateBooleanIntersection(cutterTranslated, cutted, tolerance);

      double area = 0;
      foreach (Curve curve in tab_curves)
      {

        var s = Rhino.Geometry.AreaMassProperties.Compute(curve);
        area += s.Area;
      }

      return area;
    }
  }

  public static double f(double x)
  {
    return sCC.getArea(x);
  }

  /// John D. Cook
  /// https://www.codeproject.com/Articles/79541/Three-Methods-for-Root-finding-in-C
  /// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  public delegate double FunctionOfOneVariable(double x);

  public static class RootFinding
  {
    const int maxIterations = 50;
    /// <summary>
    /// John D. Cook
    /// https://www.codeproject.com/Articles/79541/Three-Methods-for-Root-finding-in-C
    /// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    /// </summary>
    /// <param name="f"></param>
    /// <param name="left"></param>
    /// <param name="right"></param>
    /// <param name="tolerance"></param>
    /// <param name="target"></param>
    /// <returns></returns>
    public static double Bisect
      (
      FunctionOfOneVariable f,
      double left,
      double right,
      double tolerance = 1e-6,
      double target = 0.0
      )
    {
      // extra info that callers may not always want
      int iterationsUsed;
      double errorEstimate;

      return Bisect(f, left, right, tolerance, target, out iterationsUsed, out errorEstimate);
    }

    public static double Bisect
      (
      FunctionOfOneVariable f,
      double left,
      double right,
      double tolerance,
      double target,
      out int iterationsUsed,
      out double errorEstimate
      )
    {
      if (tolerance <= 0.0)
      {
        string msg = string.Format("Tolerance must be positive. Recieved {0}.", tolerance);
        throw new ArgumentOutOfRangeException(msg);
      }

      iterationsUsed = 0;
      errorEstimate = double.MaxValue;

      // Standardize the problem.  To solve f(x) = target,
      // solve g(x) = 0 where g(x) = f(x) - target.
      FunctionOfOneVariable g = delegate(double x) { return f(x) - target; };


      double g_left = g(left);  // evaluation of f at left end of interval
      double g_right = g(right);
      double mid;
      double g_mid;
      if (g_left * g_right >= 0.0)
      {
        string str = "Invalid starting bracket. Function must be above target on one end and below target on other end.";
        string msg = string.Format("{0} Target: {1}. f(left) = {2}. f(right) = {3}", str, g_left + target, g_right + target);
        throw new ArgumentException(msg);
      }

      double intervalWidth = right - left;

      for
        (
        iterationsUsed = 0;
        iterationsUsed < maxIterations && intervalWidth > tolerance;
        iterationsUsed++
        )
      {
        intervalWidth *= 0.5;
        mid = left + intervalWidth;

        if ((g_mid = g(mid)) == 0.0)
        {
          errorEstimate = 0.0;
          return mid;
        }
        if (g_left * g_mid < 0.0)           // g changes sign in (left, mid)
          g_right = g(right = mid);
        else                            // g changes sign in (mid, right)
          g_left = g(left = mid);
      }
      errorEstimate = right - left;
      return left;
    }

    public static double Brent
      (
      FunctionOfOneVariable f,
      double left,
      double right,
      double tolerance = 1e-6,
      double target = 0.0
      )
    {
      // extra info that callers may not always want
      int iterationsUsed;
      double errorEstimate;

      return Brent(f, left, right, tolerance, target, out iterationsUsed, out errorEstimate);
    }

    public static double Brent
      (
      FunctionOfOneVariable g,
      double left,
      double right,
      double tolerance,
      double target,
      out int iterationsUsed,
      out double errorEstimate
      )
    {
      if (tolerance <= 0.0)
      {
        string msg = string.Format("Tolerance must be positive. Recieved {0}.", tolerance);
        throw new ArgumentOutOfRangeException(msg);
      }

      errorEstimate = double.MaxValue;

      // Standardize the problem.  To solve g(x) = target,
      // solve f(x) = 0 where f(x) = g(x) - target.
      FunctionOfOneVariable f = delegate(double x) { return g(x) - target; };

      // Implementation and notation based on Chapter 4 in
      // "Algorithms for Minimization without Derivatives"
      // by Richard Brent.

      double c, d, e, fa, fb, fc, tol, m, p, q, r, s;

      // set up aliases to match Brent's notation
      double a = left; double b = right; double t = tolerance;
      iterationsUsed = 0;

      fa = f(a);
      fb = f(b);

      if (fa * fb > 0.0)
      {
        string str = "Invalid starting bracket. Function must be above target on one end and below target on other end.";
        string msg = string.Format("{0} Target: {1}. f(left) = {2}. f(right) = {3}", str, target, fa + target, fb + target);
        throw new ArgumentException(msg);
      }

      label_int:
        c = a; fc = fa; d = e = b - a;
      label_ext:
        if (Math.Abs(fc) < Math.Abs(fb))
        {
          a = b;  b = c;  c = a;
          fa = fb; fb = fc; fc = fa;
        }

      iterationsUsed++;

      tol = 2.0 * t * Math.Abs(b) + t;
      errorEstimate = m = 0.5 * (c - b);
      if (Math.Abs(m) > tol && fb != 0.0) // exact comparison with 0 is OK here
      {
        // See if bisection is forced
        if (Math.Abs(e) < tol || Math.Abs(fa) <= Math.Abs(fb))
        {
          d = e = m;
        }
        else
        {
          s = fb / fa;
          if (a == c)
          {
            // linear interpolation
            p = 2.0 * m * s; q = 1.0 - s;
          }
          else
          {
            // Inverse quadratic interpolation
            q = fa / fc; r = fb / fc;
            p = s * (2.0 * m * q * (q - r) - (b - a) * (r - 1.0));
            q = (q - 1.0) * (r - 1.0) * (s - 1.0);
          }
          if (p > 0.0)
            q = -q;
          else
            p = -p;
          s = e; e = d;
          if (2.0 * p < 3.0 * m * q - Math.Abs(tol * q) && p < Math.Abs(0.5 * s * q))
            d = p / q;
          else
            d = e = m;
        }
        a = b; fa = fb;
        if (Math.Abs(d) > tol)
          b += d;
        else if (m > 0.0)
          b += tol;
        else
          b -= tol;
        if (iterationsUsed == maxIterations)
          return b;

        fb = f(b);
        if ((fb > 0.0 && fc > 0.0) || (fb <= 0.0 && fc <= 0.0))
          goto label_int;
        else
          goto label_ext;
      }
      else
        return b;
    }

    public static double Newton
      (
      FunctionOfOneVariable f,
      FunctionOfOneVariable fprime,
      double guess,
      double tolerance = 1e-6,
      double target = 0.0
      )
    {
      // extra info that callers may not always want
      int iterationsUsed;
      double errorEstimate;

      return Newton(f, fprime, guess, tolerance, target, out iterationsUsed, out errorEstimate);
    }

    public static double Newton
      (
      FunctionOfOneVariable f,
      FunctionOfOneVariable fprime,
      double guess,
      double tolerance,
      double target,
      out int iterationsUsed,
      out double errorEstimate
      )
    {
      if (tolerance <= 0)
      {
        string msg = string.Format("Tolerance must be positive. Recieved {0}.", tolerance);
        throw new ArgumentOutOfRangeException(msg);
      }

      iterationsUsed = 0;
      errorEstimate = double.MaxValue;

      // Standardize the problem.  To solve f(x) = target,
      // solve g(x) = 0 where g(x) = f(x) - target.
      // Note that f(x) and g(x) have the same derivative.
      FunctionOfOneVariable g = delegate(double x) { return f(x) - target; };

      double oldX, newX = guess;
      for
        (
        iterationsUsed = 0;
        iterationsUsed < maxIterations && errorEstimate > tolerance;
        iterationsUsed++
        )
      {
        oldX = newX;
        double gx = g(oldX);
        double gprimex = fprime(oldX);
        double absgprimex = Math.Abs(gprimex);
        if (absgprimex > 1.0 || Math.Abs(gx) < double.MaxValue * absgprimex)
        {
          // The division will not overflow
          newX = oldX - gx / gprimex;
          errorEstimate = Math.Abs(newX - oldX);
        }
        else
        {
          newX = oldX;
          errorEstimate = double.MaxValue;
          break;
        }
      }
      return newX;
    }
  }
1 Like

Great! @laurent_delrieu
But if we use CreateBooleanRegions method
Then we can use to open the curve
Does it have much effect on reducing speed?

I didn’t test this one. The only problem I seebis that you will have to sort the splitted region.
Combining the 2 methods is surely the way to go. I made 3 methods you have another one with Grasshopper component plus the one from you. Up to you to decide from the speed reliability simplicity or whatever.

For my tools I often use clipper for reliability. For example if I made intersection of 2 same curve it work better with clipper than RhinoCommon

1 Like