Creating an AutoCAD group using .NET

In the last post we looked at some code to define a block and insert it into the model-space of an AutoCAD drawing. Today we're going to look at creating a group.

Before we dive into the code, it's worth taking a quick look at the difference between a block and a group, to understand the differences between these two container/aggregator objects. I know that much of this information is available elsewhere, but hey – I'm on a roll, so I request your forbearance.

Let's start with looking at the top-level structure of an AutoCAD Database (which is equivalent to a DWG file):

Overall database stucture

On the left of the above image we have a number of symbol tables. Symbol tables are the "traditional" container for data in an AutoCAD drawing, and have been in the format since the 80s (i.e. the dawn of time, from a PC technology perspective :-). Symbol tables store the names of objects within the objects themselves, which means you need to open the contained objects (symbol table records) to discover what they're called.

The containers on the right, based on dictionaries (the DBDictionary class in .NET), are the more modern container inside DWGs. Dictionaries are like lookup tables: the entries – while sequentially accessible – are stored against a unique name, which means they're more efficient if you need to identify a particular named entry.

Blocks are implemented via symbol tables, as they pre-date the introduction of dictionaries. Blocks are really about structuring content for repeated use, although – having said that – they're also the mechanism used for external references, which are clearly not used repeatedly within a particular drawing.

Let's take a look at the drawing structure we created in the last post: we created a new block definition called "Square" and then created a block reference in the model-space pointing to that block definition.

Structure of a block definition and reference

We can have multiple block references pointing to the same block definition, and if we change the contents of the block definition this will impact the various references.

Now let's look at groups: as a more recently introduced mechanism, these are implemented using dictionaries. There's a specific Group Dictionary that contains all the group objects in a drawing, and these, in turn, refer to sets of entities in the model-space (or paper-space, for that matter).

Structure of a group of entities

Groups collect entities together – and can be nested (but non-overlapping), just like blocks – but the individual elements retain the ability to be modified independently.

After groups were first implemented (back in R13, maybe? I forget…), I recall there were performance issues, and – in all honestly – I haven't used them very heavily myself, over the years. I'd be curious to hear from this blog's readership if anyone has experiences they wish to share: I would hope either that the initial performance problems have been resolved or that I'm misremembering and they work just fine for people.

Anyway, that's it for the brief overview of blocks vs. groups: here's the updated C# code that introduces a CG command to create a group.

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

 

namespace CollectionCreation

{

  public class Commands

  {

    [CommandMethod("CB")]

    public void CreateBlock()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        // Get the block table from the drawing

 

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

 

        // Check the block name, to see whether it's

        // already in use

 

        PromptStringOptions pso =

          new PromptStringOptions(

            "\nEnter new block name: "

          );

        pso.AllowSpaces = true;

 

        // A variable for the block's name

 

        string blkName = "";

 

        do

        {

          PromptResult pr = ed.GetString(pso);

 

          // Just return if the user cancelled

          // (will abort the transaction as we drop out of the using

          // statement's scope)

 

          if (pr.Status != PromptStatus.OK)

            return;

 

          try

          {

            // Validate the provided symbol table name

 

            SymbolUtilityServices.ValidateSymbolName(

              pr.StringResult,

              false

            );

 

            // Only set the block name if it isn't in use

 

            if (bt.Has(pr.StringResult))

              ed.WriteMessage(

                "\nA block with this name already exists."

              );

            else

              blkName = pr.StringResult;

          }

          catch

          {

            // An exception has been thrown, indicating the

            // name is invalid

 

            ed.WriteMessage(

              "\nInvalid block name."

            );

          }

 

        } while (blkName == "");

 

        // Create our new block table record...

 

        BlockTableRecord btr = new BlockTableRecord();

 

        // ... and set its properties

 

        btr.Name = blkName;

 

        // Add the new block to the block table

 

        bt.UpgradeOpen();

        ObjectId btrId = bt.Add(btr);

        tr.AddNewlyCreatedDBObject(btr, true);

 

        // Add some lines to the block to form a square

        // (the entities belong directly to the block)

 

        DBObjectCollection ents = SquareOfLines(5);

        foreach (Entity ent in ents)

        {

          btr.AppendEntity(ent);

          tr.AddNewlyCreatedDBObject(ent, true);

        }

 

        // Add a block reference to the model space

 

        BlockT
ableRecord
ms =

          (BlockTableRecord)tr.GetObject(

            bt[BlockTableRecord.ModelSpace],

            OpenMode.ForWrite

          );

 

        BlockReference br =

          new BlockReference(Point3d.Origin, btrId);

 

        ms.AppendEntity(br);

        tr.AddNewlyCreatedDBObject(br, true);

 

        // Commit the transaction

 

        tr.Commit();

 

        // Report what we've done

 

        ed.WriteMessage(

          "\nCreated block named \"{0}\" containing {1} entities.",

          blkName, ents.Count

        );

      }

    }

 

    [CommandMethod("CG")]

    public void CreateGroup()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        // Get the group dictionary from the drawing

 

        DBDictionary gd =

          (DBDictionary)tr.GetObject(

            db.GroupDictionaryId,

            OpenMode.ForRead

          );

 

        // Check the group name, to see whether it's

        // already in use

 

        PromptStringOptions pso =

          new PromptStringOptions(

            "\nEnter new group name: "

          );

        pso.AllowSpaces = true;

 

        // A variable for the group's name

 

        string grpName = "";

 

        do

        {

          PromptResult pr = ed.GetString(pso);

 

          // Just return if the user cancelled

          // (will abort the transaction as we drop out of the using

          // statement's scope)

 

          if (pr.Status != PromptStatus.OK)

            return;

 

          try

          {

            // Validate the provided symbol table name

 

            SymbolUtilityServices.ValidateSymbolName(

              pr.StringResult,

              false

            );

 

            // Only set the block name if it isn't in use

 

            if (gd.Contains(pr.StringResult))

              ed.WriteMessage(

                "\nA group with this name already exists."

              );

            else

              grpName = pr.StringResult;

          }

          catch

          {

            // An exception has been thrown, indicating the

  &
#160;        
// name is invalid

 

            ed.WriteMessage(

              "\nInvalid group name."

            );

          }

 

        } while (grpName == "");

 

        // Create our new group...

 

        Group grp = new Group("Test group", true);

 

        // Add the new group to the dictionary

 

        gd.UpgradeOpen();

        ObjectId grpId = gd.SetAt(grpName, grp);

        tr.AddNewlyCreatedDBObject(grp, true);

 

        // Open the model-space

 

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

 

        BlockTableRecord ms =

          (BlockTableRecord)tr.GetObject(

            bt[BlockTableRecord.ModelSpace],

            OpenMode.ForWrite

          );

 

        // Add some lines to the group to form a square

        // (the entities belong to the model-space)

 

        ObjectIdCollection ids = new ObjectIdCollection();

 

        DBObjectCollection ents = SquareOfLines(8);

        foreach (Entity ent in ents)

        {

          ObjectId id = ms.AppendEntity(ent);

        
0; ids.Add(id);

          tr.AddNewlyCreatedDBObject(ent, true);

        }

 

        grp.InsertAt(0, ids);

 

        // Commit the transaction

 

        tr.Commit();

 

        // Report what we've done

 

        ed.WriteMessage(

          "\nCreated group named \"{0}\" containing {1} entities.",

          grpName, ents.Count

        );

      }

    }

 

    private DBObjectCollection SquareOfLines(double size)

    {

      // A function to generate a set of entities for our block

 

      DBObjectCollection ents = new DBObjectCollection();

      Point3d[] pts =

          { new Point3d(-size, -size, 0),

            new Point3d(size, -size, 0),

            new Point3d(size, size, 0),

            new Point3d(-size, size, 0)

          };

      int max = pts.GetUpperBound(0);

 

      for (int i = 0; i <= max; i++)

      {

        int j = (i == max ? 0 : i + 1);

        Line ln = new Line(pts[i], pts[j]);

        ents.Add(ln);

      }

      return ents;

    }

  }

}

The CG command allows us to create a group, just as the CB command created a block. We can even run them one after the other, using the same name (because one will use the name as a unique ID into the block table, the other into the group dictionary). And as the resultant squares are of different sizes, it should be obvious when the respective commands have done their work.

Command: CG

Enter new group name: Square

Created group named "Square" containing 4 entities.

2 responses to “Creating an AutoCAD group using .NET”

  1. Kean,

    On my last projects, I used groups extensively and did not experience performance issues apart from the groups dialog box. Unnamed groups helped to overcome this problem

    Jeff

  2. Fernando Malard Avatar

    Kean, I like the diagrams you have used.

    Sometimes they are much more clear and objective than a bunch of code lines.

    Regards.

    1. yeah i concur this was very clearly explained

  3. Kean,how to draw the diagrams as you did?Photoshop?

  4. Thanks for the feedback, Jeff, Fernando & csharpbird...

    I drew the diagrams in PowerPoint.

    Regards,

    Kean

  5. I use the heck out of groups. I had performance become an issue once when I had a bazillion empty groups in a block that had been insert multiple times. A quick purge of the offending block, using one of the 'purge empty groups' lisp routines and everything was snappy as ever.

  6. auto cad drawing Avatar

    This is very good information.

  7. hi Kean,
    i'm trying to find some effective way to store SelctionSets, i was thinking about groups, but i need the possibility of overlaping entities
    the group is created using ObjectIDs, but how does it retain when session ends? does it convert the oIDs to Handles somehow, when the drawing is closed?

  8. Hi Matus,

    You can store ObjectIds for objects inside a drawing (such as inside an XRecord in a dictionary): these are ultimately memory pointers (and so transient in nature), but AutoCAD remaps them when it loads a drawing. So they will be consistent within the context of a drawing.

    If you need to cross drawing boundaries, then you'll need handles.

    To follow up on this, please post your question to the ADN team, if you're a member, or otherwise the AutoCAD .NET Discussion Group.

    Regards,

    Kean

  9. Hi, Kean.
    It´s been very helpfull to me.
    And what about extracting data of the entities of an existing group?
    Thanks a lot.

  10. Hi Jon,

    I've added this to my list of things to look at when I have time.

    Cheers,

    Kean

    1. Hi Kean, did you ever get around to this somewhere?

  11. Matt Riemenschneider Avatar
    Matt Riemenschneider

    Interesting view of groups vs. blocks. To throw another entity conglomeration practice into the mix: Mechanical Structure (AutoCAD Mechanical). I love working with structure but it is VERY buggy. To the point where I need to abandon it for core AutoCAD technologies. HOWEVER, I don't want to leave the benefits of Structure behind. GROUPS have an "easy-in easy-out" editing ability similar to structure, where BLOCKS have the ability to be have multiple references "change once, update many". Any thoughts about how .Net can help with editing blocks in a similar way to groups & structure?

    best regards,

    matt

  12. Hi Kean,
    I am trying to do 1 project using VB.net and AutoCAD. In the drawing we have multiple layers and should separately store the Layer blocks with the block names in an Excel Application. I stored the Layer names.But all the entities in the drawing will be stored in the Excel file. I need one by one.
    Thanks & Regards

  13. Hi Geena,

    Sorry - this isn't a forum for support, and your question doesn't apply to this post. I suggest posting your question on the AutoCAD .NET Discussion Group - someone there will be able to provide you with pointers.

    Regards,

    Kean

  14. Thank U Kean....

  15. Hey Kean! Thanks so much for this post, its a huge help! Is there a simple way to get the geometric extents of the group?

  16. Kean Walmsley Avatar

    Hey Nick,

    Good question... worth replying to via a blog post, in fact. 🙂

    keanw.com/2015/07/getting-the-extents-of-an-autocad-group-using-net.html

    Regards,

    Kean

  17. Hi Kean, thank you for this blog post, i found it very very clearly explained and very understandable. am curious to know of an application of groups to a problem? chrs Ben

    edit - the below answers the question actually:
    youtube.com/wat... and why you should use groups vs blocks.

  18. Alain Holloway Avatar
    Alain Holloway

    Taking a chance here, I need to react when the group gets deleted, the Group object expose an Erased event handler but it never get raised, any idea? Thank you guys

    1. Kean Walmsley Avatar

      My knowledge in this area is now getting old, but perhaps try an Editor reactor and look for ObjectErased?

      Kean

Leave a Reply to Nick Gilbert Cancel reply

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