Sunday, November 29, 2020

Find Line's Intersection Points With Block (BlockReference)

 I recently replied a question posted in AutoCAD .NET discussion forum, in which I proposed a code workflow. Then another question in similar context came up. So I thought I might as well write some code to demonstrate my idea in the reply to the first question, which would indirectly answer the second question: after all, once the intersection points of line and the block reference are known, trimming the line would be simple next step.

Firstly to simplify the case, I limit the discussion only on Line and BlockReference with nested entities being Curve type only.

As we know all classes derived from Entity have overloaded method IntersectWith(), which can be used to find intersection points between 2 entities. BlockReference, being derived from Entity, inherently also has its IntersectWith() method. However, because BlockReference is a reference of a composite object with many different entities nested, its IntersectWtith() method is implemented in its own way: it uses its bounding box (GeometricExtents) as its boundary to calculate its intersection points with other entity. Following code demonstrate this:

#region Command to find entity's intersecting point with block: block's bounding box
 
[CommandMethod("BlkIntersect1")]
public static void TestBlockIntersection1()
{
    var dwg = CadApp.DocumentManager.MdiActiveDocument;
    var ed = dwg.Editor;
 
    var res = ed.GetEntity("\nSelect block:");
    if (res.Status == PromptStatus.OK)
    {
        if (res.ObjectId.ObjectClass.DxfName.ToUpper() != "INSERT")
        {
            ed.WriteMessage("\nNot a block!");
            return;
        }
 
        dwg.Database.Pdmode = 34;
 
        try
        {
            CadHelper.Highlight(res.ObjectId, true);
 
            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                var blk = (BlockReference)tran.GetObject(
                    res.ObjectId, OpenMode.ForRead);
                var space = (BlockTableRecord)tran.GetObject(
                    dwg.Database.CurrentSpaceId, OpenMode.ForWrite);
 
                // create a rectangle polyline to show the block's GeometricExtents
                var boundBox = CreateBoundBox(blk);
                space.AppendEntity(boundBox);
                tran.AddNewlyCreatedDBObject(boundBox, true);
                tran.TransactionManager.QueueForGraphicsFlush();
 
                while (true)
                {
                    var eRes = ed.GetEntity(
                        "\nSelect an entity intersecting the block:");
                    if (eRes.Status == PromptStatus.OK)
                    {
                        FindBlockIntersectionPoint(eRes.ObjectId, blk, space, tran);
                    }
                    else
                    {
                        break;
                    }
                }
 
                tran.Commit();
            }
        }
        finally
        {
            CadHelper.Highlight(res.ObjectId, false);
        }
    }
}
 
private static void FindBlockIntersectionPoint(
    ObjectId entId, BlockReference blk, BlockTableRecord space, Transaction tran)
{
 
    var ent = (Entity)tran.GetObject(entId, OpenMode.ForRead);
 
    var pts = new Point3dCollection();
    blk.IntersectWith(ent, Intersect.OnBothOperands, pts, IntPtr.Zero, IntPtr.Zero);
 
    if (pts.Count > 0)
    {
        foreach (Point3d pt in pts)
        {
            var dbPt = new DBPoint(pt);
            space.AppendEntity(dbPt);
            tran.AddNewlyCreatedDBObject(dbPt, true);
            tran.TransactionManager.QueueForGraphicsFlush();
        }
    }
 
}
 
private static Polyline CreateBoundBox(BlockReference blk)
{
    var ext = blk.GeometricExtents;
    var poly = new Polyline(4);
    poly.AddVertexAt(
        0, new Point2d(ext.MinPoint.X, ext.MinPoint.Y),
        0.0, 0.0, 0.0);
    poly.AddVertexAt(
        0, new Point2d(ext.MinPoint.X, ext.MaxPoint.Y),
        0.0, 0.0, 0.0);
    poly.AddVertexAt(
        0, new Point2d(ext.MaxPoint.X, ext.MaxPoint.Y),
        0.0, 0.0, 0.0);
    poly.AddVertexAt(
        0, new Point2d(ext.MaxPoint.X, ext.MinPoint.Y),
        0.0, 0.0, 0.0);
    poly.Closed = true;
    poly.ColorIndex = 2;
    return poly;
}
 
#endregion

The code draws a rectangle as the block's bounding box and draws point at intersecting point of a line and the block reference. As following video shows the intersection point is at the location where the line and the bounding box intersect to each other.


Now here is the code I proposed to the first and/or second question: find actual intersection point of a line to the block's "real" boundary (outmost entity nested in the block).

#region command to find entity's intersecting point: block's real boundary
 
[CommandMethod("BlkIntersect2")]
public static void TestBlockIntersection2()
{
    var dwg = CadApp.DocumentManager.MdiActiveDocument;
    var ed = dwg.Editor;
 
    var res = ed.GetEntity("\nSelect block:");
    if (res.Status == PromptStatus.OK)
    {
        if (res.ObjectId.ObjectClass.DxfName.ToUpper() != "INSERT")
        {
            ed.WriteMessage("\nNot a block!");
            return;
        }
 
        dwg.Database.Pdmode = 34;
 
        try
        {
            CadHelper.Highlight(res.ObjectId, true);
 
            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                var blk = (BlockReference)tran.GetObject(
                    res.ObjectId, OpenMode.ForRead);
                var space = (BlockTableRecord)tran.GetObject(
                    dwg.Database.CurrentSpaceId, OpenMode.ForWrite);
 
                while (true)
                {
                    var opt = new PromptEntityOptions(
                        "\nSelect a line intersecting with the block:");
                    opt.SetRejectMessage("\nInvalid: not a line!");
                    opt.AddAllowedClass(typeof(Line), true);
                    var eRes = ed.GetEntity(opt);
                    if (eRes.Status == PromptStatus.OK)
                    {
                        GetBlockIntersectionPoints(
                            eRes.ObjectId, blk, space, tran);
                    }
                    else
                    {
                        break;
                    }
                }
 
                tran.Commit();
            }
        }
        finally
        {
            CadHelper.Highlight(res.ObjectId, false);
        }
    }
}
 
private static void GetBlockIntersectionPoints(
    ObjectId entId, BlockReference blk, BlockTableRecord space, Transaction tran)
{
    var line = (Line)tran.GetObject(entId, OpenMode.ForRead);
    if (FindOutmostIntersectingPoints(line, blk, out Point3d pt1, out Point3d pt2))
    {
        var dbPt = new DBPoint(pt1);
        space.AppendEntity(dbPt);
        tran.AddNewlyCreatedDBObject(dbPt, true);
        tran.TransactionManager.QueueForGraphicsFlush();
 
        dbPt = new DBPoint(pt2);
        space.AppendEntity(dbPt);
        tran.AddNewlyCreatedDBObject(dbPt, true);
        tran.TransactionManager.QueueForGraphicsFlush();
    }
}
 
private static bool FindOutmostIntersectingPoints(
    Line line, BlockReference blk, out Point3d pt1, out Point3d pt2)
{
    pt1 = Point3d.Origin;
    pt2 = Point3d.Origin;
 
    var points = GetAllIntersectingPoints(line, blk);
    if (points.Count>0)
    {
        pt1 = (from p in points
                orderby p.DistanceTo(line.StartPoint)
                select p).First();
 
        pt2 = (from p in points
                orderby p.DistanceTo(line.EndPoint)
                select p).First();
 
        return true;
    }
    else
    {
        return false;
    }
}
 
private static List<Point3d> GetAllIntersectingPoints(
    Line line, BlockReference blk)
{
    var points = new List<Point3d>();
 
    using (var ents = new DBObjectCollection())
    {
        blk.Explode(ents);
        foreach (DBObject o in ents)
        {
            var ent = (Entity)o;
            var pts = new Point3dCollection();
            line.IntersectWith(
                ent, Intersect.OnBothOperands, pts, IntPtr.Zero, IntPtr.Zero);
            foreach(Point3d p in pts)
            {
                points.Add(p);
            }
            o.Dispose();
        }
    }
 
    return points;
}
 
#endregion

The video below show the code is able to find the 2 intersection points of a line that pass through a block reference, which fall on the block's outmost entity or entities. Obviously this indirectly answers the second question: with these 2 points, the line could ne trimmed easily.


As aforementioned, I limit the code to only apply to Line and block with only Curve type as nested entities. Extra considerations are needed in other cases, such as:

1. If a nested entity in block is a BlockReference, Text/MText/AttributeReference, then the intersection point is on their bounding box. Recursive exploding BlockReference might be needed.

2. If start or end point, or both the intersecting Line/Curve locate inside the block, it might be quite difficult to determine the start/end point is only inside the block's bounding box but outside the outmost entities, or not.

3. If the intersecting entity with block is not a Line, it could intersect with the block more than 2 times.

So writing code to cover all possible cases would be quite some work, if it is possible at all.

Update

As the comment pointed out, I forgot to post the code of Highlight(ObjectId, bool). Here is the code:

public static void Highlight(ObjectId entId, bool highlight)
{
    using (var tran = entId.Database.TransactionManager.StartOpenCloseTransaction())
    {
        var ent = (Entity)tran.GetObject(entId, OpenMode.ForRead);
        if (highlight)
        {
            ent.Highlight();
        }
        else
        {
            ent.Unhighlight();
        }
        tran.Commit();
    }
}

2 comments:

Ahmed Shawky said...

Very useful post but I think that you forgot to mention the static method Highlight that you are using but it can be easily detected.
Thanks a lot.

Norman Yuan said...

Thank you for pointing out my miss of the code. Corrected it.

Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.