I came across an interesting request from a user, who wants some thing like the 2 commands. But after selecting the curve to be measured/divided, the user want to able to see the points as the result of measuring/dividing, or the mouse cursor can snap to these points when hovering the curve, then the user can decide what to do at the point the cursor is snapped to.
So, I decided to write some code to provide the convenience to AutoCAD users. Upon a little bit study and try-out coding, I came to 2 solutions that can do the same thing:
- Using OsnapOverrule
- Using CustomObjectSnapMode in conjunction with Glyth
Here is the code that derives from Autodesk.AutoCAD.DatabaseServices.OsnapOverrule:
1 using System;
2 using Autodesk.AutoCAD.DatabaseServices;
3 using Autodesk.AutoCAD.Geometry;
4 using Autodesk.AutoCAD.Runtime;
5
6 namespace MeasureOsnap
7 {
8 public class MeasureOsnapOverrule : OsnapOverrule
9 {
10 private enum MeasureOsnapType
11 {
12 Measure = 0,
13 Divide = 1,
14 }
15
16 private static MeasureOsnapOverrule _instance = null;
17 private bool _overruling;
18 private bool _started = false;
19 private int _segmentNumber = 1;
20 private double _segmentLength = 0.0;
21 private MeasureOsnapType _snapType = MeasureOsnapType.Measure;
22
23 private ObjectId _entId = ObjectId.Null;
24
25 public MeasureOsnapOverrule()
26 {
27 _overruling = Overrule.Overruling;
28 }
29
30 public static MeasureOsnapOverrule Instance
31 {
32 get
33 {
34 if (_instance == null)
35 {
36 _instance = new MeasureOsnapOverrule();
37 }
38 return _instance;
39 }
40 }
41
42 public void StartDivideSnap(ObjectId entId,
43 int segmentNumber)
44 {
45 if (_started) return;
46
47 _segmentNumber = segmentNumber;
48 _snapType = MeasureOsnapType.Divide;
49
50 StartSnap(entId);
51 }
52
53 public void StartMeasureSnap(ObjectId entId,
54 double segmentLength)
55 {
56 if (_started) return;
57
58 _segmentLength = segmentLength;
59 _snapType = MeasureOsnapType.Measure;
60
61 StartSnap(entId);
62 }
63
64 public void StopSnap()
65 {
66 if (!_started) return;
67
68 Type t = GetEntityType();
69 Overrule.RemoveOverrule(RXClass.GetClass(t), this);
70 Overrule.Overruling = _overruling;
71
72 _started = false;
73 _entId = ObjectId.Null;
74 }
75
76 #region Override base class mathods
77
78 public override void GetObjectSnapPoints(
79 Entity entity, ObjectSnapModes snapMode, IntPtr gsSelectionMark,
80 Point3d pickPoint, Point3d lastPoint, Matrix3d viewTransform,
81 Point3dCollection snapPoints, IntegerCollection geometryIds)
82 {
83 Curve curve = entity as Curve;
84
85 snapPoints.Clear();
86 snapMode = ObjectSnapModes.ModeNear;
87
88 //Add snap point at start point
89 snapPoints.Add(curve.StartPoint);
90
91 if (_snapType == MeasureOsnapType.Measure)
92 {
93 if (_segmentLength<=0.0) return;
94 }
95
96 if (_snapType == MeasureOsnapType.Divide)
97 {
98 if (_segmentNumber < 2) return;
99 }
100
101 double length = curve.GetDistanceAtParameter(curve.EndParam);
102
103 //get each segment length
104 double segLength = _snapType == MeasureOsnapType.Measure ?
105 _segmentLength : length / _segmentNumber;
106
107 //Add snap points. If the curve is closed. Obviously
108 //the snap points at start point and end point will
109 //be overlapped in the case of Divide-Snap
110 double l = segLength;
111 while (l <= length)
112 {
113 Point3d pt = curve.GetPointAtDist(l);
114 snapPoints.Add(pt);
115
116 l += segLength;
117 }
118 }
119
120 public override bool IsContentSnappable(Entity entity)
121 {
122 return false;
123 }
124
125 #endregion
126
127 #region private methods
128
129 private void StartSnap(ObjectId entId)
130 {
131 _entId = entId;
132
133 Type t = GetEntityType();
134
135 Overrule.AddOverrule(RXClass.GetClass(t), this, false);
136
137 Overrule.Overruling = true;
138 _started = true;
139
140 this.SetIdFilter(new ObjectId[] { _entId });
141 }
142
143 private Type GetEntityType()
144 {
145 Type t;
146 switch (_entId.ObjectClass.DxfName.ToUpper())
147 {
148 case "CIRCLE":
149 t = typeof(Circle);
150 break;
151 case "ARC":
152 t = typeof(Arc);
153 break;
154 case "LINE":
155 t = typeof(Line);
156 break;
157 default:
158 t = typeof(Polyline);
159 break;
160 }
161
162 return t;
163 }
164
165 #endregion
166 }
167 }
The code is pretty simple and straightforward: simply overriding GetObjectSnapPoints() method to generate a set points where you want the snap points to be placed.
Here is the code to use the MeasureOSnapOverrule class:
1 using Autodesk.AutoCAD.ApplicationServices;
2 using Autodesk.AutoCAD.DatabaseServices;
3 using Autodesk.AutoCAD.EditorInput;
4 using Autodesk.AutoCAD.Runtime;
5
6 [assembly: CommandClass(typeof(MeasureOsnap.MyCommands))]
7
8 namespace MeasureOsnap
9 {
10 public class MyCommands
11 {
12 private static string _snapType = "Measure";
13
14 [CommandMethod("MyOverruledSnap")]
15 public static void RunMyOverruledSnap()
16 {
17 Document dwg = Application.DocumentManager.MdiActiveDocument;
18 Editor ed = dwg.Editor;
19
20 //Pick entity to show snap for measuring or dividing
21 ObjectId selectedId = GetSanpEntity(ed);
22
23 if (selectedId == ObjectId.Null)
24 {
25 OnCommandCancelled();
26 return;
27 }
28
29 if (_snapType == "Measure")
30 {
31 //Get segment length
32 PromptDoubleOptions dop = new PromptDoubleOptions(
33 "\nEnter segment length: ");
34 dop.AllowNegative = false;
35 dop.AllowNone = false;
36 dop.AllowZero = false;
37
38 PromptDoubleResult dres = ed.GetDouble(dop);
39 if (dres.Status != PromptStatus.OK)
40 {
41 OnCommandCancelled();
42 return;
43 }
44
45 //Start Measure-Snap
46 MeasureOsnapOverrule.Instance.StartMeasureSnap(
47 selectedId, dres.Value);
48 }
49 else
50 {
51 //Get segment count
52 PromptIntegerOptions iop = new PromptIntegerOptions(
53 "\nEnter segment count: ");
54 iop.AllowNegative = false;
55 iop.AllowNone = false;
56 iop.AllowZero = false;
57
58 PromptIntegerResult ires = ed.GetInteger(iop);
59 if (ires.Status != PromptStatus.OK)
60 {
61 OnCommandCancelled();
62 return;
63 }
64
65 //Start Divide-Snap
66 MeasureOsnapOverrule.Instance.StartDivideSnap(
67 selectedId, ires.Value);
68 }
69
70 //Obtain point when taking advantage of
71 //Measure or Divide-Snap
72 PromptPointOptions pOp = new PromptPointOptions(
73 "\nPick point: ");
74 PromptPointResult pres = ed.GetPoint(pOp);
75 if (pres.Status == PromptStatus.OK)
76 {
77 ed.WriteMessage("\nPoint: X={0}, Y={1}",
78 pres.Value.X, pres.Value.Y);
79 }
80 else
81 {
82 ed.WriteMessage("\n*Cancel*");
83 }
84
85 //Stop the overrule
86 MeasureOsnapOverrule.Instance.StopSnap();
87
88 Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
89 }
90
91 private static ObjectId GetSanpEntity(Editor ed)
92 {
93 ObjectId entId = ObjectId.Null;
94
95 while (true)
96 {
97 string keyword =
98 _snapType == "Measure" ? "Divide" : "Measure";
99
100 PromptEntityOptions opt = new PromptEntityOptions(
101 "\nPick a line/polyline/arc/circle to show " +
102 _snapType + "-Snap:");
103 opt.SetRejectMessage(
104 "\nInvalid pick: must be a line/polyline/arc/circle.");
105 opt.AddAllowedClass(typeof(Line), true);
106 opt.AddAllowedClass(typeof(Polyline), true);
107 opt.AddAllowedClass(typeof(Arc), true);
108 opt.AddAllowedClass(typeof(Circle), true);
109 opt.AllowNone = true;
110 opt.Keywords.Add(keyword);
111 opt.Keywords.Default = keyword;
112 opt.AppendKeywordsToMessage = true;
113
114 PromptEntityResult res = ed.GetEntity(opt);
115
116 if (res.Status == PromptStatus.OK)
117 {
118 entId = res.ObjectId;
119 break;
120 }
121 else if (res.Status == PromptStatus.Keyword)
122 {
123 _snapType = res.StringResult;
124 }
125 else
126 {
127 break;
128 }
129 }
130
131 return entId;
132 }
133
134 private static void OnCommandCancelled()
135 {
136 Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
137 ed.WriteMessage("\n*Cancel*");
138 Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
139 }
140 }
141 }
See this video clip for the code in action.
Stay tuned for the article on another solution for the same task.