Traversing a 3D solid's brep using AutoCAD 2009's new .NET API

In a recent webcast, Gopinath Taget, from our DevTech Americas team, showed how to use the Brep API from a .NET application: something that was made possible in AutoCAD 2009. The Brep API in AutoCAD allows you to traverse the boundary representation of a Solid3d object. Without going into specifics - as this isn't really an area of AutoCAD I've had much reason to use, over the years - I went ahead and took the sample Gopi showed in his webcast and modified it for the purposes of this blog.

The following C# code traverses the Brep of a selected Solid3d, dumping the information to the command-line. It uses the technique shown in this previous post to retrieve the type of solid we're dealing with via COM.

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.BoundaryRepresentation;

using Autodesk.AutoCAD.Interop.Common;

using BrFace =

  Autodesk.AutoCAD.BoundaryRepresentation.Face;

 

namespace BRepTraversal

{

  public class Commands

  {

    [CommandMethod("TBR")]

    static public void TraverseBRep()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        try

        {

          // Prompt for selection of a solid to be traversed

 

          PromptEntityOptions prEntOpt =

            new PromptEntityOptions(

              "\nSelect a 3D solid:"

            );

          prEntOpt.SetRejectMessage(

            "\nMust be a 3D solid."

          );

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

 

          PromptEntityResult prEntRes =

            ed.GetEntity(prEntOpt);

      �
160;  
ObjectId[] objIds = { prEntRes.ObjectId };

 

          Solid3d sol =

            (Solid3d)tr.GetObject(

              prEntRes.ObjectId,

              OpenMode.ForRead

            );

 

          // Use COM to get the solid's type

 

          Acad3DSolid oSol = (Acad3DSolid)sol.AcadObject;

          ed.WriteMessage(

            "\nSolid type: {0}",

            oSol.SolidType

          );

          oSol = null;

 

          // Build the BRep topology object to traverse

 

          Brep brp = new Brep(sol);

          using (brp)

          {

            int cmpCnt = 0;

 

            // Get all the Complexes which are primary BRep

            // elements and represent a conceptual topological

            // entity of connected shell boundaries.

 

            foreach (Complex cmp in brp.Complexes)

            {

              ed.WriteMessage(

                "\n  Complex number {0}",

                ++cmpCnt

              );

 

              // Get all the shells within a complex. Shells

              // are secondary BRep entities that correspond

              // to a collection of neighboring surfaces on a

              // solid

 

              int shlCnt = 0;

 

              foreach (Shell shl in cmp.Shells)

           
;   {

                ed.WriteMessage(

                  "\n    Shell number {0} [{1}]",

                  ++shlCnt,

                  shl.ShellType

                );

 

                // Get all the faces in a shell. Faces are

                // primary BRep topological entities that

                // directly correspond to face subentities on

                // AutoCAD entities like solid, region and body

 

                int fceCnt = 0;

 

                foreach (BrFace fce in shl.Faces)

                {

                  ed.WriteMessage(

                    "\n      Face number {0}",

                    ++fceCnt

                  );

 

                  // Get all the boundary loops within a face

                  // (Secondary BRep entities and no corresponding

                  // AutoCAD entities/subentities)

 

                  try

                  {

                    int lpCnt = 0;

 

                    foreach (BoundaryLoop lp in fce.Loops)

                    {

                      ed.WriteMessage(

                        "\n        Loop number {0} [{1}]",

                        ++lpCnt,

                        lp.LoopType

                      );

 

                      // Get all the Edges in a loop (Edges are

                      // primary BRep entities and correspond to

                      // Geometric entities). Output the

 

                      int< /span> edgCnt = 0;

 

                      foreach (Edge edg in lp.Edges)

                      {

                        ed.WriteMessage(

                          "\n          Edge number {0}: " +

                          "\n          Vertex 1: {1}" +

                          "\n          Vertex 2: {2}",

                          ++edgCnt,

                          edg.Vertex1.Point,

                          edg.Vertex2.Point

                        );

                      }

                    }

                  }

                  catch

                  {

                    ed.WriteMessage(

                      "\n        Problem getting loops/edges:" +

                      " object is probably unbounded " +

                      "(e.g. a sphere or a torus)."

                    );

                  }

                }

              }

            }

          }

          tr.Commit();

        }

        catch (System.Exception ex)

        {

          ed.WriteMessage(

            "\nException during traversal: {0}",

            ex.Message

          );

        }

      }

    }

  }

}

Here's what happens when we run the TBR command against a cylinder:

Command:  TBR

Select a 3D solid:

Solid type: Cylinder

  Complex number 1

    Shell number 1 [ShellExterior]

      Face number 1

        Loop number 1 [LoopWinding]

          Edge number 1:

          Vertex 1: (15.0005647466013,4.38174252440491,0)

          Vertex 2: (15.0005647466013,4.38174252440491,0)

        Loop number 2 [LoopWinding]

          Edge number 1:

          Vertex 1: (15.0005647466013,4.38174252440491,5.78051528967497)

          Vertex 2: (15.0005647466013,4.38174252440491,5.78051528967497)

      Face number 2

        Loop number 1 [LoopExterior]

          Edge number 1:

          Vertex 1: (15.0005647466013,4.38174252440491,0)

          Vertex 2: (15.0005647466013,4.38174252440491,0)

      Face number 3

        Loop number 1 [LoopExterior]

          Edge number 1:

          Vertex 1: (15.0005647466013,4.38174252440491,5.78051528967497)

          Vertex 2: (15.0005647466013,4.38174252440491,5.78051528967497)

One point to note: in the above code I've used a try-catch block around the code to get the loops of a face and the edges of a loop. This is because the GetEnumerator() call on either a loop or an edge (which gets called implicitly by the foreach statement to loop through their respective enumerable objects) can throw an exception in the case where an object is unbounded. Here's an excerpt from the .NET reference material (currently residing in the ObjectARX Reference, which is part of the ObjectARX SDK):

A topological object may be unbounded (that is, it may have no lower dimensional bounding topology) only in the following cases:

  • A closed surface, which is intrinsically bounded in both the u and v directions (such as a full torus or sphere), is represented by a face that has no loop boundaries.
  • A closed curve, which is intrinsically bounded (such as a full circle or ellipse), is represented by an edge that has coincident start and end vertices.

For example, here's what happens when we run the TBR command against a torus:

Command:  TBR

Select a 3D solid:

Solid type: Torus

  Complex number 1

    Shell number 1 [ShellExterior]

      Face number 1

        Problem getting loops/edges: object is probably unbounded (e.g. a sphere or a torus).

So we have, at least, detected the case, even if we have to deal with the overhead of catching an exception rather than checking a property for the number of Loops (something I couldn't work out how to do).

15 responses to “Traversing a 3D solid's brep using AutoCAD 2009's new .NET API”

  1. I've tried to run this code in a loop with some solids.(23/25 3dsolids in a drawing)

    It's runs 10/12 times and then autocad crash.
    Exception Memory error read/write. Very strange.
    OK...if I don't touch the vertex ..it's run without errors.

    the structure is:

    1) Current Drawing with some solids
    2) Selectionset
    3) Foreach objectid in selectionset
    {
    traverserSolid(SolidID)
    }

    Have You the same problem if You run this ?

  2. so...If You want try take this:

    1) extrude a simple pline (4 edges)
    2) copy it to obtain other solids.(10,12 is not really matter)

    [CommandMethod("ll")]
    public void ll()
    {
    Autodesk.AutoCAD.ApplicationServices.Document d = (Autodesk.AutoCAD.ApplicationServices.Document)Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;

    // obtain your solids from drawing..
    List<objectid> lstid = B2UtilCad.B2Editor.b2Editor.GetAllObjectsByType(d,"3DSOLID");

    loop...
    for(int i = 0;i <2000;i++)
    {
    d.Editor.WriteMessage(i.ToString()+ "\n");
    foreach(ObjectId id in lstid)
    CommonCAD.ArredilBRep.keansol(d, id);

    }
    }
    and then crash 🙁

    public static void keansol(Document doc, ObjectId idsolido)
    {
    Database db = doc.Database;
    Transaction tr = db.TransactionManager.StartTransaction();
    Editor ed = doc.Editor;

    using (tr)
    {

    try
    {
    Solid3d sol = (Solid3d)tr.GetObject(idsolido, OpenMode.ForRead);

    // Use COM to get the solid's type
    Acad3DSolid oSol = (Acad3DSolid)sol.AcadObject;
    // Build the BRep topology object to traverse
    Brep brp = new Brep(sol);
    int cmpCnt = 0;

    // Get all the Complexes which are primary BRep

    // elements and represent a conceptual topological

    // entity of connected shell boundaries.

    foreach (Complex cmp in brp.Complexes)
    {

    int shlCnt = 0;

    foreach (Shell shl in cmp.Shells)
    {

    int fceCnt = 0;

    foreach (BrFace fce in shl.Faces)
    {
    // Get all the boundary loops within a face
    // (Secondary BRep entities and no corresponding
    // AutoCAD entities/subentities)

    try
    {

    int lpCnt = 0;

    foreach (BoundaryLoop lp in fce.Loops)
    {

    // Get all the Edges in a loop (Edges are

    // primary BRep entities and correspond to

    // Geometric entities). Output the

    int edgCnt = 0;

    foreach (Edge edg in lp.Edges)
    {

    Point3d p1 = edg.Vertex1.Point;
    ed.WriteMessage(p1.ToString());

    }

    }

    }

    catch
    {

    ed.WriteMessage(
    "\n Problem getting loops/edges:" +

    " object is probably unbounded " +

    "(e.g. a sphere or a torus)."

    );

    }

    }

    }

    }

    tr.Commit();
    }
    catch (System.Exception ex)
    {

    ed.WriteMessage(

    "\nException during traversal: {0}",

    ex.Message

    );

    }

    }
    }

  3. ok...that's work. loop 2000 times...with 40 3dsolids.

    public static IList<point3d> PrendiVerticiSolido(Document d, ObjectId idSolido)
    {

    IList<point3d> ptResult = new List<point3d>(0);
    try
    {
    using (DocumentLock l = d.LockDocument())
    {
    using (Transaction tr = d.Database.TransactionManager.StartTransaction())
    {
    using (Solid3d sol = tr.GetObject(idSolido, OpenMode.ForRead) as Solid3d)
    {

    //System.Diagnostics.Trace.WriteLine(sol.Handle);
    if (sol == null)
    {
    tr.Abort();
    return ptResult;
    }

    if (sol.IsNull)
    {
    sol.UpgradeOpen();
    sol.Erase();
    tr.Commit();
    sol.Dispose();
    return ptResult;
    }

    using (Brep brp = new Brep(sol))
    {
    BrepComplexEnumerator colcom = brp.Complexes.GetEnumerator();
    do
    {
    Complex complex = colcom.Current;
    ComplexShellEnumerator shllenum = complex.Shells.GetEnumerator();
    do
    {
    Shell shll = shllenum.Current;
    ShellFaceEnumerator shfaceenum = shll.Faces.GetEnumerator();
    do
    {
    Autodesk.AutoCAD.BoundaryRepresentation.Face f = shfaceenum.Current;

    FaceLoopEnumerator flpenum = f.Loops.GetEnumerator();

    do
    {
    BoundaryLoop lp = flpenum.Current;

    LoopEdgeEnumerator edgeenum = lp.Edges.GetEnumerator();
    if (edgeenum != null)
    {
    do
    {

    Edge edge = edgeenum.Current;
    if (edge != null)
    {
    using (Autodesk.AutoCAD.BoundaryRepresentation.Vertex v = edge.Vertex1)
    {
    Point3d p = v.Point;
    if (!ptResult.Contains(p))
    ptResult.Add(p);
    }
    using (Autodesk.AutoCAD.BoundaryRepresentation.Vertex v2 = edge.Vertex2)
    {
    Point3d p2 = v2.Point;
    if (!ptResult.Contains(p2))
    ptResult.Add(p2);
    }
    edge.Dispose();
    edge = null;
    }
    }
    while (edgeenum.MoveNext());
    edgeenum.Dispose();
    edgeenum = null;
    }

    lp.Dispose();
    lp = null;
    }
    while (flpenum.MoveNext());
    flpenum.Dispose();
    flpenum = null;

    f.Dispose();
    f = null;
    }
    while (shfaceenum.MoveNext());
    shfaceenum.Dispose();
    shfaceenum = null;

    shll.Dispose();
    shll = null;
    }
    while (shllenum.MoveNext());
    shllenum.Dispose();
    shllenum = null;

    complex.Dispose();
    complex = null;
    }
    while (colcom.MoveNext());
    colcom.Dispose();

    }
    tr.Commit();
    }
    }

    }
    }
    catch (Autodesk.AutoCAD.BoundaryRepresentation.Exception exp)
    {
    System.Diagnostics.Trace.WriteLine("PrendiVerticiSolido errore " + exp.Message);
    //throw new System.Exception("PrendiVerticiSolido errore " + exp.Message);

    }
    catch (System.Exception ex)
    {
    System.Diagnostics.Trace.WriteLine("PrendiVerticiSolido errore " + ex.Message);
    // throw new System.Exception("PrendiVerticiSolido errore " + ex.Message);
    }
    return ptResult;
    }

  4. it's a bit slow, but it runs without any error.
    maybe the better way is to write in C++/Arx and then get the resultbuffer as list result.
    But I'll write this another time...now it's midnight!

  5. Thanks for input, Giuliano.

    From my own tests I found two key omissions from the original code:

    1) We need to set oSol to null to make sure the COM object is released.
    2) We need to Dispose of the brp object (which you have done in your code, but I'd forgotten to do). I'll do this with a using block.

    Adding these operations allows the code to run repeatedly (I tested up to 40K iterations) without problem.

    I've updated the code in the post to reflect these omissions.

    Regards,

    Kean

  6. ok..as soon as possible i will try..
    all my dispose is just to verify the crash problem..

  7. Is there any reason why the AcBrEdge.GetCurveType() method that you can use in C++ is not available??
    This is needed to find what type to cast the edge to for closer inspection??

    For example, if the solid has holes (circular arcs) or curved edges (elliptical arcs) how can you inspect the data for these?

    Thanks,
    Mick.

  8. Ok, I worked it out but it was not as obvious as you would expect.
    Here's a snippet to query the type, it's in BOO but it should be easy enough to follow.
    --------------------------------
    for edg as Edge in lp.Edges:
    ed.WriteMessage('\nEdge Count: {0}', ++edgCount)
    crv = edg.Curve as ExternalCurve3d
    if crv.IsCircularArc:
    ed.WriteMessage('\nEdge Type: CircularArc')
    elif crv.IsEllipticalArc:
    ed.WriteMessage('\nEdge Type: EllipticalArc')
    elif crv.IsLineSegment:
    ed.WriteMessage('\nEdge Type: LineSegment')
    ------------------------------------
    Thanks, keep up the good work.
    Mick.

  9. as one can see, autocad 2009+ provides an instrument for .net to traverse solid and get its verteces.
    i'm interesting wether there is any posibility using same api tools split a solid3d into a set of parallelepiped-like entities (just blocks or other solid with ortogonal edges)?

  10. I don't know of any in-built capability to do this, but the Brep API gives access to the information you would need to determine the appropriate components yourself (given a sufficiently sophisticated algorithm).

    Kean

  11. Kean, thanks for your reply! can you give any example of such an algorithm?

  12. Sorry, no.

    Kean

  13. Kean,

    Using this exact BrepTraversal code, add the following lines to the foreach (BrFace fce in shl.Faces)
    loop:

    Autodesk.AutoCAD.Geometry.ExternalBoundedSurface[] extbndsrfs;
    Autodesk.AutoCAD.Geometry.ExternalBoundedSurface extbndsrf;
    Autodesk.AutoCAD.Geometry.CurveBoundary[] crvBnd;

    extbndsrfs = fce.GetSurfaceAsTrimmedNurbs();
    extbndsrf = extbndsrfs[0];

    // THIS FAILS WITH A MEMORY ERROR!!
    crvBnd = extbndsrf.GetContours();

    I really need to get to the contours of the face in my app. Actually, rewriting an old arx routine that uses the contours of the face. Specifically, the 2dcurve definition of the contour.

    Try this and I hope you can help as to why this happens.

    Thanks,

    David

  14. Hi David,

    I'm afraid I don't have time to look into this (especially as the error doesn't appear with the code as it stands, form what I understand).

    I suggest posting your question to the ADN team or via the AutoCAD .NET Discussion Group.

    Regards,

    Kean

  15. Thanks, will do that.

    David

Leave a Reply to David Cancel reply

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