Converting single-loop breps to curves

Is there a convenient way with RhinoCommon to convert a Brep that is known to consist of a single, simple loop into a Curve? I see that I can access the Brep’s trims as Curves, but I can’t find a convenient way to stitch them together. The best I can come up with is creating a new PolyCurve and calling Append for each trim, but that seems clunky.

Are you looking for a RhinoCommon way to do what the DupBorder command does?

I believe so. I’m still not totally familiar with the Rhino geometry model, but that looks right.

Brep b; // defined elsewhere
RhinoDoc doc; // the current document

double tolerance = doc.ModelAbsoluteTolerance;
Curve[] nakedEdges = b.DuplicateEdgeCurves(true);
Curve[] joined = Curve.JoinCurves(nakedEdges, 2.1*tolerance);
if (joined.Length < 1 || joined.Length > 1)
    RhinoApp.WriteLine("Joining of curves failed....");
    Curve loop = joined[0];

Thank you! For curiosity’s sake, what’s the basis of the 2.1 factor?

Each curve was created with a certain tolerance. When joining you have to bridge that value twice in the worst case, so a factor of 2.1 seems safe. It is also used often in examples by McNeel.

To be more certain, I typically use this method with a much larger maximumTolerance:

public static bool TryJoinCurves(IEnumerable<Curve> curves, out Curve result, double maximumTolerance = 1e-2)
	if (null == curves || !curves.Any())
		result = null;
		return false;

	int n = -8;
	// attempt to join with increasing tolerances
	Curve[] joined = curves.ToArray();
	double tolerance = Math.Pow(10, n);
	while (tolerance <= maximumTolerance && null != joined && joined.Length > 1)
		// Ignore curves that are shorter than the tolerance, 
		// if this is not done, it may lead to incorrect joins
		double tol = tolerance;
		IEnumerable<Curve> noShortCurves = curves.Where(c => c.GetLength(1e-8) > tol);
		joined = Curve.JoinCurves(noShortCurves, tol, false);
		tolerance = Math.Pow(10, ++n);

	// if at some point joined has become null return false
	if (null == joined)
		result = curves.First();
		return false;

	// if the length of joined allows it, set the first curve as the result
	result = joined.Length > 0 ? joined[0].ToNurbsCurve() : curves.First();
	return joined.Length == 1;

Here is a quick and dirty “DupBorder” equivalent:

Rhino.DocObjects.ObjectType filter = Rhino.DocObjects.ObjectType.Surface | Rhino.DocObjects.ObjectType.PolysrfFilter;
Rhino.DocObjects.ObjRef objref = null;
Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select surface or polysurface", false, filter, out objref);
if (rc != Rhino.Commands.Result.Success || objref == null)
  return rc;

Rhino.DocObjects.RhinoObject rhobj = objref.Object();
Rhino.Geometry.Brep brep = objref.Brep();
if (rhobj == null || brep == null)
  return Rhino.Commands.Result.Failure;


System.Collections.Generic.List<Rhino.Geometry.Curve> curves = new System.Collections.Generic.List<Rhino.Geometry.Curve>();
foreach (Rhino.Geometry.BrepEdge edge in brep.Edges)
  // Find only the naked edges 
  if (edge.Valence == Rhino.Geometry.EdgeAdjacency.Naked)
    Rhino.Geometry.Curve crv = edge.DuplicateCurve();
    if (null != crv)

double tol = 2.1 * doc.ModelAbsoluteTolerance;
Rhino.Geometry.Curve[] output = Rhino.Geometry.Curve.JoinCurves(curves, tol);
for (int i = 0; i < output.Length; i++)
  Guid id = doc.Objects.AddCurve(output[i]);