明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 602|回复: 0

Gathering points defining 2D AutoCAD geometry using .NET

[复制链接]
发表于 2011-2-24 11:28:52 | 显示全部楼层 |阅读模式
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.

本帖子中包含更多资源

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

x
"觉得好,就打赏"
还没有人打赏,支持一下

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2024-12-24 07:38 , Processed in 0.189213 second(s), 31 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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