Highlighting and removing blanked out AutoCAD text using .NET

I had an interesting email last week from Josh Mathews:

I'm having some trouble figuring out how to fix this problem I have and I'm not sure what the best way is to attack it. I have a large set of drawings that have these random strings of underscores strewn throughout the drawing (which look like random lines scattered all around when the drawing is printed – and make it confusing to look at), and so I'm trying to write a function that will iterate through the drawing and find all the text strings that contain ONLY underscores and then erase those text strings.

The problem is that some of the text strings are regular text entities and some are block attributes. So – the text entities I would like to erase, and the block attributes I would like to delete from the block.

The first thing that occurred to me when I read Josh's email was that it would be easy to throw together a quick overrule based on this previous post where we identified text and dimensions. From there we could look at a couple of ways to erase the problematic entities themselves, whether based on user selection (and nested selection, for the block attributes) or by cranking through the various block definitions (including the modelspace, of course).

The second thing that occurred to me was "how did the drawings get to be in this state?" Josh explained that the text was edited out and, for one reason or another, replaced with underscore characters. He wasn't sure of the drawings' origins. I wondered whether the text was redacted by a secretive 3-letter government agency, but I've decided not to ask too many questions. 😉

For the deletion approach, I ended up choosing the latter route where we scan and modify the various block definitions, making sure that along with the DBText and MText objects, we check the BlockReferences' AttributeReferences, as well. Identifying problematic text is actually done by the overrule's IsApplicable() method (more on this later), which uses a regular expression to check the string contents, a technique we saw most recently in this post. We're looking for strings that only contain spaces and underscores, but this technique could very easily be adapted to look for other kinds of string, too, of course.

All this led to the following C# implementation:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.GraphicsInterface;

using Autodesk.AutoCAD.Runtime;

using System;

using System.Text.RegularExpressions;

 

namespace DashedTextFinder

{

  public class Commands

  {     

    // Stores our global overrule instance

 

    private static TextHighlightOverrule _tho = null;

 

    [CommandMethod("EDT")]

    public static void EraseDashedText()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      var ed = doc.Editor;

      var db = doc.Database;

 

      var count = 0;

 

      // In case the SDT command hasn't been run, we need to create

      // our custom overrule object

 

      if (_tho == null)

        _tho = new TextHighlightOverrule();

 

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

      {

        // Let's iterate through the BlockTable, erasing the

        // relevant entities from the various BlockTableRecords

 

        var bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId, OpenMode.ForRead

          );

        foreach (var btrId in bt)

        {

          var btr =

            (BlockTableRecord)tr.GetObject(

              btrId, OpenMode.ForRead

            );

 

          foreach (var entId in btr)

          {

            // Also open erased objects, although we won't process

            // them, of course

 

            var ent =

              (Entity)tr.GetObject(entId, OpenMode.ForRead, true);

 

            if (!ent.IsErased)

            {

              // If the entity is a BlockReference, check its

              // AttributeReferences, otherwise just check the

              // entity itself using our overrule object

 

              if (ent is BlockReference)

              {

                var br = (BlockReference)ent;

                foreach (var aId in br.AttributeCollection)

                {

                  var ar =

                    (AttributeReference)tr.GetObject(

                      (ObjectId)aId, OpenMode.ForRead, true

                    );

                  if (!ar.IsErased && _tho.IsApplicable(ar))

                  {

                    ar.UpgradeOpen();

                    ar.Erase();

                    count++;

                  }

                }

              }

              else if (_tho.IsApplicable(ent))

              {

                ent.UpgradeOpen();

                ent.Erase();

                count++;

              }

            }

          }

        }

 

        tr.Commit();

      }

 

      // As we've erased all "problematic" entities, we might also

      // remove the overrule...

 

      //_tho.Remove();

      //_tho.Dispose();

      //_tho = null;

 

      ed.WriteMessage(

        "\nErased {0} blank or dashed text entit{1}.",

        count, count == 1 ? "y" : "ies"

      );

    }

 

    // Highlights the dashed text objects in the drawing

 

    [CommandMethod("HDT")]

    public static void HighlightDashedText()

    {

      var doc = Application.Do
cumentManager.MdiActiveDocument;

 

      if (_tho == null)

        _tho = new TextHighlightOverrule();

 

      short newCol = UpdateHighlightOverrule(doc, _tho);

      if (newCol < 0)

      {

        _tho.Dispose();

        _tho = null;

      }

      else

        _tho.HighlightColor = newCol;

    }

 

    // A helper function which applies our highlight overrule

    // to different types, adjusting the color index used

 

    private static short UpdateHighlightOverrule(

      Document doc, HighlightOverrule ho

    )

    {

      // Ask the user for the new color index to use

 

      var opts =

        new PromptIntegerOptions("\nEnter highlight color index: ");

      opts.LowerLimit = 0;

      opts.UpperLimit = 127;

      opts.Keywords.Add("Clear");

      opts.DefaultValue = ho.HighlightColor;

 

      var res = doc.Editor.GetInteger(opts);

      if (res.Status == PromptStatus.Keyword)

      {

        // If the Clear keyword was entered, let's remove the

        // overrule

 

        if (res.StringResult == "Clear")

        {

          ho.Remove();

 

          doc.Editor.Regen();

 

          return -1;

        }

      }

      else if (res.Status == PromptStatus.OK)

      {

        // Otherwise we attach the overrule for each type

 

        ho.Add();

 

        // If requested highlight color is a new color, then we

        // want to change it

 

        if (ho.HighlightColor != res.Value)

          ho.HighlightColor = (short)res.Value;

 

        doc.Editor.Regen();

      }

      return (short)res.Value;

    }

  }

 

  // Custom base class with a highlight text property

 

  public class HighlightOverrule : DrawableOverrule

  {

    private Type[] _types;

 

    public HighlightOverrule(short col = 1, Type[] entTypes = null)

    {

      _types = entTypes;

      _color = col;

    }

 

    // Color index used to highlight

 

    private short _color;

 

    // The color we highlight blocks with

 

    public short HighlightColor

    {

      get { return _
color; }

      set

      {

        if ((value >= 0) && (value <= 127))

        {

          _color = value;

        }

      }

    }

 

    public void Add()

    {

      // Call Overrule.Remove for each of the passed-in types

 

      foreach (Type t in _types)

      {

        // Use try-catch, as Overrule.HasOverrule() needs an

        // instance of an AutoCAD object, and we just have the

        // type

 

        try

        {

          Overrule.AddOverrule(RXObject.GetClass(t), this, false);

        }

        catch

        { }

      }

    }

 

    public void Remove()

    {

      // Call Overrule.Remove for each of the passed-in types

 

      foreach (Type t in _types)

      {

        // Use try-catch, as Overrule.HasOverrule() needs an

        // instance of an AutoCAD object, and we just have the

        // type

 

        try

        {

          Overrule.RemoveOverrule(RXObject.GetClass(t), this);

        }

        catch

        { }

      }

    }

  }

 

 

  // Overrule to highlight text (MText and DBText) objects

 

  public class TextHighlightOverrule : HighlightOverrule

  {

    public TextHighlightOverrule(short col = 1, Type[] types = null)

      : base(

          col,

          types == null ?

            new Type[] { typeof(DBText), typeof(MText) } : types

      )

    {

      SetCustomFilter();

    }

 

    public override bool WorldDraw(Drawable drawable, WorldDraw wd)

    {

      // Registered for MText and DBText, so proceed cautiously

 

      var ent = drawable as Entity;

      if (ent != null)

      {

        var mt = ent as MText;

        var dt = ent as DBText;

 

        if (mt != null || dt != null)

        {

        &#
160; var norm = (mt == null ? dt.Normal : mt.Normal);

 

          // Now we want to draw a box around the extents

 

          var ext = ent.Bounds;

          if (ext.HasValue)

          {

            var maxPt = ext.Value.MaxPoint;

            var minPt = ext.Value.MinPoint;

 

            // These are the vertices of the highlight box

            // (it also contains a cross, for fun)

 

            var pts =

              new Point3dCollection(

                new Point3d[]

                {

                  new Point3d(minPt.X, minPt.Y, minPt.Z),

                  new Point3d(maxPt.X, maxPt.Y, maxPt.Z)

                }

              );

 

            // Store old graphics color and lineweight

 

            short oldColor = wd.SubEntityTraits.Color;

            wd.SubEntityTraits.Color = this.HighlightColor;

 

            var oldLineweight = wd.SubEntityTraits.LineWeight;

            wd.SubEntityTraits.LineWeight = LineWeight.LineWeight070;

 

            // Draw the polyline

 

            wd.Geometry.Polyline(pts, norm, IntPtr.Zero);

 

            // Restore old settings

 

            wd.SubEntityTraits.LineWeight = oldLineweight;

            wd.SubEntityTraits.Color = oldColor;

          }

        }

      }

 

      // Let the overruled Drawable draw itself

 

      return base.WorldDraw(drawable, wd);

    }

 

    public override bool IsApplicable(RXObject overruledSubject)

    {

      // Extract the string we need to check from the types

      // of entity this overrule works on (DBText & MText)

 

      var stringToCheck = "";

 

      var dt = overruledSubject as DBText;

      if (dt != null)

      {

        stringToCheck = dt.TextString;

      }

      var mt = overruledSubject as MText;

      if (mt != null)

      {

        stringToCheck = mt.Text;

      }

 

      return IsDashedOrWhitespace(stringToCheck);

    }

 

    private static bool IsDashedOrWhitespace(string text)

    {

      if (String.IsNullOrEmpty(text))

  
      return false;

 

      // Use a simple Regular Expression to check whether

      // the string passed in consists only of spaces and/or

      // underlines

 

      return

        Regex.Match(

          text, "^[ _]+$", RegexOptions.IgnoreCase

        ).Success;

    }

  }

}

The HDT command shows dashed text using an overrule based primarily on the one we saw previously, while the EDT command goes through and erases the same entities.

They actually function independently, even if I've chosen to use TextHighlightOverrule.IsApplicable() from the EDT command as the "single source of truth" on whether the object we're iterating over deserves deletion. This function also works to identify AttributeReferences within a BlockReference as the AttributeReference class is derived from DBText.

Here's a sample drawing with a number of dashed text objects:

Our sample drawing

Here's what happens when we run the HDT command selecting 3 as the highlight colour to display the dashed text objects in green:

Our highlighted objects

And when we run EDT, we see the objects all get erased properly:

And now with the dashed text objects erased

Thanks for the interesting question, Josh!

4 responses to “Highlighting and removing blanked out AutoCAD text using .NET”

  1. Hi,

    how can i display only one text object when the two text objects overlap on one another uploads.disquscdn.c...

    1. Hi Kumar,

      Please post your support request to the relevant AutoCAD developer forum.

      Thank you,

      Kean

  2. I get that you wouldn't know that "HighlightOverrule" would become a feature of AutoCAD .net (in the 2010 blog post), but it would have been nice if you renamed the class in this blog post to something else to prevent confusion between your class and the AutoCAD class with the same name. Took me a little while to figure out why WorldDraw override wasn't available. At least I think the "HighlightOverrule" was available when this blog post was written in 2014.

    1. Kean Walmsley Avatar

      I very much doubt the AutoCAD version (which I've just heard about from your comment) was available back in 2014: I would have done exactly as you've suggested, if it were.

      Thanks for posting, in any case, as I'm sure this will help others finding the same problem.

      Kean

      1. It was available in 2014 based, on this blog post, from Spiderinnet1 in 2012: spiderinnet1.typepa...

        I looked it up, and it's in the 2012 ObjectARX help file. Its tagged with "MG2008New", but I wasn't able to find what that means. I suspect that it was included in the AutoCAD 2008 release as that is the .NET release change for 64-bit: through-the-interfa...

        1. Kean Walmsley Avatar

          I should have said that it's certainly possible (however surprising that I find it) that I missed this, back when I wrote the code. What surprises me most is that the code as it stands (just looking at it - I don't have a dev environment set up for AutoCAD, these days) wouldn't build without errors, unless I targeted an older version of the .NET SDK (again, this would surprise me). There's clearly a name clash.

          Anyway, at this stage we're nearly a decade on, and honestly I don't have the time to worry about what might have happened way back when. The main thing - as I said in my last comment - is that your comment will hopefully help others who come across this.

          Kean

Leave a Reply to Chris Fugitt Cancel reply

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