找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 1612|回复: 1

[分享] Creating the smallest possible rectangle around 2D AutoCAD geometry using .NET

[复制链接]

已领礼包: 859个

财富等级: 财运亨通

发表于 2014-5-8 15:41:27 来自手机 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
[ 本帖最后由 csharp 于 2014-5-15 10:12 编辑 ]\n\n[ 本帖最后由 csharp 于 2014-5-15 10:11 编辑 ]\n\n[ 本帖最后由 csharp 于 2014-5-9 06:53 编辑 ]\n\n[url=http://through-the-interface.typepad.com/through_the_interface/2012/11/creating-the-smallest-possible-rectangle-around-2d-autocad-geometry-using-net.html]http://through-the-interface.typepad.com/through_the_interface/2012/11/creating-the-smallest-possible-rectangle-around-2d-autocad-geometry-using-net.html[/url]
Creating the smallest possible rectangle around 2D AutoCAD geometry using .NET



There was a follow-up comment on this previous post, requesting that it also create a rectangular boundary around selected geometry. This is a much simpler problem to solve that dealing with a minimum enclosing circle, so I was happy enough to oblige. :-)

Rather than duplicate code from the previous implement, I went ahead and generalised it to contain a core set of functions that get called from different commands: MEC in the existing case where circular boundaries are required and MER for the new case of a rectangular polyline boundary.

I’ve also adjusted the prompts and code to be more general – including renaming the system variable to ENCLOSINGBOUNDARYBUFFER, although I only tested via the fallback USERI1 variable – and it all seems to work as expected.

Here’s the updated C# code:

  1. using Autodesk.AutoCAD.ApplicationServices;

  2. using Autodesk.AutoCAD.DatabaseServices;

  3. using Autodesk.AutoCAD.EditorInput;

  4. using Autodesk.AutoCAD.Runtime;

  5. using Autodesk.AutoCAD.Geometry;

  6. using System.Collections.Generic;

  7. using System.Linq;

  8. using System;



  9. namespace MinimumEnclosingBoundary

  10. {

  11.   public class Commands

  12.   {

  13.     [CommandMethod("MER", CommandFlags.UsePickSet)]

  14.     public void MinimumEnclosingRectangle()

  15.     {

  16.       MinimumEnclosingBoundary(false);

  17.     }



  18.     [CommandMethod("MEC", CommandFlags.UsePickSet)]

  19.     public void MinimumEnclosingCircle()

  20.     {

  21.       MinimumEnclosingBoundary();

  22.     }



  23.     public void MinimumEnclosingBoundary(

  24.       bool circularBoundary = true

  25.     )

  26.     {

  27.       Document doc =

  28.           Application.DocumentManager.MdiActiveDocument;

  29.       Database db = doc.Database;

  30.       Editor ed = doc.Editor;



  31.       // Ask user to select entities



  32.       PromptSelectionOptions pso =

  33.         new PromptSelectionOptions();

  34.       pso.MessageForAdding = "\nSelect objects to enclose: ";

  35.       pso.AllowDuplicates = false;

  36.       pso.AllowSubSelections = true;

  37.       pso.RejectObjectsFromNonCurrentSpace = true;

  38.       pso.RejectObjectsOnLockedLayers = false;



  39.       PromptSelectionResult psr = ed.GetSelection(pso);

  40.       if (psr.Status != PromptStatus.OK)

  41.         return;



  42.       bool oneBoundPerEnt = false;



  43.       if (psr.Value.Count > 1)

  44.       {

  45.         PromptKeywordOptions pko =

  46.           new PromptKeywordOptions(

  47.             "\nMultiple objects selected: create " +

  48.             "individual boundaries around each one?"

  49.           );

  50.         pko.AllowNone = true;

  51.         pko.Keywords.Add("Yes");

  52.         pko.Keywords.Add("No");

  53.         pko.Keywords.Default = "No";



  54.         PromptResult pkr = ed.GetKeywords(pko);

  55.         if (pkr.Status != PromptStatus.OK)

  56.           return;



  57.         oneBoundPerEnt = (pkr.StringResult == "Yes");

  58.       }



  59.       // There may be a SysVar defining the buffer

  60.       // to add to our radius



  61.       double buffer = 0.0;

  62.       try

  63.       {

  64.         object bufvar =

  65.           Application.GetSystemVariable(

  66.             "ENCLOSINGBOUNDARYBUFFER"

  67.           );

  68.         if (bufvar != null)

  69.         {

  70.           short bufval = (short)bufvar;

  71.           buffer = bufval / 100.0;

  72.         }

  73.       }

  74.       catch

  75.       {

  76.         object bufvar =

  77.           Application.GetSystemVariable("USERI1");

  78.         if (bufvar != null)

  79.         {

  80.           short bufval = (short)bufvar;

  81.           buffer = bufval / 100.0;

  82.         }

  83.       }



  84.       // Get the current UCS



  85.       CoordinateSystem3d ucs =

  86.         ed.CurrentUserCoordinateSystem.CoordinateSystem3d;



  87.       // Collect points on the component entities



  88.       Point3dCollection pts = new Point3dCollection();



  89.       Transaction tr =

  90.         db.TransactionManager.StartTransaction();

  91.       using (tr)

  92.       {

  93.         BlockTableRecord btr =

  94.           (BlockTableRecord)tr.GetObject(

  95.             db.CurrentSpaceId,

  96.             OpenMode.ForWrite

  97.           );



  98.         for (int i = 0; i < psr.Value.Count; i++)

  99.         {

  100.           Entity ent =

  101.             (Entity)tr.GetObject(

  102.               psr.Value[i].ObjectId,

  103.               OpenMode.ForRead

  104.             );



  105.           // Collect the points for each selected entity



  106.           Point3dCollection entPts = CollectPoints(tr, ent);

  107.           foreach (Point3d pt in entPts)

  108.           {

  109.             /*

  110.              * Create a DBPoint, for testing purposes

  111.              *

  112.             DBPoint dbp = new DBPoint(pt);

  113.             btr.AppendEntity(dbp);

  114.             tr.AddNewlyCreatedDBObject(dbp, true);

  115.              */



  116.             pts.Add(pt);

  117.           }



  118.           // Create a boundary for each entity (if so chosen) or

  119.           // just once after collecting all the points



  120.           if (oneBoundPerEnt || i == psr.Value.Count - 1)

  121.           {

  122.             try

  123.             {

  124.               Entity bnd =

  125.                 (circularBoundary ?

  126.                   CircleFromPoints(pts, ucs, buffer) :

  127.                   RectangleFromPoints(pts, ucs, buffer)

  128.                 );

  129.               btr.AppendEntity(bnd);

  130.               tr.AddNewlyCreatedDBObject(bnd, true);

  131.             }

  132.             catch

  133.             {

  134.               ed.WriteMessage(

  135.                 "\nUnable to calculate enclosing boundary."

  136.               );

  137.             }



  138.             pts.Clear();

  139.           }

  140.         }



  141.         tr.Commit();

  142.       }

  143.     }



  144.     private Point3dCollection CollectPoints(

  145.       Transaction tr, Entity ent

  146.     )

  147.     {

  148.       // The collection of points to populate and return



  149.       Point3dCollection pts = new Point3dCollection();



  150.       // We'll start by checking a block reference for

  151.       // attributes, getting their bounds and adding

  152.       // them to the point list. We'll still explode

  153.       // the BlockReference later, to gather points

  154.       // from other geometry, it's just that approach

  155.       // doesn't work for attributes (we only get the

  156.       // AttributeDefinitions, which don't have bounds)



  157.       BlockReference br = ent as BlockReference;

  158.       if (br != null)

  159.       {

  160.         foreach (ObjectId arId in br.AttributeCollection)

  161.         {

  162.           DBObject obj = tr.GetObject(arId, OpenMode.ForRead);

  163.           if (obj is AttributeReference)

  164.           {

  165.             AttributeReference ar = (AttributeReference)obj;

  166.             ExtractBounds(ar, pts);

  167.           }

  168.         }

  169.       }



  170.       // If we have a curve - other than a polyline, which

  171.       // we will want to explode - we'll get points along

  172.       // its length



  173.       Curve cur = ent as Curve;

  174.       if (cur != null &&

  175.           !(cur is Polyline ||

  176.             cur is Polyline2d ||

  177.             cur is Polyline3d))

  178.       {

  179.         // Two points are enough for a line, we'll go with

  180.         // a higher number for other curves



  181.         int segs = (ent is Line ? 2 : 20);



  182.         double param = cur.EndParam - cur.StartParam;

  183.         for (int i = 0; i < segs; i++)

  184.         {

  185.           try

  186.           {

  187.             Point3d pt =

  188.               cur.GetPointAtParameter(

  189.                 cur.StartParam + (i * param / (segs - 1))

  190.               );

  191.             pts.Add(pt);

  192.           }

  193.           catch { }

  194.         }

  195.       }

  196.       else if (ent is DBPoint)

  197.       {

  198.         // Points are easy



  199.         pts.Add(((DBPoint)ent).Position);

  200.       }

  201.       else if (ent is DBText)

  202.       {

  203.         // For DBText we use the same approach as

  204.         // for AttributeReferences



  205.         ExtractBounds((DBText)ent, pts);

  206.       }

  207.       else if (ent is MText)

  208.       {

  209.         // MText is also easy - you get all four corners

  210.         // returned by a function. That said, the points

  211.         // are of the MText's box, so may well be different

  212.         // from the bounds of the actual contents



  213.         MText txt = (MText)ent;

  214.         Point3dCollection pts2 = txt.GetBoundingPoints();

  215.         foreach (Point3d pt in pts2)

  216.         {

  217.           pts.Add(pt);

  218.         }

  219.       }

  220.       else if (ent is Face)

  221.       {

  222.         Face f = (Face)ent;

  223.         try

  224.         {

  225.           for (short i = 0; i < 4; i++)

  226.           {

  227.             pts.Add(f.GetVertexAt(i));

  228.           }

  229.         }

  230.         catch { }

  231.       }

  232.       else if (ent is Solid)

  233.       {

  234.         Solid sol = (Solid)ent;

  235.         try

  236.         {

  237.           for (short i = 0; i < 4; i++)

  238.           {

  239.             pts.Add(sol.GetPointAt(i));

  240.           }

  241.         }

  242.         catch { }

  243.       }

  244.       else

  245.       {

  246.         // Here's where we attempt to explode other types

  247.         // of object



  248.         DBObjectCollection oc = new DBObjectCollection();

  249.         try

  250.         {

  251.           ent.Explode(oc);

  252.           if (oc.Count > 0)

  253.           {

  254.             foreach (DBObject obj in oc)

  255.             {

  256.               Entity ent2 = obj as Entity;

  257.               if (ent2 != null && ent2.Visible)

  258.               {

  259.                 foreach (Point3d pt in CollectPoints(tr, ent2))

  260.                 {

  261.                   pts.Add(pt);

  262.                 }

  263.               }

  264.               obj.Dispose();

  265.             }

  266.           }

  267.         }

  268.         catch { }

  269.       }

  270.       return pts;

  271.     }



  272.     private void ExtractBounds(

  273.       DBText txt, Point3dCollection pts

  274.     )

  275.     {

  276.       // We have a special approach for DBText and

  277.       // AttributeReference objects, as we want to get

  278.       // all four corners of the bounding box, even

  279.       // when the text or the containing block reference

  280.       // is rotated



  281.       if (txt.Bounds.HasValue && txt.Visible)

  282.       {

  283.         // Create a straight version of the text object

  284.         // and copy across all the relevant properties

  285.         // (stopped copying AlignmentPoint, as it would

  286.         // sometimes cause an eNotApplicable error)



  287.         // We'll create the text at the WCS origin

  288.         // with no rotation, so it's easier to use its

  289.         // extents



  290.         DBText txt2 = new DBText();

  291.         txt2.Normal = Vector3d.ZAxis;

  292.         txt2.Position = Point3d.Origin;



  293.         // Other properties are copied from the original



  294.         txt2.TextString = txt.TextString;

  295.         txt2.TextStyleId = txt.TextStyleId;

  296.         txt2.LineWeight = txt.LineWeight;

  297.         txt2.Thickness = txt2.Thickness;

  298.         txt2.HorizontalMode = txt.HorizontalMode;

  299.         txt2.VerticalMode = txt.VerticalMode;

  300.         txt2.WidthFactor = txt.WidthFactor;

  301.         txt2.Height = txt.Height;

  302.         txt2.IsMirroredInX = txt2.IsMirroredInX;

  303.         txt2.IsMirroredInY = txt2.IsMirroredInY;

  304.         txt2.Oblique = txt.Oblique;



  305.         // Get its bounds if it has them defined

  306.         // (which it should, as the original did)



  307.         if (txt2.Bounds.HasValue)

  308.         {

  309.           Point3d maxPt = txt2.Bounds.Value.MaxPoint;



  310.           // Place all four corners of the bounding box

  311.           // in an array



  312.           Point2d[] bounds =

  313.             new Point2d[] {

  314.               Point2d.Origin,

  315.               new Point2d(0.0, maxPt.Y),

  316.               new Point2d(maxPt.X, maxPt.Y),

  317.               new Point2d(maxPt.X, 0.0)

  318.             };



  319.           // We're going to get each point's WCS coordinates

  320.           // using the plane the text is on



  321.           Plane pl = new Plane(txt.Position, txt.Normal);



  322.           // Rotate each point and add its WCS location to the

  323.           // collection



  324.           foreach (Point2d pt in bounds)

  325.           {

  326.             pts.Add(

  327.               pl.EvaluatePoint(

  328.                 pt.RotateBy(txt.Rotation, Point2d.Origin)

  329.               )

  330.             );

  331.           }

  332.         }

  333.       }

  334.     }



  335.     private Entity RectangleFromPoints(

  336.       Point3dCollection pts, CoordinateSystem3d ucs, double buffer

  337.     )

  338.     {

  339.       // Get the plane of the UCS



  340.       Plane pl = new Plane(ucs.Origin, ucs.Zaxis);



  341.       // We will project these (possibly 3D) points onto

  342.       // the plane of the current UCS, as that's where

  343.       // we will create our circle



  344.       // Project the points onto it



  345.       List<;Point2d> pts2d = new List<;Point2d>(pts.Count);

  346.       for (int i = 0; i < pts.Count; i++)

  347.       {

  348.         pts2d.Add(pl.ParameterOf(pts[i]));

  349.       }



  350.       // Assuming we have some points in our list...



  351.       if (pts.Count > 0)

  352.       {

  353.         // Set the initial min and max values from the first entry



  354.         double minX = pts2d[0].X,

  355.                maxX = minX,

  356.                minY = pts2d[0].Y,

  357.                maxY = minY;



  358.         // Perform a single iteration to extract the min/max X and Y



  359.         for (int i = 1; i < pts2d.Count; i++)

  360.         {

  361.           Point2d pt = pts2d[i];

  362.           if (pt.X < minX) minX = pt.X;

  363.           if (pt.X > maxX) maxX = pt.X;

  364.           if (pt.Y < minY) minY = pt.Y;

  365.           if (pt.Y > maxY) maxY = pt.Y;

  366.         }



  367.         // Our final buffer amount will be the percentage of the

  368.         // smallest of the dimensions



  369.         double buf =

  370.           Math.Min(maxX - minX, maxY - minY) * buffer;



  371.         // Apply the buffer to our point ordinates



  372.         minX -= buf;

  373.         minY -= buf;

  374.         maxX += buf;

  375.         maxY += buf;



  376.         // Create the boundary points



  377.         Point2d pt0 = new Point2d(minX, minY),

  378.                 pt1 = new Point2d(minX, maxY),

  379.                 pt2 = new Point2d(maxX, maxY),

  380.                 pt3 = new Point2d(maxX, minY);



  381.         // Finally we create the polyline



  382.         var p = new Polyline(4);

  383.         p.Normal = pl.Normal;

  384.         p.AddVertexAt(0, pt0, 0, 0, 0);

  385.         p.AddVertexAt(1, pt1, 0, 0, 0);

  386.         p.AddVertexAt(2, pt2, 0, 0, 0);

  387.         p.AddVertexAt(3, pt3, 0, 0, 0);

  388.         p.Closed = true;



  389.         return p;

  390.       }

  391.       return null;

  392.     }



  393.     private Entity CircleFromPoints(

  394.       Point3dCollection pts, CoordinateSystem3d ucs, double buffer

  395.     )

  396.     {

  397.       // Get the plane of the UCS



  398.       Plane pl = new Plane(ucs.Origin, ucs.Zaxis);



  399.       // We will project these (possibly 3D) points onto

  400.       // the plane of the current UCS, as that's where

  401.       // we will create our circle



  402.       // Project the points onto it



  403.       List<;Point2d> pts2d = new List<;Point2d>(pts.Count);

  404.       for (int i = 0; i < pts.Count; i++)

  405.       {

  406.         pts2d.Add(pl.ParameterOf(pts[i]));

  407.       }



  408.       // Assuming we have some points in our list...



  409.       if (pts.Count > 0)

  410.       {

  411.         // We need the center and radius of our circle



  412.         Point2d center;

  413.         double radius = 0;



  414.         // Use our fast approximation algorithm to

  415.         // calculate the center and radius of our

  416.         // circle to within 1% (calling the function

  417.         // with 100 iterations gives 10%, calling it

  418.         // with 10K gives 1%)



  419.         BadoiuClarksonIteration(

  420.           pts2d, 10000, out center, out radius

  421.         );



  422.         // Get our center point in WCS (on the plane

  423.         // of our UCS)



  424.         Point3d cen3d = pl.EvaluatePoint(center);



  425.         // Create the circle and add it to the drawing



  426.         return new Circle(

  427.           cen3d, ucs.Zaxis, radius * (1.0 + buffer)

  428.         );

  429.       }

  430.       return null;

  431.     }



  432.     // Algorithm courtesy (and copyright of) Frank Nielsen

  433.     // [url=http://blog.informationgeometry.org/article.php?id=164]http://blog.informationgeometry.org/article.php?id=164[/url]



  434.     public void BadoiuClarksonIteration(

  435.       List<;Point2d> set, int iter,

  436.       out Point2d cen, out double rad

  437.     )

  438.     {

  439.       // Choose any point of the set as the initial

  440.       // circumcenter



  441.       cen = set[0];

  442.       rad = 0;



  443.       for (int i = 0; i < iter; i++)

  444.       {

  445.         int winner = 0;

  446.         double distmax = (cen - set[0]).Length;



  447.         // Maximum distance point



  448.         for (int j = 1; j < set.Count; j++)

  449.         {

  450.           double dist = (cen - set[j]).Length;

  451.           if (dist > distmax)

  452.           {

  453.             winner = j;

  454.             distmax = dist;

  455.           }

  456.         }

  457.         rad = distmax;



  458.         // Update



  459.         cen =

  460.           new Point2d(

  461.             cen.X + (1.0 / (i + 1.0)) * (set[winner].X - cen.X),

  462.             cen.Y + (1.0 / (i + 1.0)) * (set[winner].Y - cen.Y)

  463.           );

  464.       }

  465.     }

  466.   }

  467. }





Here’s the MER command in action, using a 15% boundary and choosing to create individual boundaries for each object:

Rectangular boundaries around geometry
untitled.png
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

已领礼包: 859个

财富等级: 财运亨通

 楼主| 发表于 2014-5-15 10:27:27 | 显示全部楼层
Rectang 对 Spline 有误差
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|辽公网安备|晓东CAD家园 ( 辽ICP备15016793号 )

GMT+8, 2025-1-6 01:18 , Processed in 0.424028 second(s), 33 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表