Combining AutoCAD blocks in separate files into a single DWG using .NET – Take 2

Thanks for Chris, Dan and Dale for pointing out the obvious issue(s) with my last post. Let's just blame it on a few holiday cobwebs needing brushing away during the first week back in the saddle. 🙂

The main issue with my previous implementation was that I'd somehow forgotten that Database.Insert() allows you to insert into a named block definition. This simple function does all my previous, manual approach did and more.

The secondary issue – but still very important to those using annotation scaling – is that the previous code does not work for annotative blocks, as Dan very rightly pointed out. I've incorporated Dan's approach into today's post to make it more complete.

I've decided to leave the original post as it is – as the technique is somewhat interesting at a certain level – even if today's post supercedes it.

Here's the updated C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using System.IO;

using System;

 

namespace BlockImport

{

  public class BlockImportClass

  {

    [CommandMethod("CBL")]

    public void CombineBlocksIntoLibrary()

    {

      Document doc =

          Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      Database destDb = doc.Database;

 

      // Get name of folder from which to load and import blocks

 

      PromptResult pr =

        ed.GetString("\nEnter the folder of source drawings: ");

 

      if (pr.Status != PromptStatus.OK)

        return;

      string pathName = pr.StringResult;

 

      // Check the folder exists

 

      if (!Directory.Exists(pathName))

      {

        ed.WriteMessage(

          "\nDirectory does not exist: {0}", pathName

        );

        return;

      }

 

      // Get the names of our DWG files in that folder

 

      string[] fileNames = Directory.GetFiles(pathName, "*.dwg");

 

      // A counter for the files we've imported

 

      int imported = 0, failed = 0;

 

      // For each file in our list

 

      foreach (string fileName in fileNames)

      {

        // Double-check we have a DWG file (probably unnecessary)

 

        if (fileName.EndsWith(

              ".dwg",

              StringComparison.InvariantCultureIgnoreCase

            )

        )

        {

          // Catch exceptions at the file level to allow skipping

 

          try

          {

            // Suggestion from Thorsten Meinecke...

 

            string destName =

              SymbolUtilityServices.GetSymbolNameFromPathName(

                fileName, "dwg"

              );

 

            // And from Dan Glassman...

 

            destName =

              SymbolUtilityServices.RepairSymbolName(

                destName, false

              );

 

            // Create a source database to load the DWG into

 

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

            {

              // Read the DWG into our side database

 

              db.ReadDwgFile(fileName, FileShare.Read, true, "");

              bool isAnno = db.AnnotativeDwg;

 

              // Insert it into the destination database as

              // a
named block definition

 

              ObjectId btrId = destDb.Insert(

                destName,

                db,

                false

              );

 

              if (isAnno)

              {

                // If an annotative block, open the resultant BTR

                // and set its annotative definition status

 

                Transaction tr =

                  destDb.TransactionManager.StartTransaction();

                using (tr)

                {

                  BlockTableRecord btr =

                    (BlockTableRecord)tr.GetObject(

                      btrId,

                      OpenMode.ForWrite

                    );

                  btr.Annotative = AnnotativeStates.True;

                  tr.Commit();

                }

              }

 

              // Print message and increment imported block counter

 

              ed.WriteMessage("\nImported from \"{0}\".", fileName);

              imported++;

            }

          }

          catch (System.Exception ex)

          {

            ed.WriteMessage(

              "\nProblem importing \"{0}\": {1} - file skipped.",

              fileName, ex.Message

            );

            failed++;

          }

        }

      }

 

      ed.WriteMessage(

        "\nImported block defini
tions from {0} files{1} in "
+

        "\"{2}\" into the current drawing.",

        imported,

        failed > 0 ? " (" + failed + " failed)" : "",

        pathName

      );

    }

  }

}

The results are just as we saw with the previous code, so I won't repeat them.

Update

I made a small update to the code, to make use of SymbolUtilityServices.GetSymbolNameFromPathName() rather than Path.GetFileNameWithoutExtension(). Thanks to Thorsten Meinecke for making this suggestion.

Update 2

Followed by a minor code update to fix simplify the post-edit of annotative block definitions as well as an additional call to SymbolUtilityServices.RepairSymbolName() recommended by Dan Glassman (thanks, Dan! :-).

24 responses to “Combining AutoCAD blocks in separate files into a single DWG using .NET – Take 2”

  1. If I understood the last posts code correctly, the db.WblockCloneObjects only copies required referenced objects. Do you know if DB.Insert acts the same way, or will it pull in unused linetypes, ect. from the temp database?

  2. Insert() is less discriminating than WblockCloneObjects(). As far as I'm aware it brings in unused block definitions, layers, linetypes, etc. from the source database.

    If you want to effectively purge content from your drawings as you bring them in (as they contain more than just the block definitions - the opposite of the unwritten assumption made in today's post) then you should use a combination of wblock (which typically means WblockCloneObjects()) and Insert().

    Kean

  3. Hey Kean, at first a big thanks you for this amazing blog. It's always a big inspiration for my work.

    After reading your current post, I was a little surprised about your double checking the file selection with GetFiles(... ,"*.dwg") and .endswith(".dwg").
    Two hours later my code crashed, because the getfiles wildcard *.dwg delivers not only dwg-files. It delivers also files like "test.dwg-" or "test.dwg-bat"
    Only the .endswith(".dwg") guarant only dwg files for further work.

    Holger

  4. Hi Holger,

    Thanks for letting me know: I'd just picked up some code I'd written 4 years ago, and left that check in. It's good to know I had a good reason to trust my old self. 🙂

    Kean

  5. Thorsten Meinecke Avatar
    Thorsten Meinecke

    Hi Kean,
    you said:

    string destName =
    Path.GetFileNameWithoutExtension(fileName);

    What about this?

    string destName =
    SymbolUtilityServices.GetSymbolNameFromPathName(fileName, "dwg");

    ?

    Many thanks & keep on blogging...

  6. Hi Thorsten,

    Great suggestion. I'm not in the habit of using this, myself, but it makes sense (especially in this situation, where we need a valid symbol table record name for our block).

    I'll modify the code.

    Cheers,

    Kean

  7. Kean,

    Thank you for the update; glad to contribute something back after learning so much from your blog over the past few years [long time listener, first time caller].

    I missed this in my original post on the subject; saves lines, improves readability:

    <snip>
    ObjectId destBTRid = db.Insert(...)
    ...
    Using (Transaction...)
    {
    BlockTableReference btr = t.GetObject(destBTRid...)
    btr.Annotative = AnnotativeStates.True;
    }
    </snip>

    If you're updating the post to use SymbolUtilityServices.GetSymbolNameFromPathName(), follow it up with SymbolUtilityServices.RepairSymbolName(). The former doesn't guarantee a valid symbol name.

    Cheers!

  8. Would also like to test the code but does not know how I can convert to it in Autocad testing

    Würde den Code auch gern Testen weiss aber nicht wie ich den in Umwandeln kann um ihn in Autocad zu Testen

  9. You will need to build a simple Class Library using Visual C# Express or Visual Studio.

    This post may be of use.

    Kean

  10. Yes I have, but what do I have the memory that the one. dll is to run the program.
    Thanks
    No me because, unfortunately, not sufficient.

    Ja das habe ich, aber wie muss ich das Speicher das das ein .dll wird, um das Programm ausführen zu können.
    Danke
    Keine mich da leider nicht aus.

  11. I'm afraid my German is too poor - and the auto-translation, also - for me to understand what you're looking for.

    Kean

  12. Thanks it works it all

    Danke es läuft schon alles

  13. Hello Mr. Kean Walmsley...

    I,My self,N.T.Thakkar... N for Nirav..

    Right now i m studying, Master of engineering as a civil-structure engineer. In my dissertation work i m going to make one software by using V.B.Net. By using this software anyone will get direct design of steel connections. Now I want to make it more useful by providing output with drawings as per design.
    So I want all information related to this.....
    Means by which way, link Autocad with VB.net...
    Waiting 4 your positive reply..

    Regards,
    Nirav Thakkar..

  14. Hello Nirav,

    I suggest using the resources listed on the AutoCAD Developer Center, in particular the AutoCAD .NET Labs and Discussion Group.

    Regards,

    Kean

  15. I just realized that you passed false for the PreserveSourceDatabase Argument to .Insert.

    The documentation indicates that this will leave the source database dependant on the destination database.

  16. I've always taken false for that flag to indicate "I don't need access to the source db after the operation, so take whatever memory you need from it, even if destructive to the source."

    Kean

  17. Martin Müller Avatar

    Hello Kean,

    Took your post and turned it into a C# Extension method of the AutoCAD.NET Database class. Extension methods are a good way to extend the functionality of existing classes, you have no source from (as their name suggests 😉

    Hope this helps further readers.

    Greetings, today from Czech Republic - Martin

    using Autodesk.AutoCAD.DatabaseServices;
    using System;
    using System.Diagnostics;
    using System.IO;

    namespace CTModules.CTAcadTools
    {
    /// <summary>
    /// Class providing several extension methods to the AutoCAD.NET Database class.
    /// </summary>
    public static class DatabaseExtensions
    {
    #region Construction
    #endregion

    #region Enums & Datatypes
    #endregion

    #region Properties
    #endregion

    #region Methods

    public static ObjectId Insert(this Database oDestinationDatabase, string sExternalFileFullPathName)
    {
    ObjectId RetVal = ObjectId.Null;
    Debug.Assert(sExternalFileFullPathName.EndsWith(".dwg", StringComparison.OrdinalIgnoreCase));
    try
    {
    // Handle destination database null case
    if (oDestinationDatabase == null)
    {
    oDestinationDatabase = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Database;
    }
    // Create a compatible block name from file name.
    string sDestinationName = SymbolUtilityServices.RepairSymbolName(
    SymbolUtilityServices.GetSymbolNameFromPathName(sExternalFileFullPathName, "dwg"),
    false);
    // Create a source database to load the DWG into
    using (Database oSourceDb = new Database(false, true))
    {
    // Read the DWG into our source database
    oSourceDb.ReadDwgFile(sExternalFileFullPathName, FileShare.Read, true, "");
    // Handle annotative blocks.
    bool bIsAnno = oSourceDb.AnnotativeDwg;
    // Insert it into the destination database as a named block definition
    RetVal = oDestinationDatabase.Insert(sDestinationName, oSourceDb, false);

    if (bIsAnno)
    {
    using (Transaction oTr = oDestinationDatabase.TransactionManager.StartTransaction())
    {
    BlockTableRecord oBTR = oTr.GetObject(RetVal, OpenMode.ForWrite) as BlockTableRecord;
    oBTR.Annotative = AnnotativeStates.True;
    oTr.Commit();
    }
    }

    }
    }
    catch (Exception ex)
    {
    throw new ExternalDrawingInsertException(string.Format("Error while inserting '{0}' as new BlockTableRecord.", sExternalFileFullPathName), ex);
    }
    return RetVal;
    }

    #endregion

    #region EventHandlers
    #endregion

    #region Operators
    #endregion

    #region Implementation
    #endregion

    #region Data Members
    #endregion
    }
    }

  18. Martin Müller Avatar

    One can remove the "// Handle destination database null case" block, of course...

  19. Kean Walmsley Avatar

    Thanks, Martin!

    Kean

  20. Hi Kean,

    I am Using the "Insert" method to import external Dwg file as a block in to my current database.Its working fine.But I am facing Issues in some systems and the Autocad application get crashed while executing the Db.insert.

    I did Framework 4.5 uninstalled,cleanup and re-installed.And it was working for some time and subsequently it didn't work fro me.

    So have decided to use the method you have described in the previous post using
    **********
    IdMapping im = new IdMapping();

    db.WblockCloneObjects(

    ids,

    destDb.BlockTableId,

    im,

    DuplicateRecordCloning.MangleName,

    false

    );

    /***********

    It is Working now in all systems.

    Can you suggest me which method is good and right approach to insert a external drawing as a block.

    Regards,
    Appadurai.G

    1. Kean Walmsley Avatar

      Please submit your question via the AutoCAD .NET Discussion Group - this isn't a support forum.

      Thank you,

      Kean

  21. Hello kean,
    I know its off the topic but, do you have any post which deals with below,
    I have two blocks with different attributes I want to merge them in single block, and I don't want to create any nested blocks.
    Any suggestion will be greatly appreciated.

    Thanks,
    Pankaj

  22. Thank you very much Kean, You are my mentor!!

Leave a Reply to Dan Glassman Cancel reply

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