Creating an AutoCAD polyline from an exploded region using .NET
Thanks to Philippe Leefsma, from our DevTech team in Europe, for providing the code used as the basis for this post. I took Philippe's code and enhanced it to support arcs and to check for disconnected segments (which in theory should never happen, but it's better to be safe than to loop infinitely :-).
When you explode a region in AutoCAD, the resultant geometry is in the form of lines and arcs. The following technique shows how to take the lines and arcs returned by the Explode() function (which doesn't perform the equivalent of the EXPLODE command in AutoCAD, remember: it just returns the exploded geometry corresponding to the objects upon which it was called, they do not get added to the database and neither is the source entity erased) and use them to construct an equivalent Polyline object.
It's interesting code for a number of reasons:
- It loops through and connects segments that may not be listed in sequence
- It determines the bulge factor needed to make a Polyline segment geometrically equivalent to an Arc object
- This is calculated as the tangent of a quarter of the included angle
Here's the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System;
namespace RegionConversion
{
public class Commands
{
[CommandMethod("RTP")]
static public void RegionToPolyline()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
PromptEntityOptions peo =
new PromptEntityOptions("\nSelect a region:");
peo.SetRejectMessage("\nMust be a region.");
peo.AddAllowedClass(typeof(Region), true);
PromptEntityResult per =
ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead);
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace],
OpenMode.ForRead);
Region reg =
tr.GetObject(
per.ObjectId,
OpenMode.ForRead) as Region;
if (reg != null)
{
// Explode Region -> collection of Curves
DBObjectCollection cvs =
new DBObjectCollection();
reg.Explode(cvs);
// Create a plane to convert 3D coords
// into Region coord system
Plane pl =
new Plane(new Point3d(0, 0, 0), reg.Normal);
// The resulting Polyline
Polyline p = new Polyline();
// Set common entity properties from the Region
p.SetPropertiesFrom(reg);
// For initial Curve take the first in the list
Curve cv1 = cvs[0] as Curve;
p.AddVertexAt(
p.NumberOfVertices,
cv1.StartPoint.Convert2d(pl),
BulgeFromCurve(cv1, false), 0, 0
);
p.AddVertexAt(
p.NumberOfVertices,
cv1.EndPoint.Convert2d(pl),
0, 0, 0
);
cvs.Remove(cv1);
// The next point to look for
Point3d nextPt = cv1.EndPoint;
// 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.Count + 1;
while (cvs.Count > 0 && cvs.Count < prevCnt)
{
prevCnt = cvs.Count;
foreach (Curve cv in cvs)
{
// If one end of the curve connects with the
// point we're looking for...
if (cv.StartPoint == nextPt ||
cv.EndPoint == nextPt)
{
// Calculate the bulge for the curve and
// set it on the previous vertex
double bulge =
BulgeFromCurve(cv, cv.EndPoint == nextPt);
p.SetBulgeAt(p.NumberOfVertices - 1, bulge);
// Reverse the points, if needed
if (cv.StartPoint == nextPt)
nextPt = cv.EndPoint;
else
// cv.EndPoint == nextPt
nextPt = cv.StartPoint;
// Add out new vertex (bulge will be set next
// time through, as needed)
p.AddVertexAt(
p.NumberOfVertices,
nextPt.Convert2d(pl),
0, 0, 0
);
// Remove our curve from the list, which
// decrements the count, of course
cvs.Remove(cv);
break;
}
}
}
if (cvs.Count >= prevCnt)
{
p.Dispose();
ed.WriteMessage(
"\nError connecting segments."
);
}
else
{
// Once we have added all the Polyline's vertices,
// transform it to the original region's plane
p.TransformBy(Matrix3d.PlaneToWorld(pl));
// Append our new Polyline to the database
btr.UpgradeOpen();
btr.AppendEntity(p);
tr.AddNewlyCreatedDBObject(p, true);
// Finally we erase the original region
reg.UpgradeOpen();
reg.Erase();
}
}
tr.Commit();
}
}
// Helper function to calculate the bulge for arcs
private static double BulgeFromCurve(
Curve cv,
bool clockwise
)
{
double bulge = 0.0;
Arc a = cv as Arc;
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 * Math.Atan(1);
else
newStart = a.StartAngle;
// Bulge is defined as the tan of
// one fourth of the included angle
bulge = Math.Tan((a.EndAngle - newStart) / 4);
// If the curve is clockwise, we negate the bulge
if (clockwise)
bulge = -bulge;
}
return bulge;
}
}
}
To really put the code through its paces, try creating a Region in arbitrary 3D space, defined by a closed Polyline containing both arc and lines segments. The RTP command should replace the selected Region with a Polyine of the same shape.
I've done my best to anticipate as much as I can in the above code - my hope being that it will work on any Region - but if I've missed a case, be sure to let me know.

Subscribe via RSS
Kean,
Problem 1:
If the region has a hole the routine probably will fail once you cannot created either a disconnected polyline or a multi-polyline inside AutoCAD.
Problem 2:
If you have a composed region with disconnected loops the explode will return 2 new regions at it first call. The routine should repeat the explode until it does not find any AcDbRegion entity anymore.
Problem 3:
Depending on how the region is built you may end up with two or more coincident curve segments. The polyline build routine should be able to ignore this duplicated curves.
I have faced these problems with a routine I have made to deal with regions but I have not checked if this sample will face the same problems but probably it will! :)
Regards,
Fernando.
Posted by: Fernando Malard | August 25, 2008 at 04:20 PM
Excellent - thanks, Fernando!
I knew there had to be some cases I'd missed - I'll update the code and post an update later in the week.
Kean
Posted by: Kean | August 25, 2008 at 04:42 PM
Hi Kean!
Do you really want to learn how to type fast?
Many years ago, I got a software to teach typing of an unusual way (to me).
I never found it again, so I wrote my own.
Don't worry about, because it's not a virus.
If you like, I can send to you the source code (VB.NET)
Enjoy!
http://rapidshare.com/files/145722891/VBDatilog.zip.html
Posted by: Carlos Alberto Nunes | September 16, 2008 at 01:23 PM
Interesting - thank you, Carlos!
Kean
Posted by: Kean | October 09, 2008 at 03:41 PM