I demonstrate here some sample code that uses LINQ (to object) to make the code simpler and easier.
BlockTableRecord class is one the mostly used class an AutoCAD programmer may have to deal with. It implements IEnumerale interface. So, we have seen a lots of "foreach(...)" loop used against it in AutoCAD .NET code, especially when ModelSpace BlockTableRecord is opened for searching certain type of entities contained in ModelSpace. Here are some cases.
Case 1: searching certain types of entities in BlockTableRecord (ModelSpace, PaperSpace, or a block definition).
private List<ObjectId> GetCertainEntityIDs(Database db)
{
List<ObjectId> ids = null;
using (Transaction tran = db.TransactionManager.StartTransaction())
{
BlockTable tbl =
(BlockTable)tran.GetObject(db.BlockTableId, OpenMode.ForRead);
//Get modelspace BloclTableRecord
BlockTableRecord br =
(BlockTableRecord)tran.GetObject(tbl[BlockTableRecord.ModelSpace], OpenMode.ForRead);
//Cast the BlockTableRecord into IEnumeralbe<T> collection
IEnumerable<ObjectId> b = br.Cast<ObjectId>();
//==============search certain entity========================//
//"LINE" for line
//"LWPOLYLINE" for polyline
//"CIRCLE" for circle
//"INSERT" for block reference
//...
//We can use "||" (or) to search for more then one entity types
//============================================================//
//Use lambda extension method
ids = b.Where(id => id.ObjectClass.DxfName.ToUpper() == "LINE" ||
id.ObjectClass.DxfName.ToUpper() == "LWPOLYLINE").ToList<ObjectId>();
//Use LINQ statement. This is more readable
ids = (from id in b
where id.ObjectClass.DxfName.ToUpper()=="LINE" ||
id.ObjectClass.DxfName.ToUpper() == "LWPOLYLINE"
select id).ToList<ObjectId>();
tran.Commit();
}
return ids;
}
Case 2: often, we need to get a distinctive entity type list from a drawing (e.g. we need to know what types of entities the drawing's model space contains). Following code get a string array of entity types from model space.
private static string[] GetEntityType(Document dwg)
{
string[] types = null;
Database db = dwg.Database;
using (Transaction tran = db.TransactionManager.StartTransaction())
{
BlockTable tbl =
(BlockTable)tran.GetObject(db.BlockTableId, OpenMode.ForRead);
//Get modelspace BloclTableRecord
BlockTableRecord br =
(BlockTableRecord)tran.GetObject(tbl[BlockTableRecord.ModelSpace], OpenMode.ForRead);
//Cast the BlockTableRecord into IEnumeralbe<T> collection
IEnumerable<ObjectId> b = br.Cast<ObjectId>();
//Use lambda extension method
types = b.Select(id => id.ObjectClass.DxfName).Distinct().ToArray();
//Use LINQ statement. This is more readable
types = (from id in b select id.ObjectClass.DxfName).Distinct().ToArray();
tran.Commit();
}
return types;
}
Case 3: getting a name list of blocks that have been inserted into model space, or get all block references or block references with given names in model space.
From the code show above, we can see ObjectClass property of ObjectId struct, which was only made available since AutoCAD 2009, helps a lot in defining searching condition (in Where() or Distinct() extension methods). In this scenario, we need a extra method to get to entity itself. Here is it:
private static BlockReference GetBlockRef(ObjectId id, Transaction tran)
{
return (tran.GetObject(id, OpenMode.ForRead) as BlockReference);
}
Now we can get a name list of all inserted blocks:
private static string[] GetBlockNameList(Document dwg)
{
string[] names = null;
Database db = dwg.Database;
using (Transaction tran = db.TransactionManager.StartTransaction())
{
BlockTable tbl =
(BlockTable)tran.GetObject(db.BlockTableId, OpenMode.ForRead);
//Get modelspace BloclTableRecord
BlockTableRecord br =
(BlockTableRecord)tran.GetObject(tbl[BlockTableRecord.ModelSpace], OpenMode.ForRead);
//Cast the BlockTableRecord into IEnumeralbe<T> collection
IEnumerable<ObjectId> b = br.Cast<ObjectId>();
//Use lambda extension method
names=b.Where(id=>id.ObjectClass.DxfName.ToUpper()=="INSERT")
.Select(id=>GetBlockRef(id,tran).Name).Distinct().ToArray();
//Use LINQ statement. This is more readable
names = (from id in b
where id.ObjectClass.DxfName.ToUpper().Contains("INSERT")
select GetBlockRef(id, tran).Name).Distinct().ToArray();
tran.Commit();
}
return names;
}
To search blocks with given name, we do this:
private static List<BlockReference> GetBlockRefs(string blkName, Document dwg)
{
List<BlockReference> blks = null;
Database db = dwg.Database;
using (Transaction tran = db.TransactionManager.StartTransaction())
{
BlockTable tbl =
(BlockTable)tran.GetObject(db.BlockTableId, OpenMode.ForRead);
//Get modelspace BloclTableRecord
BlockTableRecord br =
(BlockTableRecord)tran.GetObject(tbl[BlockTableRecord.ModelSpace], OpenMode.ForRead);
//Cast the BlockTableRecord into IEnumeralbe<T> collection
IEnumerable<ObjectId> b = br.Cast<ObjectId>();
//Use lambda extension method
blks = b.Where(id => id.ObjectClass.DxfName.ToUpper()=="INSERT")
.Select(id => GetBlockRef(id, tran)).ToList<BlockReference>();
//Use LINQ statement. This is more readable
blks = (from id in b
where id.ObjectClass.DxfName.ToUpper()=="INSERT"
select GetBlockRef(id, tran)).ToList<BlockReference>();
tran.Commit();
}
//return only blockreferences with name starting with blkName with lambda extension method
return blks.Where(b=>b.Name.ToUpper()==blkName.ToUpper()).ToList();
//Or return with LINQ statement
return (from b in blks where b.Name.ToUpper()==blkName.ToUpper() select b).ToList();
}
Note, the last code sample "GetBlockRefs()" is only for demonstrating the use of LINQ. It may not be a good idea in certain situation to return entity instead of ObjectId outside a transaction.
As you can see, with the help of LINQ, we can write more readable code easily to search drawing database. However, what is the difference in term of code execution speed between simply looping through the model space with foreach (...) and using LINQ as I showed here? I did not look into this, honestly.I did run the code with drawing with a few thousands entities in model space and did not feel speed difference. But I assume there is difference with LINQ code might result in slightly slower speed: when casting BlockTableRecord into IEnumerable
Excellent post, although I think that programmers are still learning the power of using LINQ. This is a very good start. I am looking to expand on what you started here and apply it to BIM as well.
ReplyDeleteThank you
-JK
The AutoCAD .NET API allows you to automate tasks such as creating and modifying objects stored in the database of a drawing file or change the contents of a customization file.
ReplyDelete