AutoCAD blocks (INSERT from user perspective and BlockReference from API including .NET perspective) can be XClipped with different shapes from the very basic Rectangle to quite complex Polygonal. The XClip boundary can be created from an existing Polyline entity two though the arc segments will not be honored at all.
It is obvious that AutoCAD stores the XClip boundary information somewhere in the xclipped BlockReference itself, but the question is how we can retrieve the XClip boundary from the xclipped INSERTs or BlockReferences. This article will help you achieve this goal.
As always, it is good to start with some pretty concise and well working code.
public class XClipRetriever
{
[CommandMethod("RetrieveXClipBoundary", CommandFlags.Modal | CommandFlags.UsePickSet)]
public static void RetrieveXClipBoundary_Method()
{
Editor ed = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
try
{
if (ed.SelectImplied().Status != PromptStatus.OK) throw new System.Exception("Nothing has been pre-selected!");
RXClass BlockReferenceRXClass = RXClass.GetClass(typeof(BlockReference));
using (Transaction tr = MgdAcApplication.DocumentManager.MdiActiveDocument.TransactionManager.StartTransaction())
{
foreach (ObjectId id in ed.SelectImplied().Value.GetObjectIds())
{
if (id.ObjectClass == BlockReferenceRXClass)
{
BlockReference blkRef = (BlockReference)tr.GetObject(id, OpenMode.ForRead);
if (blkRef.ExtensionDictionary != ObjectId.Null)
{
DBDictionary extdict = (DBDictionary)tr.GetObject(blkRef.ExtensionDictionary, OpenMode.ForRead);
if (extdict.Contains("ACAD_FILTER"))
{
DBDictionary dict = (DBDictionary)tr.GetObject(extdict.GetAt("ACAD_FILTER"), OpenMode.ForRead);
if (dict.Contains("SPATIAL"))
{
SpatialFilter filter = (SpatialFilter)tr.GetObject(dict.GetAt("SPATIAL"), OpenMode.ForRead);
DrawPolygon(blkRef.Database, filter.Definition.Normal, filter.ClipSpaceToWorldCoordinateSystemTransform, filter.Definition.GetPoints());
}
}
}
}
}
tr.Commit();
}
}
catch (System.Exception ex)
{
ed.WriteMessage(Environment.NewLine + ex.Message);
}
}
public static ObjectId DrawPolygon(Database db, Vector3d normal, Matrix3d mat, Point2dCollection vertices)
{
ObjectId ret = ObjectId.Null;
Transaction tr = db.TransactionManager.TopTransaction;
BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
using (Polyline pl = new Polyline())
{
pl.SetDatabaseDefaults();
pl.ColorIndex = 3;
pl.Closed = true;
for (int i = 0; i < vertices.Count; i++)
{
pl.AddVertexAt(0, vertices[i], 0, 0, 0);
}
pl.TransformBy(mat);
btr.AppendEntity(pl);
tr.AddNewlyCreatedDBObject(pl, true);
ret = pl.ObjectId;
}
return ret;
}
}
Here is how it behaves beautifully in a UCS of AutoCAD.
As can be seen, the non-clipped BlockReference (the left top one) was bypassed as expected; the two BlockReference instances (the top right and the bottom right) that were created and xclipped in the UCS were looked after well too; needless to say, the other three BlockReference instances that were created and xclipped in the WCS behaved fine.
Another point is quite worth of mentioning. If the XCLIP boundary is a rectangle, the ‘SpatialFilter. Definition.GetPoints’ method will return only two points, which seem to be the two corners of the XCLIP boundary extent, as also can be seen from the screenshot. Not sure why the API had to behave that way and what the real difference is between a rectangle and a regular polygon with four sides, but it will surely bring some extra work to figure out what the other corners are for the ‘rectangle’. It may be straightforward if everything is in WCS and the boundary is not transformed at all but may not be so at all if UCS needs to be thought about, Paper Spaces should be covered, the XCLIP boundary along with the xclipped BlockReference are moved, rotated, mirrored, scaled, or so, …
A few highlights may be more helpful to understand the code and the AutoCAD .NET XCLIP API.
• The command works with the PickFirst (SelectImplied) selection set as the CommandFlags.UsePickSet indicates.
• The Editor.SelectImplied() method is supposed to get the pre-selected selection set.
• The Status of the prompt selection set result instance that is returned by Editor.SelectImplied() can be used to check whether there is a pre-selected selection set (indicated by PromptStatus.OK) or not (indicated by PromptStatus.ERROR).
• The BlockReference RXClass (represented by the RXClass.GetClass(typeof(BlockReference)) method call and the ObjectId.ObjectClass property) is used to compare whether each of the pre-selected entities is an INSERT or not. It has performance indication as discussed and demonstrated before.
• Inside the XClipped BlockReference, there must be a sub-dictionary named "ACAD_FILTER" in its extension dictionary.
• The "ACAD_FILTER" sub-dictionary contains a record with name "SPATIAL" and type SpatialFilter.
• The SpatialFilter has the XCLIP boundary information that we want such as Definition.Normal, Definition.GetPoints(), and ClipSpaceToWorldCoordinateSystemTransform.
• The Database.TransactionManager.TopTransaction is used to save new transaction creation and existing transaction passing around.
• The Polyline .AddVertexAt() method is used and the index 0 is always provided for the vertices properly added to the newly built Polyline.
• Besides UCS being perfectly taken care of, the command works nicely in Paper Spaces (layouts) too.
• The XCLIP is pretty a subtle thing. Though we have tried our best to cover all possible scenarios, some may still be left over due to either some unknowns of the XCLIP itself or some limitations of the AutoCAD .NET API.
The leading edge AutoCAD .NET Addin Wizard (AcadNetAddinWizard) provides various project wizards, item wizards, coders and widgets to help program AutoCAD .NET addins.
Posted by: |