Problem matching trees when slicing surfaces with multiple curves


I have a list of closed polylines and, for each of them, a list of slicing curves.

What is the best (correct) way to retrieve the set of split pieces associated with its slicing curve.

It must be a trivial task to do in Grasshopper and yet, plugging the ordered datatrees of the polylines and their corresponding slicing curves into the Surface Split component doesn’t seem to do the trick.

The only workaround I found is to duplicate each polyline (Repeat Data or using Cross Reference) so as to match the number of their respective slicing curves. This is probably not an effective way to proceed, especially knowing that I would like to input thousands of different polylines.

What am I missing here ?

Question - slicing (9.3 KB)


For this trick I recommend using Elefront’s Graft Parallel.
It basically does the same thing as your repeat operation, in one component.

Due to how data trees work it’s not possible to do this without duplicating the polylines. If it’s to slow, you’d have to do use code to deal with those lists accordingly.

Note that this doesn’t really affect the time necessary to compute the splits, the only difference is the duplication. But a thousand polylines is peanuts for Grasshopper.


Thanks for the tip (didn’t know about Elefront’s Graft Parallel) !

For my case I think I’ll go the coding way. I must admit I still struggle understanding why the sub-branches of A are not associated with the corresponding A branch of another datatree.

Anyway, thanks again for the swift reply.

If this means C# and you want a demo notify.

PS: the primitive way to do this is to split (existed RC Method that one) a BrepFace with a collection of Curves (or LineCurves == Lines) and then correlate the pieces VS the splitters (or do it the analytic way (+ Recursion): for each BrepFace and for each Splitter … blah, blah).

1 Like

Thank you for offering your help, and for the tip !

Alas my knowledge is limited to Python, I wish I had time to learn to master C# for Grasshopper though.

I hate that thing.

Anyway prior start writing mastermind the correlation strategy (general case: a BrepFace VS many Splitters). You want each piece side being assosiated with a valid Splitter? (i.e. some sort of Connectivity so to speak [a Tree of Type int where path dim 0 is the index of the OEM Face, dim 1 is the index of the piece, dim2 is the index of the edge and item is the index of the Splitter] ) Or follow some other logic ? blah, blah. Then … start coding (we are talking about a few lines in fact).

1 Like

Hope dies last.

Found a rather simple C# on that matter. Removed some internal Methods … and here we are: Get some ideas and try to translate it to P. (129.3 KB)

1 Like

Thank you for the follow up, will look into it !

I had actually already wrote a whole script in Python making use of the slicing logic I had in mind. Turns out I’m now trying to convert that script into C# using Chat GPT. I think it got me pretty far (computed values are correct) but I can’t seem to find a way to output the result as a datatree. All I get is structure followed by the length of each path:

structure {10;9;10;8;16;11;15;23;30}

Would you be willing to help me figure out what I need to change in order to output a datatree instead ?

private void RunScript(List<Polyline> polygons, ref object S)

    GH_Structure<GH_Vector> output = new GH_Structure<GH_Vector>();
    MainClass instance = new MainClass();
    instance.ComputeSignatures(polygons, ref output);
    S = output;


  // <Custom additional code> 
  public class MainClass
    public GH_Structure<GH_Vector> S = new GH_Structure<GH_Vector>(); // (Output) - DataTree of computed "signatures" defined as 2D vectors
    Plane default_plane = Plane.WorldXY; // Global constant

    public List<Vector2d> GetSignature(Polyline pline)
      // Returns a list of 2D vectors.

      // Initialization (defining some constants and variables)
      PolylineCurve pline_curve = pline.ToPolylineCurve();
      double contour_length = pline_curve.GetLength();
      double brect_diag_length = pline_curve.GetBoundingBox(default_plane).Diagonal.Length;

      // Make sure the orientation of our polyline is counter-clockwise
      if (pline_curve.ClosedCurveOrientation() == CurveOrientation.Clockwise)

      List<Vector2d> signature = new List<Vector2d>();
      double cumulative_seg_length = 0;

      // Iterate over the list of edges of the current polygon (closed polyline)
      for (int i = 0; i < pline.SegmentCount; i++)
        Line seg = pline.SegmentAt(i);
        Point3d seg_middle = seg.PointAt(0.5);
        double seg_length = seg.Length;
        Vector3d seg_direction = seg.Direction;

        // Extend the length of the current edge segment and use it to cut out the polygon to which it belongs
        seg.Extend(brect_diag_length, brect_diag_length);
        Brep[] breps = Brep.CreatePlanarBreps(seg.ToNurbsCurve(), Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
        Curve[] split_pieces = null;
        if (breps != null && breps.Length > 0)
          split_pieces = pline_curve.Split(breps[0], Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance, Rhino.RhinoDoc.ActiveDoc.ModelAngleToleranceRadians);

        double path_length = 0;

        // If the polygon is split into multiple parts:
        if (split_pieces != null && split_pieces.Length > 1)
          // Iterate over the list of these split parts
          foreach (PolylineCurve split_piece in split_pieces)
            Point3d piece_centroid = AreaMassProperties.Compute(split_piece).Centroid;
            Vector3d piece_direction = piece_centroid - seg_middle;
            Vector3d cross_prod = Vector3d.CrossProduct(seg_direction, piece_direction);

            // If the current split part lies on the left of the current edge segment:
            if (cross_prod.Z > 0)
              // Store the lengths of its edges (only those that are on the polygon outline)
              foreach (Curve edge in split_piece.DuplicateSegments())
                Point3d mid_edge = (edge.PointAtStart + edge.PointAtEnd) * 0.5;
                PointContainment containement_status = pline_curve.Contains(mid_edge, default_plane, 0.001);

                if (containement_status == PointContainment.Coincident)
                  path_length += edge.GetLength();
          // If only one part (full polygon, no cuts), path_length is maximum (=contour_length)
          path_length = contour_length;

        // Normalize and update lengths
        double normalized_seg_length = seg_length / contour_length;
        cumulative_seg_length += normalized_seg_length;        // cumulative segments length
        double normalized_path_length = path_length / contour_length; // path length associated with the current edge segment

        // Store signature at this step
        Vector2d vec = new Vector2d(cumulative_seg_length, normalized_path_length);

      return signature;

    public void ComputeSignatures(List<Polyline> polygons, ref GH_Structure<GH_Vector> S)
      for (int i = 0; i < polygons.Count; i++)
        Polyline pline = polygons[i];
        if (pline != null)
          List<Vector2d> signature = GetSignature(pline);
          GH_Path path = new GH_Path(i);
          for (int j = 0; j < signature.Count; j++)
            Vector2d vector2d = signature[j];
            Vector3d vector3d = new Vector3d(vector2d.X, vector2d.Y, 0);
            GH_Vector ghVector = new GH_Vector(vector3d);
            S.Append(ghVector, path);

GH_Structure<IGH_Goo> are only usable if you develop plugins.
In the C# scripting component, you need to use DataTree<>.
It works in a similay way, but you odn’t need to encapsulate everything in GH_xxx types.
Example :

            Vector2d vector2d = signature[j];
            Vector3d vector3d = new Vector3d(vector2d.X, vector2d.Y, 0);
            GH_Vector ghVector = new GH_Vector(vector3d);
            S.Append(ghVector, path);

would become

            Vector2d vector2d = signature[j];
            Vector3d vector3d = new Vector3d(vector2d.X, vector2d.Y, 0);
            S.Add(vector3d, path);

with S being a DataTree<Vector3d>.

1 Like

Yikes + Yikes ! That %$$#%$ thing is the definition of pointless (and/or lobotomy).

PS1: I never use VS (brakes my nerves) and GH collection types. That said I use R/GH less and less in real-life.

PS2: if your world is not restricted to the R/GH combo (as you should if you are in the broad AEC market sector) … consider avoiding DataTrees (that’s a custom collection meaning portability issues etc etc).

Challenge: mastermind a way to do the piece/piece contact connectivity: you’ll need a 2 dim Tree/Array where the first dim is the index of the OEM BrepFace and the second the index of the piece in question (indices point to neighbor pieces).

1M Q: why you care about the Poly (CW/ACW) orientation ? what’s the point IF Split is what you are after? But if you insist … get the Cross of (pt[2]-pt[1], pt[0]-pt[1]) and then the Dot using some “reference orientation” Vector ( maybe Plane.WorldXY.ZAxis ??). Or for the general case Cluster planar BrepFaces and coplanar Splitters.

1 Like

@magicteddy: Thanks for the explanation, the whole script now works like a charm !


I have mixed feelings about it. In this context (converting a Python script to C# for GH), I find it both empowering and debilitating. On one hand, it allows me to quickly convert code in a surprisingly accurate way into a language that I don’t know; the time saving is undeniable. On the other, this very ability hinders any desire to analyze the code, to understand its rules and structures. Luckily the conversion wasn’t done without mistakes and this is precisely this imperfection that pushed me to put my brain back on and analyze the script to get a better understanding of it.

Out of curiosity, what other environment for computational geometry do you use then?

The code is a naïve implementation of an old paper on curves’ “signatures” from Joseph O’Rourke. It is required that the polygon is always on the left side of the visited segment, so the path of its contour must be counter-clockwise.

Also, thank you for the code you provided. I find it “clean” and easy to grasp. Did you remove the MainClass for ease of understanding or is it something that you usually do when scripting in GH ?

Thanks again to both of you, your answers and suggestion really helped.

I own a practice (AEC market sector). Here’s how I rate things:

  1. ProjectWise (Bentley Systems).
  2. CSI MasterFormat
  3. AECOSim + various verticals (Bentley Systems)
  4. Microstation Connect (Bentley Systems)
  5. CATIA/Siemens NX

I hear you: where’s the Parametric thingy? Well … In fact I have very little interest for that sort of thing ( in real-life + real-world ). But If I use something that is Generative Components (Bentley Systems).

ALWAYS do your things using as many “modules” as possible - for more than obvious reasons. That way after some time … well … you just combine Methods

1 Like

That’s the starting point for lobotomy.

Other than that as a challenge … try to master Contact Connectivity: get the Outer Loop [i.e. a Curve from any piece as BrepFace], get the Start Pt per Segment [i.e. use Curve.DublicateSegments()] and a Point3dList collection to test what is prox to what (a bool [ , ] to monitor the visited pairs is a must).

1 Like