With .NET API, when user picks a polyline, it is fairly easy to figure out where on the polyline use picked, that is, which segment between 2 vertices is picked. However, if we code a process and ask user to select certain segment of a polyline, we need to provide good visual hint for user interaction, so that use would be able to explicitly make his/her selection. In the case of asking user to select a certain segment of polyline, there is no direct and simple API methods to use, but I thought it could achieved with a bit of coding, when I saw the question was raised in the discussion forum, and decided I would give it a try if no one would offer concrete solution later.
Now, I have found a bit time and completed some code which does what I thought is the answer to the original question. This time, let see the video showing how the code visually indicates a segment of polyline is selected first. Here is the video clip.
Here are some considerations to bear in mind:
1. Use Editor.GetEntity() for picking, with filter set to only allow polyline being selected;
2. Start handling Editor.PointMonitor event right before Editor.GetEntity() is called, so that when user moves cursor for selecting, the code would have chance to calculate which segment of the polyline the mouse cursor is hovering on (or not hover on a polyline at all); then draw transient graphics of the segment of the polyline to provide user a visual hint;
3. Use Polyline.GetSplitCurves() to obtain an non-DB residing entity to draw transient graphics and return as the segment selecting result (up to the method caller to either adding the segment entity to database, or dispose it).
Let us look at the code. First the class PartialPolylinePicker:
using System; using System.Collections.Generic; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.GraphicsInterface; using CadApp = Autodesk.AutoCAD.ApplicationServices.Application; namespace SelectPartialPolyline { public class PartialPolylinePicker : IDisposable { private Document _dwg = null; private Editor _ed = null; private ObjectId _polyId = ObjectId.Null; private TransientManager _tsMgr = TransientManager.CurrentTransientManager; private List<Drawable> _drawables = new List<Drawable>(); public void Dispose() { ClearTransientGraphics(); } public Entity PickPartialPolyline(Document dwg) { _dwg = dwg; _ed = dwg.Editor; Entity curve = null; ClearTransientGraphics(); try { _ed.PointMonitor += Editor_PointMonitor; var opt = new PromptEntityOptions( "\nSelect polyline:"); opt.SetRejectMessage("\nInvalid: not a polyline."); opt.AddAllowedClass(typeof(Autodesk.AutoCAD.DatabaseServices.Polyline), true); var res = _ed.GetEntity(opt); if (res.Status== PromptStatus.OK) { var pickPt = res.PickedPoint; var polyId = res.ObjectId;var vertexIndexes = GetVertexIndexes(pickPt, polyId); curve = GetSplitCurveAtVertex(polyId, vertexIndexes.Item1);
curve=GetPolylineSegmentCurve(pickPt, polyId);
}
}
finally
{
_ed.PointMonitor -= Editor_PointMonitor;
}
return curve;
}
private void Editor_PointMonitor(object sender, PointMonitorEventArgs e)
{
ClearTransientGraphics();
var curPt = e.Context.RawPoint;
var polyId = GetClosestPolylineAtPoint(_ed, curPt);
if (!polyId.IsNull)
{
HighlightPolyline(polyId, curPt);
}
}
#region private methods
private Curve GetPolylineSegmentCurve(
Point3d pickedPoint, ObjectId polyId, bool hightlight=false)
{
Autodesk.AutoCAD.DatabaseServices.Curve curve = null;
using (var tran = polyId.Database.TransactionManager.StartTransaction())
{
var poly = (Autodesk.AutoCAD.DatabaseServices.Polyline)tran.GetObject(polyId, OpenMode.ForRead);
var index = GetSegmentIndex(pickedPoint, poly);
curve = GetSegmentCurve(poly, index);
if (curve!=null && hightlight)
{
curve.ColorIndex = poly.ColorIndex != 2 ? 2 : 1;
}
tran.Commit();
}
return curve;
}
private int GetSegmentIndex(
Point3d pickedPosition, Autodesk.AutoCAD.DatabaseServices.Polyline poly)
{
var closestPoint = poly.GetClosestPointTo(pickedPosition, false);
var param = poly.GetParameterAtPoint(closestPoint);
return Convert.ToInt32(Math.Floor(param));
}
private Curve GetSegmentCurve(
Autodesk.AutoCAD.DatabaseServices.Polyline poly, int index)
{
Curve3d geCurve = null;
var segType = poly.GetSegmentType(index);
switch (segType)
{
case SegmentType.Line:
geCurve = poly.GetLineSegmentAt(index);
break;
case SegmentType.Arc:
geCurve=poly.GetArcSegmentAt(index);
break;
}
if (geCurve!=null)
{
return Curve.CreateFromGeCurve(geCurve);
}
else
{
return null;
}
}
private Tuple<int, int> GetVertexIndexes(Point3d pickedPosition, ObjectId polyId) { int first = 0; int second = 0; using (var tran = polyId.Database.TransactionManager.StartOpenCloseTransaction()) { var poly = (Autodesk.AutoCAD.DatabaseServices.Polyline) tran.GetObject(polyId, OpenMode.ForRead); var closestPoint = poly.GetClosestPointTo(pickedPosition, false); var len = poly.GetDistAtPoint(closestPoint); for (int i = 1; i < poly.NumberOfVertices - 1; i++) { var pt1 = poly.GetPoint3dAt(i); var l1 = poly.GetDistAtPoint(pt1); var pt2 = poly.GetPoint3dAt(i + 1); var l2 = poly.GetDistAtPoint(pt2); if (len > l1 && len < l2) { first = i; second = i + 1; break; } } tran.Commit(); } return new Tuple<int, int>(first, second); }private void ClearTransientGraphics() { if (_drawables.Count>0) { foreach (var d in _drawables) { _tsMgr.EraseTransient(d, new IntegerCollection()); d.Dispose(); } _drawables.Clear(); } } private ObjectId GetClosestPolylineAtPoint(Editor ed, Point3d position) { var returnId = ObjectId.Null; var selResult = SelectAtPickBox(ed, position); if (selResult.Status== PromptStatus.OK) { var ids = new List<ObjectId>(); foreach (ObjectId id in selResult.Value.GetObjectIds()) { if (id.ObjectClass.DxfName.ToUpper()=="LWPOLYLINE") { ids.Add(id); } } if (ids.Count > 0) { if (ids.Count == 1) { returnId = ids[0]; } else { // If the pick box hover on multiple polyline, find the one that is // closest to the pick box center returnId = FindClosestPolyline(ids, position, ed.Document.Database); } } } return returnId; } private PromptSelectionResult SelectAtPickBox(Editor ed, Point3d pickBoxCentre) { //Get pick box's size on screen System.Windows.Point screenPt = ed.PointToScreen(pickBoxCentre, 1); //Get pickbox's size. Note, the number obtained from //system variable "PICKBOX" is actually the half of //pickbox's width/height object pBox = CadApp.GetSystemVariable("PICKBOX"); int pSize = Convert.ToInt32(pBox); //Define a Point3dCollection for CrossingWindow selecting Point3dCollection points = new Point3dCollection(); System.Windows.Point p; Point3d pt; p = new System.Windows.Point(screenPt.X - pSize, screenPt.Y - pSize); pt = ed.PointToWorld(p, 1); points.Add(pt); p = new System.Windows.Point(screenPt.X + pSize, screenPt.Y - pSize); pt = ed.PointToWorld(p, 1); points.Add(pt); p = new System.Windows.Point(screenPt.X + pSize, screenPt.Y + pSize); pt = ed.PointToWorld(p, 1); points.Add(pt); p = new System.Windows.Point(screenPt.X - pSize, screenPt.Y + pSize); pt = ed.PointToWorld(p, 1); points.Add(pt ); return ed.SelectCrossingPolygon(points); } private ObjectId FindClosestPolyline(IEnumerable<ObjectId> ids, Point3d position, Database db) { ObjectId polyId = ObjectId.Null; double dist = double.MaxValue; using (var tran = db.TransactionManager.StartOpenCloseTransaction()) { foreach (var id in ids) { var poly = (Autodesk.AutoCAD.DatabaseServices.Polyline) tran.GetObject(id, OpenMode.ForRead); var pt = poly.GetClosestPointTo(position, false); var d = pt.DistanceTo(position); if (d < dist) { polyId = id; dist = d; } } tran.Commit(); } return polyId; } #endregion #region private methods: highlight polyline's segment private void HighlightPolyline(ObjectId polyId, Point3d curPt) {var vertexIndexes = GetVertexIndexes(curPt, polyId); var ent = GetSplitCurveAtVertex(polyId, vertexIndexes.Item1);
var ent=GetPolylineSegmentCurve(curPt, polyId, true);if (ent != null) { _drawables.Add(ent); _tsMgr.AddTransient(ent, TransientDrawingMode.DirectTopmost, 128, new IntegerCollection()); } }privateEntityGetSplitCurveAtVertex(ObjectIdpolyId,intvertexIndex) {Entitycurve =null;using(vartran = polyId.Database.TransactionManager.StartOpenCloseTransaction()) {varpoly = (Autodesk.AutoCAD.DatabaseServices.Polyline) tran.GetObject(polyId,OpenMode.ForRead);varvertices =newPoint3dCollection();for(inti=0; i<poly.NumberOfVertices; i++) { vertices.Add(poly.GetPoint3dAt(i)); }using(vardbObjs = poly.GetSplitCurves(vertices)) {for(inti=0; i<dbObjs.Count; i++) {if(i==vertexIndex) { curve = (Entity)dbObjs[i]; }else{ dbObjs[i].Dispose(); } } dbObjs.Clear(); }if(curve!=null) { curve.ColorIndex = poly.ColorIndex != 1 ? 1 : 2; } tran.Commit(); }returncurve; }#endregion } }
Here is the command class:
using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Runtime; using CadApp = Autodesk.AutoCAD.ApplicationServices.Application; [assembly: CommandClass(typeof(SelectPartialPolyline.MyCommand))] namespace SelectPartialPolyline { public class MyCommand { [CommandMethod("PickPoly")] public static void DoPartialPolylineSelection() { var dwg = CadApp.DocumentManager.MdiActiveDocument; var ed = dwg.Editor; try { Entity curve = null; using (var picker = new PartialPolylinePicker()) { curve = picker.PickPartialPolyline(dwg); } if (curve!=null) { ed.WriteMessage("\nPartial polyline picking suceeded.\n"); // Do whatever with the curve segment from the polyline, // which is not database residing at the moment // if it is not to be added to database, make sure to dispose it curve.Dispose(); } else { ed.WriteMessage("\nPartial polyline picking was cancelled\n"); } } catch (System.Exception ex) { ed.WriteMessage("\nError: {0}", ex.Message); } finally { Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt(); } } } }
Download the source code here.
Also, see this video clip here showing how the code works:
Update:
While this bug fix update is quite late, I did it anyway: now it works with closed polyline as expected. Actually the updated code is much simpler than the buggy old code. I do not remember what I was thinking then😅 The source code for download has also been updated. By the way, I have a later article, that was also about nearly the same topic same topic here.
Thanks for info
ReplyDeleteautocad solutions in USA
Hi sir,
ReplyDeletePlease update with closed pline!
Hi,
ReplyDeleteThank you, very good code..
Anyway as someone noticed it doesnt works well if polyline is closed..
Please can you update it?
Nice! What about using the Highlight method instead of changing color of selected object? And it is possible to maintain the selected highlighted while user continue selecting other segments? (if the user need more than one segment, but not all of them. Thanks.
ReplyDelete