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

Subscribe via RSS
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 ?
Posted by: Giuliano | March 09, 2009 at 09:14 PM
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 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
);
}
}
}
Posted by: Giuliano | March 09, 2009 at 10:28 PM
ok...that's work. loop 2000 times...with 40 3dsolids.
public static IList PrendiVerticiSolido(Document d, ObjectId idSolido)
{
IList ptResult = new List(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;
}
Posted by: Giuliano | March 09, 2009 at 11:52 PM
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!
Posted by: Giuliano | March 09, 2009 at 11:59 PM
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
Posted by: Kean Walmsley | March 10, 2009 at 01:17 PM
ok..as soon as possible i will try..
all my dispose is just to verify the crash problem..
Posted by: giuliano | March 11, 2009 at 08:02 PM