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