Creating a legend of AutoCAD drawings using .NET

This interesting question came up in our discussion forums:

Does anyone have a routine that will insert all the drawings from a single folder into one drawing to create a legend sheet? I'm trying to document the company's various blocks and details for dissemination amongst several offices.

The simplest – and most elegant, in my opinion – approach for addressing this requirement is via the Table object, which allows you to include block thumbnails in each of its cells. So we would need to import the various drawings into the current drawing as blocks, and then point the various cells of the table object at each of them, in turn. Thankfully I could borrow a good amount of the code to do this from these previous posts.

Here's the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Windows;

using System.IO;

using System.Linq;

 

namespace MythsAndLegends

{

  public class Commands

  {

    [CommandMethod("LEGEND")]

    public void CreateLegend()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      // Ask the user to select the drawings to list in the table

 

      OpenFileDialog ofd =

        new OpenFileDialog(

          "Select drawings to add to legend",

          null,

          "dwg",

          "DrawingsToImport",

          OpenFileDialog.OpenFileDialogFlags.AllowMultiple

        );

 

      System.Windows.Forms.DialogResult dr = ofd.ShowDialog();

 

      if (dr != System.Windows.Forms.DialogResult.OK)

        return;

 

      // Get the list of filenames and say how many were selected

 

      string[] names = ofd.GetFilenames();

      ed.WriteMessage(

        "\n{0} files selected.", names.Length

      );

 

      // If we have some selected, we'll create a table

 

      if (names.Length > 0)

      {

        int numRows, numCols;

 

        // If only one was selected, we know the table will

        // be 1x1

 

        if (names.Length == 1)

        {

          numRows = 1;

          numCols = 1;

        }

        else

        {

          // Otherwise we prompt for the number of columns

 

          PromptIntegerOptions opts =

            new PromptIntegerOptions(

              "\nEnter number of columns: "

            );

 

          opts.LowerLimit = 1;

          opts.UpperLimit = names.Length;

          opts.DefaultValue = 1;

          opts.UseDefaultValue = true;

 

          PromptIntegerResult pir = ed.GetInteger(opts);

 

          if (pir.Status != PromptStatus.OK)

            return;

 

          // Get whether to start at the top or the bottom

 

          PromptKeywordOptions pko =

            new PromptKeywordOptions(

              "\nPopulate from the top or from the bottom?"

            );

          pko.AllowNone = true;

          pko.Keywords.Add("Top");

          pko.Keywords.Add("Bottom");

          pko.Keywords.Default = "Top";

 

          PromptResult pkr = ed.GetKeywords(pko);

 

          bool fromTop = (pkr.StringResult == "Top");

 

          // Get whether to start at the top or the bottom

 

          pko =

            new PromptKeywordOptions(

              "\nList alphabetically or in selected order?"

            );

          pko.AllowNone = true;

          pko.Keywords.Add("Alphabetically");

          pko.Keywords.Add("Selected");

          pko.Keywords.Default = "Alphabetically";

 

          pkr = ed.GetKeywords(pko);

 

          bool sort = (pkr.StringResult == "Alphabetically");

 

          // And the insertion point of the table

 

          PromptPointResult pr =

            ed.GetPoint("\nEnter table insertion point: ");

 

          if (pr.Status == PromptStatus.OK)

          {

            Transaction tr =

              doc.TransactionManager.StartTransaction();

 

            using (tr)

            {

              // Use constants for the cell dimensions

 

              const int cellWidth = 3;

              const int cellHeight = 3;

 

              // Create the table

 

              Table tb = new Table();

              tb.TableStyle = db.Tablestyle;

 

              // We can calculate the number of rows based on

              // the number of cells and the number of columns

 

              numCols = pir.Value;

      &#
160;       numRows = (names.Length / numCols) + 1;

 

              // We'll add in our custom columns and rows

 

              if (numCols > 0)

              {

                tb.InsertColumns(1, cellWidth, numCols);

              }

              if (numRows > 0)

              {

                tb.InsertRows(1, cellHeight, numRows);

              }

 

              // And then delete the original row/column

              // that comes with the blank table

 

              tb.DeleteRows(0, 1);

              tb.DeleteColumns(0, 1);

 

              tb.Position = pr.Value;

 

              if (sort)

              {

                names = names.OrderBy(x => x).ToArray<string>();

              }

 

              // Loop through the names, adding them to the table

 

              for (int i = 0; i < names.Length; i++)

              {

                string blockName = "";

 

                try

                {

                  // Use a Database to insert a block for each

                  // drawing into the current drawing

 

                  using (Database src = new Database(false, true))

                  {

                    // First we read in the DWG

 

                    src.ReadDwgFile(

                      names[i], FileShare.Read, true, ""

          
0;         );

 

                    // Take the name of the file without the

                    // extension

 

                    blockName =

                      Path.GetFileNameWithoutExtension(names[i]);

 

                    // Check whether it works as a symbol table

                    // name (will thrown an exception if not)

 

                    SymbolUtilityServices.ValidateSymbolName(

                      blockName, false

                    );

 

                    // Insert our drawing as a block (which

                    // will take the modelspace)

 

                    ObjectId blockId =

                      db.Insert(blockName, src, false);

 

                    // Calculate the row and column for this item

                    // If from the top: we just divide by the

                    // number of columns to get the row

                    // If from the bottom, subtract from

                    // the total rows

 

                    int row =

                      (fromTop ?

                        i / numCols :

                        numRows - (i / numCols + 1)

                      );

 

                    // The column is just the modulus remainder

 

                    int col = i % numCols;

 

                    // Insert the block as the contents of our cell

 

                    Cell cell = tb.Cells[row, col];

                    cell.Contents.InsertAt(0);

                    cell.Contents[0].BlockTableRecordId =

                       blockId;

                  }

                }

                catch (System.Exception ex)

                {

                  ed.WriteMessage(

                    "\nCould not add \"{0}\": {1}",

                    blockName, ex.Message

                  );

                }

              }

              tb.GenerateLayout();

 

              // Finally we add our table to modelspace

 

              BlockTable bt =

                (BlockTable)tr.GetObject(

                  doc.Database.BlockTableId,

                  OpenMode.ForRead

                );

 

              BlockTableRecord btr =

                (BlockTableRecord)tr.GetObject(

                  bt[BlockTableRecord.ModelSpace],

                  OpenMode.ForWrite

                );

              btr.AppendEntity(tb);

              tr.AddNewlyCreatedDBObject(tb, true);

              tr.Commit();

            }

          }

        }

      }

    }

  }

}

When we run the LEGEND command, we get asked to select some drawings:

Selecting a set of drawing files

We then get prompted with the number of drawings selected and get asked to provide some additional information to determine how the table gets created. We'll run the command twice, selecting the same set of AutoCAD sample drawings but using 3 and 4 columns, populating the two tables from the top and the bottom respectively.

Command: LEGEND

13 files selected.

Enter number of columns <1>: 3

Populate from the top or from the bottom? [Top/Bottom] <Top>: Top

List alphabetically or in selected order? [Alphabetically/Selected]

<Alphabetically>: Alphabetically

Enter table insertion point:

 

[Dump of block imports deleted...]

 

Command: LEGEND

13 files selected.

Enter number of columns <1>: 4

Populate from the top or from the bottom? [Top/Bottom] <Top>: Bottom

List alphabetically or in selected order? [Alphabetically/Selected]

<Alphabetically>: Alphabetically

Enter table insertion point:

 

[Dump of block imports deleted...]

We can see that the tables contain the same order of blocks, but the first is wider and was populated from the top-left rather than the bottom-left:Two legend tables created from a folder of drawings

13 responses to “Creating a legend of AutoCAD drawings using .NET”

  1. Could you provide the code already compiled for those of us that barely know what to do with the compile code, much less having the software to do the compiling? Could you do this for all the code you show on here?

    Thanks

  2. Unfortunately not. I can certainly point you at some steps to help you build a loadable module out of the code in my blog, but there's one big reason I can't post compiled product modules: working for a publicly-traded US company, there are strict accounting rules around the provision of "free" product updates and consequent revenue recognition.

    I do provide applications via Autodesk Labs, which has legal language in place that allows our users to get hold of such functionality without the risk of impacting the company from a financial perspective, but I don't have that on my blog.

    If you're unable to build the code into an application yourself, someone on our AutoCAD .NET Discussion Forum may be able to do it for you.

    Sorry - I sincerely wish I was in a position to help.

    Kean

  3. Kean,

    Have you tested this with annotative blocks? There was a limitation in 2011 and lower that prevents you from inserting an annotative block into a table cell. I don't have 2012 to determine if this has changed.

  4. Stacy,

    I admit I have not - but as we're inserting blocks created from the modelspaces of external DWGs, I doubt this would be a problem for this particular scenario.

    Regards,

    Kean

  5. Alberto Venturini Avatar
    Alberto Venturini

    Hi kean,
    great code! But there is a bug...

    When i have two different block definitions with the same name (sometime could happen) , the block of the second drawing will be redefined with the first one.

    I need to rename all the block definitions in every drawing with the filename as prefix (like an external reference).

    Anyway, thank you for every line of your code.

    P.S.
    i think is very interesting all the post about kinect.

    Regards,
    Alberto Venturini

  6. Hi Albert,

    You're quite right - there are some cases where I haven't handled various issues related to block naming, etc. I should have mentioned that these were left as an exercise for the reader. 🙂

    Thanks for the feedback!

    Kean

  7. Simple and elegant - nice one!

  8. Can you create a dll file.

    Können Sie mir eine dll Datei erstellen.

    Danke

  9. Unfortunately not: please see my previous comment for an explanation.

    I believe someone on the discussion groups may have helped the last person that requested this.

    Kean

  10. Pradeep Jamkhandi Avatar
    Pradeep Jamkhandi

    hi Kean, I need your help,
    I have posted on the Discussion Group, about the problem I ma facing, I see that I did not get any good resposnes,
    I am getting this error !dbinsert834@ eNotdatabase, at this point in the code id = db.Insert(dwgName, sourceDb, False), it used to work for me before, but now it is throwing m an error.
    please help me.
    for detailed code: please see my post in discussion group:
    username : jamkhp
    subject : Insert Block not working.

    Thanks in advance.

  11. Sorry - I really don't have time to provide support (unless *directly* related to the code in one of my posts).

    Kean

  12. Hello Kean,
    I am working with 3D Plant design and I am trying to find a way of creating drawing legend from my P&ID drawing.
    by passing through forums I found your code.

    does it work for my case too? if yes. How do I introduce it to my P&ID? Do I need a software or just through Plant design?

    Thank you

    Nawfel

  13. Hello Nawfel,

    It'd work with Plant 3D/P&ID drawings at the file level - just as it would for AutoCAD files - but would clearly need work to do anything more granular (i.e. with the file contents).

    To load it into your AutoCAD-based product you'll need to build it into a DLL using Visual Studio (or Visual C# Express) and NETLOAD it into the product.

    Regards,

    Kean

Leave a Reply to Hugo Cancel reply

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