Adding a spatial filter to a block reference
This blog post is minor tweak to the existing post written by my colleague Xiaodong back in 2013, with the recent development changes internally, the code flow is causing crash in ACAD 2015 -32 machines, to avoid such mishap and to have seamless code across both the machines I have re written the code.
Reason for new change in the code :
The existing code sets the filter's definition (including clip boundary) before the filter is added to the database. That causes the wrong type of internal clip boundary object to be created. There are two types of clip boundaries possible, one is only for use in databases that are open in the Acad editor and the other is for databases that are not open in the Acad editor. If the filter doesn't have a database, then we create the clip boundary type that is for non-editor databases. Then, if the filter is added to a database that is open in the editor, it has the wrong type of clip boundary and that causes problems.
static void Test_Clip ()
{
ads_point pt1, pt2;
ads_name ent;
if (acedEntSel(_T("Select xref:"), ent, pt1) != RTNORM)
return;
AcDbObjectId idXref;
if (acdbGetObjectId(idXref,ent) != Acad::eOk)
return;
AcDbObjectPointer<AcDbBlockReference> pRef(idXref, AcDb::kForRead);
if (pRef.openStatus() != Acad::eOk) {
acutPrintf(_T("Not an xref!\n"));
return;
}
AcGePoint2dArray pts;
if (acedGetPoint(NULL,_T("First point:"), pt1) != RTNORM) {
pRef->close();
return;
}
//the ECS of the vertices must be defined in the
//coordinate system of the _block_ so let's calculate
//transform all points to that coordinate system
AcGeMatrix3d mat(pRef->blockTransform());
mat.invert();
AcGePoint3d pt3d(asPnt3d(pt1));
pt3d.transformBy(mat);
pts.append(AcGePoint2d(pt3d.x, pt3d.y));
while (acedGetPoint(pt1,_T("Next point:"), pt2) == RTNORM) {
acedGrDraw(pt1, pt2, 1, 1);
pt3d = asPnt3d(pt2);
pt3d.transformBy(mat);
pts.append(AcGePoint2d(pt3d.x, pt3d.y));
memcpy(pt1, pt2, sizeof(ads_point));
}
acedRedraw(NULL,0);
AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
AcGeVector3d normal;
double elev;
if (pDb->tilemode()) {
normal = pDb->ucsxdir().crossProduct(pDb->ucsydir());
elev = pDb->elevation();
} else {
normal = pDb->pucsxdir().crossProduct(pDb->pucsydir());
elev = pDb->pelevation();
}
normal.normalize();
Acad::ErrorStatus es = pRef.object()->upgradeOpen();
if (es != Acad::eOk) {
pRef->close();
return;
}
//create the filter
AcDbSpatialFilter* pFilter = new AcDbSpatialFilter;
//add it to the extension dictionary of the block reference
//the AcDbIndexFilterManger class provides convenient utility functions
if (AcDbIndexFilterManager::addFilter(pRef.object(), pFilter) != Acad::eOk)
delete pFilter;
else {
acutPrintf(_T("Filter has been succesfully added!\n"));
pRef.object()->downgradeOpen();
}
if (pFilter->setDefinition(pts,normal,elev,
ACDB_INFINITE_XCLIP_DEPTH,-ACDB_INFINITE_XCLIP_DEPTH, true) != Acad::eOk)
{
acutPrintf(L"Filter setDefinition failed.");
//remove the filter if setDefinition fails.
pFilter->erase();
}
es = pFilter->erase();
pFilter->close();
pRef->close();
}
Hi Madhukar!
You can explain why in case of "Filter setDefinition failed" you call pFilter->erase(); twice?
Hi Alex,
Sorry, it is typo , thanks for pointing out.
Thank you for the correction, I was wondering why AutoCAD would crash when stretching the clip area, if the filter definition was set before adding it. And BTW, the crashing happens on 64-bit AutoCAD 2015 as well.
XCLIP xrefs using ObjectARX
The AcDbSpatialFilter class was designed to do this. This class is used to define a spatial filter that AutoCAD uses to define the clip volume of the block reference to the xref in the host drawing.
AutoCAD uses this spatial filter to decide what object IDs will be processed during regen.
The sample below is a small demo. Note: the commands needs to be defined with the options ACRX_CMD_NOINTERNALLOCK:
ACED_ARXCOMMAND_ENTRY_AUTO(CMyTestApp,
MyTestApp,
_MyClip, MyClip,
ACRX_CMD_TRANSPARENT |
ACRX_CMD_NOINTERNALLOCK,
NULL)
static void MyTestApp_MyClip()
{
ads_point pt1,pt2;
ads_name ent;
if (acedEntSel(_T("Select xref:"),ent,pt1)!=RTNORM)
return;
AcDbObjectId idXref;
if (acdbGetObjectId(idXref,ent)!=Acad::eOk)
return;
AcDbObjectPointer<AcDbBlockReference> pRef(idXref,AcDb::kForRead);
if (pRef.openStatus()!=Acad::eOk)
{
acutPrintf(_T("Not an xref!\n"));
return;
}
AcGePoint2dArray pts;
if (acedGetPoint(NULL,_T("First point:"),pt1)!=RTNORM)
return;
//the ECS of the vertices must be defined in the
//coordinate system of the _block_ so let's calculate
//transform all points to that coordinate system
AcGeMatrix3d mat(pRef->blockTransform());
mat.invert();
AcGePoint3d pt3d(asPnt3d(pt1));
pt3d.transformBy(mat);
pts.append(AcGePoint2d(pt3d.x,pt3d.y));
while (acedGetPoint(pt1,_T("Next point:"),pt2)==RTNORM)
{
acedGrDraw(pt1,pt2,1,1);
pt3d = asPnt3d(pt2);
pt3d.transformBy(mat);
pts.append(AcGePoint2d(pt3d.x,pt3d.y));
memcpy(pt1,pt2,sizeof(ads_point));
}
acedRedraw(NULL,0);
AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
AcGeVector3d normal;
double elev;
if (pDb->tilemode())
{
normal = pDb->ucsxdir().crossProduct(pDb->ucsydir());
elev = pDb->elevation();
}
else
{
normal = pDb->pucsxdir().crossProduct(pDb->pucsydir());
elev = pDb->pelevation();
}
normal.normalize();
Acad::ErrorStatus es = pRef.object()->upgradeOpen();
if (es !=Acad::eOk)
return;
//create the filter
AcDbSpatialFilter* pFilter = new AcDbSpatialFilter;
if (pFilter->setDefinition(pts,normal,elev,
ACDB_INFINITE_XCLIP_DEPTH,-ACDB_INFINITE_XCLIP_DEPTH,true)!=Acad::eOk)
{
delete pFilter;
return;
}
//add it to the extension dictionary of the block reference
//the AcDbIndexFilterManger class provides convenient utility functions
if (AcDbIndexFilterManager::addFilter(pRef.object(),pFilter)!=Acad::eOk)
delete pFilter;
else
{
acutPrintf(_T("Filter has been succesfully added!\n"));
pFilter->close();
}
}
Querying for XCLIP information inside AutoCAD using .NET
This question came in recently by email:
I have reached a snag when trying to find the boundaries of external references that have been "xclipped" by the user. Or, to be more precise, I can't even really find the data telling me whether or not the external reference has been "xclipped" at all. I'm wondering if you have any idea how or where I could find this data.
While I found this previous post showing how to perform an XCLIP, I couldn’t find anything showing how to query for XCLIP information.
Here’s some C# code that does just that for a selected block or external reference:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.DatabaseServices.Filters;
using System.Collections.Generic;
namespace SpatialFiltering
{
public class Commands
{
const string filterDictName = "ACAD_FILTER";
const string spatialName = "SPATIAL";
[CommandMethod("DXC")]
static public void DetectXClip()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var ed = doc.Editor;
// Ask for an xclipped xref to be selected
var peo =
new PromptEntityOptions(
"\nSelect xclipped block or xref"
);
peo.SetRejectMessage("Must be a block or xref.");
peo.AddAllowedClass(typeof(BlockReference), false);
var per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
var tr = doc.TransactionManager.StartTransaction();
using (tr)
{
// Open the selected BlockReference
var br =
tr.GetObject(per.ObjectId, OpenMode.ForRead)
as BlockReference;
// To save multiple codepaths with the same message
// ("No clipping information found"), we'll use a flag to
// see whether we've found anything
bool found = false;
// It should always be a block reference, but it might
// not have an extension dictionary
if (
br != null && br.ExtensionDictionary != ObjectId.Null)
{
// The extension dictionary needs to contain a nested
// dictionary called ACAD_FILTER
var extdict =
tr.GetObject(br.ExtensionDictionary, OpenMode.ForRead)
as DBDictionary;
if (extdict != null && extdict.Contains(filterDictName))
{
var fildict =
tr.GetObject(
extdict.GetAt(filterDictName), OpenMode.ForRead
) as DBDictionary;
if (fildict != null)
{
// The nested dictionary should contain a
// SpatialFilter object called SPATIAL
if (fildict.Contains(spatialName))
{
var fil =
tr.GetObject(
fildict.GetAt(spatialName), OpenMode.ForRead
) as SpatialFilter;
if (fil != null)
{
// We have a SpatialFilter: print its bounds
var ext = fil.GetQueryBounds();
ed.WriteMessage(
"\nFound clip from {0} to {1}.",
ext.MinPoint, ext.MaxPoint
);
var pts = fil.Definition.GetPoints();
foreach (var pt in pts)
{
ed.WriteMessage(
"\nBoundary point at {0}", pt
);
}
found = true;
}
}
}
}
}
if (!found)
{
ed.WriteMessage("\nNo clipping information found.");
}
tr.Commit();
}
}
}
}
When we run the DXC command and selected an XCLIPped block or external reference, we see its clipping information printed to the command-line:
Command: DXC
Select xclipped block or xref:
Found clip from (4.05811894970383,5.24702344885421,-10000000000) to (6.37251127340078,8.93717310957857,10000000000).
Boundary point at (4.39197755714056,8.93717310957849)
Boundary point at (4.05811894970388,7.08011285053157)
Boundary point at (4.71314514170174,5.24702344885429)
Boundary point at (5.97105160306316,6.93165698166899)
Boundary point at (6.13163547119819,7.89430470599807)
Boundary point at (6.37251127340073,8.34888837222314)
AutoCAD .NET: Retrieve Real and Good Clip Boundary for INSERT/BlockReference
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.
文章评论