As predicted in the first post in this series, today's post looks at a slightly more robust and user-friendly approach for determining the point on a surface found by firing a ray normal to a selected X,Y point in the plane of the current UCS.
Here's the updated C# code that now includes an additional command called POXY (that's a shortened form of PointOnXY, but will hopefully bring a smile to the lips of British readers):
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using AcDb = Autodesk.AutoCAD.DatabaseServices;
namespace SurfaceIntersection
{
public class Commands
{
[CommandMethod("SURFRAY")]
public void SurfaceRay()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
// Ask the user to pick a surface
var peo = new PromptEntityOptions("\nSelect a surface");
peo.SetRejectMessage("\nMust be a surface.");
peo.AddAllowedClass(typeof(AcDb.Surface), false);
var per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
var sf =
new SelectionFilter(
new TypedValue[] {
new TypedValue((int)DxfCode.Start, "LINE")
}
);
// Ask the user to select some lines
var pso = new PromptSelectionOptions();
pso.MessageForAdding = "Select lines";
pso.MessageForRemoval = "Remove lines";
var psr = ed.GetSelection(pso, sf);
if (psr.Status == PromptStatus.OK && psr.Value.Count > 0)
{
using (var tr = db.TransactionManager.StartTransaction())
{
// We'll start by getting the block table and modelspace
// (which are really only needed for results we'll add to
// the database)
var bt =
(BlockTable)tr.GetObject(
db.BlockTableId, OpenMode.ForRead
);
var btr =
(BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite
);
// Next we'll get our surface object
var obj = tr.GetObject(per.ObjectId, OpenMode.ForRead);
var surf = obj as AcDb.Surface;
if (surf == null)
{
// Should never happen, but anyway
ed.WriteMessage("\nFirst object must be a surface.");
}
else
{
DoubleCollection heights;
SubentityId[] ids;
// Fire ray for each selected line
foreach (var id in psr.Value.GetObjectIds())
{
var ln = tr.GetObject(id, OpenMode.ForRead) as Line;
if (ln != null)
{
surf.RayTest(
ln.StartPoint,
ln.StartPoint.GetVectorTo(ln.EndPoint),
0.01,
out ids,
out heights
);
if (ids.Length == 0)
{
ed.WriteMessage("\nNo intersections found.");
}
else
{
// Highlight each subentity and add point
// at intersection
for (int i = 0; i < ids.Length; i++)
{
var subEntityPath =
new FullSubentityPath(
new ObjectId[] { per.ObjectId },
ids[i]
);
// Highlight the sub entity
surf.Highlight(subEntityPath, true);
// Create a point at the line-surface
// intersection
var pt =
new DBPoint(
new Point3d(
ln.StartPoint.X,
ln.StartPoint.Y,
heights[i]
)
);
// Add the new object to the block table
// record and the transaction
btr.AppendEntity(pt);
tr.AddNewlyCreatedDBObject(pt, true);
}
}
}
}
}
tr.Commit();
}
}
}
[CommandMethod("POXY")]
public void PointOnXY()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
// Ask the user to pick a surface
var peo = new PromptEntityOptions("\nSelect a surface");
peo.SetRejectMessage("\nMust be a surface.");
peo.AddAllowedClass(typeof(AcDb.Surface), false);
var per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
using (var tr = db.TransactionManager.StartTransaction())
{
// We'll start by getting the block table and modelspace
// (which are really only needed for results we'll add to
// the database)
var bt =
(BlockTable)tr.GetObject(
db.BlockTableId, OpenMode.ForRead
);
var btr =
(BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite
);
var obj = tr.GetObject(per.ObjectId, OpenMode.ForRead);
var surf = obj as AcDb.Surface;
if (surf == null)
{
ed.WriteMessage("\nFirst object must be a surface.");
}
else
{
DoubleCollection heights;
SubentityId[] ids;
// Fire ray for each selected line
var ucs = ed.CurrentUserCoordinateSystem;
PromptPointResult ppr;
do
{
ppr = ed.GetPoint("\nSelect point beneath surface");
if (ppr.Status != PromptStatus.OK)
{
break;
}
// Our selected point needs to be transformed to WCS
// from the current UCS
var start = ppr.Value.TransformBy(ucs);
// Fire a ray from the selected point in the direction
// of the UCS' Z-axis
surf.RayTest(
start,
ucs.CoordinateSystem3d.Zaxis,
0.01,
out ids,
out heights
);
if (ids.Length == 0)
{
ed.WriteMessage("\nNo intersections found.");
}
else
{
// Add point at each intersection found
for (int i = 0; i < ids.Length; i++)
{
// Create a point at the intersection
var end =
start +
new Vector3d(0, 0, heights[i]).TransformBy(ucs);
var pt = new DBPoint(end);
var ln = new Line(start, end);
// Add the new objects to the block table
// record and the transaction
btr.AppendEntity(pt);
tr.AddNewlyCreatedDBObject(pt, true);
btr.AppendEntity(ln);
tr.AddNewlyCreatedDBObject(ln, true);
}
}
}
while (ppr.Status == PromptStatus.OK);
}
tr.Commit();
}
}
}
}
Here's what happens when we run the POXY command, select a surface and start selecting points beneath it:

Leave a Reply to Kean Walmsley Cancel reply