October 27, 2011

Creating a legend of AutoCAD drawings using .NET

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 thesepreviousposts.

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

{

publicclassCommands

{

[CommandMethod("LEGEND")]

publicvoid 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 =

newOpenFileDialog(

"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 =

newPromptIntegerOptions(

"\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 =

newPromptKeywordOptions(

"\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 =

newPromptKeywordOptions(

"\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

constint cellWidth = 3;

constint cellHeight = 3;

// Create the table

Table tb = newTable();

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;

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 = newDatabase(false, true))

{

// First we read in the DWG

src.ReadDwgFile(

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

);

// 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:

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: