Splitting brep by curves fails in RhinoCommon but not in Rhino

I am trying to split a brep with some curves. The split does not work in a plugin setting using the RhinoCommon Brep.Split function, but works if I manually use the Rhino Split command.

To replicate, use the following .3dm file:
customSplit.3dm (416.2 KB)
and the following plugin script:

using Rhino;
using Rhino.Commands;
using Rhino.DocObjects;
using Rhino.Geometry;
using Rhino.Geometry.Intersect;
using Rhino.Input;
using Rhino.Input.Custom;

public class MyCurveCutter : Command
{
    public MyCurveCutter()
    {
        Instance = this;
    }

    public static MyCurveCutter Instance { get; private set; }

    public override string EnglishName => "MyCurveCutter";

    protected override Result RunCommand(RhinoDoc doc, RunMode mode)
    {
        Brep a;
        using (GetObject go = new())
        {
            go.SetCommandPrompt("Choose first object, which will create splitting curve");
            go.GeometryFilter = ObjectType.Brep;
            go.DisablePreSelect();
            if (go.Get() == GetResult.Cancel)
            {
                return Result.Cancel;
            }
            a = go.Object(0).Brep();
        }

        Brep b;
        using (GetObject go = new())
        {
            go.SetCommandPrompt("Choose second object, which will be split");
            go.GeometryFilter = ObjectType.Brep;
            go.DisablePreSelect();
            if (go.Get() == GetResult.Cancel)
            {
                return Result.Cancel;
            }
            b = go.Object(0).Brep();
        }

        Intersection.BrepBrep(a, b, 0.1, true, out Curve[] curves, out _);
        curves = Curve.JoinCurves(curves, 0.1);
        if (curves == null || curves.Length != 1)
        {
            RhinoApp.WriteLine("breps did not intersect correctly");
            if (curves == null)
            {
                RhinoApp.WriteLine("intersection returned null");
            }
            else 
            { 
                RhinoApp.WriteLine("intersection found " + curves.Length + " curves");
                foreach (Curve c in curves)
                {
                    doc.Objects.Add(c);
                }
                doc.Views.Redraw();
            }
            return Result.Failure;
        }
        Curve curve = curves[0];

        // these use the assumption that the second selected thing is in line with the Y axis
        Curve startExtension = new LineCurve(new Line(curve.PointAtStart, 1000 * Vector3d.YAxis));
        Curve endExtension = new LineCurve(new Line(curve.PointAtEnd, 1000 * Vector3d.YAxis));

        Brep[] splits = b.Split([startExtension, curve, endExtension], 0.1);
        if (splits == null || splits.Length != 2)
        {
            RhinoApp.WriteLine("split failed?");
            if (splits == null)
            {
                RhinoApp.WriteLine("split returned null");
            }
            else
            {
                RhinoApp.WriteLine("split returned " + splits.Length + " pieces");
                foreach (Brep piece in splits)
                {
                    doc.Objects.Add(piece);
                }
            }
            // bake curves so you can try it manually
            doc.Objects.Add(curve);
            doc.Objects.Add(startExtension);
            doc.Objects.Add(endExtension);
            doc.Views.Redraw();
            return Result.Failure;
        }
        else
        {
            RhinoApp.WriteLine("split succeeded");
            foreach (Brep piece in splits)
            {
                doc.Objects.Add(piece);
            }
            doc.Views.Redraw();
            return Result.Success;
        }
    }
}

First select the round shape, then select the plane. The script fails but will add the splitting curves to the document. Then, use the Rhino Split command with the plane and the added curves – it succeeds. Clearly either something happens in the process of adding the curves to the document or something is done differently in the Brep.Split method and the Split command.

My question is: How can I get RhinoCommon to successfully perform this split?

There are a number of assumptions made in the command code for the sake of replicating the behavior (e.g. extending the curve along the Y axis). The code I actually care about lives in a larger workflow, so in the general case I can’t make certain assumptions. In particular, splitting with curve projection is not what I want.

Thanks,
- Russell Emerine

Hi @Russell_Emerine,

Try using this Brep.Split override.

– Dale

This override splits using the curve extrusion, which is not the behavior I want.

Looking closer at the specification of the Split command, it seems that it extrudes curves in some way in most cases. That explains why the Split command works while the Brep.Split override that I was using does not. However, in the general case I may not know which direction to project the curve onto the brep or whether the Brep and curve are planar, and splitting against the extrusion may give a completely unintended result. What I want to do is split the Brep where the curve lies on the surface. Is there a way to do this consistently?

The Split documentation says

When you split a surface with a 3-D curve in an angled parallel or a perspective view, the cutting curve is pulled onto the surface by closest points.

Is this the best approach? Is there a way to replicate the behavior in RhinoCommon and make it work for the sample input I provided?

Hi @Russell_Emerine,

Can you post a model what shows us what results you want?

Thanks,

– Dale

I have already provided the model in the first post. This particular model works well with splitting with the curve extrusion, but I don’t want any curve extrusions in the general case. The result I want is to modify the plugin code I provided to successfully split the Brep into two pieces using the specified curves without using any curve extrusion.

Hi @Russell_Emerine,

This seems to work - maybe I’m confused.

protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
  GetObject go = new();
  go.SetCommandPrompt("Select surface or polysurface to split");
  go.GeometryFilter = ObjectType.Surface | ObjectType.PolysrfFilter;
  go.SubObjectSelect = false;
  go.Get();
  if (go.CommandResult() != Result.Success)
    return go.CommandResult();

  Brep brep = go.Object(0).Brep();
  if (null == brep)
    return Result.Failure;

  go.SetCommandPrompt("Select cutting surface or polysurface");
  go.EnablePreSelect(false, true);
  go.DeselectAllBeforePostSelect = false;
  go.Get();
  if (go.CommandResult() != Result.Success)
    return go.CommandResult();

  Brep cutter = go.Object(0).Brep();
  if (null == cutter)
    return Result.Failure;

  double tol = doc.ModelAbsoluteTolerance;
  bool rc = Intersection.BrepBrep(brep, cutter, tol, false, out Curve[] outCurves, out _);
  if (!rc || null == outCurves || 0 == outCurves.Length)
  {
    RhinoApp.WriteLine("Breps did not intersect.");
    return Result.Failure;
  }

  if (outCurves.Length > 1)
    outCurves = Curve.JoinCurves(outCurves, 2.1 * tol);

  Brep[] pieces = brep.Split(outCurves, tol);
  foreach (Brep piece in pieces)
    doc.Objects.AddBrep(piece);

  doc.Views.Redraw();
  return Result.Success;
}

– Dale

You seem to not have noticed these lines in my original code:

I am not trying to split the round object with the plane as a cutter, I am trying to split the plane with the round object as a cutter. In order to do this, I artificially extend their intersection curve in the Y direction so that it splits the plane into two pieces.

If I add the extra extension step into your code, it still fails to split.

I’ve replicated the failed split in a different way, with this file:
customSplit.3dm (46.8 KB)
and this new command:

    protected override Result RunCommand(RhinoDoc doc, RunMode mode)
    {
        GetObject go = new();
        go.SetCommandPrompt("Select surface or polysurface to split");
        go.GeometryFilter = ObjectType.Surface | ObjectType.PolysrfFilter;
        go.SubObjectSelect = false;
        go.Get();
        if (go.CommandResult() != Result.Success)
            return go.CommandResult();

        Brep brep = go.Object(0).Brep();
        if (null == brep)
            return Result.Failure;

        go.SetCommandPrompt("Select cutting curve");
        go.GeometryFilter = ObjectType.Curve;
        go.EnablePreSelect(false, true);
        go.DeselectAllBeforePostSelect = false;
        go.Get();
        if (go.CommandResult() != Result.Success)
            return go.CommandResult();

        Curve cutter = go.Object(0).Curve();
        if (null == cutter)
            return Result.Failure;

        Brep[] pieces = brep.Split([cutter], 0.01);

        foreach (Brep piece in pieces)
            doc.Objects.AddBrep(piece);

        doc.Views.Redraw();
        return Result.Success;
    }

It fails to split.

I did ask you for a model with the results you are looking for - I didn’t understand what these extra lines were…

Open the model you just posted, select the curve and plane and run the Intersect command. Looks like the intersection curve and the lines are not coplanar.

– Dale

I’ve looked into things in my codebase and it looks like I have a logical error upstream. All along it was the case that the curves should not split the Brep, because they are indeed not coplanar – I just got confused by the combination of my upstream code claiming they were and the Split command splitting when the Brep.Split function didn’t. I’ll keep in mind to use the Intersect command to check for this kind of thing when debugging in the future.

Thanks for the help,
- Russell Emerine