Sort a list of NX objects

Sort a list of custom objects

The .NET List object is a great way to store a collection of related objects; objects can be added and retrieved easily and the List can quickly be converted to an array for NXOpen methods that require an array. The List object even has a .Sort method that works great with native data types (string, integer, double, etc). But, if you have ever wanted to sort a list of NX objects, such as drawing sheets by name, the default .Sort method may error out or give unexpected results. The good news is: there is an overloaded version of the .Sort method that allows you to specify exactly what to sort by and how. This is accomplished by writing a custom function that ranks the objects and telling the .Sort method to use the custom function.



Overview

If I handed you a sheet of paper with a list of words or integers on it and told you to sort them, it would be a fairly straightforward task. However, if I handed you a box full of books and told you to sort them, you might want to ask an important question:

Sorted by what?:

  • author?
  • date?
  • publisher?
  • category?
  • number of pages?
  • color?
  • something else?

The pile of books is like the list of objects; each object may return several values, through properties and/or methods, each of which is perfectly valid to sort by. We can provide more information to the List .Sort method, in the form of a function, that specifies exactly what value to sort on and how to rank those values.

The Custom Comparison Function

Our custom comparison function will take two objects (or structures, or values, etc) as input and must return a value that indicates the ranking of these two objects. When comparing two values, let's call them x and y, there are three possible outcomes:

  • x > y, meaning x comes after y in the sort
  • x < y, meaning x comes before y in the sort
  • x = y, meaning in a sorted list either could come first

Here's how the List.Sort method will interpret the comparison function return value:

  • return value > 0: x > y
  • return value < 0: x < y
  • return value = 0: x = y

Some pseudocode for comparing fictional WidgetObjects (according to the weight property) follows:

Function CompareWidgets(byval x as WidgetObject, byval y as WidgetObject) As Integer
 
    Dim xWeight as Double = x.Weight
    Dim yWeight as Double = y.Weight
 
    If xWeight > yWeight Then
        Return 1
    End if
 
    If xWeight < yWeight Then
        Return -1
    End if
 
    If xWeight = yWeight Then
        Return 0
    End if
End Function

Sorting Sheet Names

As a simple example, let's sort all the drawing sheets in the part alphabetically. Open or create a part file with several sheets in it and run the following journal:

Option Strict Off
Imports System
Imports System.Collections.Generic
Imports NXOpen
 
Module Module1
 
    Sub Main()
 
        Dim theSession As Session = Session.GetSession()
        Dim lw As ListingWindow = theSession.ListingWindow
        lw.Open()
 
        If IsNothing(theSession.Parts.Display) Then
            'active part required, exit journal
            lw.WriteLine("Active part required, journal exiting...")
            lw.Close()
            Return
        End If
 
        'create list object to hold sheets in the work part
        Dim mySheets As New List(Of Drawings.DrawingSheet)
 
        'add each sheet object to the list
        For Each tempSheet As Drawings.DrawingSheet In theSession.Parts.Work.DrawingSheets
            mySheets.Add(tempSheet)
        Next
 
        'list the sheet names in the "as-added" order
        lw.WriteLine("Original sheet order")
        For Each tempSheet As Drawings.DrawingSheet In mySheets
            lw.WriteLine(tempSheet.Name)
        Next
        lw.WriteLine("")
 
        'sort list alphabetically by sheet name (according to our function)
        mySheets.Sort(AddressOf CompareSheetNames)
 
        'list the sorted sheet names
        lw.WriteLine("Sorted Sheet Order")
        For Each tempSheet As Drawings.DrawingSheet In mySheets
            lw.WriteLine(tempSheet.Name)
        Next
        lw.WriteLine("")
 
        'reverse the list
        mySheets.Reverse()
 
        'list the reverse sorted sheet names
        lw.WriteLine("Reverse Sorted Sheet Order")
        For Each tempSheet As Drawings.DrawingSheet In mySheets
            lw.WriteLine(tempSheet.Name)
        Next
 
 
    End Sub
 
 
    Private Function CompareSheetNames(ByVal x As Drawings.DrawingSheet, ByVal y As Drawings.DrawingSheet) As Integer
 
        'case-insensitive sort
        Dim myStringComp As StringComparer = StringComparer.CurrentCultureIgnoreCase
 
        'for a case-sensitive sort (A-Z then a-z), change the above option to:
        'Dim myStringComp As StringComparer = StringComparer.CurrentCulture
 
        Return myStringComp.Compare(x.Name, y.Name)
 
    End Function
 
End Module

At first glance, our comparison function doesn't look much like the original example code, but we are using a StringComparer object which really makes life easy for us. Using a StringComparer object has three main advantages in this case:

  • the string comparison can be based on the current computer's regional settings
  • you can choose a case-sensitive or case-insensitive sort order
  • the .Compare method's return value is in the exact form we need



Sort Bodies Based on Bounding Box Volume

For a more complicated example, let's sort all the bodies in the work part based on the volume of the body's bounding box. The bounding box size is not available directly from the body object; we'll have to call another function to determine this information.


'NXJournaling
'June 25, 2013
'compare and sort body objects based on bounding box volume
 
Option Strict Off  
Imports System  
Imports System.Collections.Generic  
Imports NXOpen  
Imports NXOpen.UF  
 
Module Module1  
 
    Dim theSession As Session = Session.GetSession()  
    Dim theUFSession As UFSession = UFSession.GetUFSession  
 
    Sub Main()  
 
        Dim lw As ListingWindow = theSession.ListingWindow  
        lw.Open()  
        Dim workPart As Part = theSession.Parts.Work  
        Dim theBodies As New List(Of Body)  
 
 'gather solid and sheet bodies into a list
        For Each temp As Body In workPart.Bodies  
            theBodies.Add(temp)  
        Next  
 
 'write the list out as it was built
        lw.WriteLine("Before sort:")  
        lw.WriteLine("Body Tag : Bounding box volume")  
        For Each temp As Body In theBodies  
            lw.WriteLine(temp.Tag.ToString & ": " & BoundingBoxVolume(temp).ToString)  
        Next  
        lw.WriteLine("")  
 
 'sort the list with our custom compare function
        theBodies.Sort(AddressOf CompareByBoundingBoxVolume)  
 
 'write out sorted list
        lw.WriteLine("After sort:")  
        lw.WriteLine("Body Tag : Bounding box volume")  
        For Each temp As Body In theBodies  
            lw.WriteLine(temp.Tag.ToString & ": " & BoundingBoxVolume(temp).ToString)  
        Next  
        lw.WriteLine("")  
 
 'reverse the list and write it out again
        lw.WriteLine("Reverse list")  
        lw.WriteLine("Body Tag : Bounding box volume")  
        theBodies.Reverse()  
        For Each temp As Body In theBodies  
            lw.WriteLine(temp.Tag.ToString & ": " & BoundingBoxVolume(temp).ToString)  
        Next  
 
    End Sub  
 
    Public Function BoundingBoxVolume(ByVal tempObj As NXObject) As Double  
 
        Dim bbox(5) As Double  
 
        theUFSession.Modl.AskBoundingBox(tempObj.Tag, bbox)  
        Return (bbox(3) - bbox(0)) * (bbox(4) - bbox(1)) * (bbox(5) - bbox(2))  
 
    End Function  
 
    Private Function CompareByBoundingBoxVolume(ByVal x As Body, ByVal y As Body) As Integer  
 
        Dim volX As Double = BoundingBoxVolume(x)  
        Dim volY As Double = BoundingBoxVolume(y)  
 
        If volX > volY Then  
            Return 1  
        End If  
 
        If volX < volY Then  
            Return -1  
        End If  
 
        If volX = volY Then  
            Return 0  
        End If  
 
    End Function  
 
End Module 



In this example, we derive values based on body X and body Y and sort based on the derived values.


Conclusion

The .NET framework makes it fairly easy to sort a list of objects; just tell the List object what value you want to sort on and how to rank the values - .NET will take care of the rest!

Have a question on sorting your list? Do you have a good tip you'd like to pass along? Leave it in the comments section below.

Comments

If you are compiling a full-blown application using Visual Studio, you could always use LINQ. I love it! It allows for lots of one-liners.

You may need to import it:

Imports System.Linq

and then you could call the above sort functions as follows:

mySheets = mySheets.OrderBy(Function(x) x.Name).ToList()

(x acts an object in the list so you can then choose the property you want to sort by)

or

theBodies = theBodies.OrderBy(Function(x) BoudingBoxVolume(x)).ToList()

This will do exactly what you're looking for. The Sort "overload" is definitely nice because it gives you more control, but LINQ can really help out with simple scenarios. There is also OrderByDescending() and the ability to sort by several items in a row (List.OrderBy().ThenBy().ThenBy()) in a similar fashion.

For some reason this doesn't seem to work in a journal, which I'm guessing is because the journal uses .NET Framework 2.0? Not sure.

> For some reason this doesn't seem to work in a journal, which
> I'm guessing is because the journal uses .NET Framework 2.0? Not sure.

No, journals use up-to-date versions of the .NET Framework. For example, NX10 uses Framework 4.5.

The problem is that code in the Journal Editor can only reference a few different assemblies, and System.Linq isn't one of them.

That makes sense. It seemed like they would use the latest framework, but I couldn't think of why else it wouldn't be there.

Thanks for the clarification!