Sorting lists of AutoCAD objects using LINQ – Part 3

After seeing a basic approach to sort arrays of ObjectIds using LINQ – and then refactoring that to be a shared extension method to be used on arrays of ObjectIds – today we're going to see that code refactored, once again, to work with types of data other than strings (up until now we could only sort objects based on string properties such as class and layer names).

One solid approach for creating operations that can deal with different types of object – while maintaining type safety – is to use template classes, which are known as generic classes in C#. I should probably have been calling these "generics" since the beginning of this series, but unfortunately my time with C++ has left the term "templates" somewhat ingrained. For the sake of this series of posts, please consider the terms template and generic to be interchangeable, even if it seems there are some differences between the two language implementations.

In case you're not already familiar with generics, you use them every time you create a generic collection – such as a List<> or a Dictionary<> – to manage objects of a particular type. In today's code we're going to define our own static template class with a static template method. Both class and method can be static as we don't need to store any per-instance data with instances of our generic class.

Which also means we don't even need to instanciate our class: we can just refer to the type and call the static method from that.

Here's the updated C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using System;

using System.Collections.Generic;

using System.Linq;

 

namespace ProcessObjectsInOrder

{

  public static class Extensions

  {

    /// <summary>

    /// Sorts an array of ObjectIds based on a string property and order.

    /// </summary>

    /// <param name="ids">The array of IDs to sort.</param>

    /// <param name="propertySelector">A function selecting the string property.</param>

    /// <param name="orderSelector">A function to specify the selection order.</param>

    /// <returns>An ordered enumerable of key-value pairs.</returns>

 

    public static List<KeyValuePair<ObjectId, string>>

      Sort(

        this ObjectId[] ids,

        Func<dynamic, string> propertySelector,

        Func<KeyValuePair<ObjectId, string>, string> orderSelector

      )

    {

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

 

      foreach (dynamic id in ids)

      {

        map.Add(id, propertySelector(id));

      }

 

      return map.OrderBy(orderSelector).ToList();

    }

  }

 

  public static class ObjectSorter<T>

  {

    public static List<KeyValuePair<ObjectId, T>>

      Sort(

        ObjectId[] ids,

        Func<dynamic, T> propertySelector,

        Func<KeyValuePair<ObjectId, T>, T> orderSelector

      )

    {

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

 

      foreach (dynamic id in ids)

      {

        map.Add(id, propertySelector(id));

      }

 

      return map.OrderBy(orderSelector).ToList();

    }

  }

 

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

   
   }

    }

 

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

    public static void ObjectsByLayer2()

    {

      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 layer name)

 

      var sorted = psr.Value.GetObjectIds().Sort(id => id.Layer, 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("OBT2", CommandFlags.UsePickSet)]

    public static void ObjectsByType2()

    {

      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 sorted =

        psr.Value.GetObjectIds().Sort(id => id.ObjectClass.Name, 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);

      }

    }

 

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

    public static void ObjectsByLayer3()

    {

      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 layer name)

 

      var sorted =

        ObjectSorter<string>.Sort(

          psr.Value.GetObjectIds(),

          id => id.Layer,

          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("OBT3", CommandFlags.UsePickSet)]

    public static void ObjectsByType3()

    {

      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 sorted =

        ObjectSorter<string>.Sort(

          psr.Value.GetObjectIds(),

          id => id.ObjectClass.Name,

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

      }

    }

 

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

    public static void ObjectsByColor()

    {

      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 short value (the layer's color index)

 

      var sorted =

        ObjectSorter<short>.Sort(

          psr.Value.GetObjectIds(),

          id => id.LayerId.Color.ColorIndex,

          kv => kv.Value

        );

 

      // Print them in order to the command-line

 

      foreach (var item in sorted)

      {

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

      }

    }

 

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

    public static void ObjectsByColor2()

    {

      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 an integer value (the layer's color index)

      // Needs to be an integer as it's signed: we're going in reverse order

 

      var sorted =

        ObjectSorter<int>.Sort(

          psr.Value.GetObjectIds(),

          id => id.LayerId.Color.ColorIndex,

          kv => -kv.Value

        );

 

      // Print them in order to the command-line

 

      foreach (var item in sorted)

      {

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

      }

    }

  }

}

 

The main additions – other than new versions of the OBL and OBT commands called OBL3 and OBT3 – are a couple of commands to sort objects by their layer's color index. The first, OBC, uses the short data type to sort the list in ascending order. The second, OBC2, uses an integer instead – as this is a signed data type – so we can use our orderSelector argument to reverse the order and list them in descending order.

Here are the OBC and OBC2 commands in action:

Sort objects by property - template class

That's it for this short series on sorting objects using LINQ and AutoCAD's dynamic .NET implementation. If you have suggestions on how to extend this implementation in interesting ways – or to create a separate series of posts – please do post a comment.

2 responses to “Sorting lists of AutoCAD objects using LINQ – Part 3”

  1. As always, Kean, you have lots of very useful code posted here.

    wtertinek.com/2016/...

    Here is a blog I found regarding AutoCAD and LINQ. Walkthrough is quit thorough and easy to follow, I think.
    I converted it all to vb.net (quit painful) and getting all the ObjectIds in the current drawing is as simple as this:


    Using mDB = MyDatabase.FromActiveDocument()
    Dim entity_Layer_List = From ent In mDB.ModelSpace()
    Select ent.Layer
    Distinct.ToList()
    ' Do your coding here
    End Using

    This returns an IEnumerable list of all Layers currently in use in the drawing. I am no good at explanations, the blog does a better job.I highly recommend it, It's a very good resource.

    1. Yes, as do I - the code is up on Github. But Mr Wolfgang seems to be implementing a container class which forces one to reimplement some Linq extension methods which can be a pain.............he's done a valiant job implementing what a lot of us felt was needed in a polished API. I don't know if he was aware that dynamic ids are now here. would be interesting to see how Mr Kean's code can be implemented there too to have a more finished and polished api library

Leave a Reply to Ben Cancel reply

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