Question
AcDbBlockReference::geomExtents() doesn't return an accurate value for a rotated
AcDbBlockReference. Is there a function that does?
Answer
The following function calculates the extents of an AcDbBlockReference. The
following restrictions apply:
-- Non uniformly scaled blocks containing solids are not supported.
-- Blocks containing AcDb3dPolylines are not supported.
-- Blocks containing AcDb2dPolylines other than of type k2dSimplePoly are not
supported.
Here's the code:
void test() { ads_name ename; ads_point pt; if (acedEntSel("Select a block reference:",ename,pt)!=RTNORM) return; AcDbObjectId id; if (acdbGetObjectId(id,ename)!=Acad::eOk) return; AcDbExtents extents; AcDbBlockReference* pRef; if (acdbOpenObject(pRef,id,AcDb::kForRead)==Acad::eOk) { if (Acad::eOk==getBlockRefGeomExtents(pRef,extents)) { //just display the 2d box AcGePoint2d minpt(extents.minPoint()[X],extents.minPoint()[Y]); AcGePoint2d maxpt(extents.maxPoint()[X],extents.maxPoint()[Y]); AcGePoint2dArray points; points.append(minpt); AcGePoint2d pt(maxpt[X],minpt[Y]); points.append(pt); points.append(maxpt); pt.set(minpt[X],maxpt[Y]); points.append(pt); AcDbPolyline* pPline; if ((pPline=makeSimplePline(points))!=NULL){ pPline->setClosed(Adesk::kTrue); AcDbObjectId id; postToDb(pPline,id); } } else acutPrintf("\nCannot calculate accurate extents"); pRef->close(); } } //creates a polyline with 0 width, without bulges AcDbPolyline* makeSimplePline(const AcGePoint2dArray& points) { AcDbPolyline* pPline = new AcDbPolyline(points.length()); if (pPline==NULL) return pPline; for (int i=0;i<points.length();i++) if (pPline->addVertexAt(i,points<i>)!=Acad::eOk) { delete pPline; pPline = NULL; break; } return pPline; }//explodes a 2dpolyline by first converting it to a new polyline Acad::ErrorStatus explodeAcDb2dPolyline(const AcDb2dPolyline* p2d,AcDbVoidPtrArray& ents){ if (p2d==NULL) return Acad::eInvalidInput; AcDbPolyline* pPline; if ((pPline=new AcDbPolyline)==NULL) return Acad::eOutOfMemory; Acad::ErrorStatus es; if ((es=pPline->convertFrom((AcDbEntity*&)p2d,Adesk::kFalse))==Acad::eOk) { es=pPline->explode(ents); } return es; }Acad::ErrorStatus getBlockRefGeomExtents(AcDbBlockReference* pRef, AcDbExtents& extents, const AcGeMatrix3d& mat) { Acad::ErrorStatus es; AcGeMatrix3d xform; AcDbEntity* pCopy; AcDbEntity* pE; xform=mat*pRef->blockTransform(); AcDbObjectId idBlock = pRef->blockTableRecord(); AcDbBlockTableRecord* pBlock; if ((es=acdbOpenObject(pBlock,idBlock,AcDb::kForRead))==Acad::eOk){ AcDbBlockTableRecordIterator* pIter; if ((es=pBlock->newIterator(pIter))==Acad::eOk){ for (pIter->start();!pIter->done();pIter->step()){ AcDbExtents ext; if ((es=pIter->getEntity(pE,AcDb::kForRead))==Acad::eOk) { AcDbBlockReference* pRefNested; if ((pRefNested=AcDbBlockReference::cast(pE))!=NULL) { if ((es=getBlockRefGeomExtents(pRefNested,ext,xform))!=Acad::eOk) { pE->close(); break; } } else { AcRxClass* pDesc = pE->isA(); if (pDesc==AcDbPolyline::desc() || pDesc==AcDb2dPolyline::desc() || pDesc==AcDbMText::desc()) { //need explode AcDbVoidPtrArray array; if (pDesc==AcDbPolyline::desc()) es=pE->explode(array); if (pDesc==AcDbMText::desc()) es=pE->explode(array); if (pDesc==AcDb2dPolyline::desc()) es=explodeAcDb2dPolyline(AcDb2dPolyline::cast(pE),array); if (es==Acad::eOk) { for(int i=0;i<array.length();i++) { AcDbExtents ext2; AcDbEntity* pPart = ((AcDbEntity*)(array<i>)); if((es=pPart->getTransformedCopy(xform,pCopy))==Acad::eOk) { es=pCopy->getGeomExtents(ext2); delete pCopy; } if (es!=Acad::eOk) { delete pPart; if (es==Acad::eNullExtents) continue; else break; } delete pPart; ext.addExt(ext2); } } } else { if((es=pE->getTransformedCopy(xform,pCopy))==Acad::eOk) { es=pCopy->getGeomExtents(ext); delete pCopy; } } } if (es!=Acad::eOk) { pE->close(); if (es==Acad::eNullExtents) continue; else break; } extents.addExt(ext); pE->close(); } } delete pIter; } } pBlock->close(); //add non constant attributes AcDbObjectIterator* pIter; if ((pIter=pRef->attributeIterator())==NULL) return Acad::eOutOfMemory; for (pIter->start();!pIter->done();pIter->step()) { AcDbExtents ext; if((es=acdbOpenObject(pE,pIter->objectId(),AcDb::kForRead))==Acad::eOk) { pE->getGeomExtents(ext); if (es!=Acad::eOk) { pE->close(); if (es==Acad::eNullExtents) continue; else break; } extents.addExt(ext); pE->close(); } } delete pIter; return es; }//Posts an entity to the database // Acad::ErrorStatus postToDb(AcDbEntity* ent, AcDbObjectId& objId) { Acad::ErrorStatus es; AcDbBlockTable* pBlockTable; AcDbBlockTableRecord* pSpaceRecord; if (NULL == ent) return Acad::eNullObjectPointer; if (NULL == acdbHostApplicationServices()->workingDatabase()) return Acad::eNoDatabase; if ((es = acdbHostApplicationServices()->workingDatabase()->getBlockTable(pBlockTable, AcDb::kForRead))!= Acad::eOk) return es; if ((es = pBlockTable->getAt(ACDB_MODEL_SPACE,pSpaceRecord,AcDb::kForWrite))!= Acad::eOk) { pBlockTable->close(); return es; } pBlockTable->close(); if ((es = pSpaceRecord->appendAcDbEntity(objId, ent)) != Acad::eOk) { pSpaceRecord->close(); return es; } pSpaceRecord->close(); return ent->close(); }
测试了一下,geomExtents的确存在问题,得不到真实的box.
找到个汉化
extents
输出块参考的WCS范围
parentXform
输入应用至块参考几何体的变换
当在非WCS坐标系统中工作时,从AcDbBlockReference::geomExtents()返回的范围可比实际上的几何体更大一些,因为AcDbBlockReference::geomExtents()通过块参考的变换矩阵而不是通过变换实体及计算一个变换的实体集的一个新的边框来变换包含的实体预计算的范围。这导致实体的范围长方体与非世界坐标对齐。这是一个快速的但不提供一个“过大”的包围范围的长方体的方法。
AcDbBlockReference::geomExtentsBestFit()通过取得被参考的块表记录中的所有实体,对它们应用parentXform,并在结果实体集上计算范围的拷贝来避免这个问题。这比使用AcDbBlockReference::geomExtents()处理更慢,但将导致最紧密地匹配封闭的几何体。
此方法递归地作用于嵌套的块参考。当此方法在最外层的块参考上调用时(ARX应用程序的标准情况),parentXform应为从最外层块参考至参考拥有调用此方法的块参考的块表记录的块参考的累积变换。
此方法使用AcDbBlockReference::explode()方法取得实体的拷贝。因为AcDbBlockReference::explode()目前不支持不一致的变换,所以如果调用此方法的块参考或任何嵌套的块参考有不一致的变换,则此方法不成功且将返回Acad::eInvalidInput。
如果成功返回Acad::eOk。
我来贴一段代码:
AcDbExtents CEntity::GetRefBoundingBox (AcDbBlockReference *pRef, const AcGeMatrix3d & mat) { AcDbExtents ext; AcDbExtents RetExt; AcDbObjectId recId = pRef->blockTableRecord(); AcDbBlockTableRecordPointer block(recId,AcDb::kForRead); Acad::ErrorStatus es = block.openStatus(); if (es == eOk) { AcDbBlockTableRecordIterator *pItr = NULL; block->newIterator(pItr); AcDbObjectId id; AcDbEntity *pEnt = NULL; for (pItr->start();!pItr->done();pItr->step()) { es = pItr->getEntity(pEnt,AcDb::kForWrite); if (es == eOk) { pEnt->transformBy(mat); if (pEnt->isKindOf(AcDbBlockReference::desc())) { AcGeMatrix3d blkmat=((AcDbBlockReference*)pEnt)->blockTransform(); ext = GetRefBoundingBox((AcDbBlockReference *)pEnt,blkmat); } else { pEnt->getGeomExtents(ext); } pEnt->transformBy(mat.inverse()); RetExt.addExt(ext); pEnt->close(); } } delete pItr; pItr = NULL; } return RetExt; }
譬如:
AcDbObjectPointer<AcDbBlockReference> blkref(id,AcDb::kForRead); //id块参照的id
AcDbExtents ext = CEntity::GetRefBoundingBox(blkref.object(),blkref->blockTransform());
这个代码得到的box跟geomExtentsBestFit是一致的。
至于lisp中,对于块参照,其boundingbox也是getGeomExtents,而不是geomExtentsBestFit
其所以如果用LISP方法求,估计也要先变换块内实体,得到真正的box,然后把实体变换回去。
文章评论