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