In the last post we saw a “general” function to erase all the entities in a drawing that fulfill a specified condition. We used it to erase all the zero-length lines in a drawing. But as I’d mentioned at the end, I thought there was an opportunity to generalize the mechanism even further. Here’s what I came up with (and I’ve included a suggestion by Parrish Husband to create an additional extension method for Curve.IsZeroLength()).
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System;
namespace ZeroLengthEntities
{
public static class Extensions
{
/// <summary>
/// Performs an operation on all entities found in this database.
/// </summary>
/// <param name="f">Function performing an operation on an entity.</param>
/// <returns>The number of times the function returned true.</returns>
public static int ForEachEntity(this Database db, Func<Entity, bool> f)
{
int count = 0;
using (var tr = db.TransactionManager.StartTransaction())
{
var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
foreach (ObjectId btrId in bt)
{
var btr = (BlockTableRecord)tr.GetObject(btrId, OpenMode.ForRead);
foreach (ObjectId entId in btr)
{
var ent = tr.GetObject(entId, OpenMode.ForRead) as Entity;
if (ent != null && f(ent))
{
count++;
}
}
}
tr.Commit();
}
return count;
}
/// <summary>
/// Erases entities found in this database that fulfill a condition.
/// </summary>
/// <param name="f">Function indicating whether an enity needs erasing.</param>
/// <param name="countOnly">Optional parameter to count but not erase.</param>
/// <returns>The number of entities found (and - if !countOnly - erased).</returns>
public static int ConditionalErase(
this Database db, Func<Entity, bool> f, bool countOnly = false
)
{
return
db.ForEachEntity(
e =>
{
if (f(e))
{
if (!countOnly)
{
e.UpgradeOpen();
e.Erase();
e.DowngradeOpen();
}
return true;
}
return false;
}
);
}
/// <summary>
/// Returns the length of a curve.
/// </summary>
/// <returns>The length of this curve.</returns>
public static double Length(this Curve cur)
{
return cur.GetDistanceAtParameter(cur.EndParam);
}
/// <summary>
/// Checks whether a curve is of zero length.
/// </summary>
/// <returns>A Boolean indicating whether this curve is of zero length.</returns>
public static bool IsZeroLength(this Curve cur)
{
return cur.Length() < Tolerance.Global.EqualPoint;
}
}
public class Commands
{
[CommandMethod("EZL")]
public void EraseZeroLength()
{
var doc = Application.DocumentManager.MdiActiveDocument;
if (doc == null)
return;
var db = doc.Database;
var ed = doc.Editor;
var count = db.ConditionalErase(
e =>
{
if (e is Curve)
return ((Curve)e).IsZeroLength();
return false;
}
);
ed.WriteMessage("\nErased {0} entities.", count);
}
[CommandMethod("EZL2")]
public void EraseZeroLength2()
{
var doc = Application.DocumentManager.MdiActiveDocument;
if (doc == null)
return;
var db = doc.Database;
var ed = doc.Editor;
var count = db.ForEachEntity(
e =>
{
var cur = e as Curve;
if (cur != null && cur.IsZeroLength())
{
e.UpgradeOpen();
e.Erase();
e.DowngradeOpen();
return true;
}
return false;
}
);
ed.WriteMessage("\nErased {0} entities.", count);
}
}
}
The code operates in exactly the same way as last time, but we now have a Database.ForEachEntity() method that is used by Database.ConditionalErase() but can also be used directly to perform read/update/delete operations on all the entities in a drawing. In fact I’ve already used it in a separate project – that we’ll see next week – to “touch” all the entities in a drawing in order to force a redraw.
You can compare the implementations of the functionally equivalent commands, EZL and EZL2. See which style you prefer.
As a reminder, from last time, here’s what the EZL and EZL2 commands do when you run them: