Kean Walmsley


  • About the Author
    Kean on Google+

April 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      







« Using Project Falcon with a ReCap Photo model – Part 2 | Main | Finding an AutoCAD spline between two others using .NET »

June 28, 2013

DGN clean-up tool on its way…

The first post I had planned for this week was to announce the availability of an officially-supported tool based on the code in this blog post from late last year.

The tool was posted to the Autodesk web-site late last week. Over the weekend, Jimmy Bergmark pointed out an issue with the tool that led to me requesting that the tool be removed, for now: for certain linetypes based on DGN linestyles, the DGNPURGE command resulted in the linetypes having missing components when the drawings were reloaded. Jimmy kindly provided a DWG that helped identify the problem: my original code had allowed for references between stroke objects but hadn’t allowed for “compound” components that contain direct references to other strokes.

Which means that – when we go through our list of stroke objects, stripping those out that have been referenced by linetypes that are currently in use – we fail to identify certain stokes as being referenced, and so strip them out even when still needed.

It was simple enough to fix the code: we already recurse for references from one stroke to another, it was straightforward to check the references on any component types we believe may have these direct references and then recurse for each of the references, too.

Here’s the updated C# code for the DGNPURGE command (you’ll need to pick up the ReferenceFiler.cs contents from the original post – that file hasn’t changed). This code is currently being reviewed internally before being built into another version of the officially-supported tool for reposting, but having it here will allow people to make use of it, in the meantime.

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();

            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.EndsWith("PatternComponent"))

      {

        // 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);

        }

      }

    }

  }

}

I’ll be sure to post an update once the tool has been tested and re-posted. Many thanks, once again, to Jimmy Bergmark for helping identify this problem.

blog comments powered by Disqus

10 Random Posts