public static class AcDbExtensions
{
// These overloads of GetObjects<T>() target BlockTableRecord
// specifically, because:
//
// 1. It is incorrect to target *any* IEnumerable, which
// includes things like System.String. You shouldn't
// allow an extension method to be invoked on a target
// that is not applicable or elegible.
//
// 2. BlockTableRecord specifically contains only entities,
// and supports opening erased entities, which requires
// specialization that would unduly burden a more generic
// solution targeting any IEnumerable that enumerates a
// sequence of ObjectIds (e.g., ObjectIdCollection,
// SymbolTableRecord, ObjectId[], etc). There are other
// overloads of GetObjects<T>() which target those same
// types and specialize them, without also carrying the
// overhead of a single, 'all-purpose' solution ( those
// latter ones are not included here ).
public static IEnumerable<T> GetObjects<T>( this BlockTableRecord btr )
where T: Entity
{
return GetObjects<T>( btr, OpenMode.ForRead, false, false );
}
public static IEnumerable<T> GetObjects<T>( this BlockTableRecord btr, OpenMode mode )
where T: Entity
{
return GetObjects<T>( btr, mode, false, false );
}
public static IEnumerable<T> GetObjects<T>( this BlockTableRecord btr, bool openErased )
where T: Entity
{
return GetObjects<T>( btr, OpenMode.ForRead, openErased, false );
}
public static IEnumerable<T> GetObjects<T>(
this BlockTableRecord btr,
OpenMode mode,
bool openErased,
bool openObjectsOnLockedLayers ) where T: Entity
{
// if caller wants erased objects, use the IncludingErase property:
BlockTableRecord source = openErased ? btr.IncludingErased : btr;
// if the generic argument is <Entity>, there is no filtering
// needed because this method targets BlockTableRecord,
// whose elements are all entities:
bool openLocked = mode == OpenMode.ForWrite && openObjectsOnLockedLayers;
// The runtime class of the generic argument managed wrapper type:
RXClass rxclass
= RXClass
.GetClass( typeof( T
) );
// if no filtering is needed, just open and return every object
// cast to the generic argument type:
if( unfiltered )
{
foreach( ObjectId id in source )
{
yield return (T) id.GetObject( mode, openErased, openLocked );
}
}
else // caller wants a subset, so filter by runtime class:
{
foreach( ObjectId id in source )
{
if( id.ObjectClass.IsSubclassOf( rxclass ) )
{
yield return (T) id.GetObject( mode, openErased, openLocked );
}
}
}
}
//////////////////////////////////////////////////////////////////////////////
// Extension method:
//
// IEnumerable<T>.UpgradeOpen<T>() where T: DBObject
//
// Upgrade each object to OpenMode.ForWrite as it is pulled
// from the sequence, which is preferable to opening everything
// for write, when we may not modify all opened objects.
//
// This method is useful when you want to upgrade the open mode
// of many objects, but do not want to modify objects that are on
// locked layers (that also presumes you are operating directly
// on a BlockTableRecord, possibly in a RealDwg application or a
// database that's not open in the drawing editor, and the source
// may include objects on locked layers).
//
// Objects on locked layers are skipped and omitted from the
// resulting sequence.
public static IEnumerable<T> UpgradeOpen<T>( this IEnumerable<T> source )
where T: DBObject
{
foreach( T item in source )
{
try
{
if( ! item.IsWriteEnabled )
item.UpgradeOpen();
}
catch( Autodesk.AutoCAD.Runtime.Exception ex )
{
if( ex.ErrorStatus != ErrorStatus.OnLockedLayer )
throw;
continue;
}
yield return item;
}
}
// Helper extension method:
// Open and return the model space BlockTableRecord for the
// given database:
public static BlockTableRecord ModelSpace( this Database db )
{
return ModelSpace( db, OpenMode.ForRead );
}
public static BlockTableRecord ModelSpace( this Database db, OpenMode mode )
{
return (BlockTableRecord) SymbolUtilityServices.GetBlockModelSpaceId( db )
.GetObject( mode, false, false );
}
// Open and return the BlockTableRecord for the current space
// in the given database:
public static BlockTableRecord CurrentSpace( this Database db )
{
return (BlockTableRecord) db.CurrentSpaceId.GetObject( OpenMode.ForRead, false, false );
}
public static BlockTableRecord CurrentSpace( this Database db, OpenMode mode )
{
return (BlockTableRecord) db.CurrentSpaceId.GetObject( mode, false, false );
}
// Extension Method: bool RXClass.IsSubclassOf( RXClass other )
//
// This helper avoids a managed-to-native transition in the
// case where the two runtime classes are the same class, by
// doing a comparison in managed code first, before calling
// IsDerivedFrom() (which is a native P/Invoke).
//
// When a significant precentage of the objects being filtered
// are of the same runtime class as the target, this will run
// faster than direct calls to IsDerivedFrom().
//
// In the latest version of the framework, P/Invoke is much
// faster and for that reason this helper may not be needed
// or will not offer any advantage over a direct call to the
// IsDerivedFrom() method.
public static bool IsSubclassOf( this RXClass thisClass, RXClass otherClass )
{
return thisClass == otherClass || thisClass.IsDerivedFrom( otherClass );
}
}
public static class Example
{
// Erase all lines in model space that are < 0.1 units
// long. This example shows a more 'proper' way to use
// LINQ to write simple commands that modify entities.
// Specifically, it only opens objects for write if they are
// going to be modified, and gracefully deals with objects
// on locked layers. It should go without saying that we
// can do this same operation using a filtered selection
// as well, but only for the model or paper space blocks
// of drawings that are open in the drawing editor. For
// other blocks, or for operating on databases that aren't
// open in the drawing editor, an operation like this one
// must be handled using a pattern like that used here.
//
// Points of interest:
//
// The ModelSpace() extension method is called to
// open and get the model space BlockTableRecord.
//
// The GetObjects<Line>() extension method is called
// to get all Line entities in model space, open for read.
//
// The Where( line => line.Length < 0.1 ) call reduces
// the sequence of lines returned by GetObjects<Line>(),
// to only those lines whose length is < 0.1 units.
//
// The call to the UpgradeOpen() method receives the
// subset of entities returned by the call to Where(),
// and upgrades the open mode of those entities, which
// is the set of entities that will actually be erased.
// If any of those entities reside on locked layers, they
// will not be included in the sequence returned by the
// call to the UpgradeOpen() extension method.
//
// Because UpgradeOpen() uses deferred execution, the
// actual upgrading of each object's open mode does not
// happen until just before the object is assigned to the
// 'line' variable within the foreach() loop.
//
// The ModelSpace() method is a handy shortcut for what
// is otherwise overly-verbose code that would only be
// correct in coding examples that many noobs mistakenly
// take too literaly, as representative examples of how
// real production code should be written.
[CommandMethod( "LINQ_WEEDLINES" )]
public static void WeedLinesCommand()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
using( doc.LockDocument() )
using( Transaction tr = doc.TransactionManager.StartTransaction() )
{
var linesToErase = doc.Database.ModelSpace()
.GetObjects<Line>()
.Where( line => line.Length < 0.1 )
.UpgradeOpen();
foreach( Line line in linesToErase )
{
line.Erase();
}
tr.Commit();
}
}
// Example: Displays the sum of the areas of
// all closed Polylines in the current space.
[CommandMethod( "LINQ_SUMPOLYLINEAREAS" )]
public static void SumPolylineAreasCommand()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
using( Transaction tr = doc.TransactionManager.StartTransaction() )
{
double area = doc.Database.CurrentSpace()
.GetObjects<Polyline>()
.Where( pline => pline.Closed )
.Sum( pline => pline.Area );
doc.Editor.WriteMessage(
"\nTotal Area of all closed polylines: {0} units",
area
);
tr.Commit();
}
}
// Example: Display the handle of the circle
// in modelspace having the largest Radius
// (or the last of multiple circles having the
// largest Radius):
[CommandMethod( "LINQ_FINDLARGESTCIRCLERADIUS" )]
public static void FindLargestCircleRadiusCommand()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
using( Transaction tr = doc.TransactionManager.StartTransaction() )
{
var circles = doc.Database.ModelSpace().GetObjects<Circle>();
if( circles.Any() )
{
Circle largest =
circles.Aggregate(
(a, b) => a.Radius > b.Radius ? a : b
);
doc.Editor.WriteMessage(
"\nLargest circle - handle: {0}, Radius: {1}",
largest.Handle.ToString(), largest.Radius
);
}
else
{
doc.Editor.WriteMessage( "\nNo Circles were found.");
}
tr.Commit();
}
}
}