November 2014

Sun Mon Tue Wed Thu Fri Sat
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30            










« Creating an AutoCAD block using .NET | Main | Alias Sketch for AutoCAD on Labs »

January 25, 2010

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

 

        BlockTableRecord 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

            // 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);

          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.

blog comments powered by Disqus

Feed/Share

10 Random Posts