This question came in via a comment, last week:
Is it possible to get the point of intersection between a spline/3dpolyline and a plane surface?
It turned out to be quite an interesting problem to solve. I started with brute force methods for the various curve types…
For Polylines I checked linear segments against the plane (this is an intersection we can test), and for arc segments I broke these into linear segments and checked those, too. It all worked well with a high enough “resolution” for arc segments.
Polyline3ds only have linear segments, so I could safely use the line-plane intersection approach for these, too.
Splines were more tricky. I started by converting them to Polylines with lots of linear (no arc) segments, before processing them as Polylines. This worked well enough, but was *really* slow to get accurate results. So I went back to the drawing board… I realised that if I projected the Spline onto the plane of interest, I could then check the new, projected curve against the original: the intersection points between the two curves should be the intersections between the Spline and the plane.
This worked perfectly! And it turns out that I could do this on all curves – not just Splines – so I ended up ripping out pretty much all the code I’d written up to that point. Coding is a process, after all, and the code we’re left with is nice and clean: no need for any curve faceting, etc.
Here’s the code in action on a set of curves intersecting a planar surface. The top curve is a Polyline, the next a Spline, the third a Polyline3d and – for good measure – the last is a Polyline2d.
Here’s the C# code defining our CPI (for CurvePlaneIntersections) command and the various extension methods upon which it relies:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System;
namespace EntityIntersections
{
public static class Extensions
{
///<summary>
/// Returns an array of Point3d objects from a Point3dCollection.
///</summary>
///<returns>An array of Point3d objects.</returns>
public static Point3d[] ToArray(this Point3dCollection pts)
{
var res = new Point3d[pts.Count];
pts.CopyTo(res, 0);
return res;
}
///<summary>
/// Get the intersection points between this planar entity and a curve.
///</summary>
///<param name="cur">The curve to check intersections against.</param>
///<returns>An array of Point3d intersections.</returns>
public static Point3d[] IntersectWith(this Plane p, Curve cur)
{
var pts = new Point3dCollection();
// Get the underlying GeLib curve
var gcur = cur.GetGeCurve();
// Project this curve onto our plane
var proj = gcur.GetProjectedEntity(p, p.Normal) as Curve3d;
if (proj != null)
{
// Create a DB curve from the projected Ge curve
using (var gcur2 = Curve.CreateFromGeCurve(proj))
{
// Check where it intersects with the original curve:
// these should be our intersection points on the plane
cur.IntersectWith(
gcur2, Intersect.OnBothOperands, pts, IntPtr.Zero, IntPtr.Zero
);
}
}
return pts.ToArray();
}
///<summary>
/// Test whether a point is on this curve.
///</summary>
///<param name="pt">The point to check against this curve.</param>
///<returns>Boolean indicating whether the point is on the curve.</returns>
public static bool IsOn(this Curve cv, Point3d pt)
{
try
{
// Return true if operation succeeds
var p = cv.GetClosestPointTo(pt, false);
return (p - pt).Length <= Tolerance.Global.EqualPoint;
}
catch { }
// Otherwise we return false
return false;
}
}
public class Commands
{
[CommandMethod("CPI")]
public void CurvePlaneIntersection()
{
var doc = Application.DocumentManager.MdiActiveDocument;
if (null == doc)
return;
var db = doc.Database;
var ed = doc.Editor;
// Ask the user to select a curve
var peo = new PromptEntityOptions("\nSelect a curve");
peo.SetRejectMessage("Must be a curve.");
peo.AddAllowedClass(typeof(Curve), false);
var per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
var curId = per.ObjectId;
// And a PlaneSurface to check intersections against
var peo2 = new PromptEntityOptions("\nSelect plane surface");
peo.SetRejectMessage("Must be a planar surface.");
peo.AddAllowedClass(typeof(PlaneSurface), false);
var per2 = ed.GetEntity(peo2);
if (per2.Status != PromptStatus.OK)
return;
var planeId = per2.ObjectId;
using (var tr = doc.TransactionManager.StartTransaction())
{
// Start by opening the plane
var plane = tr.GetObject(planeId, OpenMode.ForRead) as PlaneSurface;
if (plane != null)
{
// Get the PlaneSurface's defining GeLib plane
var p = plane.GetPlane();
// Open our curve...
var cur = tr.GetObject(curId, OpenMode.ForRead) as Curve;
if (cur != null) // Should never fail
{
var pts = p.IntersectWith(cur);
// If we have results we'll place them in modelspace
var ms =
(BlockTableRecord)tr.GetObject(
SymbolUtilityServices.GetBlockModelSpaceId(db),
OpenMode.ForWrite
);
foreach (Point3d pt in pts)
{
// Create a point in the drawing for each intersection
var dbp = new DBPoint(pt);
dbp.ColorIndex = 2; // Make them yellow
ms.AppendEntity(dbp);
tr.AddNewlyCreatedDBObject(dbp, true);
// Quick test to make sure each point lies on our source curve
ed.WriteMessage(
"\nPoint is {0} curve.", cur.IsOn(pt) ? "on" : "off"
);
}
}
}
tr.Commit();
}
}
}
}