A couple of streamlined solutions for AutoCAD using LINQ

I owe Chris Kratz a huge thanks for inspiring me to do more with LINQ. This post uses LINQ to provide more elegant solutions to a couple of problems described in previous posts.

The code in today's post is probably my second brief foray into the world of LINQ. The comments Chris posted on the last post prompted me to spend a little time looking at LINQ over the weekend, and I really like it. I'd heard before that "LINQ is really just functional programming", and in many ways I see that more clearly now, myself: the set of higher-order functions for manipulating data feel a lot like the capabilities provided in F#, for instance.

Here's the C# code I put together:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using System.Collections.Generic;

using System.Linq;

using System;

 

namespace PointSort

{

  public class Commands

  {

    public void PrintPoints(Editor ed, IEnumerable<Point3d> pts)

    {

      foreach (Point3d pt in pts)

      {

        ed.WriteMessage("{0}\n", pt);

      }

    }

 

    [CommandMethod("PTS")]

    public void PointSort()

    {

      Document doc =

          Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

 

      // We'll use a random number generator

 

      Random rnd = new Random();

 

      // Generate the random set of points

 

      var pts =

        from n in Enumerable.Range(1, 20)

        select

          new Point3d(

            (2 * rnd.NextDouble() - 1) * 1000,

      
60;     (2 * rnd.NextDouble() - 1) * 1000,

            (2 * rnd.NextDouble() - 1) * 1000

          );

 

      // Order them by X then Y then Z

 

      var sorted =

        from Point3d pt in pts

        orderby pt.X, pt.Y, pt.Z

        select pt;

 

      ed.WriteMessage("\nPoints before sort:\n\n");

      PrintPoints(ed, pts);

      ed.WriteMessage("\n\nPoints after sort:\n\n");

      PrintPoints(ed, sorted);

    }

 

    [CommandMethod("CG")]

    public void CompleteGraph()

    {

      Document doc =

          Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      // Generate points, put them in a list

 

      double alpha = Math.PI * 2 / 19;

      var pts =

        (from n in Enumerable.Range(0, 19)

          select new Point3d(

            Math.Cos(n * alpha) * 5, Math.Sin(n * alpha) * 5, 0

          )

        ).ToList();

 

      // Generate lines from points

 

      var lns =

        from Point3d a in pts

        from Point3d b in pts

        where pts.IndexOf(b) > pts.IndexOf(a)

        select new Line(a, b);

 

      // Add them to the current space in the active drawing

 

      Transaction tr = db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            db.CurrentSpaceId,

            OpenMode.ForWrite

          );

        foreach (Line ln in lns)

        {

          btr.AppendEntity(ln);

          tr.AddNewlyCreatedDBObject(ln, true);

        }

        tr.Commit();

      }

    }

  }

}

The above code actually solves a couple of problems from previous posts.

Let's start by looking at the PTS command:

  • This now generates the random points directly, without the need for a set of helper functions
  • The sort is now really easy, and (for the sake of it) has been modified to sort on X, then Y, then Z
  • The code is shorter, also, as I've removed the Point2d solution

I decided to go back to this previous post and rework that, also, resulting in the CG command:

  • This code generates a Complete Graph in AutoCAD (much more succinctly than before)
  • One statement generates the points, one generates the lines between them
    • The point generation does so with a redundant range enumeration (which we replace with points, without using the original enumeration)
    • Someone may have a better (meaning more elegant – efficiency isn't likely to be a major concern) way to do this, but this way does work

I won't show the results again: they are as before in the respective, original posts.

In the next post we'll use LINQ once more, this time to provide a solution to a request for a future Plugin of the Month (and this may prove to be the most succinct plugin published, to date, but we'll see).

  1. I like it a lot! Hooray for functional programming and computational geometry in the CAD space!

    Cheers, Jeremy.

  2. Is there any chance we'll ever see ObjectARX.net run on the Mac version of AutoCAD?

    I mean, with Mono it would be theoretically possible, but I'm not aware of Autodesk plans regarding this.

    The obvious advantage would be developing cross-platform plugins.

    Thanks!

  3. We're considering possibilities. Beyond that I'm really not able to discuss future product plans publicly.

    Kean

  4. Thanks for your reply Kean.

    Autodesk always respects developers and clients' investiments a lot and ObjectARX.net on the Mac would make so much sense that I hope to see it coming.

  5. Thorsten Meinecke Avatar
    Thorsten Meinecke

    Hi Kean,

    TTIF goes functional! It occured to me that you are performing a self join, unfortunately there's no direct equivalent of F#'s Seq.distinctBy in Linq to make it triangular.

    Anyway, what I ended up with as F# implementation for CG is this:

        let allPairs f =
    Seq.unfold
    (function
    | [] -> None
    | x::xs -> Some(Seq.map (f x) xs, xs) )
    >> Seq.concat

    let lns = allPairs (fun x y -> new Line(x, y)) pts

    which demonstrates at least function composition and partial application, if nothing else.

    Many thanks for your blog.

  6. Thanks, Thorsten - I was hoping to get a better F# implementation than the one I'd posted previously.

    Kean

Leave a Reply to Kean Walmsley Cancel reply

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