Sorting lists of AutoCAD objects using LINQ – Part 1

I received this interesting question by email from Henrik Ericson, last week.

Is it possible to sort the selected objects (only 3dFaces in my case) in a SelectionSet by their layers?

I want to process all the objects, one layer at a time.

In other words, first all objects on layer A, and then all objects on layer B and then C and so on. In my case it isn't necessary to process the layers alphabetically.

It's especially interesting as I'd just been thinking of approaches for sorting toolbars based on an index property, to get the code in this post to work properly. Henrik's question gave me a solid reason to think the problem through and create a solution.

We're going to take a look at the problem in three stages. In today's post we're going to look at a simple way to address this by using LINQ to sort on an object's layer and (for bonus points) on its object type. As you'll see the code for the two commands is very similar, so in the next post we're going to abstract the implementation away to create an extension method that can sort based on any string property.

In the last (planned) post, we're going to abstract things away even further, creating a static template class that will allow us (hopefully) to sort based on any object property, such as the color index of an object's layer. Which should be pretty cool. 🙂

To avoid a lot of hassle with transactions or manual open/close, we're going to make heavy use of dynamic .NET: we're going to declare ObjectId types as dynamic, so that we can simply access the object's properties directly. Or those of its layer, for that matter. We'll then add entries to a dictionary: the key will be the ObjectId and the value will be the property we want to sort on. Then we can just call LINQ's OrderBy() extension method, passing in a selector lambda from which we return the value to sort on (the value of the key-value pair, in our case).

We can then simply loop through the sorted results, printing the contents to the command-line.

So here's the basic implementation, showing how we can use LINQ to sort an ObjectId array and let us process the objects in sequence:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using System.Collections.Generic;

using System.Linq;

 

namespace ProcessObjectsInOrder

{

  public class Commands

  {

    [CommandMethod("OBL", CommandFlags.UsePickSet)]

    public static void ObjectsByLayer()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null) return;

      var ed = doc.Editor;

 

      // Select the objects to sort

 

      var psr = ed.GetSelection();

      if (psr.Status != PromptStatus.OK)

        return;

 

      // We'll sort them based on a string value (the layer name)

 

      var map = new Dictionary<ObjectId, string>();

 

      foreach (dynamic id in psr.Value.GetObjectIds())

      {

        map.Add(id, id.Layer);

      }

 

      var sorted = map.OrderBy(kv => kv.Value);

 

      // Print them in order to the command-line

 

      foreach (var item in sorted)

      {

        ed.WriteMessage("\nObject {0} on layer {1}", item.Key, item.Value);

      }

    }

 

    [CommandMethod("OBT", CommandFlags.UsePickSet)]

    public static void ObjectsByType()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null) return;

      var ed = doc.Editor;

 

      // Select the objects to sort

 

      var psr = ed.GetSelection();

      if (psr.Status != PromptStatus.OK)

        return;

 

      // Sort them based on a string value (the class name)

 

      var map = new Dictionary<ObjectId, string>();

 

      foreach (dynamic id in psr.Value.GetObjectIds())

      {

        map.Add(id, id.ObjectClass.Name);

      }

 

      var sorted = map.OrderBy(kv => kv.Value);

 

      // Print them in order to the command-line

 

      foreach (var item in sorted)

      {

        ed.WriteMessage("\nObject {0} of type {1}", item.Key, item.Value);

      }

    }

  }

}

 

The OBL and OBT commands sort based on layer and type, respectively. Here's how they work on a simple drawing:

Sorting based on object properties

As mentioned already, there's a great deal of commonality between the two commands. We're going to take a shot at factoring that away, in the next post, creating an extension method to sort an array of ObjectIds.

13 responses to “Sorting lists of AutoCAD objects using LINQ – Part 1”

  1. I've not seen the dynamic tag before. I primarily work in VB, is that a C# thing?

    1. It is, but it's a bit like having 'Option strict off' in VB.NET: it allows you to do use late-binding when accessing object properties and methods.

      stackoverflow.com/qu...

      Kean

      1. Thanks for the info 🙂

  2. note that dictionary order is only valid if you do not remove or add anything that is not at the end. I use dictionaries a lot like you are doing and must be careful of that. I also use
    somelist= somelist.OrderBy(o => o.layer).ToList(); to avoid having to make the intermediate dictionary.

    1. Could you explain a bit more about the order problem? (Thanks for the suggestion of ordering the list - I'll think about that for the next post. 🙂

      Kean

      1. well, when you add items to a dictionary, they stay in order until you add or remove an element in the middle. Sorted dictionary does not suffer from this. So if you sort a list, then make a dictionary from it and expect the dictionary sorted when iterating, don't change the number of elements. There are reasons to choose the different collection types, with the dictionary having advantages of fastest ContainsKey as a hashset is used for the keys inside the collection. Speed matters when you are dealing with lists of say 100,000 points, and doing multiple loops such as when finding groups of triangles that all share an edge and thus form a continuous region.

        1. OK, thanks. That doesn't seem to be an issue for this situation, but the point's well taken.

          Kean

  3. Jürgen A. Becker Avatar
    Jürgen A. Becker

    Hi Kean,
    I'm already used LINQ in my previously projects. Your post is a good idea for working with objects in a current drawing.
    What about a drawing which is not opened? Here we got no editor only the database.
    It would be great to use LINQ within a Database-Object or a BlockTable.
    Regards Jürgen

    1. Kean Walmsley Avatar

      Hi Jürgen,

      It should also work on side databases (although I haven't specifically tried it).

      Regards,

      Kean

      1. Jürgen A. Becker Avatar
        Jürgen A. Becker

        Hi Kean,
        I found a script from the AU. Here is the Link:
        aucache.autodesk.com...
        I'm very exited, so cool.
        Maybe you know it, if so then it's maybe interesting for others.
        Regards Jürgen

        1. Kean Walmsley Avatar

          Looks great - thanks for sharing this!

          Kean

  4. Very interesting Mr Kean. do you know of any libraries/APIs which eliminate a lot of the painful plumbing associated with the current autocad .net API. like a lot of it the code we currently write is very repetitive and can be hidden away: just as is done with dynamic id which is very useful - e.g. opening a transaction, opening for read, closing the transaction and comitting it - while still allowing intellisense to work for compile time type safety?

    1. I do not: I suggest posting to the discussion forums to see what people suggest there.

      Kean

Leave a Reply to James Maeding Cancel reply

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