November 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            










« A quick trip back to the Fab Lab | Main | More knots in Tinkercad using JavaScript »

July 15, 2013

Minor update to the DGN Purge command

For those who have (quite understandably) not been following the story unfolding around this tool, here’s a quick timeline so far. I’ve largely pieced this together from emails, as I don’t typically tag blog updates with a date (something it seems I ought to consider starting).

  • 12/12/12 – In response to a request from our Product Support team, the original implementation was posted to this blog.
  • 03/28/13 – I posted an update to the original post with a minor fix to handle corrupted drawings a bit better (Update).
  • 06/21/13 – A tool based on the (updated) code was published as an official tool (Update 2).
  • 06/23/13 – Jimmy Bergmark identified an issue with the code, in that it purged information needed by compound linestyles even when in use. We removed the tool and I submitted an updated version of the code for review (Update 3).

The code has since been reviewed by the developers of the feature (thanks, Scott and Markus!), and a couple of suggestions have been made. Based on feedback – and confirmation of the specific compound linestyle components that contain direct references to other components – I’ve adjusted the code to look for two specific classes, “AcDbLSCompoundComponent” and “AcDbLSPointComponent” (rather than performing pattern-matching). I’ve also added a small section of code that follows the references on “AcDbLSSymbolComponent” objects and erases their associated anonymous blocks: this should remove (or at least reduce) the need to call a traditional PURGE after DGNPURGE has completed.

All in all these are minor changes – in addition to the more important fix of properly handling compound linestyle components – but worth publishing here for people to pick up.

The following C# code is now being rebuilt and tested by our QA team, and should form the basis of the republished tool (along with the “ReferenceFiler” implementation that has remained unchanged from the original post).

using System;

using System.Runtime.InteropServices;

using Autodesk.AutoCAD.ApplicationServices.Core;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Runtime;

using System.Collections.ObjectModel;

 

namespace DgnPurger

{

  public class Commands

  {

    const string dgnLsDefName = "DGNLSDEF";

    const string dgnLsDictName = "ACAD_DGNLINESTYLECOMP";

 

    public struct ads_name

    {

      public IntPtr a;

      public IntPtr b;

    };

 

    [DllImport("acdb19.dll",

      CharSet = CharSet.Unicode,

      CallingConvention = CallingConvention.Cdecl,

      EntryPoint = "acdbHandEnt")]

    public static extern int acdbHandEnt(string h, ref ads_name n);

 

    [CommandMethod("DGNPURGE")]

    public void PurgeDgnLinetypes()

    {

      var doc =

        Application.DocumentManager.MdiActiveDocument;

      var db = doc.Database;

      var ed = doc.Editor;

 

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

      {

        // Start by getting all the "complex" DGN linetypes

        // from the linetype table

 

        var linetypes = CollectComplexLinetypeIds(db, tr);

 

        // Store a count before we start removing the ones

        // that are referenced

 

        var ltcnt = linetypes.Count;

 

        // Remove any from the "to remove" list that need to be

        // kept (as they have references from objects other

        // than anonymous blocks)

 

        var ltsToKeep =

          PurgeLinetypesReferencedNotByAnonBlocks(db, tr, linetypes);

 

        // Now we collect the DGN stroke entries from the NOD

 

        var strokes = CollectStrokeIds(db, tr);

 

        // Store a count before we start removing the ones

        // that are referenced

 

        var strkcnt = strokes.Count;

 

        // Open up each of the "keeper" linetypes, and go through

        // their data, removing any NOD entries from the "to

        // remove" list that are referenced

 

        PurgeStrokesReferencedByLinetypes(tr, ltsToKeep, strokes);

 

        // Erase each of the NOD entries that are safe to remove

 

        int erasedStrokes = 0;

 

        foreach (ObjectId id in strokes)

        {

          try

          {

            var obj = tr.GetObject(id, OpenMode.ForWrite);

            obj.Erase();

            if (

              obj.GetRXClass().Name.Equals("AcDbLSSymbolComponent")

            )

            {

              EraseReferencedAnonBlocks(tr, obj);

            }

            erasedStrokes++;

          }

          catch (System.Exception ex)

          {

            ed.WriteMessage(

              "\nUnable to erase stroke ({0}): {1}",

              id.ObjectClass.Name,

              ex.Message

            );

          }

        }

 

        // And the same for the complex linetypes

 

        int erasedLinetypes = 0;

 

        foreach (ObjectId id in linetypes)

        {

          try

          {

            var obj = tr.GetObject(id, OpenMode.ForWrite);

            obj.Erase();

            erasedLinetypes++;

          }

          catch (System.Exception ex)

          {

            ed.WriteMessage(

              "\nUnable to erase linetype ({0}): {1}",

              id.ObjectClass.Name,

              ex.Message

            );

          }

        }

 

        // Remove the DGN stroke dictionary from the NOD if empty

 

        var nod =

          (DBDictionary)tr.GetObject(

            db.NamedObjectsDictionaryId, OpenMode.ForRead

          );

 

        ed.WriteMessage(

          "\nPurged {0} unreferenced complex linetype records" +

          " (of {1}).",

          erasedLinetypes, ltcnt

        );

 

        ed.WriteMessage(

          "\nPurged {0} unreferenced strokes (of {1}).",

          erasedStrokes, strkcnt

        );

 

        if (nod.Contains(dgnLsDictName))

        {

          var dgnLsDict =

            (DBDictionary)tr.GetObject(

              (ObjectId)nod[dgnLsDictName],

              OpenMode.ForRead

            );

 

          if (dgnLsDict.Count == 0)

          {

            dgnLsDict.UpgradeOpen();

            dgnLsDict.Erase();

 

            ed.WriteMessage(

              "\nRemoved the empty DGN linetype stroke dictionary."

            );

          }

        }

 

        tr.Commit();

      }

    }

 

    // Collect the complex DGN linetypes from the linetype table

 

    private static ObjectIdCollection CollectComplexLinetypeIds(

      Database db, Transaction tr

    )

    {

      var ids = new ObjectIdCollection();

 

      var lt =

        (LinetypeTable)tr.GetObject(

          db.LinetypeTableId, OpenMode.ForRead

        );

      foreach (var ltId in lt)

      {

        // Complex DGN linetypes have an extension dictionary

        // with a certain record inside

 

        var obj = tr.GetObject(ltId, OpenMode.ForRead);

        if (obj.ExtensionDictionary != ObjectId.Null)

        {

          var exd =

            (DBDictionary)tr.GetObject(

              obj.ExtensionDictionary, OpenMode.ForRead

            );

          if (exd.Contains(dgnLsDefName))

          {

            ids.Add(ltId);

          }

        }

      }

      return ids;

    }

 

    // Collect the DGN stroke entries from the NOD

 

    private static ObjectIdCollection CollectStrokeIds(

      Database db, Transaction tr

    )

    {

      var ids = new ObjectIdCollection();

 

      var nod =

        (DBDictionary)tr.GetObject(

          db.NamedObjectsDictionaryId, OpenMode.ForRead

        );

 

      // Strokes are stored in a particular dictionary

 

      if (nod.Contains(dgnLsDictName))

      {

        var dgnDict =

          (DBDictionary)tr.GetObject(

            (ObjectId)nod[dgnLsDictName],

            OpenMode.ForRead

          );

 

        foreach (var item in dgnDict)

        {

          ids.Add(item.Value);

        }

      }

 

      return ids;

    }

 

    // Remove the linetype IDs that have references from objects

    // other than anonymous blocks from the list passed in,

    // returning the ones removed in a separate list

 

    private static ObjectIdCollection

      PurgeLinetypesReferencedNotByAnonBlocks(

        Database db, Transaction tr, ObjectIdCollection ids

      )

    {

      var keepers = new ObjectIdCollection();

 

      // To determine the references from objects in the database,

      // we need to open every object. One reasonably efficient way

      // to do so is to loop through all handles in the possible

      // handle space for this drawing (starting with 1, ending with

      // the value of "HANDSEED") and open each object we can

 

      // Get the last handle in the db

 

      var handseed = db.Handseed;

 

      // Copy the handseed total into an efficient raw datatype

 

      var handseedTotal = handseed.Value;

 

      // Loop from 1 to the last handle (could be a big loop)

 

      var ename = new ads_name();

 

      for (long i = 1; i < handseedTotal; i++)

      {

        // Get a handle from the counter

 

        var handle = Convert.ToString(i, 16);

 

        // Get the entity name using acdbHandEnt()

 

        var res = acdbHandEnt(handle, ref ename);

 

        if (res != 5100) // RTNORM

          continue;

 

        // Convert the entity name to an ObjectId

 

        var id = new ObjectId(ename.a);

 

        // Open the object and check its linetype

 

        var obj = tr.GetObject(id, OpenMode.ForRead, true);

        var ent = obj as Entity;

        if (ent != null && !ent.IsErased)

        {

          if (ids.Contains(ent.LinetypeId))

          {

            // If the owner does not belong to an anonymous

            // block, then we take it seriously as a reference

 

            var owner =

              (BlockTableRecord)tr.GetObject(

                ent.OwnerId, OpenMode.ForRead

              );

            if (

              !owner.Name.StartsWith("*") ||

              owner.Name.ToUpper() == BlockTableRecord.ModelSpace ||

              owner.Name.ToUpper().StartsWith(

                BlockTableRecord.PaperSpace

              )

            )

            {

              // Move the linetype ID from the "to remove" list

              // to the "to keep" list

 

              ids.Remove(ent.LinetypeId);

              keepers.Add(ent.LinetypeId);

            }

          }

        }

      }

      return keepers;

    }

 

    // Remove the stroke objects that have references from

    // complex linetypes (or from other stroke objects, as we

    // recurse) from the list passed in

 

    private static void PurgeStrokesReferencedByLinetypes(

      Transaction tr,

      ObjectIdCollection tokeep,

      ObjectIdCollection nodtoremove

    )

    {

      foreach (ObjectId id in tokeep)

      {

        PurgeStrokesReferencedByObject(tr, nodtoremove, id);

      }

    }

 

    // Remove the stroke objects that have references from this

    // particular complex linetype or stroke object from the list

    // passed in

 

    private static void PurgeStrokesReferencedByObject(

      Transaction tr, ObjectIdCollection nodIds, ObjectId id

    )

    {

      var obj = tr.GetObject(id, OpenMode.ForRead);

      if (obj.ExtensionDictionary != ObjectId.Null)

      {

        // Get the extension dictionary

 

        var exd =

          (DBDictionary)tr.GetObject(

            obj.ExtensionDictionary, OpenMode.ForRead

          );

 

        // And the "DGN Linestyle Definition" object

 

        if (exd.Contains(dgnLsDefName))

        {

          var lsdef =

            tr.GetObject(

              exd.GetAt(dgnLsDefName), OpenMode.ForRead

            );

 

          // Use a DWG filer to extract the references

 

          var refFiler = new ReferenceFiler();

          lsdef.DwgOut(refFiler);

 

          // Loop through the references and remove any from the

          // list passed in

 

          foreach (ObjectId refid in refFiler.HardPointerIds)

          {

            if (nodIds.Contains(refid))

            {

              nodIds.Remove(refid);

            }

 

            // We need to recurse, as linetype strokes can reference

            // other linetype strokes

 

            PurgeStrokesReferencedByObject(tr, nodIds, refid);

          }

        }

      }

      else if (

        obj.GetRXClass().Name.Equals("AcDbLSCompoundComponent") ||

        obj.GetRXClass().Name.Equals("AcDbLSPointComponent")

      )

      {

        // We also need to consider compound components, which

        // don't use objects in their extension dictionaries to

        // manage references to strokes...

 

        // Use a DWG filer to extract the references from the

        // object itself

 

        var refFiler = new ReferenceFiler();

        obj.DwgOut(refFiler);

 

        // Loop through the references and remove any from the

        // list passed in

 

        foreach (ObjectId refid in refFiler.HardPointerIds)

        {

          if (nodIds.Contains(refid))

          {

            nodIds.Remove(refid);

          }

 

          // We need to recurse, as linetype strokes can reference

          // other linetype strokes

 

          PurgeStrokesReferencedByObject(tr, nodIds, refid);

        }

      }

    }

 

    // Erase the anonymous blocks referenced by an object

 

    private static void EraseReferencedAnonBlocks(

      Transaction tr, DBObject obj

    )

    {

      var refFiler = new ReferenceFiler();

      obj.DwgOut(refFiler);

 

      // Loop through the references and erase any

      // anonymous block definitions

      //

      foreach (ObjectId refid in refFiler.HardPointerIds)

      {

        BlockTableRecord btr =

          tr.GetObject(refid, OpenMode.ForRead) as BlockTableRecord;

        if (btr != null && btr.IsAnonymous)

        {

          btr.UpgradeOpen();

          btr.Erase();

        }

      }

    }

  }

}

If you’d really like to build this yourself in advance of the official tool being available, this comment has some brief instructions. Hopefully the official tool will be reposted soon, but this should be of some help to people with an urgent need who are unfamiliar with .NET development.

Update:

The AutoCAD DGN Hotfix is now available. See this post for more details.

blog comments powered by Disqus

Feed/Share

10 Random Posts