Background
This article is inspired by a question asked in Autodesk .NET API discussion forum. The OP expect to use a JIG as visual aid for user to select a point on a Polyline so that the Polyline could be split into 2. Obviously, selecting a point on Polyline itself is really simple, especially if OSNAP is enabled. But to select a point accurately that makes business sense, it would be better for the command to provide sufficient visual hints about where the expected point should be and make the accurate pick easy. In the case of the posted question, the OP wants to show the length of potentially split 2 segments where user moves the mouse to pick point.
While this surely can be done with custom Jig class (likely derived from DrawJig), I thought it would be as simple as wrap the Editor.GetPoint() in between with add/removing Editor.PointMonitor event handler. After all, the nature of the operation is to get an accurate split point on the Polyline. Once an expected split point is obtained, use would not have to worry how the Polyline gets split.
Note: the OP not only wants to split the Polyline into 2 segments, but also makes the 2 segments overlap to each other in a given range. However, for simplicity, in this article, I ignore the "overlap" requirement and only focus on how to make helpful, user-friendly visual aid while user is trying to select the split point.
Design of The Code
Firstly, I make some assumptions to make the code sample simple:
- the entity to be split is a Line
- user intends to select the split point based on the center point of the Line
- the desired/possible split point would be apart from center point with given gap incrementally
- Ask user to select the target Line
- Once a Line entity is selected, the center point of the Line and potential split points along the Line, before and after the center point is visually shown
- While user moved the mouse, one the potential split point that is closest to the mouse cursor would be visually prompted
- When the user clicks the mouse, the mouse cursor does not need to be accurately at the prompted split point, thus the point selecting is really user-friendly
using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.GraphicsInterface; using System; using System.Collections.Generic; using System.Linq; namespace DragToSplitLine { public class LineSplitter : IDisposable { private Document _dwg; private Editor _ed; private ObjectId _lineId = ObjectId.Null; private Line _line1 = null; private Line _line2 = null; private List<DBPoint> _incrementPoints = null; private DBText _text1 = null; private DBText _text2 = null; private Point3d _splitPoint = Point3d.Origin; private double _increment = 0.0; private double _textHeight = 1.0; private TransientManager _tsManager = TransientManager.CurrentTransientManager; public LineSplitter(Document dwg, ObjectId lineId) { _dwg= dwg; _ed = _dwg.Editor; _lineId= lineId; } public void Dispose() { ClearTransientDrawables(); } public Point3d? DragSplitter() { var ok = false; var originalPdMode = Convert.ToInt32(Application.GetSystemVariable("PDMODE")); var originalPdSize = Convert.ToDouble(Application.GetSystemVariable("PDSIZE")); try { // Set DBPoint display mode Application.SetSystemVariable("PDMODE", 2); Application.SetSystemVariable("PDSIZE", -1.0); using (var tran = _dwg.TransactionManager.StartTransaction()) { var line = (Line)tran.GetObject(_lineId, OpenMode.ForRead); // Get initial increment, which could be any value _increment = (Math.Floor(line.Length / 10) * 10) / 20; _textHeight = line.Length / 100; CreateTransientDrawables(line); do { var changeIncrement = false; try { AddTransientDrawables(); _ed.PointMonitor += Editor_PointMonitor; var opt = new PromptPointOptions( "\nSelect split point on selected line, or change increment:"); opt.AllowNone = true; opt.Keywords.Add("Increment"); opt.Keywords.Default = "Increment"; var res = _ed.GetPoint(opt); if (res.Status == PromptStatus.OK) { ok = true; break; } else if (res.Status == PromptStatus.Keyword) { changeIncrement = true; } else { break; } } finally { ClearTransientDrawables(); _ed.PointMonitor -= Editor_PointMonitor; } if (changeIncrement) { if (ChangeIncrement(out double inc)) { _increment = inc; ClearTransientDrawables(); CreateTransientDrawables(line); } else { break; } } } while (true); tran.Commit(); } } finally { // Restore DBPoint diaplay mode Application.SetSystemVariable("PDMODE", originalPdMode); Application.SetSystemVariable("PDSIZE", originalPdSize); _ed.UpdateScreen(); } if (ok) { return _splitPoint; } else { return null; } } #region private methods private void Editor_PointMonitor(object sender, PointMonitorEventArgs e) { _splitPoint = GetSplitPoint(e.Context.RawPoint); SetLines(); SetLengthTexts(); UpdateTransientDrawables(); } private void CreateTransientDrawables(Line line) { _incrementPoints = new List<DBPoint>(); // Get center point var pt = line.GetPointAtDist(line.Length / 2.0); var dbPt = new DBPoint(pt); dbPt.ColorIndex = 1; _incrementPoints.Add(dbPt); _splitPoint = pt; // Add DBPoints on first half of the line var l = line.Length / 2.0 - _increment; while (l > 0) { pt = line.GetPointAtDist(l); dbPt = new DBPoint(pt); dbPt.ColorIndex = 2; _incrementPoints.Insert(0, dbPt); l -= _increment; } // Add DBPoint on second half of the line l = line.Length / 2.0 + _increment; while (l < line.Length) { pt = line.GetPointAtDist(l); dbPt = new DBPoint(pt); dbPt.ColorIndex = 2; _incrementPoints.Add(dbPt); l += _increment; } // add 2 lines _line1 = new Line(line.StartPoint, _splitPoint); _line1.ColorIndex = 5; _line2=new Line(line.EndPoint, _splitPoint); _line2.ColorIndex = 6; // add 2 DBTexts _text1 = CreateLineLengthText(_line1); _text2 = CreateLineLengthText(_line2); } private DBText CreateLineLengthText(Line line) { var txt = new DBText(); txt.TextString = line.Length.ToString("######0.0"); txt.ColorIndex = 7; txt.Height = _textHeight; txt.Rotation = line.Angle; txt.Position = line.GetPointAtDist(line.Length / 2.0); txt.Justify = AttachmentPoint.BottomCenter; txt.AdjustAlignment(_dwg.Database); if (txt.Rotation >= Math.PI && txt.Rotation < Math.PI * 2.0) { txt.TransformBy( Matrix3d.Rotation(Math.PI, Vector3d.ZAxis, txt.AlignmentPoint)); } return txt; } private Point3d GetSplitPoint(Point3d movePoint) { // Reset DBPoints' color for (int i=0; i<_incrementPoints.Count; i++) { var pt= _incrementPoints[i]; if (i == (_incrementPoints.Count - 1) / 2) { pt.ColorIndex = 1; } else { pt.ColorIndex = 2; } } var dbPt = (from p in _incrementPoints orderby p.Position.DistanceTo(movePoint) ascending select p).First(); dbPt.ColorIndex = 4; return dbPt.Position; } private void SetLines() { _line1.EndPoint = _splitPoint; _line2.EndPoint = _splitPoint; } private void SetLengthTexts() { _text1.TextString= _line1.Length.ToString("######0.0"); _text1.Position = _line1.GetPointAtDist(_line1.Length / 2.0); _text2.TextString = _line2.Length.ToString("######0.0"); _text2.Position = _line2.GetPointAtDist(_line2.Length / 2.0); } private void AddTransientDrawables() { foreach (var dbPt in _incrementPoints) { _tsManager.AddTransient( dbPt, TransientDrawingMode.DirectTopmost, 128, new IntegerCollection()); } _tsManager.AddTransient( _line1, TransientDrawingMode.DirectShortTerm, 128, new IntegerCollection()); _tsManager.AddTransient( _line2, TransientDrawingMode.DirectShortTerm, 128, new IntegerCollection()); _tsManager.AddTransient( _text1, TransientDrawingMode.DirectShortTerm, 128, new IntegerCollection()); _tsManager.AddTransient( _text2, TransientDrawingMode.DirectShortTerm, 128, new IntegerCollection()); } private void UpdateTransientDrawables() { foreach (var dbPt in _incrementPoints) { _tsManager.UpdateTransient(dbPt, new IntegerCollection()); } _tsManager.UpdateTransient(_line1, new IntegerCollection()); _tsManager.UpdateTransient(_line2, new IntegerCollection()); _tsManager.UpdateTransient(_text1, new IntegerCollection()); _tsManager.UpdateTransient(_text2, new IntegerCollection()); } private void ClearTransientDrawables() { foreach (var dbPt in _incrementPoints) { if (!dbPt.IsDisposed) { _tsManager.EraseTransient(dbPt, new IntegerCollection()); dbPt.Dispose(); } } _incrementPoints.Clear(); if (_line1 != null && !_line1.IsDisposed) { _tsManager.EraseTransient(_line1, new IntegerCollection()); _line1.Dispose(); } if (_line2 != null && !_line2.IsDisposed) { _tsManager.EraseTransient(_line2, new IntegerCollection()); _line2.Dispose(); } if (_text1 != null && !_text1.IsDisposed) { _tsManager.EraseTransient(_text1, new IntegerCollection()); _text1.Dispose(); } if (_text2 != null && !_text2.IsDisposed) { _tsManager.EraseTransient(_text2, new IntegerCollection()); _text2.Dispose(); } } private bool ChangeIncrement(out double increment) { increment = _increment; var opt = new PromptDoubleOptions( "\nEnter increment value:"); opt.AllowZero = false; opt.AllowNegative = false; opt.DefaultValue = _increment; var res=_ed.GetDouble(opt); if (res.Status== PromptStatus.OK) { increment = res.Value; return true; } else { return false; } } #endregion } }
using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.Runtime; using CadApp = Autodesk.AutoCAD.ApplicationServices.Application; [assembly: CommandClass(typeof(DragToSplitLine.MyCommands))] namespace DragToSplitLine { public class MyCommands { [CommandMethod("SplitLine")] public static void RunCommand() { var dwg = CadApp.DocumentManager.MdiActiveDocument; var ed = dwg.Editor; var opt = new PromptEntityOptions("\nSelect a line:"); opt.SetRejectMessage("\nInvalid selection: must be a line."); opt.AddAllowedClass(typeof(Line), true); var res = ed.GetEntity(opt); if (res.Status!= PromptStatus.OK) { ed.WriteMessage("\n*Cancel*\n"); return; } Point3d? splitPoint = null; using (var splitter = new LineSplitter(dwg, res.ObjectId)) { splitPoint = splitter.DragSplitter(); } if (splitPoint.HasValue) { // Do whatever is needed to the selected line ed.WriteMessage($"\nSplit Point: {splitPoint.Value}\n"); } else { ed.WriteMessage("\n*Cancel*\n"); } } } }
The Video Clip Showing The Code Running Effect:
Point of Interests
- As I mentioned the article is only meant to show the techniques to adding visual aids for the simply task of selecting point, thus I choose to target a Line entity for simplicity. It can be easily enhanced to target other types of entity (Arc, Polyline, or in general Curve). One only needs to show the potential split points in the same way showed here, and then use Curve.GetSplitCurves() to generate 2 visual curves
- The length DBText entities could have been displayed more appropriately by moving it above the split visual segments
1 comment:
Amazing work, Norman!
This is undoubtedly the finest resource I've found for C# code examples for AutoCAD.
Whenever I can, I make sure to browse through here.
I've become quite intrigued to see how this particular code would behave with polylines and curves.
Congratulations! I greatly admire your work.
Post a Comment