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
{
public class Commands
{
[CommandMethod("SS")]
public void 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 =
new PromptEntityOptions(
"\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 =
new Point3dCollection();
PromptPointResult ppr =
ed.GetPoint("\nPick first point for section: ");
if (ppr.Status != PromptStatus.OK)
return;
pts.Add(ppr.Value);
PromptPointOptions ppo =
new PromptPointOptions(
"\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 =
new PromptKeywordOptions(
"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;
else if (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 =
new Section(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 =
new ObjectIdCollection();
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
);
}
else if (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:
sec.IsLiveSectionEnabled = true;