« August 2007 | Main | October 2007 »

Driving a multi-sheet AutoCAD plot using .NET

Somewhat symmetrically I’m posting this from Chicago airport, once again, but thankfully I’m now on my way home. It was a busy week of meetings, but I did get the chance to put together some code that extended the last post into the realm of multi-sheet plot jobs.

The following code took some work, but I finally managed to iron out the obvious wrinkles and put together an approach to plot multiple sheets into a single document. The standard DWF6 driver doesn’t appear to support multiple sheet jobs (directly, at least), so I chose to use the DWFx driver that I probably downloaded and installed from here.

I haven’t “diffed” and colour-coded the changed lines with the previous post, as there ended up being quite a lot of swapping around etc., but you should be able to perform that comparison yourself, if you so wish.

Here’s the C# code:

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.PlottingServices;


namespace PlottingApplication

{

  public class PlottingCommands

  {

    [CommandMethod("mplot")]

    static public void MultiSheetPlot()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      Database db = doc.Database;


      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );


        PlotInfo pi = new PlotInfo();

        PlotInfoValidator piv =

          new PlotInfoValidator();

        piv.MediaMatchingPolicy =

          MatchingPolicy.MatchEnabled;


        // A PlotEngine does the actual plotting

        // (can also create one for Preview)


        if (PlotFactory.ProcessPlotState ==

            ProcessPlotState.NotPlotting)

        {

          PlotEngine pe =

            PlotFactory.CreatePublishEngine();

          using (pe)

          {

            // Create a Progress Dialog to provide info

            // and allow thej user to cancel


            PlotProgressDialog ppd =

              new PlotProgressDialog(false, 1, true);

            using (ppd)

            {

              ObjectIdCollection layoutsToPlot =

                new ObjectIdCollection();


              foreach (ObjectId btrId in bt)

              {

                BlockTableRecord btr =

                  (BlockTableRecord)tr.GetObject(

                    btrId,

                    OpenMode.ForRead

                  );

                if (btr.IsLayout &&

                    btr.Name.ToUpper() !=

                      BlockTableRecord.ModelSpace.ToUpper())

                {

                  layoutsToPlot.Add(btrId);

                }

              }


              int numSheet = 1;


              foreach (ObjectId btrId in layoutsToPlot)

              {

                BlockTableRecord btr =

                  (BlockTableRecord)tr.GetObject(

                    btrId,

                    OpenMode.ForRead

                  );

                Layout lo =

                  (Layout)tr.GetObject(

                    btr.LayoutId,

                    OpenMode.ForRead

                  );


                // We need a PlotSettings object

                // based on the layout settings

                // which we then customize


                PlotSettings ps =

                  new PlotSettings(lo.ModelType);

                ps.CopyFrom(lo);


                // The PlotSettingsValidator helps

                // create a valid PlotSettings object


                PlotSettingsValidator psv =

                  PlotSettingsValidator.Current;


                // We'll plot the extents, centered and

                // scaled to fit


                psv.SetPlotType(

                  ps,

                Autodesk.AutoCAD.DatabaseServices.PlotType.Extents

                );

                psv.SetUseStandardScale(ps, true);

                psv.SetStdScaleType(ps, StdScaleType.ScaleToFit);

                psv.SetPlotCentered(ps, true);


                // We'll use the standard DWFx PC3, as

                // this supports multiple sheets


                psv.SetPlotConfigurationName(

                  ps,

                  "DWFx ePlot (XPS Compatible).pc3",

                  "ANSI_A_(8.50_x_11.00_Inches)"

                );


                // We need a PlotInfo object

                // linked to the layout


                pi.Layout = btr.LayoutId;


                // Make the layout we're plotting current


                LayoutManager.Current.CurrentLayout =

                  lo.LayoutName;


                // We need to link the PlotInfo to the

                // PlotSettings and then validate it


                pi.OverrideSettings = ps;

                piv.Validate(pi);


                if (numSheet == 1)

                {

                  ppd.set_PlotMsgString(

                    PlotMessageIndex.DialogTitle,

                    "Custom Plot Progress"

                  );

                  ppd.set_PlotMsgString(

                    PlotMessageIndex.CancelJobButtonMessage,

                    "Cancel Job"

                  );

                  ppd.set_PlotMsgString(

                    PlotMessageIndex.CancelSheetButtonMessage,

                    "Cancel Sheet"

                  );

                  ppd.set_PlotMsgString(

                    PlotMessageIndex.SheetSetProgressCaption,

                    "Sheet Set Progress"

                  );

                  ppd.set_PlotMsgString(

                    PlotMessageIndex.SheetProgressCaption,

                    "Sheet Progress"

                  );

                  ppd.LowerPlotProgressRange = 0;

                  ppd.UpperPlotProgressRange = 100;

                  ppd.PlotProgressPos = 0;


                  // Let's start the plot, at last


                  ppd.OnBeginPlot();

                  ppd.IsVisible = true;

                  pe.BeginPlot(ppd, null);


                  // We'll be plotting a single document


                  pe.BeginDocument(

                    pi,

                    doc.Name,

                    null,

                    1,

                    true, // Let's plot to file

                    "c:\\test-multi-sheet"

                  );

                }


                // Which may contain multiple sheets


                ppd.StatusMsgString =

                  "Plotting " +

                  doc.Name.Substring(

                    doc.Name.LastIndexOf("\\") + 1

                  ) +

                  " - sheet " + numSheet.ToString() +

                  " of " + layoutsToPlot.Count.ToString();


                ppd.OnBeginSheet();


                ppd.LowerSheetProgressRange = 0;

                ppd.UpperSheetProgressRange = 100;

                ppd.SheetProgressPos = 0;


                PlotPageInfo ppi = new PlotPageInfo();

                pe.BeginPage(

                  ppi,

                  pi,

                  (numSheet == layoutsToPlot.Count),

                  null

                );

                pe.BeginGenerateGraphics(null);

                ppd.SheetProgressPos = 50;

                pe.EndGenerateGraphics(null);


                // Finish the sheet

                pe.EndPage(null);

                ppd.SheetProgressPos = 100;

                ppd.OnEndSheet();

                numSheet++;

              }


              // Finish the document


              pe.EndDocument(null);


              // And finish the plot


              ppd.PlotProgressPos = 100;

              ppd.OnEndPlot();

              pe.EndPlot(null);

            }

          }

        }

        else

        {

          ed.WriteMessage(

            "\nAnother plot is in progress."

          );

        }

      }

    }

  }

}

The output of the MPLOT command will be created in “c:\test-multi-sheet.dwfx”, which can then be viewed using Autodesk Design Review 2008 or the XPS viewer that ships with Windows Vista or from here for Windows XP.

Update

I spent some more time looking at this code and noticed a minor issue... We need to tell the plot dialog that we're working with multiple sheets in its constructor. So we first need to count the sheets and then create the dialog. Here's the modified section of code:

          PlotEngine pe =

            PlotFactory.CreatePublishEngine();

          using (pe)

          {

            // Collect all the paperspace layouts

            // for plotting

            ObjectIdCollection layoutsToPlot =

              new ObjectIdCollection();

            foreach (ObjectId btrId in bt)

            {

              BlockTableRecord btr =

                (BlockTableRecord)tr.GetObject(

                  btrId,

                  OpenMode.ForRead

                );

              if (btr.IsLayout &&

                  btr.Name.ToUpper() !=

                    BlockTableRecord.ModelSpace.ToUpper())

              {

                layoutsToPlot.Add(btrId);

              }

            }

            // Create a Progress Dialog to provide info

            // and allow thej user to cancel

            PlotProgressDialog ppd =

              new PlotProgressDialog(

                false,

                layoutsToPlot.Count,

                true

              );

            using (ppd)

            {

This now leads to the plot progress dialog showing multiple progress bars:

Multisheet_plot_progress

September 29, 2007 in AutoCAD, AutoCAD .NET, Plotting | Permalink | Comments (7) | TrackBack

Driving a basic AutoCAD plot using .NET

I just missed my connecting flight in Chicago, so have 3 hours to pass until the next, and decided to post some code I finally got around to writing on the plane from Zurich.

I've had a few requests for code showing how to plot using the .NET API in AutoCAD. There's an existing ObjectARX (C++) sample on the SDK, under samples/editor/AsdkPlotAPI, but there isn't a publicly posted .NET version right now.

Here's the C# code I put together. Please bear in mind that it was written during less than ideal coding conditions, and I haven't spent a substantial amount of time going through it... it seems to work fine for me, but do post a comment if you have trouble with it.

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.PlottingServices;


namespace PlottingApplication

{

  public class PlottingCommands

  {

    [CommandMethod("simplot")]

    static public void SimplePlot()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      Database db = doc.Database;


      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        // We'll be plotting the current layout


        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            db.CurrentSpaceId,

            OpenMode.ForRead

          );

        Layout lo =

          (Layout)tr.GetObject(

            btr.LayoutId,

            OpenMode.ForRead

          );


        // We need a PlotInfo object

        // linked to the layout


        PlotInfo pi = new PlotInfo();

        pi.Layout = btr.LayoutId;


        // We need a PlotSettings object

        // based on the layout settings

        // which we then customize


        PlotSettings ps =

          new PlotSettings(lo.ModelType);

        ps.CopyFrom(lo);


        // The PlotSettingsValidator helps

        // create a valid PlotSettings object


        PlotSettingsValidator psv =

          PlotSettingsValidator.Current;


        // We'll plot the extents, centered and

        // scaled to fit


        psv.SetPlotType(

          ps,

          Autodesk.AutoCAD.DatabaseServices.PlotType.Extents

        );

        psv.SetUseStandardScale(ps, true);

        psv.SetStdScaleType(ps, StdScaleType.ScaleToFit);

        psv.SetPlotCentered(ps, true);


        // We'll use the standard DWF PC3, as

        // for today we're just plotting to file


        psv.SetPlotConfigurationName(

          ps,

          "DWF6 ePlot.pc3",

          "ANSI_A_(8.50_x_11.00_Inches)"

        );


        // We need to link the PlotInfo to the

        // PlotSettings and then validate it


        pi.OverrideSettings = ps;

        PlotInfoValidator piv =

          new PlotInfoValidator();

        piv.MediaMatchingPolicy =

          MatchingPolicy.MatchEnabled;

        piv.Validate(pi);


        // A PlotEngine does the actual plotting

        // (can also create one for Preview)


        if (PlotFactory.ProcessPlotState ==

            ProcessPlotState.NotPlotting)

        {

          PlotEngine pe =

            PlotFactory.CreatePublishEngine();

          using (pe)

          {

            // Create a Progress Dialog to provide info

            // and allow thej user to cancel


            PlotProgressDialog ppd =

              new PlotProgressDialog(false, 1, true);

            using (ppd)

            {

              ppd.set_PlotMsgString(

                PlotMessageIndex.DialogTitle,

                "Custom Plot Progress"

              );

              ppd.set_PlotMsgString(

                PlotMessageIndex.CancelJobButtonMessage,

                "Cancel Job"

              );

              ppd.set_PlotMsgString(

                PlotMessageIndex.CancelSheetButtonMessage,

                "Cancel Sheet"

              );

              ppd.set_PlotMsgString(

                PlotMessageIndex.SheetSetProgressCaption,

                "Sheet Set Progress"

              );

              ppd.set_PlotMsgString(

                PlotMessageIndex.SheetProgressCaption,

                "Sheet Progress"

              );

              ppd.LowerPlotProgressRange = 0;

              ppd.UpperPlotProgressRange = 100;

              ppd.PlotProgressPos = 0;


              // Let's start the plot, at last


              ppd.OnBeginPlot();

              ppd.IsVisible = true;

              pe.BeginPlot(ppd, null);


              // We'll be plotting a single document


              pe.BeginDocument(

                pi,

                doc.Name,

                null,

                1,

                true, // Let's plot to file

                "c:\\test-output"

              );


              // Which contains a single sheet


              ppd.OnBeginSheet();


              ppd.LowerSheetProgressRange = 0;

              ppd.UpperSheetProgressRange = 100;

              ppd.SheetProgressPos = 0;


              PlotPageInfo ppi = new PlotPageInfo();

              pe.BeginPage(

                ppi,

                pi,

                true,

                null

              );

              pe.BeginGenerateGraphics(null);

              pe.EndGenerateGraphics(null);


              // Finish the sheet

              pe.EndPage(null);

              ppd.SheetProgressPos = 100;

              ppd.OnEndSheet();


              // Finish the document


              pe.EndDocument(null);


              // And finish the plot


              ppd.PlotProgressPos = 100;

              ppd.OnEndPlot();

              pe.EndPlot(null);

            }

          }

        }

        else

        {

          ed.WriteMessage(

            "\nAnother plot is in progress."

          );

        }

      }

    }

  }

}

A few comments on the code: I chose to plot to a file using the standard DWF plot driver/PC3 file, mainly as it avoids having to second-guess what printers/plotters everyone uses. :-) The SDK sample populates comboboxes in a dialog to allow selection of the device and the media size, but I've just gone with a choice that should work on all systems.

Here's what you should see when you launch the SIMPLOT command:

Plot_progress

The output file should be created in "c:\test-output.dwf".

I added a simple check to fail gracefully when another plot is in progress... as this code will drive either a foreground or a background plot (depending on the value of the BACKGROUNDPLOT system variable), there's clear scope for failure if the code doesn't check via PlotFactory.ProcessPlotState (or handle the exception if another plot is already happening when you call PlotFactory.CreatePublishEngine()).

Now that I'm online I've found there's quite a similar code sample to the ADN site (also converted from the original SDK sample, I suspect):

How to plot using AutoCAD's .NET Managed API?

September 25, 2007 in AutoCAD, AutoCAD .NET, Plotting | Permalink | Comments (2) | TrackBack

Creating a multileader in AutoCAD using a jig from .NET

I'm now back from a fantastic break in Italy and am trying hard to catch back up. Next week I'm off again to San Diego (work, this time), which may cause further interruptions in blog postings.

This question came through from Genésio from Brazil:

I wish jig a leader with an bubble in the and of the leader, at the same time. Can you help me. Perhaps post the solution in your blog (through the interface).

It took me a while - frustratingly long, in fact, and probably this is not exactly what Genésio is after - but here's what I managed to come up with. The "bubble" is framed MText, but it should be modifiable to use a classic bubble block, instead. I drew heavily on this previous post for the jig code.

The positioning of the text took some work, but I'm reasonably happy with the results. If anyone has tweaks to suggest, please post a comment.

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;


namespace DimensionLibrary

{

  public class DimensionCmds

  {

    class MLeaderJig : EntityJig

    {

      Point3dCollection m_pts;

      Point3d m_tempPoint;

      string m_contents;

      int m_leaderIndex;

      int m_leaderLineIndex;


      public MLeaderJig(string contents)

        : base(new MLeader())

      {

        // Store the string passed in


        m_contents = contents;


        // Create a point collection to store our vertices


        m_pts = new Point3dCollection();


        // Create mleader and set defaults


        MLeader ml = Entity as MLeader;

        ml.SetDatabaseDefaults();


        // Set up the MText contents


        ml.ContentType = ContentType.MTextContent;

        MText mt = new MText();

        mt.SetDatabaseDefaults();

        mt.Contents = m_contents;

        ml.MText = mt;

        ml.TextAlignmentType =

          TextAlignmentType.LeftAlignment;

        ml.TextAttachmentType =

          TextAttachmentType.AttachmentMiddle;


        // Set the frame and landing properties


        ml.EnableDogleg = true;

        ml.EnableFrameText = true;

        ml.EnableLanding = true;


        // Reduce the standard landing gap


        ml.LandingGap = 0.05;


        // Add a leader, but not a leader line (for now)


        m_leaderIndex = ml.AddLeader();

        m_leaderLineIndex = -1;

      }


      protected override SamplerStatus Sampler(

        JigPrompts prompts

      )

      {

        JigPromptPointOptions opts =

          new JigPromptPointOptions();


        // Not all options accept null response

        opts.UserInputControls =

          (UserInputControls.Accept3dCoordinates |

          UserInputControls.NoNegativeResponseAccepted

          );


        // Get the first point

        if (m_pts.Count == 0)

        {

          opts.UserInputControls |=

            UserInputControls.NullResponseAccepted;

          opts.Message =

            "\nStart point of multileader: ";

          opts.UseBasePoint = false;

        }

        // And the second

        else if (m_pts.Count == 1)

        {

          opts.BasePoint = m_pts[m_pts.Count - 1];

          opts.UseBasePoint = true;

          opts.Message =

            "\nSpecify multileader vertex: ";

        }

        // And subsequent points

        else if (m_pts.Count > 1)

        {

          opts.UserInputControls |=

            UserInputControls.NullResponseAccepted;

          opts.BasePoint = m_pts[m_pts.Count - 1];

          opts.UseBasePoint = true;

          opts.SetMessageAndKeywords(

            "\nSpecify multileader vertex or [End]: ",

            "End"

          );

        }

        else // Should never happen

          return SamplerStatus.Cancel;


        PromptPointResult res =

          prompts.AcquirePoint(opts);


        if (res.Status == PromptStatus.Keyword)

        {

          if (res.StringResult == "End")

          {

            return SamplerStatus.Cancel;

          }

        }


        if (m_tempPoint == res.Value)

        {

          return SamplerStatus.NoChange;

        }

        else if (res.Status == PromptStatus.OK)

        {

          m_tempPoint = res.Value;

          return SamplerStatus.OK;

        }

        return SamplerStatus.Cancel;

      }


      protected override bool Update()

      {

        try

        {

          if (m_pts.Count > 0)

          {

            // Set the last vertex to the new value


            MLeader ml = Entity as MLeader;

            ml.SetLastVertex(

              m_leaderLineIndex,

              m_tempPoint

            );


            // Adjust the text location


            Vector3d dogvec =

              ml.GetDogleg(m_leaderIndex);

            double doglen =

              ml.DoglegLength;

            double landgap =

              ml.LandingGap;

            ml.TextLocation =

              m_tempPoint +

              ((doglen + landgap) * dogvec);

          }

        }

        catch (System.Exception ex)

        {

          Document doc =

            Application.DocumentManager.MdiActiveDocument;

          doc.Editor.WriteMessage(

            "\nException: " + ex.Message

          );

          return false;

        }

        return true;

      }


      public void AddVertex()

      {

        MLeader ml = Entity as MLeader;


        // For the first point...


        if (m_pts.Count == 0)

        {

          // Add a leader line


          m_leaderLineIndex =

            ml.AddLeaderLine(m_leaderIndex);


          // And a start vertex


          ml.AddFirstVertex(

            m_leaderLineIndex,

            m_tempPoint

          );


          // Add a second vertex that will be set

          // within the jig


          ml.AddLastVertex(

            m_leaderLineIndex,

            new Point3d(0, 0, 0)

          );

        }

        else

        {

          // For subsequent points,

          // just add a vertex


          ml.AddLastVertex(

            m_leaderLineIndex,

            m_tempPoint

          );

        }


        // Reset the attachment point, otherwise

        // it seems to get forgotten


        ml.TextAttachmentType =

          TextAttachmentType.AttachmentMiddle;


        // Add the latest point to our history


        m_pts.Add(m_tempPoint);

      }


      public void RemoveLastVertex()

      {

        // We don't need to actually remove

        // the vertex, just reset it


        MLeader ml = Entity as MLeader;

        if (m_pts.Count >= 1)

        {

          Vector3d dogvec =

            ml.GetDogleg(m_leaderIndex);

          double doglen =

            ml.DoglegLength;

          double landgap =

            ml.LandingGap;

          ml.TextLocation =

            m_pts[m_pts.Count - 1] +

            ((doglen + landgap) * dogvec);

        }

      }


      public Entity GetEntity()

      {

        return Entity;

      }

    }


    [CommandMethod("MYML")]

    public void MyMLeaderJig()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      Database db = doc.Database;


      // Get the text outside of the jig


      PromptStringOptions pso =

        new PromptStringOptions(

          "\nEnter text: "

        );

      pso.AllowSpaces = true;

      PromptResult pr =

        ed.GetString(pso);

      if (pr.Status == PromptStatus.OK)

      {

        // Create MleaderJig


        MLeaderJig jig =

          new MLeaderJig(pr.StringResult);


        // Loop to set vertices


        bool bSuccess = true, bComplete = false;

        while (bSuccess && !bComplete)

        {

          PromptResult dragres = ed.Drag(jig);

          bSuccess =

            (dragres.Status == PromptStatus.OK);

          if (bSuccess)

            jig.AddVertex();

          bComplete =

            (dragres.Status == PromptStatus.None);

          if (bComplete)

            jig.RemoveLastVertex();

        }


        if (bComplete)

        {

          // Append entity


          Transaction tr =

            db.TransactionManager.StartTransaction();

          using (tr)

          {

            BlockTable bt =

              (BlockTable)tr.GetObject(

                db.BlockTableId,

                OpenMode.ForRead,

                false

              );

            BlockTableRecord btr =

              (BlockTableRecord)tr.GetObject(

                bt[BlockTableRecord.ModelSpace],

                OpenMode.ForWrite,

                false

              );

            btr.AppendEntity(jig.GetEntity());

            tr.AddNewlyCreatedDBObject(

              jig.GetEntity(),

              true

            );

            tr.Commit();

          }

        }

      }

    }

  }

}

Here's what you get when you run the MYML command:

Jigged_mleader

September 21, 2007 in AutoCAD, AutoCAD .NET, Dimensions, Jigs | Permalink | Comments (5) | TrackBack

Heading for the hills

Well, through them, anyway.

Just letting you know that I'll be away from my desk, and from this blog, for the next week or so. We're heading through the Alps to Italy: first to Milan and then down to Ascoli Piceno, where an old friend is getting married.

We decided to take the train, which should be interesting. It's 4 hours from Switzerland to Milan, plus another 5 or so hours from Milan to the rail hub nearest to Ascoli P. Luckily we have family in Milan and so get to spend time with them and break the journey in each direction.

Back in a week!

September 12, 2007 in Personal | Permalink | Comments (0) | TrackBack

Accessing the active space or layout in an AutoCAD drawing using .NET

This question was asked as comment to a previous post by har!s:

Thanks a lot for the code. I have yet to see 2008 and MultiLeader. But I presume that it works on both Model and paper spaces. In that case, what is the best method to make the operation space independent? i.e., it should work on active space irrespective of whether it's model or paper. I think this will be generally applicable to almost all the entity creations.

The question is very valid and does indeed apply to a lot of entity creation - and other - activities. Most of the time I simply show how to open the modelspace in my code, for example:

  Document doc =

    Application.DocumentManager.MdiActiveDocument;

  Editor ed = doc.Editor;

  Database db = doc.Database;

  Transaction tr =

    db.TransactionManager.StartTransaction();

  using (tr)

  {

    BlockTable bt =

      (BlockTable)tr.GetObject(

        db.BlockTableId,

        OpenMode.ForRead

      );

    BlockTableRecord btr =

      (BlockTableRecord)tr.GetObject(

        bt[BlockTableRecord.ModelSpace],

        OpenMode.ForWrite

      );

    // ...

  }

The key statement here is at the end, where we use GetObject() to open the BlockTableRecord to which we want to (for example) append an entity. The form we use is:

  bt[BlockTableRecord.ModelSpace]

Breaking this down: we're actually looking up the ObjectId of the BlockTableRecord with the name of "*MODEL_SPACE", which is the string stored in the static ModelSpace property of the BlockTableRecord class.

Here are a few different options for what we might do here:

  1. Use either BlockTableRecord.ModelSpace or BlockTableRecord.PaperSpace, if we know that we want to access either of these containers (the current approach).
  2. Use foreach() on the BlockTable to iterate through the various BlockTableRecords: you