October 2014

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  










« 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

Update:

I've just made a few minor changes to the above code to update it to work with AutoCAD 2012 (and maybe this is needed for prior versions, too - I'm not sure when the behaviour changed).

Here's the updated C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Geometry;

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;

        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;

      }

    }

  }

}

Update 2:

Please see this more recent post for an updated solution to this problem.

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:

blog comments powered by Disqus

Feed/Share

10 Random Posts