Kean Walmsley


  • About the Author
    Kean on Google+

August 2014

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            








« 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.

Update:

Here's an updated version of the code in this post that correctly disposes of temporary curves, avoiding the problem highlighted in a comment on the last post. Thanks, ali!

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;

 

            // We no longer need the curve

 

            fstCv.Dispose();

 

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

                    cv.Dispose();

 

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

                subReg.Dispose();

              }

            }

          }

          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;

    }

  }

}

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:

blog comments powered by Disqus

10 Random Posts