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):
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.
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).
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.