明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 798|回复: 0

Gathering points defining 3D AutoCAD geometry using .NET

[复制链接]
发表于 2011-2-24 11:27 | 显示全部楼层 |阅读模式
After revealing the purpose for collecting points from 2D geometry in the last post, this post extends the 2D collection code to work with additional 3D objects. I don’t know whether it’s exhaustive or not – I’ve added more specific support for Solid3d and Surface objects – but I have no doubt people will let me know if I’ve missed anything, over time.
The good news is that the previous approach of exploding complex entities and processing their components means that many types of standard solid – such as boxes, cones and pyramids – will get adequately captured using this approach, as they have faces that comfortably get decomposed down to primitive objects that are handled by the existing code. The same is true of SubDMesh objects and even – to some degree – surfaces (although for these we are likely to only get the defining curves, rather than points along the surface itself).
To get more information from Surface objects was reasonably easy: the code creates
NURBS surfaces from the surface and processes each of these by getting points inside the u and v parameter space. In addition to any enclosing curves that get processed as the Surface is exploded, of course. There may be a more straightforward way, but this appears to work, at least.
For Solid3ds it was a little more complex. I took some F# code from this previous post as a basis, to fire rays in random directions from the centroid of the solid and pick up the points at which the objects intersect. It’s not a guaranteed way to get points defining the solid, but with a couple of thousand points being generated by a thousand intersection operations, it should be representative enough. And that’s once again in addition to any curves or faces extracted from the solid as it’s exploded.
It should be said that none of these collection operations have been optimized for performance – this is mostly about getting lots of points to help generate an
enclosing sphere, later on. A simple optimization would be to not perform the ray-firing technique on solids that we know are handled well by the explode approach (although that may require the use of COM to get the solid type, if memory serves me correctly). Anyway – this isn’t a definitive approach, this is more about sharing some code that I hope will prove useful to people who wish to take it further.
Here’s the updated C# code – the main changes are at the beginning of the CollectPoints() function, where I’ve added the handling of Solid3d and Surface objects. The function;s protocol has also be changed, slightly – rather than returning a collection of points from each call, we just pass the collection around and populate it directly. Not a big change, but it saved a couple of foreach loops that simply copy points from one place to another. Oh, and I was also forgetting to call Dispose() on the results of the Explode() operation, which only became apparent as a problem when dealing with larger models.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using
DbSurface =
  Autodesk.AutoCAD.DatabaseServices.Surface;
using
DbNurbSurface =
  Autodesk.AutoCAD.DatabaseServices.NurbSurface;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using System.Collections.Generic;
using System;

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 = new
Point3dCollection();
          CollectPoints(tr, ent, entPts);


// 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
void CollectPoints(

Transaction tr, Entity ent, Point3dCollection pts
    )
    {

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


// For surfaces we'll do something similar: we'll

// collect points across its surface (by first getting

// NURBS surfaces for the surface) and will still

// explode the surface later to get points from the

// curves


DbSurface sur = ent as
DbSurface;

if (sur != null)
      {

DbNurbSurface[] nurbs = sur.ConvertToNurbSurface();

foreach (DbNurbSurface nurb in nurbs)
        {

// Calculate the parameter increments in u and v


double ustart = nurb.UKnots.StartParameter,
                uend = nurb.UKnots.EndParameter,
                uinc = (uend - ustart) / nurb.UKnots.Count,
                vstart = nurb.VKnots.StartParameter,
                vend = nurb.VKnots.EndParameter,
                vinc = (vend - vstart) / nurb.VKnots.Count;


// Pick up points across the surface


for (double u = ustart; u <= uend; u += uinc)
          {

for (double v = vstart; v <= vend; v += vinc)
            {
              pts.Add(nurb.Evaluate(u, v));
            }
          }
        }
      }


// For 3D solids we'll fire a number of rays from the

// centroid in random directions in order to get a

// sampling of points on the outside


Solid3d sol = ent as
Solid3d;

if (sol != null)
      {

for (int i = 0; i < 500; i++)
        {

Solid3dMassProperties mp = sol.MassProperties;


using (Plane pl = new
Plane())
          {
            pl.Set(mp.Centroid, randomVector3d());

using (Region reg = sol.GetSection(pl))
            {

using (Ray ray = new
Ray())
              {
                ray.BasePoint = mp.Centroid;
                ray.UnitDir = randomVectorOnPlane(pl);

                reg.IntersectWith(
                  ray, Intersect.OnBothOperands, pts,

IntPtr.Zero, IntPtr.Zero
                );
              }
            }
          }
        }
      }


// Now we start the terminal cases - for basic objects -

// before we recurse for more complex objects (including

// the ones we've already collected points for above).


// 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 s = (Solid)ent;

try
        {

for (short i = 0; i < 4; i++)
          {
            pts.Add(s.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)
              {
                CollectPoints(tr, ent2, pts);
              }
              obj.Dispose();
            }
          }
        }

catch { }
      }
    }


// Return a random 3D vector on a plane


private
Vector3d randomVectorOnPlane(Plane pl)
    {

// Create our random number generator


Random ran = new
Random();


// First we get the absolute value

// of our x, y and z coordinates


double absx = ran.NextDouble();

double absy = ran.NextDouble();


// Then we negate them, half of the time


double x = (ran.NextDouble() < 0.5 ? -absx : absx);

double y = (ran.NextDouble() < 0.5 ? -absy : absy);


Vector2d v2 = new
Vector2d(x, y);

return
new
Vector3d(pl, v2);
    }


// Return a random 3D vector


private
Vector3d randomVector3d()
    {

// Create our random number generator


Random ran = new
Random();


// First we get the absolute value

// of our x, y and z coordinates


double absx = ran.NextDouble();

double absy = ran.NextDouble();

double absz = ran.NextDouble();


// Then we negate them, half of the time


double x = (ran.NextDouble() < 0.5 ? -absx : absx);

double y = (ran.NextDouble() < 0.5 ? -absy : absy);

double z = (ran.NextDouble() < 0.5 ? -absz : absz);


return
new
Vector3d(x, y, z);
    }


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

Here’s the updated GP command in action with a selection of 3D geometry.
The basic geometry:

With our points:

And with PDMODE set to 2:

In the next post we’ll extend this code that little bit further to create an enclosing sphere, as we did for enclosing circles last time.

本帖子中包含更多资源

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

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

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

GMT+8, 2024-5-4 12:37 , Processed in 0.363310 second(s), 31 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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