Disabling snapping to specific AutoCAD objects using .NET – Part 2

In the last post, we introduced the idea of preventing object snapping on certain objects by tagging them with XData and then attaching an overrule within AutoCAD to stop it from getting osnap information for them.

This worked very well for standard, single-object snap modes – such as center, quad, mid, end, etc. – but didn't work for intersection points.

Intersection points are determined by calling an object's intersectsWith() method, which can thankfully also be overruled using a GeometryOverrule. Overruling this behaviour comes with a few important caveats, though:

  1. intersectsWith() can be called in a number of different scenarios, so it's recommended that you only overrule it for the absolutely minimum time you need to. This could be achieved by limiting the time you have either the overrule or the XData attached, and is left to the reader to worry about. But please do worry about it. 🙂
  2. This method also takes two entities as parameters, as it returns the intersection between two entities. If your entity is the primary entity, this will work fine. But if it ends up being the secondary one, it may slip through the net and have intersection points generated for it. The only real way to address this is to remove the XData filtering of the overrule and implement it yourself for each of the entities passed in. That said, if your entities are physically co-located and all have their snapping turned off, there shouldn't be a need to jump through these additional hoops (and so I've chosen not to do so).

Here's the updated C# code with the additional overrule implementation and the very slightly updated logic to manage it:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using System;

 

namespace ObjectSnapping

{

  public class Commands

  {

    const string regAppName = "TTIF_SNAP";

 

    private static OSOverrule _osOverrule = null;

    private static IntOverrule _geoOverrule = null;

 

    // Object Snap Overrule to prevent snapping to objects

    // with certain XData attached

 

    public class OSOverrule : OsnapOverrule

    {

      public OSOverrule()

      {

        // Tell AutoCAD to filter on our application name

        // (this should mean our overrule only gets called

        // on objects possessing XData with this name)

 

        SetXDataFilter(regAppName);

      }

 

      public override void GetObjectSnapPoints(

        Entity ent,

        ObjectSnapModes mode,

      &
#160;
IntPtr gsm,

        Point3d pick,

        Point3d last,

        Matrix3d view,

        Point3dCollection snap,

        IntegerCollection geomIds

      )

      {

      }

 

      public override void GetObjectSnapPoints(

        Entity ent,

        ObjectSnapModes mode,

        IntPtr gsm,

        Point3d pick,

        Point3d last,

        Matrix3d view,

        Point3dCollection snaps,

        IntegerCollection geomIds,

        Matrix3d insertion

      )

      {

      }

 

      public override bool IsContentSnappable(Entity entity)

      {

        return false;

      }

    }

 

    // Geometry Overrule to prevent IntersectsWith() working on

    // objects with certain XData attached

 

    public class IntOverrule : GeometryOverrule

    {

      public IntOverrule()

      {

        // Tell AutoCAD to filter on our application name

        // (this should mean our overrule only gets called

        // on objects possessing XData with this name)

 

        SetXDataFilter(regAppName);

      }

 

      public override void IntersectWith(

        Entity ent1,

        Entity ent2,

        Intersect intType,

        Plane proj,

        Point3dCollection points,

        IntPtr thisGsm,

        IntPtr otherGsm

      )

      {

      }

 

      public override void IntersectWith(

        Entity ent1,

        Entity ent2,

        Intersect intType,

        Point3dCollection points,

        IntPtr thisGsm,

        IntPtr otherGsm

      )

      {

      }

    }

 

    private static void ToggleOverruling(bool on)

    {

      if (on)

      {

        if (_osOverrule == null)

        {

          _osOverrule = new OSOverrule();

 

          ObjectOverrule.AddOverrule(

            RXObject.GetClass(typeof(Entity)),

            _osOverrule,

            false

          );

        }

 

        if (_geoOverrule == null)

        {

          _geoOverrule = new IntOverrule();

 

          ObjectOverrule.AddOverrule(

            RXObject.GetClass(typeof(Entity)),

            _geoOverrule,

            false

          );

        }

 

        ObjectOverrule.Overruling = true;

      }

      else

      {

        if (_osOverrule != null)

        {

          ObjectOverrule.RemoveOverrule(

            RXObject.GetClass(typeof(Entity)),

            _osOverrule

          );

 

          _osOverrule.Dispose();

          _osOverrule = null;

        }

 

        if (_geoOverrule != null)

        {

          ObjectOverrule.RemoveOverrule(

            RXObject.GetClass(typeof(Entity)),

            _geoOverrule

          );

 

          _geoOverrule.Dispose();

          _geoOverrule = null;

        }

 

        // I don't like doing this and so have commented it out:

        // there's too much risk of stomping on other overrules...

 

        // ObjectOverrule.Overruling = false;

      }

    }

 

    [CommandMethod("DISNAP")]

    public static void DisableSnapping()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      var db = doc.Database;

      var ed = doc.Editor;

 

      // Start by getting the entities to disable snapping for.

      // If none selected, turn off the overrule

 

      var psr = ed.GetSelection();

 

      if (psr.Status != PromptStatus.OK)

        return;

 

      ToggleOverruling(true);

 

      // Start a transaction to modify the entities' XData

 

      using (var tr = doc.TransactionManager.StartTransaction())

      {

        // Make sure our RegAppID is in the table

 

        var rat =

          (RegAppTable)tr.GetObject(

            db.RegAppTableId,

            OpenMode.ForRead

          );

 

        if (!rat.Has(regAppName))

        {

          rat.UpgradeOpen();

          var ratr = new RegAppTableRecord();

          ratr.Name = regAppName;

          rat.Add(ratr);

          tr.AddNewlyCreatedDBObject(ratr, true);

        }

 

        // Create the XData and set it on the object

 

        using (

          var rb =

            new ResultBuffer(

              new TypedValue(

                (int)DxfCode.ExtendedDataRegAppName, regAppName

              ),

              new TypedValue(

                (int)DxfCode.ExtendedDataInteger16, 1

              )

            )

        )

        {

          foreach (SelectedObject so in psr.Value)

          {

            var ent =

              tr.GetObject(so.ObjectId, OpenMode.ForWrite) as Entity;

            if (ent != null)

            {

              ent.XData = rb;

            }

          }

        };

 

        tr.Commit();

      }

    }

 

    [CommandMethod("ENSNAP")]

    public static void EnableSnapping()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      var db = doc.Database;

      var ed = doc.Editor;

 

      // Start by getting the entities to enable snapping for

 

      var pso = new PromptSelectionOptions();

      pso.MessageForAdding =

        "Select objects (none to remove overrule)";

      var psr = ed.GetSelection(pso);

 

      if (psr.Status == PromptStatus.Error)

      {

        ToggleOverruling(false);

        ed.WriteMessage("\nOverruling turned off.");

        return;

      }

      else if (psr.Status != PromptStatus.OK)

        return;

 

      // Start a transaction to modify the entities' XData

 

      using (var tr = doc.TransactionManager.StartTransaction())

      {

        // Create a ResultBuffer and use it to remove the XData

        // from the object

 

        using (

          var rb =

            new ResultBuffer(

              new TypedValue(

                (int)DxfCode.ExtendedDataRegAppName, regAppName

              )

            )

        )

        {

          foreach (SelectedObject so in psr.Value)

          {

            var ent =

              tr.GetObject(so.ObjectId, OpenMode.ForWrite) as Entity;

            if (ent != null)

            {

              ent.XData = rb;

            }

          }

        };

 

        tr.Commit();

      }

    }

  }

}

Here's the revised code in action, the main difference being you no longer see intersection osnaps displayed between our tagged geometry:

Disnap without intersections

8 responses to “Disabling snapping to specific AutoCAD objects using .NET – Part 2”

  1. this sparked a thought I have been wondering about. You use xdata here, but I have apps that need to attach too much data and must use xrecords.
    I noticed a wblock of a lwpolyline loses this data, while a wblock (to another drawing) keeps it.
    This is a bit of a problem for me because my prog has the option to display the pline as 2d or 3d, depending on the situation (plan sheets or DTM surface modeling...)
    Do you agree on this conclusion? I would love to discover I am just doing it wrong. I'll post code of do a DG post if its not an issue of "everyone knows wblock of lwpline drops xrecord data..." thx

  2. It should work fine with XRecords - that's just overkill for this particular scenario.

    The only issue I recall wrt Polylines and XData is when developers used to store XData per vertex, which clearly doesn't map to lightweight polylines.

    If there's an issue with WBLOCK and Polylines, please do make sure it's submitted via ADN.

    Kean

  3. Thanks Kean. This will help out a lot. I'm assuming that SetXdataFilter is an AutoCAD .net function. I don't see this function in ObjectARX. I guess I'll just use the isApplicable() virtual to determine if it's one to process.

  4. Hi Joe,

    Thank you for asking the question at AU! 🙂

    Yes, SetXDataFilter is a convenience that's available in .NET: in ObjectARX you'll have to do a bit more work within isApplicable(), but it should be simple enough to put together.

    All the best to you and yours for the holiday season,

    Kean

  5. Wblock handling of Xrecords (and other Dictionary-resident objects) can be modified by changing the value of Dictionary.TreatElementsAsHard. Maybe this is what James is seeing?

    And - of course - you can customize deepcloning operations to meet your special needs using another Overrule ;-).

  6. oh, that would be great! I have been using the actual commands in my lisp, but it seems a net wblock function is needed. I will look around for one. great tip if that works!

  7. I am trying to implement this in AutoCAD but I am stuck at the compilation stage. I cant find the proper reference for Autodesk.

    "Error 1 The type or namespace name 'Autodesk' could not be found (are you missing a using directive or an assembly reference?)"

    Any help is appreciated.

    1. Kean Walmsley Avatar

      The simplest is to use NuGet (depending on the version of AutoCAD you're building your app for). For instance:

      keanw.com/2015/03/nuget-packages-now-available-for-autocad-2016.html

      Otherwise you can reference the acmgd.dll, accoremgd.dll and acdbmgd.dll assemblies from the AutoCAD product folder (a bad idea) or from the ObjectARX SDK's inc folder (a better idea) for your AutoCAD version.

      Kean

Leave a Reply to James Maeding Cancel reply

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