Tuesday, August 18, 2015

Find all AcDbLine Intersections

Hello,

This question came from Sandhya who asked how to find all line intersections into a drawing.
In this case, we will consider only intersections between AcDbLine entities.

First we need to prepare our CMap structure to be able to handle AcGePoint3d as the map key. The idea is to group all Lines passing through each intersection point.

CMap does not support AcGePoint3d because it does not know how to Hash it and also how to compare it as a key. To allow that we will need to define both HasKey and CompareElements templates as follows:

// These are template classes to allow AcGePoint3d do be used as a Key do CMap class
const double _dTol = 0.0001;

template<> UINT AFXAPI HashKey<AcGePoint3d> (AcGePoint3d key)
{
CString sPoint;
sPoint.Format(_T("%f,%f,%f"),key.x, key.y ,key.z);

UINT iHash = (NULL == &key) ? 0 : HashKey((LPCSTR)sPoint.GetBuffer());
return iHash;
}

template<> BOOL AFXAPI CompareElements<AcGePoint3d, AcGePoint3d> 
     (const AcGePoint3d* pElement1, const AcGePoint3d* pElement2)
{
if ((pElement1 == NULL) || (pElement2 == NULL))
return false;
AcGeTol gTol;
gTol.setEqualPoint(_dTol); // Point comparison tolerance
return (pElement1->isEqualTo(*pElement2,gTol));
}

Next, we will collect the AcDbLine entities in ModelSpace:

// Collect lines from ModelSpace
Acad::ErrorStatus es;
AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
AcDbBlockTableRecordPointer pBTR(acdbSymUtil()->blockModelSpaceId(pDb),AcDb::kForWrite);

AcDbBlockTableRecordIterator *pIter = NULL;
pBTR->newIterator(pIter, true);
AcDbObjectIdArray arrLines;

while(!pIter->done())
{
AcDbEntity *pEnt = NULL;
es = pIter->getEntity(pEnt, AcDb::kForRead);
if (es == Acad::eOk)
{
if (pEnt->isKindOf(AcDbLine::desc()))
arrLines.append(pEnt->objectId());

pEnt->close();
}

pIter->step(true);
}
delete pIter;
pIter = NULL;

if (arrLines.length() == 0)
{
acutPrintf(_T("There are no lines in Model Space!\n"));
return;
}
else
{
acutPrintf(_T("We've found %d lines in Model Space!\nChecking intersection with tolerance %f...\n"), 
arrLines.length(), _dTol);
}

Ok, with the lines collected we will then build our CMap with the information we need:

// Process lines in pairs
CMap<AcGePoint3d,AcGePoint3d,AcDbObjectIdArray,AcDbObjectIdArray&> mapLines;

acdbTransactionManager->startTransaction();
for (int i=0; i<arrLines.length()-1; i++)
{
AcDbLine* pLineA = NULL;
if (acdbTransactionManager->getObject((AcDbObject*&)pLineA,arrLines[i], AcDb::kForRead) == Acad::eOk)
{
for (int j=i+1; j<arrLines.length(); j++)
{
AcDbLine* pLineB = NULL;
if (acdbTransactionManager->getObject((AcDbObject*&)pLineB,arrLines[j], AcDb::kForRead) == Acad::eOk)
{
AcGePoint3dArray arrPts;
if (pLineA->intersectWith(pLineB,AcDb::kOnBothOperands,arrPts) == Acad::eOk)
{
if (arrPts.length() > 0)
{
for (int p=0; p<arrPts.length(); p++)
{
AcDbObjectIdArray arrExist;
if (mapLines.Lookup(arrPts[p],arrExist) == TRUE)
{
// Existing point...
if (arrExist.contains(pLineA->objectId()) == false)
mapLines[arrPts[p]].append(pLineA->objectId());

if (arrExist.contains(pLineB->objectId()) == false)
mapLines[arrPts[p]].append(pLineB->objectId());
}
else
{
// New point...
AcDbObjectIdArray arrNewEnts;
arrNewEnts.append(pLineA->objectId());
arrNewEnts.append(pLineB->objectId());
mapLines.SetAt(arrPts[p],arrNewEnts);
}
}
}
}
}
}
}
}

acdbTransactionManager->endTransaction();

To demonstrate the use of this information, we then use our CMap data to create AcDbPoint entities on ModeSpace and also print a small report at the command prompt:

// Just as demonstration, walk through points and add an AcDbPoint entity to ModelSpace then print the info
POSITION pos = mapLines.GetStartPosition();
while (pos)
{
AcGePoint3d ptKey(0,0,0);
AcDbObjectIdArray arrEnts;
mapLines.GetNextAssoc(pos,ptKey, arrEnts);

AcDbPoint* ptEnt = new AcDbPoint(ptKey);
AcDbObjectId idPointEnt;
pBTR->appendAcDbEntity(idPointEnt,ptEnt);
ptEnt->close();

CString sEnts;
for (int e=0; e<arrEnts.length(); e++)
{
ACHAR pBuff[255] = _T("");
arrEnts[e].handle().getIntoAsciiBuffer(pBuff);
CString sBuff;
sBuff.Format( (e==arrEnts.length()-1) ? _T("%s"): _T("%s,"), pBuff);
sEnts += sBuff;
}

CString sPromptReport;
sPromptReport.Format(_T("Point (%.4f, %.4f, %.4f) - Entities [%s]\n"),ptKey.x, ptKey.y, ptKey.z, sEnts);
acutPrintf(sPromptReport);
}

This is the sample DWG (remember to adjust the PTYPE):




And this is the prompt result:

Command: LINEINTS
We've found 8 lines in Model Space!
Checking intersection with tolerance 0.000100...
Point (49.4194, 7.7097, 0.0000) - Entities [1E0,1E1]
Point (43.8908, 18.4889, 0.0000) - Entities [1DF,1E0]
Point (37.2051, 11.5104, 0.0000) - Entities [1DF,1E1]
Point (32.4059, 13.0038, 0.0000) - Entities [1DE,1E1]
Point (33.7651, 7.9199, 0.0000) - Entities [1DE,1DF]
Point (30.4900, 20.1703, 0.0000) - Entities [1DD,1DE]
Point (20.6648, 16.6573, 0.0000) - Entities [1E1,1E2]
Point (22.1562, 13.7469, 0.0000) - Entities [1DD,1E2]
Point (24.4172, 15.4897, 0.0000) - Entities [1DD,1E1]
Point (17.0892, 9.8415, 0.0000) - Entities [1DC,1DD]
Point (16.3380, 17.5581, 0.0000) - Entities [1DB,1DC]

Full source code can be downloaded from here:

LineIntersections_VS2012_ARX2015.zip

Best regards!

4 comments :

Unknown said...

hii Fernando,
iam trying to read all the entities from the screen and convert them into region ...but only one entity is converted into region ..pls suggest something ..

here is my code..


Acad::ErrorStatus es;
AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
AcDbBlockTable *pBlkTbl;
pDb->getSymbolTable(pBlkTbl, AcDb::kForRead);

AcDbBlockTableRecord *pBlkTblRcd;
pBlkTbl->getAt(ACDB_MODEL_SPACE, pBlkTblRcd, AcDb::kForRead);
pBlkTbl->close();

AcDbBlockTableRecordIterator *pBlkTblRcdItr;
pBlkTblRcd->newIterator(pBlkTblRcdItr);
AcDbVoidPtrArray regions;
AcDbVoidPtrArray polylineArray;

for (pBlkTblRcdItr->start(); !pBlkTblRcdItr->done();pBlkTblRcdItr->step())
{
AcDbEntity *pEnt;
es = pBlkTblRcdItr->getEntity(pEnt,AcDb::kForRead);
AcDbVoidPtrArray polylineArray;

polylineArray.append (static_cast (pEnt));

AcDbVoidPtrArray regions;

es = AcDbRegion :: createFromCurves (polylineArray, regions);
assert(regions.length() == 1);

AcDbRegion * pRegion = NULL;

pRegion = AcDbRegion :: cast ((AcRxObject *) regions [0]);

pEnt-> close ();
}

pBlkTblRcd->close();
delete pBlkTblRcdItr;


Add to the database REGION

AcDbBlockTable * pBlockTable;

acdbHostApplicationServices () -> workingDatabase () -> getBlockTable (pBlockTable, AcDb :: kForRead);

AcDbBlockTableRecord * pBlockTableRecord;

pBlockTable-> getAt (ACDB_MODEL_SPACE, pBlockTableRecord, AcDb :: kForWrite);

pBlockTable-> close ();
for(int i=0;i (regions [0]);
AcDbObjectId regionId;

es = pBlockTableRecord-> appendAcDbEntity (regionId, regionObj);

pBlockTableRecord-> close ();

regionObj-> close ();
}


if(Acad::eOk != es)
{
pPoly->close();
acutPrintf(L"\nFailed to create region\n");
}
else
{
acutPrintf(L"\nregion created\n");
return;
}





return;

}

Fernando Malard said...

Hi Karishma,

The first issue with your code is "polylineArray.append (static_cast (pEnt));"
You can't use static_cast with AutoCAD entities because you may ending up with a corrupt class.
The correct way to do it is:

AcDbPolyline* pPoly = AcDbPolyline::cast(pEnt);
if (pPoly != NULL)
{
polylineArray.append(pPoly);
}

Note that not all entities inside Model Space will be Polylines so this is why you need to test if the cast() succeeded.

Second issue is "createFromCurves" returned ErrorStatus. You need to check it because the region creation may fail in some cases like when the boundary is not entirely closed.

Third issue is "assert(regions.length() == 1);". You cannot force the returned array to be a single region. If you send curves that shape more than one non-intersect boundary you will receive more than one region as the result. So you should handle this properly.

Fourth issue is the line "for(int i=0;i (regions [0]);". You put a semi-column ";" at the end of this FOR statement so it will do nothing. The code you want to repeat should be a single line next to the FOR statement or a embraced code fragment. You should indeed do a FOR statement by the number of Regions returned by the createFromCurves() call and add each one to Model Space.

I would suggest you to reorganize your code in 2 isolated methods doing specific things. One method collecting the Polylines and another to process these Polylines and create regions out of them.

Regards,

Unknown said...

FAZ MAIS DE MES QUE ESTOU TENTANDO RESOLVER ISTO E NÃO CONSIGO...

Aviso 1 d:\documentos\visual studio 2012\Projects\ArxProject3\ArxProject3\rxapi.lib(libinit.obj) warning LNK4099: PDB 'rxapi_cl.pdb' não foi encontrado 'rxapi.lib(libinit.obj)' ou no 'd:\documentos\visual studio 2012\Projects\ArxProject3\x64\Release\rxapi_cl.pdb'; vinculando objeto quando quando sem nenhuma informação de depuração ArxProject3

PODE ME AJUDAR??

Fernando Malard said...

Wilson, esses warnings (avisos) são devido a arquivos de Debug não fornecidos pela Autodesk para depurar o código do AutoCAD. Pode ignorar sem problemas pois não afetam em nada o programa gerado.

Abraços,