I’m sorry, I got confused with pyrevitlib facilities for retrieving the parameters, you’re already doing it the right way. (I’ve developed a shortcut in my own extension and I always think it is part of the pyrevit library )
What I meant for using dictionaries was to drop all the lists and having a single one with the name and values of the parameters read.
The following could be hard to read at first but bear with me…
from pyrevit.revit.db.query import get_elements_by_categories
from collections import Counter
# by convention, constants in python are SNAKE_UPPERCASE
#Shared parameter code circuit
CODE_CIR = Guid(r'55934d0c-0246-4ce2-9bdf-57ed4244e11b')
#Shared parameter FMF_Angle
ANGLE = Guid(r'a8b84336-4f16-462c-a50f-f0f8b2e4f7c2')
### DA : Création d'un BOM de DUCT ACCESSORIES sous forme de liste de tuple
# using pyrevit library to simplify element collection
# by convention, python variables are snake_case
das = get_elements_by_categories([BuiltInCategory.OST_DuctAccessory], doc=doc)
#Créer des listes vides
duct_accessories = []
for da in das:
da_type = doc.GetElement(da.GetTypeId())
# no need to create a separate functrion/loop to set _N/A on empty or null string
code_circuit = da.get_Parameter(CODE_CIR).AsString() or "_N/A"
family_name = da_type.get_Parameter(BuiltInParameter.SYMBOL_FAMILY_NAME_PARAM).AsString()
description = da_type.get_Parameter(BuiltInParameter.ALL_MODEL_DESCRIPTION).AsString()
size = da.get_Parameter(BuiltInParameter.RBS_CALCULATED_SIZE).AsString()
duct_accessories.append(
{
"code_circuit": code_circuit,
"family_name": family_name,
"description": description,
"size": size,
}
)
counter = Counter((tuple(i.items() for i in duct_accessories))
lst_da = sorted(
({**dict(itm), "count": count} for itm, count in counter.items()),
key=lambda x: x["count"]
)
The last 5 lines create a sorted list of the unique duct accessories dictionaries, eliminating the need to create a list with the code and the concatenation of the other fields, count the items one by one and then removing the duplicates.
There’s the need transform the dict to tuple and back because of how the Counter works; we could have created a tuple instead of a dictionary in the loop, but if we keep it like this we can extract these last line to create a generic count
function that can be reused for any list of dictionaries (like the elements in the rest of the code)
def count(records):
counter = Counter((tuple(r.items() for r in records))
lst_da = sorted(
({**dict(itm), "count": count} for itm, count in counter.items()),
key=lambda x: x["count"]
)
you can also extract the element parameter reading into its own function
def read_da_parameters(da):
da_type = doc.GetElement(da.GetTypeId())
# no need to create a separate functrion/loop to set _N/A on empty or null string
code_circuit = da.get_Parameter(CODE_CIR).AsString() or "_N/A"
family_name = da_type.get_Parameter(BuiltInParameter.SYMBOL_FAMILY_NAME_PARAM).AsString()
description = da_type.get_Parameter(BuiltInParameter.ALL_MODEL_DESCRIPTION).AsString()
size = da.get_Parameter(BuiltInParameter.RBS_CALCULATED_SIZE).AsString()
return {
"code_circuit": code_circuit,
"family_name": family_name,
"description": description,
"size": size,
}
and then the main code would look like
das = get_elements_by_categories([BuiltInCategory.OST_DuctAccessory], doc=doc)
duct_accessories = count((read_da_parameters(da) for da in das))
You then only need to create the read_xx_parameters
for the other types and repeat these 2 lines with the right category and function (you could also parametrize those inputs to reach the ultimate DRY-Don’t Repeat Yourself, but that’s another story…).
Also note that I used generators wherever I could to avoid wasting memory with intermediate lists, since all these sequences are read only once.
The usual disclaimer is: I didn’t fully test the code, there might be some (many) errors in there…