One great thing about having an engaged blog readership is that people catch issues quickly (which helps keep me honest :-). When I posted late on a Friday night (under self-imposed pressure to get a third post up in a four-day first week back after the holidays), I suspected I’d missed something. I even checked the protocol I expected to contain the function I knew had to be there, but obviously not thoroughly enough.
Anyway, all this to say that there’s a better, more general approach to finding whether a point is on a polyline, the subject of the last post. I knew it was there – as part of the abstract Curve protocol that is implemented by all linear geometry stored in the AutoCAD drawing database – but I needed a reminder from Owen Wengard, who sent me a direct Twitter message when a TypePad quirk stopped him from humiliating me publicly ;-) – to realise what I’d missed. Thanks for the prod, Owen!
While there is no “IsOn()” method at the Curve level, there are GetParamAtPoint() (the method Owen suggested) and GetDistAtPoint() (the one I ended up using) methods, which throw exceptions if the provided point is not on the provided curve.
Catching an exception does have performance implications – in pure ObjectARX, from C++, the equivalent AcDbCurve::getDistAtPoint() method returns an error code indicating success or failure, which is less expensive – so Owen also suggested using GetClosestPointTo(), instead.
The below implementation shows two implementations of an “IsPointOnCurve” function: IsPointOnCurveGDAP() (for GetDistAtPoint()) and IsPointOnCurveGCP() (for GetClosestPoint()). The updated POC command makes use of the latter implementation – as I expect that to be more efficient in the case of the point not being on the curve – but we’ll compare performance of the two implementations in a post later on in the week.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
namespace CurveTesting
{
public class Commands
{
[CommandMethod("POC")]
public void PointOnCurve()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
PromptEntityOptions peo =
new PromptEntityOptions("\nSelect a curve");
peo.SetRejectMessage("Please select a curve");
peo.AddAllowedClass(typeof(Curve), false);
PromptEntityResult per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
PromptPointResult ppr = ed.GetPoint("\nSelect a point");
if (ppr.Status != PromptStatus.OK)
return;
Transaction tr = db.TransactionManager.StartTransaction();
using (tr)
{
Curve curve =
tr.GetObject(per.ObjectId, OpenMode.ForRead) as Curve;
if (curve != null)
{
bool isOn = IsPointOnCurveGCP(curve, ppr.Value);
ed.WriteMessage(
"\nSelected point is {0}on the curve.",
isOn ? "" : "not "
);
}
tr.Commit();
}
}
// A generalised IsPointOnCurve function that works on all
// types of Curve (including Polylines), but catches an
// Exception on failure
private bool IsPointOnCurveGDAP(Curve cv, Point3d pt)
{
try
{
// Return true if operation succeeds
cv.GetDistAtPoint(pt);
return true;
}
catch { }
// Otherwise we return false
return false;
}
// A generalised IsPointOnCurve function that works on all
// types of Curve (including Polylines), and checks the position
// of the returned point rather than relying on catching an
// exception
private bool IsPointOnCurveGCP(Curve cv, Point3d pt)
{
try
{
// Return true if operation succeeds
Point3d p = cv.GetClosestPointTo(pt, false);
return (p - pt).Length <= Tolerance.Global.EqualPoint;
}
catch { }
// Otherwise we return false
return false;
}
}
}
You should see exactly the same behaviour as in the last post, but now the code works for all curve classes, such as Polyline, Polyline2d, Polyline3d, Line, Arc, Circle and Spline.