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



    « Creating an AutoCAD polyline from an exploded region using .NET | Main | Autodesk API technology learning resources & webcasts »

    August 27, 2008

    Creating a series of AutoCAD polylines by exploding a complex region using .NET

    This is a follow-up to the last post, where we looked at some C# code to generate a Polyline from a Region. Fernando very kindly pointed out that complex Regions would not be handled properly, so I went away and enhanced the RTP command to create separate Polylines for each of the loops (or nested Regions) contained in a Region we're trying to explode.

    The significant changes were:

    • To return a collection of objects, rather than a single Polyline
    • To recurse when we find a Region in the results of the Explode() call

    Otherwise the code is reasonably similar, although I suspect it's now somewhat cleaner from having been worked with more closely.

    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)

            {

              DBObjectCollection objs =

                PolylineFromRegion(reg);


              // Append our new entities to the database


              btr.UpgradeOpen();


              foreach (Entity ent in objs)

              {

                btr.AppendEntity(ent);

                tr.AddNewlyCreatedDBObject(ent, true);

              }


              // Finally we erase the original region


              reg.UpgradeOpen();

              reg.Erase();

            }

            tr.Commit();

          }

        }


        private static DBObjectCollection PolylineFromRegion(

          Region reg

        )

        {

          // We will return a collection of entities

          // (should include closed Polylines and other

          // closed curves, such as Circles)


          DBObjectCollection res =

            new DBObjectCollection();


          // Explode Region -> collection of Curves / Regions


          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);


          using(pl)

          {

            bool finished = false;


            while (!finished && cvs.Count > 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.Count; i++)

              {

                Curve tmpCv = cvs[i] as Curve;

                if (tmpCv == null)

                  nonCvCnt++;

                else

                {

                  // Closed curves can go straight into the

                  // results collection, and aren't added

                  // to the Curve count


                  if (tmpCv.Closed)

                  {

                    res.Add(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


                Curve fstCv = (Curve)cvs[fstCvIdx];


                // The resulting Polyline


                Polyline p = new Polyline();


                // Set common entity properties from the Region


                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


                p.AddVertexAt(

                  p.NumberOfVertices,

                  fstCv.StartPoint.Convert2d(pl),

                  BulgeFromCurve(fstCv, false), 0, 0

                );


                p.AddVertexAt(

                  p.NumberOfVertices,

                  fstCv.EndPoint.Convert2d(pl),

                  0, 0, 0

                );


                cvs.Remove(fstCv);


                // The next point to look for


                Point3d nextPt = fstCv.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 > nonCvCnt && cvs.Count < prevCnt)

                {

                  prevCnt = cvs.Count;

                  foreach (DBObject obj in cvs)

                  {

                    Curve cv = obj as Curve;


                    if (cv != null)

                    {

                      // 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);

                        if (bulge != 0.0)

                          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;

                      }

                    }

                  }

                }


                // Once we have added all the Polyline's vertices,

                // transform it to the original region's plane


                p.TransformBy(Matrix3d.PlaneToWorld(pl));

                res.Add(p);


                if (cvs.Count == nonCvCnt)

                  finished = true;

              }


              // If there are any Regions in the collection,

              // recurse to explode and add their geometry


              if (nonCvCnt > 0 && cvs.Count > 0)

              {

                foreach (DBObject obj in cvs)

                {

                  Region subReg = obj as Region;

                  if (subReg != null)

                  {

                    DBObjectCollection subRes =

                      PolylineFromRegion(subReg);


                    foreach (DBObject o in subRes)

                      res.Add(o);


                    cvs.Remove(subReg);

                  }

                }

              }

              if (cvs.Count == 0)

                finished = true;

            }

          }

          return res;

        }


        // 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;

        }

      }

    }

    Each of the complex regions I tested - and I created quite a few, starting with the REGION command to create simple Regions from Circles and closed Polylines which I then UNIONed and SUBTRACTed to create complex Regions with holes and islands - was handled by this code. The one case I didn't explicitly code for was around coincident geometry: I didn't spend time working out a way to determine which of multiple segments with vertexes at the same location should be selected while constructing a particular loop. I can imagine it would take some logic to back-track (effectively creating a decision tree), but that seemed beyond the scope of this sample.

    If anyone comes across further cases that they feel should be handled by the above code (but currently are not), then please do let me know.

    TrackBack

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

    Listed below are links to weblogs that reference Creating a series of AutoCAD polylines by exploding a complex region using .NET:

    Comments

    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