The class Autodesk.AutoCAD.DatabaseServices.CustomObjectSnapMode provides a way to customize object snapping through an user defined class that is derived from an abstract class Autodesk.AutoCAD.GraphicsInterface.Glyph. Since I did not keep ObjectARX SDK documents older than AutoCAD 2010, I cannot say for sure, but fairly certain that the two classes have been available in AutoCAD .NET API from beginning (AutoCAD2005/6). So, until Overrule was available since AutoCAD 2010, people can only use these 2 classes to do object snapping customization.
Basically, we use a custom Glyph class to draw a geometry shape at object snap point, and use CustomObjectSnapMode class to control where the snap points should be.
Here is the class that derived from Glyph, in which CustomObjectSnapMode class is wrapped in order to make the code easy to use:
1 using System;
2 using Autodesk.AutoCAD.DatabaseServices;
3 using Autodesk.AutoCAD.GraphicsInterface;
4 using Autodesk.AutoCAD.Geometry;
5 using Autodesk.AutoCAD.Runtime;
6
7 namespace MeasureWithSnap
8 {
9 public class MeasureOsnap : Glyph
10 {
11 private enum MeasureOsnapType
12 {
13 Measure = 0,
14 Divide = 1,
15 }
16
17 private static MeasureOsnap _instance = null;
18 private int _segmentNumber = 1;
19 private double _segmentLength = 0.0;
20 private MeasureOsnapType _snapType = MeasureOsnapType.Measure;
21 private bool _started = false;
22
23 private const string LOCAL_MODEL_STRING = "MeasureSnap";
24 private const string GLOBAL_MODEL_STRING = "_MeasureSnap";
25 private const string TOOL_TIP_STRING = "Measure and/or Divide snapping";
26
27 private CustomObjectSnapMode _snapMode;
28
29 private ObjectId _entId = ObjectId.Null;
30 private Point3d _point;
31
32 public static MeasureOsnap Instance
33 {
34 get
35 {
36 if (_instance == null) _instance = new MeasureOsnap();
37 return _instance;
38 }
39 }
40
41 public void StartMeasureSnap(ObjectId entId, double segmentLength)
42 {
43 if (_started) return;
44
45 _segmentLength = segmentLength;
46 _entId = entId;
47 _snapType = MeasureOsnapType.Measure;
48 _snapMode = CreateCustomObjectSnapMode();
49
50 _started = true;
51 }
52
53 public void StartDivideSnap(ObjectId entId, int segmentNumber)
54 {
55 if (_started) return;
56
57 _segmentNumber = segmentNumber;
58 _entId = entId;
59 _snapType = MeasureOsnapType.Divide;
60 _snapMode = CreateCustomObjectSnapMode();
61
62 _started = true;
63 }
64
65 public void StopSnap()
66 {
67 if (!_started) return;
68
69 RemoveCustomObjectSnapMode();
70
71 _started = false;
72 }
73
74 #region Overriding base class methods
75
76 public override void SetLocation(Point3d point)
77 {
78 _point = point;
79 }
80
81 protected override void SubViewportDraw(ViewportDraw vd)
82 {
83 //Draw a square polygon at snap point
84 Point2d gSize = vd.Viewport.GetNumPixelsInUnitSquare(_point);
85 double gHeight = CustomObjectSnapMode.GlyphSize / gSize.Y;
86 Matrix3d dTOw = vd.Viewport.EyeToWorldTransform;
87
88 Point3d[] gPts =
89 {
90 new Point3d(
91 _point.X - gHeight/2.0,
92 _point.Y - gHeight/2.0,
93 _point.X).TransformBy(dTOw),
94 new Point3d(
95 _point.X + gHeight/2.0,
96 _point.Y - gHeight/2.0,
97 _point.X).TransformBy(dTOw),
98 new Point3d(
99 _point.X + gHeight/2.0,
100 _point.Y + gHeight/2.0,
101 _point.X).TransformBy(dTOw),
102 new Point3d(
103 _point.X - gHeight/2.0,
104 _point.Y + gHeight/2.0,
105 _point.X).TransformBy(dTOw),
106 };
107
108 vd.Geometry.Polygon(new Point3dCollection(gPts));
109
110 ////-----------------------------------------------------------
111 ////If you want to draw a circle at snap point,
112 ////simply comment out above code and
113 ////uncomment code below
114 ////-----------------------------------------------------------
115 //Point2d gSize = vd.Viewport.GetNumPixelsInUnitSquare(_point);
116 //double dia = CustomObjectSnapMode.GlyphSize / gSize.Y;
117 //vd.Geometry.Circle(_point, dia / 2.0, Vector3d.ZAxis);
118 }
119
120 #endregion
121
122 #region private methods of creating CustomObjectSnapMode object
123
124 protected CustomObjectSnapMode CreateCustomObjectSnapMode()
125 {
126 CustomObjectSnapMode snap = new CustomObjectSnapMode(
127 LOCAL_MODEL_STRING, GLOBAL_MODEL_STRING,
128 TOOL_TIP_STRING, Instance);
129
130 Type t = GetEntityType();
131
132 snap.ApplyToEntityType(
133 RXClass.GetClass(t), AddMeasureObjectSnapInfo);
134
135 CustomObjectSnapMode.Activate(GLOBAL_MODEL_STRING);
136
137 return snap;
138 }
139
140 protected void RemoveCustomObjectSnapMode()
141 {
142 CustomObjectSnapMode.Deactivate(GLOBAL_MODEL_STRING);
143
144 Type t = GetEntityType();
145 _snapMode.RemoveFromEntityType(RXClass.GetClass(t));
146 _snapMode.Dispose();
147 _snapMode = null;
148
149 _segmentLength = 0.0;
150 _segmentNumber = 1;
151 }
152
153 protected void AddMeasureObjectSnapInfo(
154 ObjectSnapContext context, ObjectSnapInfo result)
155 {
156 if (context.PickedObject.ObjectId != _entId) return;
157
158 if (_snapType == MeasureOsnapType.Measure)
159 {
160 if (_segmentLength <= 0.0) return;
161 }
162
163 if (_snapType == MeasureOsnapType.Divide)
164 {
165 if (_segmentNumber < 2) return;
166 }
167
168 Curve curve = (Curve)context.PickedObject;
169
170 Point3dCollection points = result.SnapPoints;
171 points.Clear();
172
173 //Add snap point at start point
174 points.Add(curve.StartPoint);
175
176 double length = curve.GetDistanceAtParameter(curve.EndParam);
177
178 //get each segment length
179 double segLength = _snapType == MeasureOsnapType.Measure ?
180 _segmentLength : length / _segmentNumber;
181
182 //Add snap points. If the curve is closed. Obviously
183 //the snap points at start point and end point will
184 //be overlapped in the case of Divide-Snap
185 double l = segLength;
186 while (l <= length)
187 {
188 Point3d pt = curve.GetPointAtDist(l);
189 points.Add(pt);
190
191 l += segLength;
192 }
193 }
194
195 #endregion
196
197 #region private methods
198
199 private Type GetEntityType()
200 {
201 Type t;
202 switch (_entId.ObjectClass.DxfName.ToUpper())
203 {
204 case "CIRCLE":
205 t = typeof(Circle);
206 break;
207 case "ARC":
208 t = typeof(Arc);
209 break;
210 case "LINE":
211 t = typeof(Line);
212 break;
213 default:
214 t = typeof(Autodesk.AutoCAD.DatabaseServices.Polyline);
215 break;
216 }
217
218 return t;
219 }
220
221 #endregion
222 }
223 }
The same as I did in previous article, in order to simplify the calculation of measuring/dividing points, I deliberately limit the applied entity types only to Line, Polyline, Arc and Circle. The code itself is just as simple as the custom OsnapOverrule class in my previous article.
Here is the code to use the MeasureOSnap class, which is exactly the same as the command class in previous article, except for the substituting MeasureOsnapOverrule with MeasureOSnap:
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(MeasureWithSnap.MeasureWithSnapCommands))]
7
8 namespace MeasureWithSnap
9 {
10 public class MeasureWithSnapCommands
11 {
12 private static string _snapType = "Measure";
13
14 [CommandMethod("MyCustomSnap")]
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 MeasureOsnap.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 MeasureOsnap.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 MeasureOsnap.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 }
This video clip shows the code in action.
If you watched the video clip carefully, you would notice that when the new CustomObjectSnapMode in the code is activated, AutoCAD actually adds it into the context menu of "OSnap" button in AutoCAD's status bar and allow user to activate/deactivate it transparently.
Now, between the 2 custom object snapping approach, which one to use? For the custom OsnapOverrule one presented in my previous article, the Overrule's built-in entity filtering mechanism might be key factor to use, if you want to apply the object snapping on specific entity or entities; whole for Glyph derived object snapping approach, you can easily draw the snapping point in your preferred geometry to make it more eye-catching. Use whichever that suit your need and whichever you can come up with.