Sort elements in a list

Hi there,

How can I sort assemblies in the same order as they are listed in the project browser.

Notice 11A and 11B. Please suggest if there’s a shorter way too.

import re

liste = IN[0]

def natural_sort(l): 
    convert = lambda text: int(text) if text.isdigit() else text.lower() 
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(l, key = alphanum_key)

OUT = natural_sort(liste)

7 Likes

IF you are looking to set the numbering part to three characters as standard for all the assembly names, please check this.

Assemblies.dyn (21.2 KB)

1 Like

List.Sort after List.GetItematIndex maybe… because he wants to sort them naturally.

1 Like

I don’t know if sorting them is necessary since you’re working off of the original elements, the list values will still be aligned. Despite the fact that they are not sorted, when you go to rename the assemblies, they’ll still rename the correct assembly that the new data was created from. If you were creating the names from scratch, then you would want to get the list values aligned. Since that’s not what it looks like to me, I wouldn’t really bother with sorting them.

Take this example of the wall types I have in the sample arch template, the walls in the project browser are sorted alphanumerically. However, when I pull them in Dynamo, they are not sorted as it is in the project browser. Regardless if it’s sorted, I am working off of that original list of elements and modifying the name in the same order as it was grabbed in Dynamo and not changing the order. When I go to set the name of the wall to the new name it’ll set correctly because of the elements and the new name lists match.

Hope this helps and makes sense!

3 Likes

You’re right. But in my case I need to accommodate the Assembly 11A and Assembly 11B as Assembly 11 and Assembly 12 respectively and then rename all the following assemblies by ordering one number higher (13, 14…).

a = List.Clean(String.ToNumber(String.Split(str, "")),false);
b = List.SortByKey(str, String.ToNumber(String.Concat(a + "")))["sorted list"];
3 Likes

It’s working but I don’t know why 11B is coming before 11A.

I have 2 questions about this method.

  1. I have to put a lot of “DSCore” before the actual OOTB node notations. Am I at risk while using this in another system where I may not have custom packages loaded (different definitions)?
  2. If I unravel this code block into separate nodes, I get an innocuous warning at String.ToNumber node which might put off another user of this Dynamo graph?

@Daniz_Maral,

This works fine. But I’m wondering how can I now sort the actual elements using this sorted names list.

1 Like

You were in the right way: instead of using Sort node, use List.SortByKey so elements (assemblies) and their names will ordered and keeper accordingly :wink:

Okay. I got it working for me. Just used List.AllIdicesOf to get the indexes to sort the elements.
Thanks @Deniz_Maral

2 Likes

In the aplhanum_key lambda where is the value for “key”(7 at right) coming from for the regexp split?

I don’t understand the ‘in’ point for that lambda.

I am trying to pass a list of generic annotations and sort by the objAnno.Family.Name or something thereabouts… to get an ordered list by name of the collection.

Specifically I have a collection of Generic annotation TYPES:

[
  [
    Family Type: DCI-1029 xyz V1.1, Family: TAG-__-DCI-Bulletin,
    Family Type: DCI-0128 pdq v1.1, Family: TAG-__-DCI-Bulletin,
    Family Type: Family1, Family: Family1,
    Family Type: Family1 2, Family: Family1,
  ]
]

I am trying to get into grouped lists so I can place them in drafting views by element.Family.Name

I cannot figure out how to introduce the natural sort to the list(element) so #1 - the Element>Family.Names sort and #2. the Element.Name (which is they type name) sorts.

This would also allow me to filter the GenericAnnotation.Family.Name to include or exclude later when creating the drafting views.

Current overall code looks like this:

##https://gist.github.com/gtalarico/e6be055472dfcb6f597e3dcd20d11f37
import clr
import sys
import System
import math                 ##For truncate to integer-RA
import re                   ##Regular expressions for 'natural' sort

import System.Collections.Generic
from System.Collections.Generic import List

from itertools import groupby

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Structure import *

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *

clr.AddReference('System')
from System.Collections.Generic import List

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.ImportExtensions(Revit.Elements)

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument
uidoc=DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument

''' Creates a Drafting View'''
from Autodesk.Revit.DB import Transaction, Element
##https://forum.dynamobim.com/t/collecting-all-elements-of-family-types-in-active-view/19838/2
from Autodesk.Revit.DB import FilteredElementCollector, BuiltInCategory, BuiltInParameter, ParameterValueProvider, ElementId, ElementParameterFilter, FilterNumericEquals, FilterElementIdRule

clr.AddReference("RevitNodes")

#  Drafting Views
from Autodesk.Revit.DB import ViewFamilyType, ViewDrafting, Element
from Autodesk.Revit.DB import ViewFamily

##cast CONSTANTS
xSpace = 8.5/12     ##Horizontal spacing 8-1/2" Wide
ySpace = 3/16/12    ##Vertical spacing  3/16" tall (Text is 3/32" high)

yLimit = 10/12      ##Limit of Y column height 10" (8-1/2"x11" Guide to format into sheet size references)
yCount = int(math.floor(yLimit/ySpace))  ##Number of rows in a column for reset y value
##uidoc = __revit__.ActiveUIDocument
##doc = __revit__.ActiveUIDocument.Document
#####################################################################################
##Create a Drafting View#
def get_drafting_type_id():
    ##Selects First available ViewType that Matches Drafting Type.##
    viewfamily_types = FilteredElementCollector(doc).OfClass(ViewFamilyType) 
    for i in viewfamily_types:
        if i.ViewFamily == ViewFamily.Drafting:     ##*.ViewFamily Backwards 'parent' ref I've been looking for!##<<<<<<<<<<<<<<<<<<<<<<<<<<HERE
            return i.Id

def GetDraftingViews():
    ##https://forums.autodesk.com/t5/revit-api-forum/view3d-collector/td-p/5277451
    DraftingViewsList = FilteredElementCollector(doc).OfClass(ViewDrafting).ToElements() 
    ##DraftingViewTemplates = [v.Id for v in DraftingViewsList if v.IsTemplate == True]
    DraftingViewsList = [v.Id for v in DraftingViewsList if v.IsTemplate == False]
    return [DraftingViewsList]

def GetDraftingViewTemplates():
    ##https://forums.autodesk.com/t5/revit-api-forum/view3d-collector/td-p/5277451
    DraftingViewsList = FilteredElementCollector(doc).OfClass(ViewDrafting).ToElements()
    DraftingViewTemplates = [v.Id for v in DraftingViewsList if v.IsTemplate == True]
    ##DraftingViewsList = [v.Id for v in DraftingViewsList if v.IsTemplate == False]
    return [DraftingViewTemplates]

def CreateDraftingView():
    drafting_type_id = get_drafting_type_id()
    drafting_view = ViewDrafting.Create(doc, drafting_type_id)
    return drafting_view

def GetDraftingView(strName=""):      ##Check if drafting view existds = if not, create it and assign view name
    if strName == "":
        DraftingView = CreateDraftingView ()
        ##Return default drafting view    
    else:
        DraftingView = FilteredElementCollector(doc).OfClass(ViewDrafting).ToElements() 
        ##DraftingViewTemplates = [v.Id for v in DraftingViewsList if v.IsTemplate == True]
        DraftingView = [v.Id for v in DraftingView if v.Name == strName]
        
    if not DraftingView:
        DraftingView = CreateDraftingView ()
        if not strName=="" :
            DraftingView.Name = strName
    else:
        DraftingView =DraftingView [0]
    return [DraftingView]

def GetGenericAnnotationTypes():
    ##BAsed in part https://forum.dynamobim.com/t/how-can-i-collect-all-family-types-that-are-considered-annotation-symbols-in-revit/37480/10
    filterAnnot = System.Predicate[System.Object](lambda x : x.Family.FamilyCategory.Name ==  "Generic Annotations") 
    symbAnnot = List[Element](FilteredElementCollector(doc).OfClass(FamilySymbol).ToElements()).FindAll(filterAnnot)
    return symbAnnot

def GenAnnosInView(objView):
    ##GET ANNOTATIONSIN VIEW
    ##https://forum.dynamobim.com/t/get-all-generic-annotations-in-a-view/80514/2
    return [x.Name for x in FilteredElementCollector(doc,objView.Id).OfCategory(BuiltInCategory.OST_GenericAnnotation).WhereElementIsNotElementType()]

def PlaceGenAnnoArray(AnnosToPlace, viewView):   #Single family of generic annotaiton types to place
    ##PResets at head of main definitpon else:
    ##xSpace = 8.5/12     ##Horizontal spacing 8-1/2" Wide
    ##ySpace = 3/16/12    ##Vertical spacing  3/16" tall (Text is 3/32" high)
    
    ##yLimit = 10/12      ##Limit of Y column height 10" (8-1/2"x11" Guide to format into sheet size references)
    ##yCount = math.floor(yLimit/ySpace)  ##Number of rows in a column for reset y value
    
    ##https://forum.dynamobim.com/t/place-generic-annotation-by-point-in-family-editor/65883/19
    ##get AllGenAnnosInView  - if any...
    for objAnno in AnnosToPlace:
        #from 0,0 run down then over for simplicity. These typically will NOT end up on printed sheets
        #But are head to use against scheduling    
        column = math.floor( yLimit / count * yspace)   ##Get column based on placed elements and Y limit sizes
        x= column * xSpace                              ##Get x location
        y= ySpace * (count - (column * yCount))         ##Offset Y by column displacemnet 
        objPoint = Point.ByCoordinates(x, y, 0)
        
        ##for this case we assume it will only be in ONE view...
        ##if objAnno in AllGenAnnosInView then
            ##objCurrent = objanno
            ##Move obj anno to objPoint 
        ##ELSE  ##Create a new object at the position
            ##objCurrent = doc.FamilyCreate.NewFamilyInstance(objPoint,objAnnoType,viewView)

        count +=1                                       ##increment counter
        
def natural_sort(objList): 
    ##https://forum.dynamobim.com/t/sort-elements-in-a-list/53520/2
    convert = lambda strText: int(strText) if strText.isdigit() else strText.lower()  ##Digit or lcase
    
    alphanum_key = lambda xs: list( convert(c) for c in re.split('([0-9]+)', xs.Family.Name) )  ##Return list
    
    return sorted(objList, key = alphanum_key)


#######################################################################################

t = Transaction(doc, 'Create Drafting View')

t.Start()

GenAnno = []
##GenAnnoDict = System.Collections.Generic.Dictionary[System.String,System.Object]()
GenAnno = GetGenericAnnotationTypes()          ##LIST type for sort/groupfuncitons

GenAnno = natural_sort(GenAnno)

##GenAnno.sort(key=lambda x: x.Family.Name)         ##Generic sort doesn't prioritize numbers in names

##GenAnno=natural_sort(GenAnno)         
#GenAnno.sort(key=lambda x: x.Family.Name)

#lstGroup=[]
#lstKey=[]
#for k, g in groupby(GenAnno, lambda x:x.Family.Name):
#	lstGroup.append(g)
#	lstKey.append(k)
#GenAnno.sort(key=lambda x: x.Family.Name)

##symbAnnot = [x for x in FilteredElementCollector(doc,viewDraft.Id).OfCategory(BuiltInCategory.OST_GenericAnnotation).WhereElementIsNotElementType()]

t.Commit()

OUT=GenAnno ##,lstGroup,lstKey

That could be the problem. Use instead of that:

@Deniz_Maral - these are generic annotation family types.

UPDATE- Working code- had to find a back door to get the GenericAnnotationFamilyType.NAME

My first working lambda after much hacking:

GAstrName = lambda j: [ alphanum_key(c) for c in doc.GetElement(j.Id).get_Parameter(BuiltInParameter.ALL_MODEL_TYPE_NAME).AsString()]

Current code here:

##https://gist.github.com/gtalarico/e6be055472dfcb6f597e3dcd20d11f37
import clr
import sys
import System
import math                 ##For truncate to integer-RA
import re                   ##Regular expressions for 'natural' sort

import System.Collections.Generic
from System.Collections.Generic import List

from itertools import groupby

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Structure import *

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *

clr.AddReference('System')
from System.Collections.Generic import List

clr.AddReference('RevitNodes')
import Revit

clr.ImportExtensions(Revit.GeometryConversion)
clr.ImportExtensions(Revit.Elements)

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument
uidoc=DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument

''' Creates a Drafting View'''
from Autodesk.Revit.DB import Transaction, Element
##https://forum.dynamobim.com/t/collecting-all-elements-of-family-types-in-active-view/19838/2
from Autodesk.Revit.DB import FilteredElementCollector, BuiltInCategory, BuiltInParameter, ParameterValueProvider, ElementId, ElementParameterFilter, FilterNumericEquals, FilterElementIdRule

clr.AddReference("RevitNodes")

#  Drafting Views
from Autodesk.Revit.DB import ViewFamilyType, ViewDrafting, Element
from Autodesk.Revit.DB import ViewFamily

##cast CONSTANTS
xSpace = 8.5/12.0     ##Horizontal spacing 8-1/2" Wide
ySpace = 3.0/16.0/12.0    ##Vertical spacing  3/16" tall (Text is 3/32" high)

yLimit = 10.0/12.0      ##Limit of Y column height 10" (8-1/2"x11" Guide to format into sheet size references)
yCount = int(math.floor(yLimit/ySpace))  ##Number of rows in a column for reset y value
##uidoc = __revit__.ActiveUIDocument
##doc = __revit__.ActiveUIDocument.Document
#####################################################################################
##Create a Drafting View#
def get_drafting_type_id():
    ##Selects First available ViewType that Matches Drafting Type.##
    viewfamily_types = FilteredElementCollector(doc).OfClass(ViewFamilyType)
    for i in viewfamily_types:
        if i.ViewFamily == ViewFamily.Drafting:     ##*.ViewFamily Backwards 'parent' ref I've been looking for!##<<<<<<<<<<<<<<<<<<<<<<<<<<HERE
            return i.Id

def GetDraftingViews():
    ##https://forums.autodesk.com/t5/revit-api-forum/view3d-collector/td-p/5277451
    DraftingViewsList = FilteredElementCollector(doc).OfClass(ViewDrafting).ToElements() 
    ##DraftingViewTemplates = [v.Id for v in DraftingViewsList if v.IsTemplate == True]
    DraftingViewsList = [v.Id for v in DraftingViewsList if v.IsTemplate == False]
    return [DraftingViewsList]

def GetDraftingViewTemplates():
    ##https://forums.autodesk.com/t5/revit-api-forum/view3d-collector/td-p/5277451
    DraftingViewsList = FilteredElementCollector(doc).OfClass(ViewDrafting).ToElements()
    DraftingViewTemplates = [v.Id for v in DraftingViewsList if v.IsTemplate == True]
    ##DraftingViewsList = [v.Id for v in DraftingViewsList if v.IsTemplate == False]
    return [DraftingViewTemplates]

def CreateDraftingView():
    drafting_type_id = get_drafting_type_id()
    drafting_view = ViewDrafting.Create(doc, drafting_type_id)
    return drafting_view

def GetDraftingView(strName=""):      ##Check if drafting view existds = if not, create it and assign view name
    if strName == "":
        DraftingView = CreateDraftingView ()
        ##Return default drafting view    
    else:
        DraftingView = FilteredElementCollector(doc).OfClass(ViewDrafting).ToElements() 
        ##DraftingViewTemplates = [v.Id for v in DraftingViewsList if v.IsTemplate == True]
        DraftingView = [v.Id for v in DraftingView if v.Name == strName]
        
    if not DraftingView:
        DraftingView = CreateDraftingView ()
        if not strName=="" :
            DraftingView.Name = strName
    else:
        DraftingView =DraftingView [0]
    return [DraftingView]

def GetGenericAnnotationTypes():
    ##BAsed in part 
    ##https://forum.dynamobim.com/t/how-can-i-collect-all-family-types-that-are-considered-annotation-symbols-in-revit/37480/10
    ##https://forum.dynamobim.com/t/get-all-generic-annotations-in-a-view/80514/2
    filterAnnot = System.Predicate[System.Object](lambda x : x.Family.FamilyCategory.Name ==  "Generic Annotations") 
    symbAnnot = List[Element](FilteredElementCollector(doc).OfClass(FamilySymbol).ToElements()).FindAll(filterAnnot)
    
    return symbAnnot

def GenAnnosInView(objView):
    ##GET ANNOTATIONSIN VIEW
    ##https://forum.dynamobim.com/t/get-all-generic-annotations-in-a-view/80514/2
    return [x.Name for x in FilteredElementCollector(doc,objView.Id).OfCategory(BuiltInCategory.OST_GenericAnnotation).WhereElementIsNotElementType()]

def PlaceGenAnnoArray(AnnosToPlace, viewView):   #Single family of generic annotaiton types to place
    ##PResets at head of main definitpon else:
    ##xSpace = 8.5/12.0     ##Horizontal spacing 8-1/2" Wide
    ##ySpace = 3.0/16.0/12.0    ##Vertical spacing  3/16" tall (Text is 3/32" high)
    
    ##yLimit = 10.0/12.0      ##Limit of Y column height 10" (8-1/2"x11" Guide to format into sheet size references)
    ##yCount = math.floor(yLimit/ySpace)  ##Number of rows in a column for reset y value
    count=0                 ##Count position in array of all family types
    ##https://forum.dynamobim.com/t/place-generic-annotation-by-point-in-family-editor/65883/19
    
    ##get AllGenAnnosInView  - if any...

    for objAnno in AnnosToPlace:
        #from 0,0 run down then over for simplicity. These typically will NOT end up on printed sheets
        #But are head to use to schedule. Any object in any view will schedule regardless of on/off sheet palcement.
        column = math.floor( yLimit / count * yspace)   ##Get column based on placed elements and Y limit sizes
        x= column * xSpace                              ##Get x location
        y= ySpace * (count - (column * yCount))         ##Offset Y by column displacemnet 
        objPoint = Point.ByCoordinates(x, y, 0)
        
        ##for this case we assume it will only be in ONE view...
        ##if objAnno in AllGenAnnosInView then
            ##objCurrent = objanno
            ##Move obj anno to objPoint 
        ##ELSE  ##Create a new object at the position
            ##objCurrent = doc.FamilyCreate.NewFamilyInstance(objPoint,objAnnoType,viewView)
       
        count +=1                                       ##increment counter

def Lambda_Natural_sort(l):                             ##Slow 50x but effective ##ORiginal function doesn't work with Objects.
    convert = lambda text: int(text) if text.isdigit() else text.lower() 
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(l, key = alphanum_key)

def Lambda_GenAnno_Sort(lstObj): 

    convert = lambda text: int(text) if text.isdigit() else text.lower() 
    
    alphanum_key = lambda i: [ convert(c) for c in re.split('(\d+)', i) ]
    
    ##GenAnnoTypeName##https://forum.dynamobim.com/t/python-script-to-get-type-name-from-an-element/63306/2
    GAstrName = lambda j: [ alphanum_key(c) for c in doc.GetElement(j.Id).get_Parameter(BuiltInParameter.ALL_MODEL_TYPE_NAME).AsString()]
        
    return sorted(lstObj, key = GAstrName)

    
#######################################################################################
def Main():  ###Entry point to procedures
    t = Transaction (doc, 'Create Drafting View')
    t.Start()
    
    GenAnno = []
    ##GenAnnoDict = System.Collections.Generic.Dictionary[System.String,System.Object]()
    GenAnno = GetGenericAnnotationTypes()   ##LIST of FAMILY TYPE of GENANNO 

    GenAnno = Lambda_GenAnno_Sort(GenAnno)                     ##Sort in natural order- numbers as numbers text as text
    #GenAnno = natural_sortGenAnnos(GenAnno)
    t.Commit()
    

    return GenAnno 

############################################################################################
############################################################################################
OUT=Main () ##Entry point to run all scripts
############################################################################################
############################################################################################

@jacob.small @jacob.small there appears to be a BUG in Revit Dynamo Python R22 V 2.7. In trying to use the functions to get GenericAnnotations then sort:

def Lambda_GenAnno_Sort(lstObj): 

    convert = lambda text: int(text) if text.isdigit() else text.lower() 
    
    alphanum_key = lambda i: [ convert(c) for c in re.split('(\d+)', i) ]
    
    ##GenAnnoTypeName##https://forum.dynamobim.com/t/python-script-to-get-type-name-from-an-element/63306/2
    GAstrName = lambda j: [ alphanum_key(c) for c in doc.GetElement(j.Id).get_Parameter(BuiltInParameter.ALL_MODEL_TYPE_NAME).AsString()]
        
    return sorted(lstObj, key = GAstrName)

“Name” is inaccessible for Generic Annotation family types…

I was able to work around the issue:

GAstrName = lambda j: [ alphanum_key(c) for c in doc.GetElement(j.Id).get_Parameter(BuiltInParameter.ALL_MODEL_TYPE_NAME).AsString()]

Is the non-existent GenAnnoType.Name a bug? If so where do I report it?

I don’t see where your code change is, but it’s likely an issue with the configuration of your python environment, not Revit or Dynamo.

Hello,
It’s an inheritance issue in IronPython and PythonNet (fixed for both in future versions)
see this article

2 Likes

You can use Element.Name.GetValue(elemType). It solves that problem with Name property of ElementType.

Yep. I took out the design script references and that fixed that issue.

Added this tweak I found form StackExchange:

def Lambda_GenAnno_Sort_w_fam_name(lstObj):

    ConvertText = lambda ConvertText: int(ConvertText) if ConvertText.isdigit() else ConvertText.lower()

    alphanum_key = lambda i: [ ConvertText(c) for c in re.split('(\d+)', i) ]

    ##GenAnnoTypeName##https://forum.dynamobim.com/t/python-script-to-get-type-name-from-an-element/63306/2
    GAstrName = lambda j: [ alphanum_key(c) for c in (j.Family.Name + GetGAName(j))]

    return sorted(lstObj, key = GAstrName)

@Deniz_Maral thanks for the suggestion - I found yet another way to get around the .Name not working:


def GetGAName(GA):  ##GenericAnnotationFamilyType".Name"
    return doc.GetElement(GA.Id).get_Parameter(BuiltInParameter.ALL_MODEL_TYPE_NAME).AsString()
1 Like