October 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  










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

Update:

The above code doesn't take care of more complex regions and neither does it dispose of the temporary curves properly (thanks for ali for pointing out this second issue). Rather than fix this post, I've made the changes in an update to the next post, which is an evolution of this one.

 

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:

blog comments powered by Disqus

Feed/Share

10 Random Posts