Kean Walmsley

July 2009

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Twitter Updates

    follow me on Twitter



    « DevTV on programming with AutoCAD Map 3D's Geospatial API | Main | Creating a series of AutoCAD polylines by exploding a complex region using .NET »

    August 25, 2008

    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.

    TrackBack

    TrackBack URL for this entry:
    http://www.typepad.com/services/trackback/6a00d83452464869e200e554704a028834

    Listed below are links to weblogs that reference Creating an AutoCAD polyline from an exploded region using .NET:

    Comments

    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.

    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

    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

    Interesting - thank you, Carlos!

    Kean

    Verify your Comment

    Previewing your Comment

    This is only a preview. Your comment has not yet been posted.

    Working...
    Your comment could not be posted. Error type:
    Your comment has been posted. Post another comment

    The letters and numbers you entered did not match the image. Please try again.

    As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

    Having trouble reading this image? View an alternate.

    Working...

    Post a comment

    Feed & Share

    Search