Breaking it down - a closer look at the C# code for importing blocks
I didn't spend as much time as would have liked talking about the code in the previous topic (it was getting late on Friday night when I posted it). Here is a breakdown of the important function calls.
The first major thing we do in the code is to declare and instantiate a new Database object. This is the object that will represent our in-memory drawing (our side database). The information in this drawing will be accessible to us, but not loaded in AutoCAD's editor.
Database sourceDb = new Database(false, true);
Very importantly, the first argument (buildDefaultDrawing) is false. You will only ever need to set this to true in two situations. If you happen to pass in true by mistake when not needed, the function will return without an error, but the DWG will almost certainly be corrupt. This comes up quite regularly, so you really need to watch for this subtle issue.
Here are the two cases where you will want to set buildDefaultDrawing to true:
- When you intend to create the drawing yourself, and not read it in from somewhere
- When you intend to read in a drawing that was created in R12 or before
Although this particular sample doesn't show the technique, if you expect to be reading pre-R13 DWGs into your application in a side database, you will need to check the DWG's version and then pass in the appropriate value into the Database constructor. Here you may very well ask, "but how do I check a DWG's version before I read it in?" Luckily, the first 6 bytes of any DWG indicate its version number (just load a DWG into Notepad and check out the initial characters):
AC1.50 = R2.05
AC1002 = R2.6
AC1004 = R9
AC1006 = R10
AC1009 = R11/R12
AC1012 = R13
AC1014 = R14
AC1015 = 2000/2000i/2002
AC1018 = 2004/2005/2006
AC1021 = 2007
You'll be able to use the file access routines of your chosen programming environment to read the first 6 characters in - AC1009 or below will require a first argument of true, otherwise you're fine with false.
Next we ask the user for the path & filename of the DWG file:
sourceFileName =
ed.GetString("\nEnter the name of the source drawing: ");
Nothing very interesting here, other than the fact I've chosen not to check whether the file actually exists (or even whether the user entered anything). The reason is simple enough: the next function call (to ReadDwgFile()) will throw an exception if the file doesn't exist, and the try-catch block will pick this up and report it to the user. We could, for example, check for that particular failure and print a more elegant message than "Error during copy: eFileNotFound", but frankly that's just cosmetic - the exception is caught and handled well enough.
sourceDb.ReadDwgFile(sourceFileName.StringResult,
System.IO.FileShare.Read,
true,
"");
This is the function call that reads in our drawing into the side database. We pass in the results of the GetString() call into the filename argument, specifying we're just reading the file (for the purposes of file-locking: this simply means that other applications will be able to read the DWG at the same time as ours but not write to it). We then specify that we wish AutoCAD to attempt silent conversion of DWGs using a code-page (a pre-Unicode concept related to localized text) that is different to the one used by the OS we're running on. The last argument specifies a blank password (we're assuming the drawing being opened is either not password protected or its password has already been entered into the session's password cache).
Next we instantiate a collection object to store the IDs of all the blocks we wish to copy across from the side database to the active one:
ObjectIdCollection blockIds = new ObjectIdCollection();
We then create a transaction which will allow us to access interesting parts of the DWG (this is the recommended way to access DWG content in .NET). Using the transaction we open the block table of the side database for read access, specifying that we only wish to access it if it has not been erased:
BlockTable bt =
(BlockTable)tm.GetObject(sourceDb.BlockTableId,
OpenMode.ForRead,
false);
From here - and this is one of the beauties of using the managed API to AutoCAD - we simply use a standard foreach loop to check each of the block definitions (or "block table records" in AcDb parlance).
foreach (ObjectId btrId in bt)
{
BlockTableRecord btr =
(BlockTableRecord)tm.GetObject(btrId,
OpenMode.ForRead,
false);
// Only add named & non-layout blocks to the copy list
if (!btr.IsAnonymous && !btr.IsLayout)
blockIds.Add(btrId);
btr.Dispose();
}
This code simply opens each block definition and only adds its ID to the list to copy if it is neither anonymous nor a layout (modelspace and each of the paperspaces are stored in DWGs as block definitions - we do not want to copy them across). We also call Dispose() on each block definition once we're done (this is a very good habit to get into).
And finally, here's the function call that does the real work:
sourceDb.WblockCloneObjects(blockIds,
destDb.BlockTableId,
mapping,
DuplicateRecordCloning.Replace,
false);
WblockCloneObjects() takes a list of objects and attempts to clone them across databases - we specify the owner to be the block table of the target database, and that should any of the blocks we're copying (i.e. their names) already exist in the target database, then they should be overwritten ("Replace"). You could also specify that the copy should not happen for these pre-existing blocks ("Ignore").