Generating larger preview images for all blocks in an AutoCAD drawing using .NET

There was an interesting coincidence, earlier in the week. A blog comment came in on an old post on the exact day there was an internal email discussion on the same topic.

The post was about using the BLOCKICON command on a document that gets loaded into the editor just for that purpose, generating a thumbnail image on disk for each of the contained block definitions. The problem with this approach โ€“ as was suggested by a couple of people in blog comments โ€“ is that the generated thumbnail is really small at just 32 x 32 pixels.

The exact same question โ€“ and concern โ€“ came up on an internal email discussion and the following solution was suggested by Kun Qian from our Shanghai office: to use CMLContentSearchPreviews.GetBlockTRThumbnail(BlockTableRecord blockTR) to generate them, instead.

I was instantly intrigued, as I hadn't come across this function before (and thought it might be flagged as internal-only), but โ€“ sure enough โ€“ it's part of the public .NET API, in the Autodesk.AutoCAD.Windows.Data namespace.

Here's an adapted version of the C# code in the older post that allows us to compare and contrast the two techniques:

using System.IO;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Windows.Data;

 

namespace BlockPreviews

{

  public class Commands

  {

    [CommandMethod("GBP", CommandFlags.Session)]

    public static void GenerateBlockPreviews()

    {

      var ed =

        Application.DocumentManager.MdiActiveDocument.Editor;

 

      var res =

        ed.GetFileNameForOpen(

          "Select file for which to generate previews"

        );

      if (res.Status != PromptStatus.OK)

        return;

 

      string dwgname = res.StringResult;

      Document doc = null;

 

      try

      {

        doc =

          Application.DocumentManager.Open(dwgname, false);

      }

      catch

      {

        ed.WriteMessage("\nUnable to read drawing.");

        return;

      }

 

      var db = doc.Database;

&
#160;

      string path = Path.GetDirectoryName(dwgname),

             name = Path.GetFileName(dwgname),

             iconPath = path + "\\" + name + " icons";

 

      int numIcons = 0;

 

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

      {

        var bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId, OpenMode.ForRead

          );

 

        foreach (ObjectId btrId in bt)

        {

          var btr =

            (BlockTableRecord)tr.GetObject(

              btrId, OpenMode.ForRead

            );

 

          // Ignore layouts and anonymous blocks

 

          if (btr.IsLayout || btr.IsAnonymous)

            continue;

 

          // Attempt to generate an icon, where one doesn't exist

 

          if (btr.PreviewIcon == null)

          {

            object ActiveDocument = doc.GetAcadDocument();

            object[] data = { "_.BLOCKICON " + btr.Name + "\n" };

            ActiveDocument.GetType().InvokeMember(

              "SendCommand",

              System.Reflection.BindingFlags.InvokeMethod,

              null, ActiveDocument, data

            );

          }

 

          // Hopefully we now have an icon

 

          if (btr.PreviewIcon != null)

          {

            // Create the output directory, if it isn't yet there

 

            if (!Directory.Exists(iconPath))

              Directory.CreateDirectory(iconPath);

 

            var fname = iconPath + "\\" + btr.Name + ".bmp";

 

            // Delete the image if it already exists

 

            if (File.Exists(fname))

              File.Delete(fname);

 

            // Save the icon to our out directory

 

            btr.PreviewIcon.Save(fname);

 

            // Increment our icon counter

 

            numIcons++;

          }

        }

        tr.Commit();

      }

      doc.CloseAndDiscard();

 

      ed.WriteMessage(

        "\n{0} block icons saved to \"{1}\".", numIcons, iconPath

      );

    }

 

    [CommandMethod("GBP2")]

    public static void GenerateBlockPreviews2()

    {

      Editor ed =

        Application.DocumentManager.MdiActiveDocument.Editor;

 

      PromptFileNameResult res =

        ed.GetFileNameForOpen(

          "Select file for which to generate previews"

        );

      if (res.Status != PromptStatus.OK)

        return;

 

      string dwgname = res.StringResult;

      int numIcons;

      string iconPath;

 

      // We don't need a document for access to the BlockTable

 

      using (var db = new Database(false, true))

      {

        try

        {

          db.ReadDwgFile(

            dwgname,

            FileOpenMode.OpenForReadAndReadShare,

            true,

            ""

          );

        }

        catch

        {

          ed.WriteMessage("\nUnable to read drawing.");

          return;

        }

 

        var path = Path.GetDirectoryName(dwgname);

        var name = Path.GetFileName(dwgname);

        iconPath = path + "\\" + name + " icons";

 

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

        {

          var bt =

            (BlockTable)tr.GetObject(

              db.BlockTableId, OpenMode.ForRead

            );

 

          numIcons = ExtractThumbnails(iconPath, tr, bt);

 

          tr.Commit();

        }

      }

 

      ed.WriteMessage(

        "\n{0} block icons saved to \"{1}\".", numIcons, iconPath

      );

    }

 

    [CommandMethod("GBPC")]

    public static void GenerateCurrentBlockPreviews()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      var ed = doc.Editor;

      var db = doc.Database;

 

      var path = (string)Application.GetSystemVariable("DWGPREFIX");

      var name = (string)Application.GetSystemVariable("DWGNAME");

      var iconPath = path + name + " icons";

 

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

      {

        var bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId, OpenMode.ForRead

          );

 

        int numIcons = ExtractThumbnails(iconPath, tr, bt);

 

        tr.Commit();

 

        ed.WriteMessage(

          "\n{0} block icons saved to \"{1}\".", numIcons, iconPath

        );

      }

    }

 

    private static int ExtractThumbnails(

      string iconPath, Transaction tr, BlockTable bt

    )

    {

      int numIcons = 0;

 

      foreach (ObjectId btrId in bt)

      {

        var btr =

          (BlockTableRecord)tr.GetObject(

            btrId, OpenMode.ForRead

          );

 

        // Ignore layouts and anonymous blocks

 

        if (btr.IsLayout || btr.IsAnonymous)

          continue;

 

        // Attempt to generate an icon, where one doesn't exist

 

        {

   
60;     
// Create the output directory, if it isn't yet there

 

          if (!Directory.Exists(iconPath))

            Directory.CreateDirectory(iconPath);

 

          // Save the icon to our out directory

 

          var imgsrc =

            CMLContentSearchPreviews.GetBlockTRThumbnail(btr);

          var bmp =

            ImageSourceToGDI(

              imgsrc as System.Windows.Media.Imaging.BitmapSource

            );

 

          var fname = iconPath + "\\" + btr.Name + ".bmp";

 

          if (File.Exists(fname))

            File.Delete(fname);

 

          bmp.Save(fname);

 

          // Increment our icon counter

 

          numIcons++;

        }

      }

      return numIcons;

    }

 

    // Helper function to generate an Image from a BitmapSource

 

    private static System.Drawing.Image ImageSourceToGDI(

      System.Windows.Media.Imaging.BitmapSource src

    )

    {

      var ms = new MemoryStream();

      var encoder =

        new System.Windows.Media.Imaging.BmpBitmapEncoder();

      encoder.Frames.Add(

        System.Windows.Media.Imaging.BitmapFrame.Create(src)

      );

      encoder.Save(ms);

      ms.Flush();

      return System.Drawing.Image.FromStream(ms);

    }

  }

}

 

 

The code implements a helper function that iterates thr
ough a block table and creates thumbnails for each block (I could certainly have decomposed the process further, creating a function to export a single block, for instance, but that's left up to the reader).

Then there are two new commands: GBP2 and GBPC. GBP2 is an exact equivalent of the GBP command, but it uses the static GetBlockTRThumbnail() method โ€“ via our new helper function โ€“ to generate a thumbnail image for a particular BlockTableRecord. And this function doesn't need a document to be open โ€“ unlike the GBP command โ€“ so it's really much cleaner. GBPC calls the same helper but this time on the current drawing.

Here are the results of using the GBP command on one of the AutoCAD sample files. You can see the images are 32 x 32 pixels.

Results of GBP commandHere are the results of using GBP2 on the same file. Each of the images is now 190 x 120 pixels.

Results of GBP2 command

That's really much better. Many thanks for the tip, Kun! ๐Ÿ™‚

14 responses to “Generating larger preview images for all blocks in an AutoCAD drawing using .NET”

  1. Since this is a recent post CMLContentSearchPreviews must be included in a newer API than what is delivered with AutoCAD 2012. I am using AutoCAD 2012 and it is not available in the AutoCAD.Windows.Data namespace.

  2. Yes, from what I can tell it was added in AutoCAD 2013.

    Kean

  3. Is there a known equivalent to this approach using native C++ ARX?

  4. Not that I'm aware of (off the top of my head). I had a quick look at the IL in acmgd.dll, but found it used a function called getBlockTableRecordThumbnail() that doesn't appear to be in the public API).

    You might try posting to ADN or the discussion groups, to see if anyone has something more to share.

    Kean

  5. Hi Kean,

    object ActiveDocument = doc.GetAcadDocument();
    object[] data = { "_.BLOCKICON " + btr.Name + "\n" };
    ActiveDocument.GetType().InvokeMember(
    "SendCommand",
    System.Reflection.BindingFlags.InvokeMethod,
    null, ActiveDocument, data
    );

    Sometimes its working sometime its not. If i encountered an error, autocad shutsdowns, then we i debug again, result will always be null. I have to wait for a few minutes to execute again.

    I am trying to get the image into the picture box using
    System.Drawing.Image img = blk.PreviewIcon

    but if i get to convert it successfully autocad again crashes.

    1. Hi Mark,

      What version of AutoCAD are you using?

      If 2015, you'll be better off using Editor.Command().

      Otherwise I'll need some steps to reproduce this...

      Kean

  6. Kean - I really like this concept to generate larger images of the blocks. I was wondering if there was any way to avoid writing the bmp files to disk and simply display the bmp in a image control on a windows form. One image control would be added for each bmp. As another alternative, is it possible to simply display the blocks (bmps) in a tool pallette - the pallette would be created on the fly and then displayed giving the user the ability to select a "block" for insertion. I see a lot of these type questions all over the net so if you could possibly show both options I'm certain many people would find them very useful. - Thank you!!

    1. Billy,

      It's really a choice whether we save the bitmap to disk: we could also display it directly in a form.

      Tool palettes would probably need the data stored to disk, though.

      Regards,

      Kean

  7. To avoid writing bmp files to disk and simply showing the thumbnail in a PictureBox Control here is an example I came up with...

    _acDocumentManager = Application.DocumentManager

    _acActiveDocument = _acDocumentManager.MdiActiveDocument

    _acActiveDocumentDatabase = _acActiveDocument.Database

    _acActiveDocumentEditor = _acActiveDocument.Editor

    Me.HeaderVersionUltraLabel.Text = "Version: " & GetType(AutoCAD2015TestClass).Assembly.GetName().Version.ToString

    Dim acTrans As Transaction = _acActiveDocumentDatabase.TransactionManager.StartTransaction

    Using acTrans

    Dim acBlockTable As BlockTable = acTrans.GetObject(_acActiveDocumentDatabase.BlockTableId, OpenMode.ForRead)

    For Each acBlockTableRecordId As ObjectId In acBlockTable

    Dim acBlockTableRecord As BlockTableRecord = CType(acTrans.GetObject(acBlockTableRecordId, OpenMode.ForRead), BlockTableRecord)

    If acBlockTableRecord.Name = "INSTAG8" Then

    Dim imgBF As System.Windows.Media.Imaging.BitmapFrame

    imgBF = System.Windows.Media.Imaging.BitmapFrame.Create(CMLContentSearchPreviews.GetBlockTRThumbnail(acBlockTableRecord))

    Dim imgEncoder As New System.Windows.Media.Imaging.BmpBitmapEncoder

    imgEncoder.Frames.Add(imgBF)

    Dim imgStream As New MemoryStream

    imgEncoder.Save(imgStream)

    Me.PictureBox1.Image = New System.Drawing.Bitmap(imgStream) 'Image is 190 x 120

    Me.PictureBox1.Tag = acBlockTableRecord.Name

    End If

    Next

    End Using 'acTrans

  8. I think I have a problem with the BlockTableRecotd bounds. The image is shown but it has more black area than the drawing block.
    When I select the inserted blockReference two invisible lines appear, almost as long as the black screen area.
    Could it be there's a hidden element on the block, or can I change the bounds when creating the image preview.
    I'm using GBP2 mode.

    Thanks for the help, I'm using AutoCAD 2016

    1. Kean Walmsley Avatar

      Yes - I'd try the code with other blocks and take a look at what's in this particular one.

      Kean

      1. Is it possible to generate the previews of the blocks but with the attributes filled in?

Leave a Reply to Miguel Cancel reply

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