Implementing a custom AutoCAD object snap mode using .NET
Thanks again to Augusto Gonçalves, from our DevTech Americas team, for providing the original VB.NET code for this sample, as well as helping investigate an issue I faced during implementation.
When I saw a recent reply to a developer, showing how to implement a custom object snap in AutoCAD using .NET, I had a really strong sense of nostalgia: it reminded me of a couple of early samples I contributed to the ObjectARX SDK: the "third" sample, which showed how to create a custom osnap that snapped to a third of the way along a curve, and "divisor" which generalised the approach to fractions of any size and was my first real attempt at using C++ templates. Ah, the memories. The samples were retired from this year's SDK, but were still included up to and including the ObjectARX SDK for AutoCAD 2008.
Anyway, the code Augusto sent was very familiar, and it turns out he based it on some documentation that was probably, in turn, based on my C++ sample. So it has come full circle. :-)
One thing I hadn't realised until I saw Augusto's email was that the ability to define custom object snaps had been exposed through .NET.
Here's the C# code that implements a new "quarter" object snap, which snaps to 1/4 and 3/4 along the length of a curve.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
[assembly:ExtensionApplication(
typeof(OsnapApp.CustomOSnapApp))
]
namespace OsnapApp
{
// Register and unregister custom osnap
public class CustomOSnapApp : IExtensionApplication
{
private QuarterOsnapInfo _info =
new QuarterOsnapInfo();
private QuarterGlyph _glyph =
new QuarterGlyph();
private CustomObjectSnapMode _mode;
public void Initialize()
{
// Register custom osnap on initialize
_mode =
new CustomObjectSnapMode(
"Quarter",
"Quarter",
"Quarter of length",
_glyph
);
// Which kind of entity will use the osnap
_mode.ApplyToEntityType(
RXObject.GetClass(typeof(Polyline)),
new AddObjectSnapInfo(_info.SnapInfoPolyline)
);
_mode.ApplyToEntityType(
RXObject.GetClass(typeof(Curve)),
new AddObjectSnapInfo(_info.SnapInfoCurve)
);
_mode.ApplyToEntityType(
RXObject.GetClass(typeof(Entity)),
new AddObjectSnapInfo(_info.SnapInfoEntity)
);
// Activate the osnap
CustomObjectSnapMode.Activate("_Quarter");
}
// Unregister custom osnap on terminate
public void Terminate()
{
CustomObjectSnapMode.Deactivate("_Quarter");
}
}
// Create new quarter object snap
public class QuarterGlyph : Glyph
{
private Point3d _pt;
public override void SetLocation(Point3d point)
{
_pt = point;
}
public override void ViewportDraw(ViewportDraw vd)
{
int glyphPixels =
CustomObjectSnapMode.GlyphSize;
Point2d glyphSize =
vd.Viewport.GetNumPixelsInUnitSquare(_pt);
// Calculate the size of the glyph in WCS
// (use for text height factor)
// We'll add 20% to the size, as otherwise
// it looks a little too small
double glyphHeight =
(glyphPixels / glyphSize.Y) * 1.2;
string text = "¼";
// Translate the X-axis of the DCS to WCS
// (for the text direction) and the snap
// point itself (for the text location)
Matrix3d e2w = vd.Viewport.EyeToWorldTransform;
Vector3d dir = Vector3d.XAxis.TransformBy(e2w);
Point3d pt = _pt.TransformBy(e2w);
// Draw the centered text representing the glyph
vd.Geometry.Text(
pt,
vd.Viewport.ViewDirection,
dir,
glyphHeight,
1,
0,
text
);
}
}
// OSnap info
public class QuarterOsnapInfo
{
public void SnapInfoEntity(
ObjectSnapContext context,
ObjectSnapInfo result)
{
// Nothing here
}
public void SnapInfoCurve(
ObjectSnapContext context,
ObjectSnapInfo result
)
{
// For any curve
Curve cv = context.PickedObject as Curve;
if (cv == null)
return;
double startParam = cv.StartParam;
double endParam = cv.EndParam;
// Add osnap at first quarter
double param =
startParam + ((endParam - startParam) * 0.25);
Point3d pt = cv.GetPointAtParameter(param);
result.SnapPoints.Add(pt);
// Add osnap at third quarter
param =
startParam + ((endParam - startParam) * 0.75);
pt = cv.GetPointAtParameter(param);
result.SnapPoints.Add(pt);
if (cv.Closed)
{
pt = cv.StartPoint;
result.SnapPoints.Add(pt);
}
}
public void SnapInfoPolyline(
ObjectSnapContext context,
ObjectSnapInfo result)
{
// For polylines
Polyline pl = context.PickedObject as Polyline;
if (pl == null)
return;
// Get the overall start and end parameters
double plStartParam = pl.StartParam;
double plEndParam = pl.EndParam;
// Get the local
double startParam = plStartParam;
double endParam = startParam + 1.0;
while (endParam <= plEndParam)
{
// Calculate the snap point per vertex...
// Add osnap at first quarter
double param =
startParam + ((endParam - startParam) * 0.25);
Point3d pt = pl.GetPointAtParameter(param);
result.SnapPoints.Add(pt);
// Add osnap at third quarter
param =
startParam + ((endParam - startParam) * 0.75);
pt = pl.GetPointAtParameter(param);
result.SnapPoints.Add(pt);
startParam = endParam;
endParam += 1.0;
}
}
}
}
Some comments on the implementation:
- There's a blank callback that is the base implementation for entities
- We then override that for all Curve objects, using some code to divide a curve into quarters
- We do yet another implementation for all Polyline objects (which are Curves, but we want to treat them as a special case)
- For Polylines we snap within segments
- We could have implemented this by retrieving each segment and dividing that into quarters
- Instead I chose to rely on the fact that a Polyline's parameter is a "whole number" at each vertex, which means the code is the same for any kind of segment
- For Polylines we snap within segments
- In my original sample I adjusted the position of the text, to centre it on the snap point
- In this example I haven't done this, as when I looked at the code it wasn't accurate - when you zoomed in the text appeared in the wrong position
- As we're just using a single character (¼) as our glyph, this isn't a significant problem
Here's what happens when we load our module and try snapping to a line inside AutoCAD:

Subscribe via RSS
Is there a way to control this option within CAD, or add it into the Osnap dialog?
Posted by: Daniel S | October 31, 2008 at 01:53 PM
Also, is there a compatible text symbol for 1/3? 1/2 and 1/4 are recognized by AutoCAD, but if I pull 1/3 out of the character map, it isn't recognized by CAD (Just shows up as a question mark on the line).
Posted by: Daniel S | October 31, 2008 at 02:28 PM
The Object Snap dialog isn't customizable, so you can either tie it to another mode (check the setting to see whether that is enabled) or implement your own application settings UI.
I used to use "1/3" (three characters, not one) for the "third" osnap glyph. "Half" and "quarter" are more likely to be available, as they're standard ASCII. I don't believe "1/3" (one character) is, though. If you're using three characters centering the text is likely to become more important.
Kean
Posted by: Kean | November 03, 2008 at 09:00 AM
Thanks for the example.
I actually gave up on trying to do custom osnaps in .NET because the glyphs that you draw via viewportDraw() do not go away, and you end up with them littering the view.
Any ideas on how to make them behave more like AutoCAD's built-in osnap markers (which disappear when you move out of the range of the aperture target)?
Posted by: Tony Tanzillo | November 04, 2008 at 05:53 PM
I think I had the same problem, until I enabled hardware acceleration. I hadn't seen it on anyone else's system, so assumed it was a personal glitch I was hitting.
When the problem occurred my glyphs were blue, rather than yellow, in case that helps confirm the behaviour.
Kean
Posted by: Kean | November 04, 2008 at 06:38 PM
All osnap marker glpyphs I see are red, and the text you draw is also red, but doesn't go away.
Strange that hardware acceleration would have any effect on this.
I did notice that if I use the DeviceContextXxxxx methods of the ViewportDraw class to draw graphics, the graphics disappear as expected.
Another thing I've found unreasonable about the custom osnap API design, is that you can't supply the tooltip text dynamically, although there is a workaround involving the Editor's PointMonitor event
Posted by: Tony Tanzillo | November 06, 2008 at 09:12 AM
Hi,
How will you implement the above code in AUTOCAD?
Is the code common for all the versions of the AUTOCAD.
Let me know how customization & implementation can be done through coding in AUTOCAD.
Looking for reply.....
Posted by: Mukundhan | December 03, 2008 at 07:50 AM