Introduction
To implement a plug-in for AutoCAD is accomplished by using Autodesk’s ObjectARX for C# .NET. This initial explanation will include an introduction to passing AutoCAD commands and receiving messages. This will be followed with how to update named title blocks that have pre-defined attributes on the currently open drawing. Finally, we will side load all drawing files from a specified directory and update the title block of each.
To download the ObjectARX SDK for.NET and find more information, please start here. Make sure you download the SDK that matches your AutoCAD version. Check out the PDF of AutoCAD .NET developer’s Guide. The PDF is from an older version, to find newer online documentation look for the document section at the AutoCAD Developer Center. In addition, there are two blogs at Through the Interface and ADN Dev Blog.
Background
Part of my responsibility as a control engineer is to create electrical drawing sets for various manufacturing equipment. For over 14 years, I had the benefit of using AutoCAD Electrical for this task. The efficiency, ease of use, decreased human error, and power of AutoCAD Electrical became more obvious when I recently changed jobs. Processes that took seconds (e.g., updating title blocks on drawings) now took hours and were error prone. I decided to start writing code to replace some of those lost features, starting with automatic title block updates. Because I could find little help beyond Autodesk, I decided to give back to the community.
The Project
The plug-in for AutoCAD utilizes a C# .NET 4.7 class library project. I'm using 4.7 because I am targeting AutoCAD 2018 and windows 10. The SDK comes with several DLL files that can be referenced and used in your class library. Three of the DLL files (i.e., AcCoreMgd.dll, AcDbMgd.dll, and AcMgd.dll) are the most common and are referenced in this library project.
The below code shows the beginning of the Update Title Block class project. The using
statements utilize the referenced DLL files I described above. The field variables shown at the beginning of the class allow for hard coded values. In my production code, I utilize SQL database code to pull this information into my command methods.
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.ApplicationServices.Core;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.IO;
namespace UpdateTitleBlock
{
public class Revision
{
private string BlockName = "Your Block Name";
private Dictionary<string, string> Attributes = new Dictionary<string, string>
{
{"AttributeName1", "AttributeValue1"},
{"AttributeName2", "AttributeValue2"},
{"AttributeName3", "AttributeValue3"}
};
string path = "C:\\Users\\";
Using a Command
In the project properties Debug tab, add AutoCADs EXE file into the Start Action section. Now when you press Start button to test your code, the AutoCAD program will launch. Once AutoCAD is up, type NETLOAD into its command prompt. Then point to your UpdateTitleBlock.dll file inside your bin/debug folder. Now your plug-in is loaded and you can use your new commands. Note that you can debug as normal with break points and the like to solve any issues that may arise.
The below code shows a simple series of commands. Notice this command is marked with [CommandMethod("CMD")]
. This declaration both marks the method as a new AutoCAD command but also specifies the keyword to run the command. This Command Method uses CMD
as the keyword. Once the DLL is loaded, as described above, type CMD
in AutoCAD command prompt which will run the command.
This command is actually three commands in one (i.e., Regenerate, Zoom Extents, and then Quick Save). The commands are controlled through AutoCADs Editor from the active drawing. The Editor
object allows your code to interface with AutoCADs command prompt. This object can give commands directly (like this one does), give messages, and obtain input from the users. The ed.command()
parameter accepts an array, with each indices causing an enter key. For example, the zoom command puts ZOOM
in AutoCAD's command prompt followed by enter. The second element (E
) selects the extents option of ZOOM
.
[CommandMethod("CMD")]
public void Commands()
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
ed.Command("REGEN");
ed.Command("ZOOM", "E");
ed.Command("QSAVE");
}
Updating the Title Block
The below allows the active drawings title block attributes to be updated based on a hard coded Dictionary. The first step is to grab the database from the active drawing. This database stores all of the information of the drawing including the blocks. The title block is just one of many blocks that might be included in a drawing. Attributes is variable text in blocks. This text is what we are updating. Note that this code will only work if your title block is in model space and not in a layout.
The database is then used to start a transaction. Like all transactions, no changes take effect till commit is used. Within the transaction, we first grab the block table from the database. The block table is used to grab the block table record of the model space. The block table record stores the block references. This is a reference to the various blocks of the drawing. Each reference has an attribute collection. Finally, the attribute collection holds the attributes we are trying to change.
Now that we have the block table record, we can iterate through looking at each ID. Each ID is used to create the corresponding block reference. Once we have the block reference, we can compare its name against the one we are searching for (i.e., hard coded with BlockName
). Once we find a block name that matches the title block, then the attribute collection is iterated through. Each time an attribute with a tag name matches a dictionary key, we change its value with the corresponding dictionary value.
[CommandMethod("REV")]
public void EditBlock()
{
var acDb = HostApplicationServices.WorkingDatabase;
using (var acTrans = acDb.TransactionManager.StartTransaction())
{
var acBlockTable =
acTrans.GetObject(acDb.BlockTableId, OpenMode.ForRead) as BlockTable;
if (acBlockTable == null) return;
var acBlockTableRecord = acTrans.GetObject(acBlockTable[BlockTableRecord.ModelSpace],
OpenMode.ForRead) as BlockTableRecord;
if (acBlockTableRecord == null) return;
foreach (var blkId in acBlockTableRecord)
{
var acBlock = acTrans.GetObject(blkId, OpenMode.ForWrite) as BlockReference;
if (acBlock == null) continue;
if (!acBlock.Name.Equals
(BlockName, StringComparison.CurrentCultureIgnoreCase)) continue;
foreach (ObjectId attId in acBlock.AttributeCollection)
{
var acAtt = acTrans.GetObject(attId, OpenMode.ForWrite) as AttributeReference;
if (acAtt == null) continue;
if (!Attributes.ContainsKey(acAtt.Tag)) continue;
acAtt.UpgradeOpen();
acAtt.TextString = Attributes[acAtt.Tag];
}
}
acTrans.Commit();
}
}
Updating All the Title Blocks from a Directory
This command side loads all of the drawings located in the directory hard coded with the field string 'path
'. As each drawing is side loaded, the title block is updated then saved. Side load means the drawing is loaded into memory and never shows up on the AutoCAD user interface.
If the directory of drawings contains a large number of drawings, this command could be long running. For that reason, the command utilizes a task to send the process on a separate thread. The first thing the command does is store the database of the current drawing. This database will be restored at the end of this command. Note that you should not have any of the drawings you are trying to side load open in AutoCAD.
This command simply uses the same title block update code from above iterated over an array of files. First, the DirectoryInfo
is collected using the path. Next, this is used to collect an array of FileInfo
which contains all of the drawings in the path directory. As each file is iterated, a new database is used to read the drawing into the database in memory. Then the memory based database is used as before to start a transaction and update the title block as was done above.
[CommandMethod("REVALL")]
public void RevAllDwgFiles()
{
Task t = Task.Run(() =>
{
Database currentDatabase =
Autodesk.AutoCAD.ApplicationServices.Core.Application.
DocumentManager.MdiActiveDocument.Database;
try
{
DirectoryInfo d = new DirectoryInfo(path);
FileInfo[] Files = d.GetFiles("*.dwg");
foreach (FileInfo file in Files)
{
var fileName = Path.GetFileName(file.FullName);
string dwgFlpath = path + fileName;
using (Database acDb = new Database(false, true))
{
acDb.ReadDwgFile
(dwgFlpath, FileOpenMode.OpenForReadAndAllShare, false, null);
HostApplicationServices.WorkingDatabase = acDb;
using (Transaction acTrans =
acDb.TransactionManager.StartTransaction())
{
var acBlockTable = acTrans.GetObject
(acDb.BlockTableId, OpenMode.ForRead) as BlockTable;
if (acBlockTable == null) return;
var acBlockTableRecord = acTrans.GetObject
(acBlockTable[BlockTableRecord.ModelSpace],
OpenMode.ForRead) as BlockTableRecord;
if (acBlockTableRecord == null) return;
foreach (var blkId in acBlockTableRecord)
{
var acBlock =
acTrans.GetObject(blkId, OpenMode.ForWrite)
as BlockReference;
if (acBlock == null) continue;
if (!acBlock.Name.Equals
(BlockName, StringComparison.CurrentCultureIgnoreCase))
continue;
foreach (ObjectId attId in acBlock.AttributeCollection)
{
var acAtt =
acTrans.GetObject(attId, OpenMode.ForWrite)
as AttributeReference;
if (acAtt == null) continue;
if (!Attributes.ContainsKey(acAtt.Tag)) continue;
acAtt.UpgradeOpen();
acAtt.TextString = Attributes[acAtt.Tag];
}
}
acTrans.Commit();
}
acDb.SaveAs(dwgFlpath, DwgVersion.AC1027);
HostApplicationServices.WorkingDatabase = currentDatabase;
}
}
Autodesk.AutoCAD.ApplicationServices.Application.ShowAlertDialog
("All files processed");
}
catch (System.Exception ex)
{
Autodesk.AutoCAD.ApplicationServices.Application.
DocumentManager.MdiActiveDocument.Editor.WriteMessage(ex.ToString());
}
});
t.Wait();
}
}
History
- June 2018 - Created plug-in
- January 2020 - Created article