ObjectARX
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Create close polyline from Region

7 REPLIES 7
Reply
Message 1 of 8
jignesh.rana
325 Views, 7 Replies

Create close polyline from Region

i want to make completly closed polyline from acdbregion 

 

i tried but not getting accurate result

7 REPLIES 7
Message 2 of 8
tbrammer
in reply to: jignesh.rana

It's a bit tricky indeed. You have to deal with ECS coordinats of the polyline's vertices and take into account that some of the lines and arcs you get from region->explode() could be reverted. Try the code below. 

This code assumes that the explosion fragments are in the same order as the segments of the region. I'm not 100% sure whether this is guaranteed. 

A completely different approach would be to use the BREP API (<ARX>\utils\brep) instead of explode(). It allows to analyze an AcDbRegion directly.

 

 

 

struct PlineSegment {
	AcGePoint2d p1, p2;
	double bulge=0.0;
	int   reversed=0; // 0 unknown, -1 reversed, 1 not reversed
};

void cmdRegion2Pline()
{
	ads_point    pt;
	ads_name     ent;
	AcDbObjectId idRegion;

	if (acedEntSel(_T("\nSelect region"), ent, pt) != RTNORM)
		return;

	if (acdbGetObjectId(idRegion, ent) != Acad::eOk)
		return;

	Acad::ErrorStatus es;
	AcDbVoidPtrArray curves;
	AcDbRegion *region;
	AcGePlane regionPlane;
	if ((es = acdbOpenObject(region, idRegion, AcDb::kForRead)) == Acad::eOk)
	{
		region->getPlane(regionPlane);
		region->explode(curves);
		region->close();
	}

	AcGePoint3d origin;
	AcGeVector3d axis1, axis2, norm(regionPlane.normal());
	regionPlane.getCoordSystem(origin, axis1, axis2);
	// Recalculate origin to be the closest point on the plane
	double dp = origin.asVector().dotProduct(norm);
	origin = AcGePoint3d::kOrigin + dp * norm;

	AcGeMatrix3d mat_WCS_ECS, mat_ECS_WCS;
	mat_WCS_ECS.setCoordSystem(origin, axis1, axis2, norm);
	mat_ECS_WCS = mat_WCS_ECS.inverse();

	int i, j, N=curves.length();
	AcDbObject *obj;
	AcDbCurve *crv;
	AcDbArc *arc;
	AcGePoint3d ps, pe;
	double angle=0.0;
	std::vector<PlineSegment> segments;
	PlineSegment segment;
	// Collect segment data:
	for (i=0; i<N; ++i)
	{
		obj = (AcDbObject*)curves[i];
		if (crv = AcDbCurve::cast(obj))
		{			
			crv->getStartPoint(ps);
			crv->getEndPoint(pe);
			// Transform to ECS. After this z-components should be 0
			ps.transformBy(mat_ECS_WCS);
			pe.transformBy(mat_ECS_WCS);
			segment.p1 = AcGePoint2d(ps.x, ps.y);
			segment.p2 = AcGePoint2d(pe.x, pe.y);
			if (arc = AcDbArc::cast(obj))
				segment.bulge = tan(0.25 * arc->totalAngle());
			else
				segment.bulge = 0.0;
			segments.push_back(segment);
		}
		delete obj;
	}
	// When we stitch the polyline together we must make sure that start- and endpoints match.
	AcDbDatabase *pDB = acdbHostApplicationServices()->workingDatabase();
	AcDbPolyline* pline = new AcDbPolyline();
	pline->setDatabaseDefaults(pDB);
	unsigned int ix = 0;

	for (i = 0; i < N; ++i)	{
		j = (i>0)? i-1 : N-1;
		PlineSegment &ps = segments[i];
		PlineSegment& psPrev = segments[j];
		if (psPrev.p2 == ps.p1)	{
			psPrev.reversed = ps.reversed = 1;
			pline->addVertexAt(ix++, ps.p1, ps.reversed * ps.bulge);
		} else if (psPrev.p1 == ps.p1){
			ps.reversed     = 1;
			psPrev.reversed = -1;
			pline->addVertexAt(ix++, ps.p1, ps.reversed * ps.bulge);
		} else if (psPrev.p2 == ps.p2) {
			ps.reversed     = -1;
			psPrev.reversed = 1;
			pline->addVertexAt(ix++, ps.p2, ps.reversed * ps.bulge);
		} else if (psPrev.p1 == ps.p2) {
			psPrev.reversed = ps.reversed = -1;
			pline->addVertexAt(ix++, ps.p2, ps.reversed * ps.bulge);
		}
	}
	pline->setClosed(true);
	pline->transformBy(mat_WCS_ECS);
	PostToDb(pDB, pline);
}

 

 

 

 


Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

Message 3 of 8
jignesh.rana
in reply to: tbrammer

how to deal with PlineSegment ?

 

it is giving compile error

Message 4 of 8
jignesh.rana
in reply to: tbrammer

sir i tried your logic and its working but at some points it not match and cze of it  polyline created different 

 

i attached screenshot for reference and also attach dwgimg_20240326.png

Message 5 of 8
tbrammer
in reply to: jignesh.rana

I see. This is an accuracy problem. I didn't expect this.

If you look at the content of std::vector<PlineSegment> segments you will see these values at index 0 and 1:

[0]	{p1={x=-30.203451326888171 y=30.376315339599387 } p2={x=-12.919102255473263 y=30.376311689869908 } bulge=...}
[1]	{p1={x=-31.203567541422672 y=30.376309680192207 } p2={x=-30.203451326887865 y=30.376309734909317 } bulge=...}

As you can see segments[0].p1 and segments[1].p2 are slightly differnt. The difference is only 0.00001 but seems to be greater than the tolerance specifed in AcGeContext::gTol.

 

You can either raise the tolerance or modify the code so that it looks for the pair of points with the smallest distance, which is the safer way. For example you can add this else-block after else if (psPrev.p1 == ps.p2){...}:

		else
		{
			double d11, d12, d21, d22, *dMin = &d11;
			d11 = psPrev.p1.distanceTo(ps.p1);
			d12 = psPrev.p1.distanceTo(ps.p2);
			if (d12 < *dMin)
				dMin = &d12;
			d21 = psPrev.p2.distanceTo(ps.p1);
			if (d21 < *dMin)
				dMin = &d21;
			d22 = psPrev.p2.distanceTo(ps.p2);
			if (d22 < *dMin)
				dMin = &d22;

			if (dMin == &d21)
			{
				psPrev.reversed = ps.reversed = 1;
				pline->addVertexAt(ix++, ps.p1, ps.reversed * ps.bulge);
			}
			else if (dMin == &d11)
			{
				ps.reversed = 1;
				psPrev.reversed = -1;
				pline->addVertexAt(ix++, ps.p1, ps.reversed * ps.bulge);
			}
			else if (dMin == &d22)
			{
				ps.reversed = -1;
				psPrev.reversed = 1;
				pline->addVertexAt(ix++, ps.p2, ps.reversed * ps.bulge);
			}
			else if (dMin == &d12)
			{
				psPrev.reversed = ps.reversed = -1;
				pline->addVertexAt(ix++, ps.p2, ps.reversed * ps.bulge);
			}

 


Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

Message 6 of 8
jignesh.rana
in reply to: tbrammer

thanx a lot sir but for single region it is working fine

 

but subtracted or union region is not work with this logic 

 

i have attached dwg for sample cases

 

and i got some logic and modified in it i putted epsilon(tolerance) but i don't know for how much put tolerance so from your logic last else block is good thing but its not work with my logic 

 

here i wrote that logic

 

AcDbVoidPtrArray PolylineFromRegion(AcDbRegion *reg)
{
// We will return a collection of entities
// (should include closed Polylines and other
// closed curves, such as Circles)
 
AcDbVoidPtrArray res;
// Explode Region -> collection of Curves / Regions
Acad::ErrorStatus es = Acad::eOk;
AcDbVoidPtrArray cvs;
if (reg == NULL)
return res;
es = reg->explode(cvs);
 
 
// Create a plane to convert 3D coords
// into Region coord system
 
AcGeVector3d vec;
es = reg->getNormal(vec);
AcGePlane *pl = new AcGePlane(AcGePoint3d(0,0,0),vec);
//es = reg->getPlane(pl);
 
if(pl != NULL)
{
bool finished = false;
 
while (!finished && cvs.length() > 0)
{
// Count the Curves and the non-Curves, and find
// the index of the first Curve in the collection
 
int cvCnt = 0, nonCvCnt = 0, fstCvIdx = -1;
 
for (int i = 0; i < cvs.length(); i++)
{
AcDbCurve *tmpCv = (AcDbCurve *)cvs.at(i);
//AcGePoint3d stPnt;
//tmpCv->getStartPoint(stPnt);
//pl = new AcGePlane(stPnt,vec);
//if (tmpCv == NULL || tmpCv->isKindOf(AcDbRegion::desc()) == true)
if(tmpCv == NULL || tmpCv->isA() == AcDbRegion::desc())
nonCvCnt++;
else
{
// Closed curves can go straight into the
// results collection, and aren't added
// to the Curve count
if (tmpCv->isClosed())
{
res.append(tmpCv);
cvs.remove(tmpCv);
// Decrement, so we don't miss an item
i--;
}
else
{
cvCnt++;
if (fstCvIdx == -1)
fstCvIdx = i;
}
}
}
 
if (fstCvIdx >= 0)
{
// For the initial segment take the first
// Curve in the collection
 
AcDbCurve *fstCv = (AcDbCurve *)cvs.at(fstCvIdx);
 
// The resulting Polyline
 
AcDbPolyline *p = new AcDbPolyline();
 
// Set common entity properties from the Region
 
es = p->setPropertiesFrom(reg);
 
// Add the first two vertices, but only set the
// bulge on the first (the second will be set
// retroactively from the second segment)
 
// We also assume the first segment is counter-
// clockwise (the default for arcs), as we're
// not swapping the order of the vertices to
// make them fit the Polyline's order
AcGePoint3d pntStartPoint;
fstCv->getStartPoint(pntStartPoint);
AcGePoint2d pntStart, pntEnd;
pntStart.set(pntStartPoint.x, pntStartPoint.y);
 
 
p->addVertexAt(p->numVerts(),pntStart,BulgeFromCurve(fstCv, false), 0, 0);
 
AcGePoint3d pntEndPoint;
fstCv->getEndPoint(pntEndPoint);
pntEnd.set(pntEndPoint.x, pntEndPoint.y);
p->addVertexAt(p->numVerts(),pntEnd,0, 0, 0);
 
cvs.remove(fstCv);
 
// The next point to look for
 
AcGePoint3d nextPt;
fstCv->getEndPoint(nextPt);
 
// We no longer need the curve
DeleteEntity(fstCv);
 
 
// Find the line that is connected to
// the next point
 
// If for some reason the lines returned were not
// connected, we could loop endlessly.
// So we store the previous curve count and assume
// that if this count has not been decreased by
// looping completely through the segments once,
// then we should not continue to loop.
// Hopefully this will never happen, as the curves
// should form a closed loop, but anyway...
 
// Set the previous count as artificially high,
// so that we loop once, at least.
 
int prevCnt = cvs.length() + 1;
while (cvs.length() > nonCvCnt && cvs.length() < prevCnt)
{
prevCnt = cvs.length();
//for(INT_PTR i(0);i<prevCnt;++i) //commented on 26-05-2023: as we remove points from cvs so don't take variable(prevCnt) for loop
for (INT_PTR i(0); i<cvs.length(); ++i)
{
AcDbCurve *cv = (AcDbCurve *)cvs.at(i);
 
if (cv != NULL)
{
// If one end of the curve connects with the
// point we're looking for...
AcGePoint3d StartPt, EndPt;
cv->getStartPoint(StartPt);
cv->getEndPoint(EndPt);
//if (StartPt == nextPt ||EndPt == nextPt)
double epsilon = 0.00001;
double dist1 = StartPt.distanceTo(nextPt);
double dist2 = EndPt.distanceTo(nextPt);
if (dist1 <= epsilon || dist2 <= epsilon)
{
// Calculate the bulge for the curve and
// set it on the previous vertex
 
double bulge = BulgeFromCurve(cv, EndPt == nextPt);
if (bulge != 0.0)
p->setBulgeAt(p->numVerts()-1, bulge);
 
// Reverse the points, if needed
 
//if (StartPt == nextPt)
if(dist1 <= epsilon)
nextPt = EndPt;
else
// cv.EndPoint == nextPt
nextPt = StartPt;
 
// Add out new vertex (bulge will be set next
// time through, as needed)
AcGePoint2d nextTempPt;
nextTempPt.set(nextPt.x,nextPt.y);
p->addVertexAt(p->numVerts(),nextTempPt,0, 0, 0);
 
// Remove our curve from the list, which
// decrements the count, of course
 
cvs.remove(cv);
DeleteEntity(cv);
//break;
i--;
}
}
}
}
 
 
// Once we have added all the Polyline's vertices,
// transform it to the original region's plane
AcGeMatrix3d mat = AcGeMatrix3d::planeToWorld(pl->normal());
p->transformBy(mat);
//RemoveSameVertex(p);
res.append(p);
 
if (cvs.length() == nonCvCnt)
finished = true;
}
 
// If there are any Regions in the collection,
// recurse to explode and add their geometry
 
if (nonCvCnt > 0 && cvs.length() > 0)
{
INT_PTR iLen = cvs.length();
for(INT_PTR j(0);j<cvs.length();++j)
{
AcDbRegion *subReg = (AcDbRegion *)cvs.at(j);
AcDbVoidPtrArray cvs1;
//es = subReg->explode(cvs1);
if (subReg != NULL)
{
AcDbVoidPtrArray subRes = PolylineFromRegion(subReg);
for(INT_PTR k(0);k<subRes.length();++k)
res.append(subRes.at(k));
 
cvs.remove(subReg);
DeleteEntity(subReg);
}
 
}
}
if (cvs.length() == 0)
finished = true;
}
}
return res;
}
 
double BulgeFromCurve(AcDbCurve *cv,bool clockwise)
{
double bulge = 0.0;
 
AcDbArc *a = (AcDbArc *)cv;
if (a != NULL)
{
double newStart;
 
// The start angle is usually greater than the end,
// as arcs are all counter-clockwise.
// (If it isn't it's because the arc crosses the
// 0-degree line, and we can subtract 2PI from the
// start angle.)
 
if (a->startAngle() > a->endAngle())
newStart = a->startAngle() - 8 * atan(double(1));// * Math::Atan(1);
else
newStart = a->startAngle();
 
// Bulge is defined as the tan of
// one fourth of the included angle
 
bulge = tan((a->endAngle() - newStart) / 4);
 
// If the curve is clockwise, we negate the bulge
 
if (clockwise)
bulge = -bulge;
}
return bulge;
}
Message 7 of 8
tbrammer
in reply to: jignesh.rana

The algorithm was designed for an AcDbRegion that has exactly one face and no holes. It expects exactly one "outer loop". When you explode() an AcDbRegion  that has N faces, the results are N AcDbRegions that must be processed each. 

 

I stated that I'm not 100% sure whether it is guaranteed, that the explosion fragments appear in the right order.

I'm also not sure whether the fragments of a region with holes or multiple faces will appear in a defined order. 

 

If you want to analyze these regions I would recommend to use the BREP API. It allows to iterate over outer and inner loops in a region. 

 

The following code is not optimized but it works. Basically it implements

void BuildLoops(
    std::vector<PlineSegment> &segments, 
    std::vector< std::vector<int> >& loops, 
    double minGap
);

It calculates "loops" which are arrays of indices of connected PlineSegments.

It would be more effective to explode a multi-face loop to multiple vector<PlineSegment> arrays.

 

struct PlineSegment {
	AcGePoint2d p1, p2;
	double bulge=0.0;
	int   reversed=0; // 0 unknown, -1 reversed, 1 not reversed
	bool  inserted=false;
};

// MyRegion2Pline, R2P
void BuildLoops(std::vector<PlineSegment> &segments, std::vector< std::vector<int> >& loops, double minGap)
{
	int i, N = (int)segments.size();	
	std::vector<int> loop;
	bool bInserted = false;
	do 
	{
		bInserted = false;
		for (i = 0; i < N; ++i)
		{
			if (segments[i].inserted)
				continue;
			if (loop.empty())
			{
				loop.push_back(i);
				segments[i].inserted = true;
			}
			else
			{
				PlineSegment& psPrev = segments[loop.back()];
				PlineSegment& ps = segments[i];
				if (ps.inserted)
					continue;
				double d11, d12, d21, d22, *dMin = &d11;
				d11 = psPrev.p1.distanceTo(ps.p1);
				d12 = psPrev.p1.distanceTo(ps.p2);
				if (d12 < *dMin)
					dMin = &d12;
				d21 = psPrev.p2.distanceTo(ps.p1);
				if (d21 < *dMin)
					dMin = &d21;
				d22 = psPrev.p2.distanceTo(ps.p2);
				if (d22 < *dMin)
					dMin = &d22;

				if (*dMin < minGap)
				{
					loop.push_back(i);
					ps.inserted = true;

					if (dMin == &d21)
					{
						psPrev.reversed = ps.reversed = 1;
					}
					else if (dMin == &d11)
					{
						ps.reversed = 1;
						psPrev.reversed = -1;
					}
					else if (dMin == &d22)
					{
						ps.reversed = -1;
						psPrev.reversed = 1;
					}
					else if (dMin == &d12)
					{
						psPrev.reversed = ps.reversed = -1;
					}

					// Is the loop closed now?
					PlineSegment& psStart = segments[loop.front()];
					const AcGePoint2d& ptStart = (psStart.reversed > 0) ? psStart.p1 : psStart.p2;
					const AcGePoint2d& pt = (ps.reversed>0) ? ps.p2 : ps.p1;
					double d = ptStart.distanceTo(pt);
					if (d < minGap)
					{
						// Yes! Start a new loop
						loops.push_back(std::move(loop));
						loop.clear();
					}
					bInserted = true;
					break; // exit for(j..) loop
				}
			}
		}
	}
	while (bInserted);
}


void cmdRegion2Pline()
{
	ads_point    pt;
	ads_name     ent;
	AcDbObjectId idRegion;

	if (acedEntSel(_T("\nSelect region"), ent, pt) != RTNORM)
		return;

	if (acdbGetObjectId(idRegion, ent) != Acad::eOk)
		return;

	Acad::ErrorStatus es;
	AcDbVoidPtrArray curves;
	AcDbRegion* region;
	AcGePlane regionPlane;
	if ((es = acdbOpenObject(region, idRegion, AcDb::kForRead)) == Acad::eOk)
	{
		region->getPlane(regionPlane);
		region->explode(curves);
		region->close();

		// AcDbRegions with multiple faces will create single-face regions upon explode
		AcDbVoidPtrArray curvesFromSubregions;
		int i, N = curves.length();
		for (i = 0; i < N; ++i)
		{
			AcDbObject *obj = (AcDbObject*)curves[i];
			AcDbRegion *rg;
			if (rg = AcDbRegion::cast(obj))
			{
				AcDbVoidPtrArray curves2;
				rg->explode(curves2);
				curvesFromSubregions.append(curves2);
				delete obj;
				curves.removeAt(i--);
				--N;
			}
		}
		curves.append(curvesFromSubregions);
	}

	AcGePoint3d origin;
	AcGeVector3d axis1, axis2, norm(regionPlane.normal());
	regionPlane.getCoordSystem(origin, axis1, axis2);
	// Recalculate origin to be the closest point on the plane
	double dp = origin.asVector().dotProduct(norm);
	origin = AcGePoint3d::kOrigin + dp * norm;

	AcGeMatrix3d mat_WCS_ECS, mat_ECS_WCS;
	mat_WCS_ECS.setCoordSystem(origin, axis1, axis2, norm);
	mat_ECS_WCS = mat_WCS_ECS.inverse();

	int i, j, N = curves.length();
	AcDbObject* obj;
	AcDbCurve* crv;
	AcDbArc* arc;
	AcGePoint3d ps, pe;
	double angle = 0.0;
	std::vector<PlineSegment> segments;
	PlineSegment segment;
	// Collect segment data:
	for (i = 0; i < N; ++i)
	{
		obj = (AcDbObject*)curves[i];
		if (crv = AcDbCurve::cast(obj))
		{
			crv->getStartPoint(ps);
			crv->getEndPoint(pe);
			// Transform to ECS. After this z-components should be 0
			ps.transformBy(mat_ECS_WCS);
			pe.transformBy(mat_ECS_WCS);
			segment.p1 = AcGePoint2d(ps.x, ps.y);
			segment.p2 = AcGePoint2d(pe.x, pe.y);
			if (segment.p1 != segment.p2) // don't add coincident points.
			{
				if (arc = AcDbArc::cast(obj))
					segment.bulge = tan(0.25 * arc->totalAngle());
				else
					segment.bulge = 0.0;
			}
			segments.push_back(segment);
		}
		delete obj;
	}

	AcDbDatabase* pDB = acdbHostApplicationServices()->workingDatabase();
	double minGap = 1000 * AcGeContext::gTol.equalPoint();
	std::vector< std::vector<int> > loops;
	BuildLoops(segments, loops, minGap);
	for (const std::vector<int> &loop : loops)
	{
		AcDbPolyline* pline = new AcDbPolyline();
		pline->setDatabaseDefaults(pDB);
		unsigned int ix = 0;
		for (int i : loop)
		{
			const PlineSegment& ps = segments[i];
			pline->addVertexAt(ix++, ps.reversed ? ps.p2:ps.p1, ps.reversed * ps.bulge);
		}
		pline->setClosed(true);
		pline->transformBy(mat_WCS_ECS);
		PostToDb(pDB, pline);
	}
}

 


Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

Message 8 of 8
tbrammer
in reply to: jignesh.rana

I have implemented a solution based on the BREP API. It is attached in Region2Pline.zip. This solution also works well for regions with multiple faces and holes.  You should be able to build the project out of the box. It is written for ObjectARX 2024. Just adjust the path <ArxSdkDir>X:\ObjectARX 2024\</ArxSdkDir> in Autodesk.arx-2022.props to your own ARX directory.

 

The ARX implements the command "R2P" => void cmdR2P()

It uses the BREP API to fill a std::vector<OuterLoopWithHoles>:

class CurveSegment {
public:
	AcDbFullSubentPath subentPath;
	AcGePoint3d ptStart;
	double bulge = 0.0;
};

class OuterLoopWithHoles {
public:
	std::vector<CurveSegment> OuterLoop;  // 1..N outer shapes of the region
	std::vector < std::vector<CurveSegment> > InnerLoops; // 0..M holes
};

 

By changing the static variable  static bool bCreatePline  you can choose whether you want to create polylines from ptsStart and bulge or arcs and lines per segment based on subentPath.


Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

”Boost