Implementing a custom AutoCAD object snap mode using .NET (redux)

I had an email from Martin Duke about this old post a couple of weeks ago. I started to update the original post but then realised that a) I couldn't easily go that far back using Windows Live Writer and the built-in Typepad editor often messes up code in old posts when I edit them and b) there was some value in revisiting this topic again now that nearly 6 years has passed. I'll hopefully manage to link to this updated post from the old one without things going awry.

Martin was having some trouble with text display in object snap glyphs: it worked well enough once some geometry has been drawn – even if it didn't automatically use the colour of the other object snap glyph graphics – but it didn't work well when a drawing has just been created or loaded. If the previously implemented object snap was used in a fresh drawing, the text didn't get displayed.

No osnap glyph text in a fresh drawing

As soon as additional geometry was created in the drawing, however, the text did get displayed as expected.

Once more geometry has been created, it works

The answer, it turns out, was to use an SHX font: this allowed the transient text to be displayed properly from the start and with the colour the glyph markers should have.

Switching the text to use an SHX font makes it work consistently

I also took the opportunity to centre the text properly on the snap point, which is ultimately a matter of taste: I think I even moved it away from the snap point on purpose 6 years ago. 😉

It would clearly be nice to have TrueType fonts display properly, but this is an artifact of the 2D graphics system (and is logged as an issue in our internal tracking system). The behaviour is different when using a 3D Visual Style, for whatever that's worth.

Here's the updated C# code that shows how this can be made to work in this way:

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using AcGi = 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 : AcGi.Glyph

  {

    private Point3d _pt;

 

    public override void SetLocation(Point3d point)

    {

      _pt = point;

    }

 

    protected override void SubViewportDraw(AcGi.ViewportDraw vd)

    {

      int glyphPixels = CustomObjectSnapMode.GlyphSize;

      var glyphSize = vd.Viewport.GetNumPixelsInUnitSquare(_pt);

 

      // Calculate the size of the glyph in WCS

      //  (use for text height factor)

 

      // We'll add 10% to the size, as otherwise

      //  it looks a little too small

 

      double glyphHeight =

        (glyphPixels / glyphSize.Y) * 1.1;

 

      string text = "¼";

 

      // Translate the X-axis of the DCS to WCS

      //  (for the text direction) and the snap

      //  point itself (for the text location)

 

      var dist = -glyphHeight / 2.0;

      var offset = new Vector3d(dist, dist, 0);

      var e2w = vd.Viewport.EyeToWorldTransform;

      var dir = Vector3d.XAxis.TransformBy(e2w);

      var pt = (_pt + offset).TransformBy(e2w);

 

      //  Draw the centered text representing the glyph

 

      var style = new AcGi.TextStyle();

      var fd =

        new AcGi.FontDescriptor("txt.shx", false, false, 0, 0);

      style.Font = fd;

      style.TextSize = glyphHeight;

 

      vd.Geometry.Text(

        pt,

        vd.Viewport.ViewDirection,

        dir,

        text,

        false,

        style

      );

    }

  }

 

  // OSnap info

 

  public class QuarterOsnapInfo

  {

    public void SnapInfoEntity(

      Obje
ctSnapContext
context,

      ObjectSnapInfo result)

    {

      // Nothing here

    }

 

    public void SnapInfoCurve(

      ObjectSnapContext context,

      ObjectSnapInfo result

    )

    {

      // For any curve

 

      var cv = context.PickedObject as Curve;

      if (cv == null)

        return;

 

      double startParam = cv.StartParam;

      double endParam = cv.EndParam;

 

      // Added a check to avoid zero-length curve problems

 

      if (startParam == endParam)

        return;

 

      // Add osnap at first quarter

 

      double param =

        startParam + ((endParam - startParam) * 0.25);

      var 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

 

      var 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);

        var 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;

      }

    }


0; }

}

I'm easing back into the swing of things after my 10+ days out of the office. I'll hopefully get the time to post on more new AutoCAD 2015 APIs during the coming few days.

Update:

Added a fix for a crash when hovering over zero-length lines.

13 responses to “Implementing a custom AutoCAD object snap mode using .NET (redux)”

  1. Hello Kean,

    I'm using a slightly altered version of this example, and I encountered an AutoCAD crash while using it.
    I've tested your version as well to be sure, but it has the same problem.

    When there is a line with zero length in the drawing, AutoCAD will crash when the cursor is hovered over the object.
    I have fixed the problem with an extra check for the length in the SnapInfoCurve sub.

    [code]
    If cv Is Nothing Or (cv.GetDistanceAtParameter(cv.EndParam) - cv.GetDistanceAtParameter(cv.StartParam) = 0) Then
    Return
    End If
    [/code]

    As far as I can see the problem only occurs with zere length curves. Zero length polylines are not a problem.
    I have tested this on AutoCAD 2014 and 2015.

    In my own version, I'm drawing a new glyph instead of adding a text.
    I use vd.Geometry.Polygon(Point3dCollection) for this. However, these lines are drawn a lot thinner than the original glyphs. Do you know if it is possible to add a thickness to the polygon? And is it possible to change the color?

    1. Kean Walmsley Avatar

      Great - thanks for sharing this, Robert!

      I'll fix the post.

      Kean

    2. Kean Walmsley Avatar

      Checking whether the start and end parameters are the same appears to be enough to fix this, FYI.

      Kean

  2. Hi Kean - Great post. Can you suppress this so that it only activates when you call it? OSNAP is turned off and it is still active.

    Cheers,

    Brent

    1. Hi Brent,

      The code could (should?) certainly check the OSMODE sysvar to determine whether it should be active, assuming this isn't provided automatically (and it sounds like it isn't - I don't remember whether I tried this, now).

      Cheers,

      Kean

  3. Hi Kean,
    I have just installed AutoCAD 2016, and unfortunately it crashes immediately with this routine when snapping to an object.
    I have tried to compile the dll with objectarx 2015 and 2016 references, but this has no effect.

    I have not yet figured out why it crashes, but maybe you know of any changes to AutoCAD 2016 that can cause this?

    1. Hi Robert,

      Something strange is going on: the parameters passed in don't appear to have been initialized properly.

      I'll ask someone to take a look.

      Regards,

      Kean

      1. thanks for looking into it!

      2. Ольга Левина Avatar
        Ольга Левина

        HI, it seems to me, I have the same problem. I have System.InvalidOperationException on result.SnapPoints.Add(pt) when I use this your code (pt is calculating. it's not "null"). Was the problem fix somehow?

    2. Ольга Левина Avatar
      Ольга Левина

      HI, it seems to me, I have the same problem. I have System.InvalidOperationException on result.SnapPoints.Add(pt) when I use this code (pt is calculating. it's not "null"). Was the problem fixed somehow?

      1. I have no way of checking this (I'm out of the office for the rest of the year). Could you please post to the discussion groups, to see if someone there can help?

        Kean

  4. Michael Hudon Avatar

    If you wanted to use a glyph or icon instead of the fractional text, where would one find information about size requirments and file locations

    1. Kean Walmsley Avatar

      I'd start by posting to the Autodesk forums. (I don't provide support via this blog.)

      Kean

Leave a Reply

Your email address will not be published. Required fields are marked *