Kean Walmsley

July 2009

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Twitter Updates

    follow me on Twitter



    « A video introduction | Main | Creating a custom AutoCAD table style using .NET »

    October 31, 2008

    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
    • 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:

    Custom quarter object snap

    TrackBack

    TrackBack URL for this entry:
    http://www.typepad.com/services/trackback/6a00d83452464869e2010535c78132970c

    Listed below are links to weblogs that reference Implementing a custom AutoCAD object snap mode using .NET:

    Comments

    Is there a way to control this option within CAD, or add it into the Osnap dialog?

    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).

    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

    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)?

    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

    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

    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.....


    Verify your Comment

    Previewing your Comment

    This is only a preview. Your comment has not yet been posted.

    Working...
    Your comment could not be posted. Error type:
    Your comment has been posted. Post another comment

    The letters and numbers you entered did not match the image. Please try again.

    As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

    Having trouble reading this image? View an alternate.

    Working...

    Post a comment

    Feed & Share

    Search