For example, the user may be asked to pick a point among a few possible/known points. If these points of interest are associated with visible entities, or even are the entities themselves (DBPoint), it might be a bit easier for the user to visually locate them and choose the targeted ones. However, if the points of interest are meant to be a pair of coordinate numbers, the user would not be able to pick the point with mouse easily, the user would have to enter the coordinate manually instead of mouse picking.
Another example is like this: user is asked to select points among many known points and during the process of selecting, the user wants to be able to tell which points have already been selected. Again, if the points are associated with entities in drawing, we could simply highlight the entities. If the points are just coordinate numbers, some visual assistance would definitely be very helpful.
We could insert DBPoint with proper display style temporarily at these points to help user to locate the points and erase it later. This would be most likely way to do it, if we still had only AutoLISP and VBA to work with.
With ObjectARX/NET API, providing some kind of visual assistance to use in similar AutoCAD work process is fairly easy thing to do. I recently worked in a small project, in which user is expected to select one or more points from many known points. These points are just coordinates, not AutoCAD entities. So, I thought it would be better to let AutoCAD shows where the points are, so user can easily pick some of them based on need, or user can easily tell which points have been picked and which points are not picked.
Here are the code that visually helps user to select points.
Class MyPointHinter:
1 using System;
2 using System.Collections.Generic;
3 using Autodesk.AutoCAD.DatabaseServices;
4 using Autodesk.AutoCAD.Geometry;
5 using Autodesk.AutoCAD.ApplicationServices;
6 using Autodesk.AutoCAD.EditorInput;
7 using Autodesk.AutoCAD.GraphicsInterface;
8
9 namespace PointHinter
10 {
11 public class MyPointHinter : IDisposable
12 {
13 private Document _dwg;
14 private Editor _ed;
15 private double _screenSizePercent = 0.05;
16 private List<Circle> _visualEnts;
17 private TransientManager _manager;
18 private int _colorIndex = 2;
19 private double _diameter;
20 private bool _tooltipEnabled = false;
21
22 #region constructors
23
24 public MyPointHinter(double circleDiaPercent = 0.05,
25 int hintColorIndex = 2)
26 {
27 _dwg = Application.DocumentManager.MdiActiveDocument;
28 _ed = _dwg.Editor;
29 _manager = TransientManager.CurrentTransientManager;
30
31 _screenSizePercent = circleDiaPercent;
32 _colorIndex = hintColorIndex;
33 _diameter = GetCircleDiameter();
34
35 _visualEnts = new List<Circle>();
36 }
37
38 #endregion
39
40 #region public properties
41
42 public double CircleSizeAsScreenPercent
43 {
44 set
45 {
46 _screenSizePercent = value;
47 _diameter = GetCircleDiameter();
48 }
49 get { return _screenSizePercent; }
50 }
51
52 public int ColorIndex
53 {
54 set { _colorIndex = value; }
55 get { return _colorIndex; }
56 }
57
58 public bool EnableTooltip
59 {
60 set { _tooltipEnabled = value; }
61 get { return _tooltipEnabled; }
62 }
63
64 #endregion
65
66 #region public methods
67
68 public void Dispose()
69 {
70 ClearPoints();
71 }
72
73 public void ShowPoints(Point3d[] points, bool enableTooltip = true)
74 {
75 ClearPoints();
76 CreateVisuals(points);
77 ShowVisuals();
78 }
79
80 public void ClearPoints()
81 {
82 _manager.EraseTransients(
83 TransientDrawingMode.Highlight,
84 128, new IntegerCollection());
85
86 foreach (var ent in _visualEnts)
87 {
88 ent.Dispose();
89 }
90
91 _visualEnts.Clear();
92 }
93
94 public Point3d[] SelectPoint(
95 bool turnOffVisualAfter = true,
96 string pickingMessage = null,
97 string pickingTargetName = null)
98 {
99 ClearPoints();
100
101 List<Point3d> points = new List<Point3d>();
102
103 string msg;
104 if (string.IsNullOrEmpty(pickingMessage))
105 msg = "Select a point:";
106 else
107 msg = pickingMessage + ":";
108
109 string targetName;
110 if (string.IsNullOrEmpty(pickingTargetName))
111 targetName = "point";
112 else
113 targetName = pickingTargetName;
114
115 while (true)
116 {
117 Point3d pt;
118 if (PickPoint(msg, out pt))
119 {
120 if (pt.X != double.MinValue ||
121 pt.Y != double.MinValue ||
122 pt.Z != double.MinValue)
123 {
124 if (!PointExists(pt))
125 {
126 AddVisual(pt);
127 points.Add(pt);
128 _ed.WriteMessage(
129 "\n{0} " + targetName + "{1} selected",
130 points.Count, points.Count > 1 ? "s" : "");
131 }
132 else
133 {
134 _ed.WriteMessage(
135 "\nSelected point is duplicated.");
136 }
137 }
138 else
139 {
140 break;
141 }
142 }
143 else
144 {
145 return null;
146 }
147 }
148
149 if (turnOffVisualAfter)
150 {
151 ClearPoints();
152 }
153
154 return points.ToArray();
155 }
156
157 public Point3d[] SelectFromPoints(
158 Point3d[] knownPoints,
159 bool turnOffVisualAfter = true,
160 bool zoomToNextPoint=false,
161 string pickingMessage = null,
162 string pickingTargetName = null)
163 {
164 if (zoomToNextPoint) ZoomToPoint(knownPoints[0]);
165 ShowPoints(knownPoints);
166
167 List<Point3d> points = new List<Point3d>();
168
169 string msg;
170 if (string.IsNullOrEmpty(pickingMessage))
171 msg = "Select a point by clicking inside of a highlight circle";
172
173 else
174 msg = pickingMessage;
175
176 string targetName;
177 if (string.IsNullOrEmpty(pickingTargetName))
178 targetName = "point";
179 else
180 targetName = pickingTargetName;
181
182 while (true && _visualEnts.Count > 0)
183 {
184 Point3d pt;
185 if (PickPointFromKnownPoints(pickingMessage, out pt))
186 {
187 if (pt.X != double.MinValue ||
188 pt.Y != double.MinValue ||
189 pt.Z != double.MinValue)
190 {
191 points.Add(pt);
192 RemoveVisual(pt);
193
194 _ed.WriteMessage(
195 "\n{0} " + targetName + "{1} selected",
196 points.Count,
197 points.Count > 1 ? "s" : "");
198 }
199 else
200 {
201 break;
202 }
203 }
204 else
205 {
206 return null;
207 }
208
209 if (_visualEnts.Count > 0 && zoomToNextPoint)
210 {
211 ZoomToPoint(_visualEnts[0].Center);
212 }
213 }
214
215 if (turnOffVisualAfter)
216 {
217 ClearPoints();
218 }
219
220 return points.ToArray();
221 }
222
223 #endregion
224
225 #region private methods
226
227 private void ShowVisuals()
228 {
229 foreach (var cl in _visualEnts)
230 {
231 _manager.AddTransient(
232 cl, TransientDrawingMode.Highlight,
233 128, new IntegerCollection());
234 }
235
236 _ed.UpdateScreen();
237 }
238
239 private void AddVisual(Point3d point)
240 {
241 Circle cl = CreateVisualCircle(point);
242 _manager.AddTransient(
243 cl, TransientDrawingMode.Highlight,
244 128, new IntegerCollection());
245 _visualEnts.Add(cl);
246 _ed.UpdateScreen();
247 }
248
249 private void RemoveVisual(Point3d point)
250 {
251 foreach (var cl in _visualEnts)
252 {
253 if (IsTheSamePoint(cl.Center, point))
254 {
255 _manager.EraseTransient(
256 cl, new IntegerCollection());
257 cl.Dispose();
258 _visualEnts.Remove(cl);
259 break;
260 }
261 }
262 _ed.UpdateScreen();
263 }
264
265 private void CreateVisuals(Point3d[] points)
266 {
267 foreach (var point in points)
268 {
269 _visualEnts.Add(CreateVisualCircle(point));
270 }
271 }
272
273 private Circle CreateVisualCircle(Point3d pt)
274 {
275 Circle c = new Circle();
276 c.Center = pt;
277 c.Diameter = _diameter;
278 c.ColorIndex = _colorIndex;
279 return c;
280 }
281
282 private double GetCircleDiameter()
283 {
284 double dia = double.MaxValue;
285
286 Point2d size = GetCurrentViewSize();
287 double d = size.X * _screenSizePercent;
288 if (d < dia) dia = d;
289 d = size.Y * _screenSizePercent;
290 if (d < dia) dia = d;
291
292 return dia;
293 }
294
295 private void ZoomToPoint(Point3d point)
296 {
297 double length = 2 / _screenSizePercent;
298
299 Point3d minPt = new Point3d(point.X - length, point.Y - length, 0.0);
300 Point3d maxPt = new Point3d(point.X + length, point.Y + length, 0.0);
301 Extents3d exts = new Extents3d(minPt, maxPt);
302
303 ZoomToExtents(exts);
304 }
305
306 private void ZoomToExtents(Extents3d zoomExt)
307 {
308 using (ViewTableRecord view = _ed.GetCurrentView())
309 {
310 Matrix3d WCS2DCS =
311 Matrix3d.Rotation(-view.ViewTwist, view.ViewDirection, view.Target) *
312 Matrix3d.Displacement(view.Target - Point3d.Origin) *
313 Matrix3d.PlaneToWorld(view.ViewDirection);
314
315 zoomExt.TransformBy(WCS2DCS.Inverse());
316
317 Point2d center = new Point2d(
318 (zoomExt.MinPoint.X + zoomExt.MaxPoint.X) / 2.0,
319 (zoomExt.MinPoint.Y + zoomExt.MaxPoint.Y) / 2.0);
320
321 view.Height = (zoomExt.MaxPoint.Y - zoomExt.MinPoint.Y);
322 view.Width = (zoomExt.MaxPoint.X - zoomExt.MinPoint.X);
323 view.CenterPoint = center;
324
325 _ed.SetCurrentView(view);
326 _dwg.Database.UpdateExt(true);
327 }
328 }
329
330 private Point2d GetCurrentViewSize()
331 {
332 //Get current view height
333 double h = (double)Application.GetSystemVariable("VIEWSIZE");
334
335 //Get current view width,
336 //by calculate current view's width-height ratio
337 Point2d screen =
338 (Point2d)Application.GetSystemVariable("SCREENSIZE");
339 double w = h * (screen.X / screen.Y);
340 return new Point2d(w, h);
341 }
342
343 private bool VisualCircleExists(Point3d cCenter)
344 {
345 foreach (var cl in _visualEnts)
346 {
347 double dist = cl.Center.DistanceTo(cCenter);
348 if (dist <= Tolerance.Global.EqualPoint) return true;
349 }
350
351 return false;
352 }
353
354 #endregion
355
356 #region private methods: picking points
357
358 private bool PointExists(Point3d pt)
359 {
360 foreach (Circle cl in _visualEnts)
361 {
362 if (IsTheSamePoint(pt, cl.Center)) return false;
363 }
364
365 return false;
366 }
367
368 private bool IsTheSamePoint(Point3d pt1, Point3d pt2)
369 {
370 double dist = pt1.DistanceTo(pt2);
371 return dist <= Tolerance.Global.EqualPoint;
372 }
373
374 private bool PickPoint(
375 string pickingMessage, out Point3d point)
376 {
377 point = new Point3d(double.MinValue, double.MinValue, double.MinValue);
378 PromptPointOptions opt = new PromptPointOptions(
379 "\n" + pickingMessage);
380 opt.AllowNone = true;
381 opt.Keywords.Add("Done");
382 opt.Keywords.Default = "Done";
383 PromptPointResult res = _ed.GetPoint(opt);
384 if (res.Status == PromptStatus.OK)
385 {
386 point = res.Value;
387 return true;
388 }
389 else if (res.Status == PromptStatus.Keyword)
390 {
391 return true;
392 }
393 else
394 {
395 return false;
396 }
397 }
398
399 private bool PickPointFromKnownPoints(
400 string pickingMessage, out Point3d point)
401 {
402 point = new Point3d(double.MinValue, double.MinValue, double.MinValue);
403 while (true)
404 {
405 PromptPointOptions opt = new PromptPointOptions(
406 "\n" + pickingMessage + "(" + _visualEnts.Count + " to select):");
407 opt.AllowNone = true;
408 opt.Keywords.Add("Done");
409 opt.Keywords.Default = "Done";
410 PromptPointResult res = _ed.GetPoint(opt);
411 if (res.Status == PromptStatus.OK)
412 {
413 Point3d pt = MatchToAKnownPoint(res.Value);
414 if (pt.X == double.MinValue &&
415 pt.Y == double.MinValue &&
416 pt.X == double.MinValue)
417 {
418 _ed.WriteMessage(
419 "\nInvalid: picked point is outside a highlight circle.");
420 }
421 else
422 {
423 point = pt;
424 return true;
425 }
426 }
427 else if (res.Status == PromptStatus.Keyword)
428 {
429 return true;
430 }
431 else
432 {
433 return false;
434 }
435 }
436 }
437
438 private Point3d MatchToAKnownPoint(Point3d pt)
439 {
440 Point3d point = new Point3d(
441 double.MinValue, double.MinValue, double.MinValue);
442
443 foreach (var cl in _visualEnts)
444 {
445 double dist = pt.DistanceTo(cl.Center);
446 if (dist <= _diameter / 2.0)
447 {
448 point = cl.Center;
449 break;
450 }
451 }
452
453 return point;
454 }
455
456 #endregion
457 }
458 }
This class implements IDisposable interface, so that it can be disposed by placing it in a using{...} block to guarantee the visual hint added by the code can be erased when the code execution gets out of the using{...} block.
Here the code to use MyPointHinter class for selecting points, or selecting points from a group of known points:
1 using Autodesk.AutoCAD.ApplicationServices;
2 using Autodesk.AutoCAD.EditorInput;
3 using Autodesk.AutoCAD.Geometry;
4 using Autodesk.AutoCAD.Runtime;
5
6 [assembly: CommandClass(typeof(PointHinter.MyCommands))]
7
8 namespace PointHinter
9 {
10 public class MyCommands
11 {
12 [CommandMethod("SelectPt1")]
13 public void SelectPoint1()
14 {
15 Document dwg = Application.DocumentManager.MdiActiveDocument;
16 Editor ed = dwg.Editor;
17
18 Point3d[] selectedPoints = null;
19 using (MyPointHinter ph = new MyPointHinter())
20 {
21 selectedPoints = ph.SelectPoint(
22 true,
23 "Select ABC's location",
24 "ABC");
25 }
26
27 PromptSelectingResult(selectedPoints, ed);
28 }
29
30 [CommandMethod("SelectPt2")]
31 public void SelectPoints2()
32 {
33 Document dwg = Application.DocumentManager.MdiActiveDocument;
34 Editor ed = dwg.Editor;
35
36 Point3d[] selectedPoints = null;
37 using (MyPointHinter ph = new MyPointHinter())
38 {
39 selectedPoints = ph.SelectFromPoints(GetKnownPoints());
40 }
41
42 PromptSelectingResult(selectedPoints, ed);
43 }
44
45 private Point3d[] GetKnownPoints()
46 {
47 return new Point3d[]
48 {
49 new Point3d(0.0, 0.0, 0.0),
50 new Point3d(2.0, 0.0, 0.0),
51 new Point3d(4.0, 0.0, 0.0),
52 new Point3d(0.0, 2.0, 0.0),
53 new Point3d(2.0, 2.0, 0.0),
54 new Point3d(4.0, 2.0, 0.0),
55 new Point3d(0.0, 4.0, 0.0),
56 new Point3d(2.0, 4.0, 0.0),
57 new Point3d(4.0, 4.0, 0.0)
58 };
59 }
60
61 private void PromptSelectingResult(Point3d[] selectedPoints, Editor ed)
62 {
63 if (selectedPoints == null)
64 {
65 ed.WriteMessage("\n*Cancel*");
66 }
67 else
68 {
69 ed.WriteMessage("\n{0} object{1} selected.",
70 selectedPoints.Length, selectedPoints.Length > 1 ? "s" : "");
71 }
72
73 Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
74 }
75 }
76 }
Click here to see how the code behaves.