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(); } }
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.
ReplyDeleteThanks a lot.
Thank you for pointing out my miss of the code. Corrected it.
ReplyDelete