A blog for developers programming with Autodesk platforms, particularly AutoCAD and Forge. With a special focus on AR/VR and IoT.

May 26, 2008

Sectioning an AutoCAD solid using .NET

In the last post we saw how to access some of the 3D modeling functionality introduced in AutoCAD 2007. This post continues that theme, by looking at how to section a Solid3d object programmatically inside AutoCAD. Thanks to Wayne Brill, from DevTech Americas, for providing the original code that inspired this post.

Here's the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Geometry;

using System;

namespace SolidSection

{

publicclassCommands

{

[CommandMethod("SS")]

publicvoid SectionSolid()

{

Document doc =

Application.DocumentManager.MdiActiveDocument;

Database db = doc.Database;

Editor ed = doc.Editor;

// Ask the user to select an entity to section

PromptEntityOptions peo =

newPromptEntityOptions(

"\nSelect entity to section: "

);

peo.SetRejectMessage(

"\nEntity must be a 3D solid, " +

"surface, body or region."

);

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

peo.AddAllowedClass(

typeof(Autodesk.AutoCAD.DatabaseServices.Surface),

false

);

peo.AddAllowedClass(typeof(Body), false);

peo.AddAllowedClass(typeof(Region), false);

PromptEntityResult per =

ed.GetEntity(peo);

if (per.Status != PromptStatus.OK)

return;

ObjectId entId =

per.ObjectId;

// Ask the user to define a section plane

Point3dCollection pts =

newPoint3dCollection();

PromptPointResult ppr =

ed.GetPoint("\nPick first point for section: ");

if (ppr.Status != PromptStatus.OK)

return;

pts.Add(ppr.Value);

PromptPointOptions ppo =

newPromptPointOptions(

"\nPick end point for section: "

);

ppo.BasePoint = ppr.Value;

ppo.UseBasePoint = true;

ppr =

ed.GetPoint(ppo);

if (ppr.Status != PromptStatus.OK)

return;

pts.Add(ppr.Value);

// Ask what type of section to create

PromptKeywordOptions pko =

newPromptKeywordOptions(

"Enter section type "

);

pko.AllowNone = true;

pko.Keywords.Add("2D");

pko.Keywords.Add("3D");

pko.Keywords.Add("Live");

pko.Keywords.Default = "3D";

PromptResult pkr =

ed.GetKeywords(pko);

if (pkr.Status != PromptStatus.OK)

return;

SectionType st;

if (pkr.StringResult == "2D")

st = SectionType.Section2d;

elseif (pkr.StringResult == "Live")

st = SectionType.LiveSection;

else// pkr.StringResult == "3D"

st = SectionType.Section3d;

// Now we're ready to do the real work

Transaction tr =

db.TransactionManager.StartTransaction();

using (tr)

{

try

{

BlockTable bt =

(BlockTable)tr.GetObject(

db.BlockTableId,

OpenMode.ForRead

);

BlockTableRecord ms =

(BlockTableRecord)tr.GetObject(

bt[BlockTableRecord.ModelSpace],

OpenMode.ForWrite

);

// Now let's create our section

Section sec =

newSection(pts, Vector3d.ZAxis);

sec.State = SectionState.Plane;

// The section must be added to the drawing

ObjectId secId =

ms.AppendEntity(sec);

tr.AddNewlyCreatedDBObject(sec, true);

// Set up some of its direct properties

sec.SetHeight(

SectionHeight.HeightAboveSectionLine,

3.0

);

sec.SetHeight(

SectionHeight.HeightBelowSectionLine,

1.0

);

// ... and then its settings

SectionSettings ss =

(SectionSettings)tr.GetObject(

sec.Settings,

OpenMode.ForWrite

);

// Set our section type

ss.CurrentSectionType = st;

// We only set one additional option if "Live"

if (st == SectionType.LiveSection)

sec.EnableLiveSection(true);

else

{

// Non-live (i.e. 2D or 3D) settings

ObjectIdCollection oic =

newObjectIdCollection();

oic.Add(entId);

ss.SetSourceObjects(st, oic);

if (st == SectionType.Section2d)

{

// 2D-specific settings

ss.SetVisibility(

st,

SectionGeometry.BackgroundGeometry,

true

);

ss.SetHiddenLine(

st,

SectionGeometry.BackgroundGeometry,

false

);

}

elseif (st == SectionType.Section3d)

{

// 3D-specific settings

ss.SetVisibility(

st,

SectionGeometry.ForegroundGeometry,

true

);

}

// Finish up the common 2D/3D settings

ss.SetGenerationOptions(

st,

SectionGeneration.SourceSelectedObjects |

SectionGeneration.DestinationFile

);

}

// Open up the main entity

Entity ent =

(Entity)tr.GetObject(

entId,

OpenMode.ForRead

);

// Generate the section geometry

Array flEnts, bgEnts, fgEnts, ftEnts, ctEnts;

sec.GenerateSectionGeometry(

ent,

out flEnts,

out bgEnts,

out fgEnts,

out ftEnts,

out ctEnts

);

// Add the geometry to the modelspace

// (start by combining the various arrays,

// so we then have one loop, not four)

int numEnts =

flEnts.Length + fgEnts.Length +

bgEnts.Length + ftEnts.Length +

ctEnts.Length;

// Create the appropriately-sized array

Array ents =

Array.CreateInstance(

typeof(Entity),

numEnts

);

// Copy across the contents of the

// various arrays

int index = 0;

flEnts.CopyTo(ents, index);

index += flEnts.Length;

fgEnts.CopyTo(ents, index);

index += fgEnts.Length;

bgEnts.CopyTo(ents, index);

index += bgEnts.Length;

ftEnts.CopyTo(ents, index);

index += ftEnts.Length;

ctEnts.CopyTo(ents, index);

// Our single loop to add entities

foreach (Entity ent2 in ents)

{

ms.AppendEntity(ent2);

tr.AddNewlyCreatedDBObject(ent2, true);

}

tr.Commit();

}

catch (System.Exception ex)

{

ed.WriteMessage(

"\nException: " + ex.Message

);

}

}

}

}

}

To see the results of the various options in the SS command, I created three identical spheres in an empty drawing:

I then used the SS command, selecting each sphere in turn and selecting a similar section line for each (as close as I could get without measuring), choosing, of course, a different command option each time (2D, 3D and Live, from left to right):

Orbitting this view, we see the section planes for each sphere:

The objects we've added to the drawing for the two left-hand sections are basic (2D or 3D, depending) geometry. The third, however, includes a section object:

A quick note on the code at the end which adds the various generated geometry to the drawing: in order to avoid having multiple foreach loops (one for each of flEnts, fgEnts, bgEnts, ftEnts & ctEnts), I opted to create an über-array which then gets populated by the contents of each of the other lists. This simple exercise was a pain in C#, as you can see from the code. In fact, having five separate loops could probably be considered less ugly, depending on your perspective. This is the kind of operation that's a breeze in a language like F#, and, with hindsight, I probably should have chosen F# from the beginning for just that reason. Maybe I'll throw an F# version together for comparison's sake.

Update

In AutoCAD 2010, Section.EnableLiveSection(bool) has become a Boolean property. For the above code to work in AutoCAD 2010, change the line containing the call to sec.EnableLiveSection(true) to: