找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 4811|回复: 0

[分享] Gathering points defining 2D AutoCAD geometry using .NET

[复制链接]

已领礼包: 6个

财富等级: 恭喜发财

发表于 2013-6-12 20:25:54 | 显示全部楼层 |阅读模式

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

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

×

The reason for this post may be obvious to some – probably those who are doing this kind of analysis already – and less obvious to others – who will have to wait for the next post to see why it’s helpful. :-)
I won’t ruin the surprise, but suffice it to say that for various types of spatial analysis it’s helpful to acquire first points from geometry. This post attempts to do that for 2D geometry, and hopefully deals with a few of the trickier cases related to rotated text and such-like.
For this particular task I chose a recursive algorithm, as it’s common to have AutoCAD entities containing – or decomposable into – simpler entities. The fundamental approach is to Explode() complex entities – which doesn’t actually explode the entity inside AutoCAD, it just returns an array of the decomposed entities for (optional) adding to the drawing database – and then process the elements. We have some code in place to deal with the “leaves” of the geometry tree – the lines and other simple curves, the text, the points – and we recurse for the complex entities (the blocks, the groups, the polylines…).
For now we take the results and create DBPoints from them, just to see what we get.
Here’s the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using System.Collections.Generic;

namespace PointGathering
{
  public class Commands
  {
    [CommandMethod("GP", CommandFlags.UsePickSet)]
    public void GatherPoints()
    {
      Document doc =
          Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;

      // Ask user to select entities

      PromptSelectionOptions pso =
        new PromptSelectionOptions();
      pso.MessageForAdding = "\nSelect entities to enclose: ";
      pso.AllowDuplicates = false;
      pso.AllowSubSelections = true;
      pso.RejectObjectsFromNonCurrentSpace = true;
      pso.RejectObjectsOnLockedLayers = false;

      PromptSelectionResult psr = ed.GetSelection(pso);
      if (psr.Status != PromptStatus.OK)
        return;

      // Collect points on the component entities

      Point3dCollection pts = new Point3dCollection();

      Transaction tr =
        db.TransactionManager.StartTransaction();
      using (tr)
      {
        BlockTableRecord btr =
          (BlockTableRecord)tr.GetObject(
            db.CurrentSpaceId,
            OpenMode.ForWrite
          );

        foreach (SelectedObject so in psr.Value)
        {
          Entity ent =
            (Entity)tr.GetObject(
              so.ObjectId,
              OpenMode.ForRead
            );

          // Collect the points for each selected entity

          Point3dCollection entPts = CollectPoints(tr, ent);

          // Add a physical DBPoint at each Point3d

          foreach(Point3d pt in entPts)
          {
            DBPoint dbp = new DBPoint(pt);
            btr.AppendEntity(dbp);
            tr.AddNewlyCreatedDBObject(dbp, true);
          }
        }
        tr.Commit();
      }
    }

    private Point3dCollection CollectPoints(
      Transaction tr, Entity ent
    )
    {
      // The collection of points to populate and return

      Point3dCollection pts = new Point3dCollection();

      // We'll start by checking a block reference for
      // attributes, getting their bounds and adding
      // them to the point list. We'll still explode
      // the BlockReference later, to gather points
      // from other geometry, it's just that approach
      // doesn't work for attributes (we only get the
      // AttributeDefinitions, which don't have bounds)

      BlockReference br = ent as BlockReference;
      if (br != null)
      {
        foreach (ObjectId arId in br.AttributeCollection)
        {
          DBObject obj = tr.GetObject(arId, OpenMode.ForRead);
          if (obj is AttributeReference)
          {
            AttributeReference ar = (AttributeReference)obj;
            ExtractBounds(ar, pts);
          }
        }
      }

      // If we have a curve - other than a polyline, which
      // we will want to explode - we'll get points along
      // its length

      Curve cur = ent as Curve;
      if (cur != null &&
          !(cur is Polyline ||
            cur is Polyline2d ||
            cur is Polyline3d))
      {
        // Two points are enough for a line, we'll go with
        // a higher number for other curves

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

        double param = cur.EndParam - cur.StartParam;
        for (int i = 0; i < segs; i++)
        {
          try
          {
            Point3d pt =
              cur.GetPointAtParameter(
                cur.StartParam + (i * param / (segs - 1))
              );
            pts.Add(pt);
          }
          catch { }
        }
      }
      else if (ent is DBPoint)
      {
        // Points are easy

        pts.Add(((DBPoint)ent).Position);
      }
      else if (ent is DBText)
      {
        // For DBText we use the same approach as
        // for AttributeReferences

        ExtractBounds((DBText)ent, pts);
      }
      else if (ent is MText)
      {
        // MText is also easy - you get all four corners
        // returned by a function. That said, the points
        // are of the MText's box, so may well be different
        // from the bounds of the actual contents

        MText txt = (MText)ent;
        Point3dCollection pts2 = txt.GetBoundingPoints();
        foreach (Point3d pt in pts2)
        {
          pts.Add(pt);
        }
      }
      else if (ent is Face)
      {
        Face f = (Face)ent;
        try
        {
          for (short i = 0; i < 4; i++)
          {
            pts.Add(f.GetVertexAt(i));
          }
        }
        catch { }
      }
      else if (ent is Solid)
      {
        Solid sol = (Solid)ent;
        try
        {
          for (short i = 0; i < 4; i++)
          {
            pts.Add(sol.GetPointAt(i));
          }
        }
        catch { }
      }
      else
      {
        // Here's where we attempt to explode other types
        // of object

        DBObjectCollection oc = new DBObjectCollection();
        try
        {
          ent.Explode(oc);
          if (oc.Count > 0)
          {
            foreach (DBObject obj in oc)
            {
              Entity ent2 = obj as Entity;
              if (ent2 != null && ent2.Visible)
              {
                foreach (Point3d pt in CollectPoints(tr, ent2))
                {
                  pts.Add(pt);
                }
              }
              obj.Dispose();
            }
          }
        }
        catch { }
      }
      return pts;
    }

    private void ExtractBounds(
      DBText txt, Point3dCollection pts
    )
    {
      // We have a special approach for DBText and
      // AttributeReference objects, as we want to get
      // all four corners of the bounding box, even
      // when the text or the containing block reference
      // is rotated

      if (txt.Bounds.HasValue && txt.Visible)
      {
        // Create a straight version of the text object
        // and copy across all the relevant properties
        // (stopped copying AlignmentPoint, as it would
        // sometimes cause an eNotApplicable error)

        // We'll create the text at the WCS origin
        // with no rotation, so it's easier to use its
        // extents

        DBText txt2 = new DBText();
        txt2.Normal = Vector3d.ZAxis;
        txt2.Position = Point3d.Origin;

        // Other properties are copied from the original

        txt2.TextString = txt.TextString;
        txt2.TextStyleId = txt.TextStyleId;
        txt2.LineWeight = txt.LineWeight;
        txt2.Thickness = txt2.Thickness;
        txt2.HorizontalMode = txt.HorizontalMode;
        txt2.VerticalMode = txt.VerticalMode;
        txt2.WidthFactor = txt.WidthFactor;
        txt2.Height = txt.Height;
        txt2.IsMirroredInX = txt2.IsMirroredInX;
        txt2.IsMirroredInY = txt2.IsMirroredInY;
        txt2.Oblique = txt.Oblique;

        // Get its bounds if it has them defined
        // (which it should, as the original did)

        if (txt2.Bounds.HasValue)
        {
          Point3d maxPt = txt2.Bounds.Value.MaxPoint;

          // Place all four corners of the bounding box
          // in an array

          Point2d[] bounds =
            new Point2d[] {
              Point2d.Origin,
              new Point2d(0.0, maxPt.Y),
              new Point2d(maxPt.X, maxPt.Y),
              new Point2d(maxPt.X, 0.0)
            };

          // We're going to get each point's WCS coordinates
          // using the plane the text is on

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

          // Rotate each point and add its WCS location to the
          // collection

          foreach (Point2d pt in bounds)
          {
            pts.Add(
              pl.EvaluatePoint(
                pt.RotateBy(txt.Rotation, Point2d.Origin)
              )
            );
          }
        }
      }
    }
  }
}


I think it’s important to note one of the trickier cases this handles: while MText has a very handy GetBoundingPoints() method, DBText and AttributeReference (which derives from DBText) only implement the standard Entity.Bounds, which gives the bottom-left and top-right corners of the bounding box. As we ideally want to determine all four corners, we need to do a little more work. Taking the X and Y values and creating a box works well if the text isn’t rotated, but when the text is rotated the bounding box isn’t what we need:
To address this we take a copy of the DBText/AttributeReference – making sure it isn’t rotated – and then determine its four bounding points before rotating them to determine the actual corners of the text:
It should also be noted that we can’t rely on Explode() to get us the AttributeReferences to analyse: this returns AttributeDefinitions which do not have any geometric bounds. So we actually need to deal with block attributes as a special case, looking for AttributeReferences in each BlockReference and processing them separately.
To test our command, let’s run it on the sports car block from the Sample/Dynamic Blocks/Architectural – Imperial.dwg sample drawing.
And here it is after the GP command, with PDMODE set to 2:
Straight-line segments only have the end-points included in the set, but each curved segment (arc or spline) will have 20 points along its length (hence the areas of high density). You can tweak the code to generate more or fewer points for each segment type, as fits your needs.
In the next post, all will be revealed… :-)
Update
I’ve added support for Face and Solid objects to the above collection code: I found these entities used in blocks in AutoCAD’s sample folder (which probably means that others will be using them in similar circumstances). I should have thought of Solids, which are 2D, but was a little surprised to find Faces, which are generally 3D. Anyway – both have now been added.
Update 2
I realised when working on this more recent post that I was missing a Dispose() call on the results of Explode(). This has now been added.




评分

参与人数 1D豆 +2 收起 理由
ScmTools + 2 很给力!经验;技术要点;资料分享奖!

查看全部评分

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

本版积分规则

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

GMT+8, 2024-5-1 12:24 , Processed in 0.397812 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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