Identifying holes in an AutoCAD solid using .NET

A big thanks to Ishwar Nagwani โ€“ an old friend, colleague and member of Autodesk Consulting working in our Bangalore office โ€“ for kindly providing this code.

Ishwar tells me that he has come across many developers struggling to identify holes in 3D solid using its boundary representation (Brep). The code he has provided works on the basis that a hole's normal is typically facing inwards and will therefore intersect the hole's axis of symmetry, providing we extend the line representing the normal by the hole's radius and the line representing the axis of symmetry by the cylinder's height (just to make sure).

Here is the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.BoundaryRepresentation;

using AcBr = Autodesk.AutoCAD.BoundaryRepresentation;

using Autodesk.AutoCAD.Colors;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using AcGe = Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using System.Collections.Generic;

using System;

 

// Not mandatory, but improves loading performance

 

[assembly: CommandClass(typeof(HoleFeature.MyCommands))]

 

namespace HoleFeature

{

  public class MyCommands

  {

    [CommandMethod("GETHOLES")]

    public void GetHoles()

    {

      Document doc =

          Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      PromptEntityOptions peo =

          new PromptEntityOptions("\nSelect a 3D solid: ");

      peo.SetRejectMessage("\nMust be a 3D solid.");

      peo.AddAllowedClass(typeof(Solid3d), true);

 

      PromptEntityResult per = ed.GetEntity(peo);

 

      if (per.Status != PromptStatus.OK)

        return;

 

      Transaction tr = db.TransactionManager.StartTransaction();

      using (tr)

      {

        Solid3d solid =

          tr.GetObject(per.ObjectId, OpenMode.ForWrite) as Solid3d;

 

        ObjectId[] ids = new ObjectId[] { solid.ObjectId };

 

        FullSubentityPath path =

          new FullSubentityPath(

            ids,

            new SubentityId(SubentityType.Null, IntPtr.Zero)

          );

 

        // For storing SubentityIds of cylinderical faces

 

        List<SubentityId> subentIds = new List<SubentityId>();

 

        using (Brep brep = new Brep(path))

        {

          foreach (AcBr.Face face in brep.Faces)

          {

            AcGe.Surface surf = face.Surface;

 

            ExternalBoundedSurface ebSurf =

              surf as ExternalBoundedSurface;

 

            // We are only looking only cylinders

 

            if (ebSurf != null && ebSurf.IsCylinder)

            {

              Cylinder cyl = ebSurf.BaseSurface as Cylinder;

 

              // And fully closed cylinders

 

              if (cyl != null && cyl.IsClosed())

              {

                // Get normal and point on surface

 

                Vector3d normal = new Vector3d();

                Point3d pt = new Point3d();

 

                GetNormalAndPoint(surf, ref normal, ref pt);

 

                if (IsHole(face, normal, pt, cyl))

                {

                  subentIds.Add(face.SubentityPath.SubentId);

                }

              }

            }

          }

        }

 

        // Assign red color to hole features

 

        if (subentIds.Count > 0)

        {

          short colorIdx = 1;

          AssignColor(solid, subentIds, colorIdx);

        }

 

        tr.Commit();

      }

    }

 

    // Get normal and point at mid U and V parameters

 

    void GetNormalAndPoint(

      AcGe.Surface surf, ref Vector3d normal, ref Point3d pt

    )

    {

      Interval[] box = surf.GetEnvelope();

      double p1 = box[0].LowerBound + box[0].Length / 2.0;

      double p2 = box[1].LowerBound + box[1].Length / 2.0;

      Point2d ptParams = new Point2d(p1, p2);

      PointOnSurface pos = new PointOnSurface(surf, ptParams);

      normal = pos.GetNormal(ptParams);

      pt = pos.GetPoint(ptParams);

    }

 

    // A cylinder is a hole if the normal points inwards

    // and the normal after extending by radius intersects

    // with axis of symmetry, the axis of symmetry is also

    // extended by height of cylinder

 

    Boolean IsHole(

      AcBr.Face face, Vector3d normal, Point3d pt, Cylinder cyl

    )

    {

      if (!face.IsOrientToSurface)

      {

        // Correct the normal and save back

 

        normal = normal.Negate();

      }

 

      // Calculate another point on normal by extending the normal

      // by radius of cylinder

 

      Point3d opt = new Point3d(

        pt.X + normal.X * cyl.Radius,

        pt.Y + normal.Y * cyl.Radius,

        pt.Z + normal.Z * cyl.Radius

      );

 

      // Get the cylinder's axis

 

      Vector3d v1 = cyl.AxisOfSymmetry;

 

      double dist = cyl.Height.Length;

 

      // Calculate another point on axis by extending v1 by dist

 

      Point3d pt2 = new Point3d(

        cyl.Origin.X + v1.X * dist,

        cyl.Origin.Y + v1.Y * dist,

        cyl.Origin.Z + v1.Z * dist

      );

 

      // Create line segment representing the cylinder's normal

 

      LineSegment3d ls1 = new LineSegment3d(pt, opt);

 

      // Create line segment representing the cylinder's axis

 

      LineSegment3d ls2 = new LineSegment3d(cyl.Origin, pt2);

 

      // Get intersection of normal and cylinder axis

 

      Point3d[] intpt;

      intpt = ls1.IntersectWith(ls2);

 

      return (intpt != null);

    }

 

    // Assign color to cylinderical surfaces which are holes   

 

    void AssignColor(

      Solid3d solid, List<SubentityId> subentIds, short idx

    )

    {

      foreach (SubentityId subentId in subentIds)

      {

        Color col = Color.FromColorIndex(ColorMethod.ByColor, idx);

        solid.SetSubentityColor(subentId, col);

      }

    }

  }

}

 

 

Here's a 3D solid for which we would like to identify holes:

Holey solid

When we run the GETHOLES command and select our solid, its cylindrical holes are turned red:

Solid with holes identified

Thanks again, Ishwar! ๐Ÿ™‚

Update:

See this post for an updated version of the above code (as well as a description of why the changes are needed).

6 responses to “Identifying holes in an AutoCAD solid using .NET”

  1. Constantin Gherasim Avatar
    Constantin Gherasim

    Hi Kean,

    What about square holes? ๐Ÿ™‚

    youtu.be/ALiqAXiTQBg

    Regards,

    Constantin

  2. Hi Constantin,

    That's very cool!

    Yes, triangular, square, pentagonal, etc. holes are left as an exercise for the reader. ๐Ÿ˜‰

    Cheers,

    Kean

  3. Hi Kean,

    I had to multiply the Cylinder.Height.Length by an arbitrary number (10000) to make sure the cylinder's axis intersected with the normal when comparing the two line segments. I had 26 holes in a Solid3d that were not getting identified until I did this.

    The issue seems to be with the Cylinder.Origin property. I attached a screenshot to show how the two line segments were calculating, and as you can see, they do not intersect.

    double dist = cyl.Height.Length * 10000;

    1. That didn't quite solve my problem. I had to add this as well, to mirror the axis line in the other direction to check for an intersect. Might not be the most elegant way, but it seems to work.

      using (LineSegment3d ls2 = new LineSegment3d(cyl.Origin, pt2))
      {
      intpt = ls1.IntersectWith(ls2);
      // if null then mirror our axis line
      // to check for an intersect
      if (intpt == null)
      {
      Matrix3d mm = Matrix3d.Mirroring(ls2.GetLine());
      Line3d ls3 = ls2.GetLine().Clone() as Line3d;
      ls3.TransformBy(mm);
      intpt = ls1.IntersectWith(ls3);
      }
      }

  4. Zhmayev Yaroslav Avatar
    Zhmayev Yaroslav

    Hi Kean, I'm building a command where I need to subtract one solid from another (it's a worktop and a sink), but later I might decide I want to move that smaller element to another location within bigger element and I want parent solid the hole (subtraction) to be created in the new location and removed in the old one.

    A good example is what happens on these videos about AutoCAD Architecture which creates holes in the wall when a new fixture is added on it.

    uploads.disquscdn.c...

    1. Kean Walmsley Avatar

      Hi Yaroslav,

      Please submit your support questions to the relevant Autodesk forum.

      Many thanks,

      Kean

Leave a Reply to Ted Cancel reply

Your email address will not be published. Required fields are marked *