2121# This can be packaged with `pyinstaller --onefile --clean --icon=icon.ico ifccsv.py`
2222
2323import os
24+ import re
2425import csv
2526import argparse
2627import ifcopenshell
@@ -70,20 +71,23 @@ def export(
7071 null = "-" ,
7172 bool_true = "YES" ,
7273 bool_false = "NO" ,
74+ sort = None ,
7375 ):
7476 self .ifc_file = ifc_file
7577 self .results = []
78+ self .headers = []
79+ attributes = attributes or []
80+
7681 if not headers :
7782 headers = [None ] * len (attributes )
83+
84+ if include_global_id :
85+ attributes .insert (0 , "GlobalId" )
86+ headers .insert (0 , "GlobalId" )
87+
7888 for element in elements :
7989 result = []
80- if include_global_id :
81- if hasattr (element , "GlobalId" ):
82- result .append (element .GlobalId )
83- else :
84- result .append (None )
85-
86- for index , attribute in enumerate (attributes or []):
90+ for index , attribute in enumerate (attributes ):
8791 if "*" in attribute :
8892 attributes .extend (self .get_wildcard_attributes (attribute ))
8993 del attributes [index ]
@@ -99,13 +103,29 @@ def export(
99103 result .append (value )
100104 self .results .append (result )
101105
102- self .headers = ["GlobalId" ] if include_global_id else [ ]
103- for i , attribute in enumerate (attributes or [] ):
106+ self .headers = []
107+ for i , attribute in enumerate (attributes ):
104108 if headers [i ]:
105109 self .headers .append (headers [i ])
106110 else :
107111 self .headers .append (attribute )
108112
113+ if sort :
114+ def natural_sort (value ):
115+ if isinstance (value , str ):
116+ convert = lambda text : int (text ) if text .isdigit () else text .lower ()
117+ return [convert (c ) for c in re .split ('([0-9]+)' , value )]
118+ return value
119+
120+ # Sort least important keys first, then more important keys.
121+ # https://stackoverflow.com/questions/11476371/sort-by-multiple-keys-using-different-orderings
122+ for sort_data in reversed (sort ):
123+ i = attributes .index (sort_data ["name" ])
124+ reverse = sort_data ["order" ] == "DESC"
125+ self .results = sorted (self .results , key = lambda x : natural_sort (x [i ]), reverse = reverse )
126+ else :
127+ self .results = sorted (self .results , key = lambda x : x [1 if include_global_id else 0 ])
128+
109129 if format == "csv" :
110130 self .export_csv (output , delimiter = delimiter )
111131 elif format == "ods" :
@@ -319,18 +339,20 @@ def process_row(self, ifc_file, row, headers, attributes, null, bool_true, bool_
319339 help = "Specify attributes that are part of the extract, using the IfcQuery syntax such as 'class', 'Name' or 'Pset_Foo.Bar'" ,
320340 )
321341 parser .add_argument (
322- "-h" ,
323- "--headers" ,
324- nargs = "+" ,
325- help = "Specify human readable headers that correlate to each attribute." ,
342+ "-h" , "--headers" , nargs = "+" , help = "Specify human readable headers that correlate to each attribute."
326343 )
327- parser .add_argument ("--export" , action = "store_true" , help = "Export from IFC to CSV" )
328- parser .add_argument ("--import" , action = "store_true" , help = "Import from CSV to IFC" )
344+ parser .add_argument ("--sort" , nargs = "+" , help = "Specify one or more attributes to sort by." )
345+ parser .add_argument ("--order" , nargs = "+" , help = "Choose the sort order from ASC or DESC for each sorted attribute." )
346+ parser .add_argument ("--export" , action = "store_true" , help = "Export from IFC to the desired format." )
347+ parser .add_argument ("--import" , action = "store_true" , help = "Import from the autodetected format to IFC." )
329348 args = parser .parse_args ()
330349
331350 if args .export :
332351 ifc_file = ifcopenshell .open (args .ifc )
333352 results = ifcopenshell .util .selector .filter_elements (ifc_file , args .query )
353+ sort = None
354+ if args .sort and len (args .sort ) == len (args .order ):
355+ sort = [{"name" : s , "order" : args .order [i ]} for i , s in enumerate (args .sort )]
334356 ifc_csv = IfcCsv ()
335357 ifc_csv .export (
336358 ifc_file ,
@@ -343,6 +365,7 @@ def process_row(self, ifc_file, row, headers, attributes, null, bool_true, bool_
343365 null = args .null ,
344366 bool_true = args .bool_true ,
345367 bool_false = args .bool_false ,
368+ sort = sort ,
346369 )
347370 elif getattr (args , "import" ):
348371 ifc_csv = IfcCsv ()
0 commit comments