Well the following seems to work.
There's not much code but it took a long time to get the timing right.
When I found the ecs z axis gives the normal I thought I had saved checking each object's type but for some odd reason an ellipse and a spline follow different rules and always show an ecs Z of 0,0,1. Odd.This works for xrefs as well.
The sequense for picking an entity within a block is:
Derive the wcs picked point
Find the entity's normal
Multiply the blocks matricies of each nested block refs and transform the picked point to the primary block definition's plane.
Transform the viewdirection to the match the primary block definition's plane.
Get the point along the translated view direction on the picked entity's plane.
Use GetClosestPointTo to get the desired point on the picked entity.
Translate that point back to world.
Bob's your uncle.
static public Point3d PickedPtToPlane(Point3d p, Vector3d v, Vector3d n, Editor ed, Point3d ptonplane)
{
double Elev = ElevationFromPt(ptonplane, n);
double Dist=(Elev-(p.X*n.X)-(p.Y*n.Y)-(p.Z*n.Z))/((v.X*n.X)+(v.Y*n.Y)+(v.Z*n.Z));
return new Point3d(p.X+Dist*v.X,p.Y+Dist*v.Y,p.Z+Dist*v.Z);
}
static public double ElevationFromPt(Point3d pt ,Vector3d N )
//Ax+ By + Cz + d = 0 formula for a plane where d=-LWPolyline.Elevation
{
return (pt.X * N.X) + (pt.Y * N.Y) + (pt.Z * N.Z);
}
[CommandMethod("ne")]
public void TestNearestPointOnObject()
{
Point3d p;
if (!NearestPointOnObject(ref p)) return;
DBPoint Pt = new DBPoint(p);
Document doc = acadApp.DocumentManager.MdiActiveDocument;
using (Transaction tr = doc.TransactionManager.StartTransaction())
{
Database db = HostApplicationServices.WorkingDatabase;
BlockTableRecord btr = tr.GetObject
(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord;
if (btr != null)
{
Pt.SetDatabaseDefaults(db);
btr.AppendEntity(Pt);
tr.AddNewlyCreatedDBObject(Pt, true);
tr.Commit();
}
}
}
public bool NearestPointOnObject(ref Point3d P2)
{
Document doc = acadApp.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
BlockReference b; Ellipse ellipse; Spline spline;
Matrix3d ucs = ed.CurrentUserCoordinateSystem;
PromptNestedEntityResult pne=ed.GetNestedEntity("\nPick ent");
if (pne.Status!=PromptStatus.OK) return false;
Point3d P1 = pne.PickedPoint.TransformBy(ucs);//wcs point
ObjectId id = pne.ObjectId;
ObjectId[] Cons = pne.GetContainers();
Matrix3d m=Matrix3d.Identity;
Vector3d normal;
Vector3d v = ed.GetCurrentView().ViewDirection.GetNormal();
using (Transaction tr = doc.TransactionManager.StartTransaction())
{
Entity ent = tr.GetObject( id,OpenMode.ForRead) as Entity;
if ( !(ent is Curve)) return false;
Curve curve = (Curve)ent;
normal = ent.Ecs.CoordinateSystem3d.Zaxis;
if (ent is Ellipse)
{
ellipse = (Ellipse)ent;
normal = ellipse.Normal;
}
if (ent is Spline)
{
spline = (Spline)ent;
Plane pn=spline.GetPlane();
normal = pn.Normal;
}
//normal = curve.Ecs.CoordinateSystem3d.Zaxis;
if (Cons.Length > 0) //owner is a blockref
{
for (int i = 0; i < Cons.Length; i++)
{
b = tr.GetObject(Cons[i], OpenMode.ForRead) as BlockReference;
m = m.PreMultiplyBy(b.BlockTransform);
}
//transform the viewdirection to the match the most nested block
//(primary block)
v=v.TransformBy(m.Inverse()).GetNormal();
}
//transform the picked point to the primary block definition's plane
P1 = P1.TransformBy(m.Inverse());
//Get the point along the translated view direction on the
//picked entity's plane
P1 = PickedPtToPlane(P1,v,normal, ed, curve.StartPoint);
P2 = curve.GetClosestPointTo(P1,false);
P2 = P2.TransformBy(m);
tr.Commit();
return true;
}
}