Merging AutoCAD blocks using .NET

I decided to dust off Visual Studio and write a quick AutoCAD app, this morning. It tackles a question received via a blog comment from Pankaj Potdar, over the weekend.

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.

I haven't had much time to spend on AutoCAD, of late. Part of the reason has been work-related: I'm heads-down getting the VR/AR track in shape for the upcoming Forge DevCon, as well as spending time on new duties in Autodesk Research. The other part is personal (or perhaps I should say medical): over the last 10 days I've made 7 separate visits to the local hospital. Not bad for someone who has lived in the area for 10 years and wasn't even registered at the hospital (it turns out I'd only ever been there for births and the odd childhood emergency).

I haven't been going there for fun, of course: the presumed insect bite I found at the end of our stay in Cuba was actually the manifestation of an infection caused by an antibiotic-resistant strain of staph. I'm now on the right (more targeted) antibiotics, but I'm still having to spend time on a more-or-less daily basis having the site cleaned and treated.

Anyway, it's nice to be distracted by technical challenges, so I decided to have a shot at tackling Pankaj's problem.

Here's some C# code that uses Database.DeepCloneObjects() to create a new block with the contents of two others:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using System.Linq;

 

namespace BlockMerging

{

  public static class Extensions

  {

    public static void Add(this ObjectIdCollection col, ObjectId[] ids)

    {

      foreach (var id in ids)

      {

        if (!col.Contains(id))

          col.Add(id);

      }

    }

  }

 

  public class Commands

  {

    [CommandMethod("MB")]

    public static void MergeBlocks()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

 

      var db = doc.Database;

      var ed = doc.Editor;

 

      // Get the name of the first block to merge

 

      var pr = ed.GetString("\nEnter name of first block");

      if (pr.Status != PromptStatus.OK)

        return;

 

      string first = pr.StringResult.ToUpper();

 

      using (var tr = doc.TransactionManager.StartTransaction())

      {

        var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);

 

        // Check whether the first block exists

 

        if (bt.Has(first))

        {

          // Get the name of the second block to merge

 

          pr = ed.GetString("\nEnter name of second block");

          if (pr.Status != PromptStatus.OK)

            return;

 

          string second = pr.StringResult.ToUpper();

 

          // Check whether the second block exists

 

          if (bt.Has(second))

          {

            // Get the name of the new block

 

            pr = ed.GetString("\nEnter name for new block");

            if (pr.Status != PromptStatus.OK)

              return;

 

            string merged = pr.StringResult.ToUpper();

 

            // Make sure the new block doesn't already exist

 

            if (!bt.Has(merged))

            {

              // We need to collect the contents of the two blocks

 

              var ids = new ObjectIdCollection();

 

              // Open the two blocks to be merged

 

              var btr1 =

                tr.GetObject(bt[first], OpenMode.ForRead) as BlockTableRecord;

              var btr2 =

                tr.GetObject(bt[second], OpenMode.ForRead) as BlockTableRecord;

 

              // Use LINQ to get IEnumerable<ObjectId> for the blocks

 

              var en1 = btr1.Cast<ObjectId>();

              var en2 = btr2.Cast<ObjectId>();

 

              // Add the complete contents to our collection

              // (we could also apply some filtering, here, such as making

              // sure we only include attributes with the same name once)

 

              ids.Add(en1.ToArray<ObjectId>());

              ids.Add(en2.ToArray<ObjectId>());

 

              // Create a new block table record for our merged block

 

              var btr = new BlockTableRecord();

              btr.Name = merged;

 

              // Add it to the block table and the transaction

 

              bt.UpgradeOpen();

              var btrId = bt.Add(btr);

              tr.AddNewlyCreatedDBObject(btr, true);

 

              // Deep clone the contents of our two blocks into the new one

 

              var idMap = new IdMapping();

              db.DeepCloneObjects(ids, btrId, idMap, false);

 

              ed.WriteMessage("\nBlock \"{0}\" created.", merged);

            }

            else

            {

              ed.WriteMessage(

                "\nDrawing already contains a block named \"{0}\".", merged

              );

            }

          }

          else

          {

            ed.WriteMessage("\nBlock \"{0}\" not found.", second);

          }

        }

        else

        {

          ed.WriteMessage("\nBlock \"{0}\" not found.", first);

        }

 

        // Always commit the transaction

 

        tr.Commit();

      }

    }

  }

}

 

We can use DeepCloneObjects() here because we're within the same drawing: if we wanted to move the new block between databases, for some reason, then we'd use WblockCloneObjects() followed by Insert(). This latter approach ensures that any "hard" references are followed and the contents copied, too: something we shouldn't need when working in the same database.

Here's the MB command working with a couple of simple blocks containing lines and circles, respectively.

Merge blocks

It also works fine with attributes: here we combine attributes from two separate blocks that have the same names (etc.). We can see that we get the combined set of attributes – with duplicate names – in the resultant block.

Merge blocks with attributes

We could certainly filter out any attributes we don't want to appear twice, should that be a requirement. I've also made no attempt to worry about things such as layout: you can see I've simply placed the insertion point of the two blocks at a place that helps us avoid overlaps. Both areas have been left as exercises for the reader, as we'll quickly descend into requirements that are specific to a particular user scenario.

3 responses to “Merging AutoCAD blocks using .NET”

  1. Welcome back to AutoCAD Kean. Long after Autodesk Research has returned to ether, at least you have a trade to fall back on.

  2. Hello Kean,
    Thanks for post, I have a quick question
    While merging blocks, how you define datum point or relative point of block which will be used as position point while placing that block in model space?

    Thanks,
    Pankaj

    1. Hi Pankaj,

      You can transform the geometry, displacing it by an appropriate amount. I haven't done this in my code, it's one of the things "left as an exercise for the reader".

      Regards,

      Kean

      1. thank you very much!

  3. friendfromarea51 Avatar
    friendfromarea51

    Hope you get better soon (but did miss the coding Kean)! When your Autocad-controlled nanobots have replaced that infected limb, please provide a tutorial.

  4. Hello Kean,
    I would like to make a contribution.
    If I don't want to keep the chosen blocks in the drawing, it is possible to do the following:

    // instead of db.DeepCloneObjects()
    btr.AssumeOwnershipOf(ids);

    Best regards,
    Antonio.

    1. Useful suggestion - thanks, Antonio!

      You'll obviously need to erase the BTRs (and check for any referencing blockrefs), too.

      Kean

  5. Pankaj Potdar Avatar
    Pankaj Potdar

    Hello Kean,

    I have a question may be off the topic,
    I have a block reference in model space and want to create multiple copies of it within same model space.
    how to do it in side database?
    I dont want to use block table record and then modelspace.append as my block definition is different than block reference.
    I was thinking I can use deepcloneobjects but there I can not define position, just confused how to do it.
    Thanks,
    Pankaj

    1. Kean Walmsley Avatar
      Kean Walmsley

      Hi Pankaj,

      It's not clear whether you want to copy the block reference or the block table record. What exactly do you want to do?

      Regards,

      Kean

      1. Pankaj Potdar Avatar
        Pankaj Potdar

        Hello Kean,

        I want to copy blockreference.
        Blockreference is available in model space and I want to make another copy of blockreference in same model space, using in ReadDwgFile method.
        Thanks,
        Pankaj

        1. Kean Walmsley Avatar
          Kean Walmsley

          Is it only for BlockReferences? If so, why don't you create a number of BlockReference objects with different positions pointing to the same BTR?

          Kean

          1. Pankaj Potdar Avatar
            Pankaj Potdar

            I have lot of blockreferences which are edited like, positions of attributes are moved, attribute reference text is changed etc. If I point my blockreference to BTR then it will take all positions, texts etc from BTR which I don't want.
            I hope it makes sense.

            1. Kean Walmsley Avatar
              Kean Walmsley

              OK, then sure, you can DeepCloneObjects and modify the positions of the results each time. I'm not able to code this for you, though.
              Kean

              1. Pankaj Potdar Avatar
                Pankaj Potdar

                Thank you very much!

  6. Jonas Šablickas Avatar
    Jonas Šablickas

    Hello, can someone attach compiled .dll for this script? I'm not a programmer

  7. great!
    but it that possible to maintain distance? seems like while merging block second instance is placed on first instance base point.

    1. It's been a long time since I looked at this code, but you should be able to translate (transform) the block reference as you see fit.

      Kean

  8. Hello, how to load this file onto the AutoCAD?

Leave a Reply to Said Ibrahim Cancel reply

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