Sectioning an AutoCAD solid using F#

In this earlier post we saw some C# code that creates 2D, 3D or "live" section geometry in AutoCAD 2007 or higher. I mentioned at the end of the post that I was curious to see how equivalent F# code compared with this C# source, especially in the area of array concatenation. Well, it turns out that there's still a little pain, just different pain (more like a sharp pain that's over quickly, rather than a dull, nagging ache :-).

Here's the F# code:

#light

module SolidSection

// Import managed assemblies

#I @"C:\Program Files\Autodesk\AutoCAD 2009"

#r "acdbmgd.dll"

#r "acmgd.dll"

open Autodesk.AutoCAD.Runtime

open Autodesk.AutoCAD.ApplicationServices

open Autodesk.AutoCAD.EditorInput

open Autodesk.AutoCAD.DatabaseServices

open Autodesk.AutoCAD.Geometry

open System

let selectInfo() =

  let doc =

    Application.DocumentManager.MdiActiveDocument

  let ed = doc.Editor

  let pts = new Point3dCollection()

  // Ask the user to select an entity to section

  let 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)

  let per = ed.GetEntity(peo)

  if per.Status <> PromptStatus.OK then

    []

  else

    let ppr =

      ed.GetPoint

        ("\nPick first point for section: ")

    if ppr.Status <> PromptStatus.OK then

      []

    else

      pts.Add(ppr.Value) |> ignore

      let ppo =

        new PromptPointOptions

          ("\nPick end point for section: ")

      ppo.BasePoint <- ppr.Value

      ppo.UseBasePoint <- true

      let ppr2 = ed.GetPoint(ppo)

      if ppr2.Status <> PromptStatus.OK then

        []

      else

        pts.Add(ppr2.Value) |> ignore

        // Ask what type of section to create

        let 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"

        let pkr = ed.GetKeywords(pko)

        if pkr.Status <> PromptStatus.OK then

          []

        else

          let st =

            match pkr.StringResult with

            | "2D" -> SectionType.Section2d

            | "Live" -> SectionType.LiveSection

            | _ -> SectionType.Section3d

          [per.ObjectId, pts, st]

[<CommandMethod("SS")>]

let sectionSolid () =

  match selectInfo() with

  | [entId, pts, st] ->

    let doc =

      Application.DocumentManager.MdiActiveDocument

    let ed = doc.Editor

    let db = doc.Database

    // "use" has the same effect as "using" in C#

    use tr =

      db.TransactionManager.StartTransaction()

    // Get appropriately-typed BlockTable and BTRs

    let bt =

      tr.GetObject

        (db.BlockTableId,OpenMode.ForRead)

      :?> BlockTable

    let ms =

      tr.GetObject

        (bt.[BlockTableRecord.ModelSpace],

        OpenMode.ForWrite)

      :?> BlockTableRecord

    // Now let's create our section

    let sec =

      new Section(pts, Vector3d.ZAxis)

    sec.State <- SectionState.Plane

    // The section must be added to the drawing

    let 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

    let ss =

      tr.GetObject

        (sec.Settings,

        OpenMode.ForWrite)

      :?> SectionSettings

    // Set our section type

    ss.CurrentSectionType <- st

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

    if st = SectionType.LiveSection then

      sec.EnableLiveSection(true)

    else

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

      let oic =

        new ObjectIdCollection()

      oic.Add(entId) |> ignore

      ss.SetSourceObjects(st, oic)

      if st = SectionType.Section2d then

        // 2D-specific settings

        ss.SetVisibility

          (st,

          SectionGeometry.BackgroundGeometry,

          true)

        ss.SetHiddenLine

          (st,

          SectionGeometry.BackgroundGeometry,

          false)

      else if st = SectionType.Section3d then

        // 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

    let ent =

      tr.GetObject

        (entId,

        OpenMode.ForRead)

      :?> Entity

    // Declare (and bind) the arrays to be filled

    let (flEnts : Array ref) = ref null

    let (bgEnts : Array ref) = ref null

    let (fgEnts : Array ref) = ref null

    let (ftEnts : Array ref) = ref null

    let (ctEnts : Array ref) = ref null

    // Generate the section geometry

    sec.GenerateSectionGeometry

      (ent,flEnts,bgEnts,fgEnts,ftEnts,ctEnts)

    // Combine the arrays

    let ents =

      Array.concat

        [(!flEnts :?> Entity array);

        (!bgEnts :?> Entity array);

        (!fgEnts :?> Entity array);

        (!ftEnts :?> Entity array);

        (!ctEnts :?> Entity array)]

    // Add each of the entities to the modelspace

    for ent in ents do

      ms.AppendEntity(ent) |> ignore

      tr.AddNewlyCreatedDBObject(ent, true)

    tr.Commit()

  // For the case our input function returned

  // anything but a list of three items

  | _ -> ()

When it runs we see the same thing as the previous post... before the SS command:

Spheres to Section

After it:

Sectioned Spheres - Plan

And now in glorious 3D:

Sectioned Spheres - Live Section

You can see that the code to combine the arrays is indeed cleaner than in C#, but it did take some effort (probably due to lack of knowledge) to get it working. The main hurdles were related to finding out how best to create the Array reference parameters to pass into the GenerateSectionGeometry() function, as well as working out how best to handle the resultant Arrays (whether/how to cast the arrays themselves or their contents, and depending on that decision how best to combine the contents into a single array or list). So it proved harder than is reflected by the end product (just like most things, I suppose).

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

3 responses to “Sectioning an AutoCAD solid using F#”

  1. James Maeding Avatar

    Hey, thanks for improving the calendar controls at the top! That was fast. Super handy.

  2. James Maeding Avatar

    while I am here, I sure wish the section command used the layers of the items cut.
    I cut pipes, and have to figure out what was what after the fact.

  3. Kean Walmsley Avatar

    Hi James,

    Please go ahead and submit that as a request via ADN.

    Thanks,

    Kean

Leave a Reply to Kean Walmsley Cancel reply

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