Skip to content

Commit 5dd2b5e

Browse files
committed
New support for sorting IfcCSV outputs
1 parent 37dcb92 commit 5dd2b5e

4 files changed

Lines changed: 58 additions & 23 deletions

File tree

src/blenderbim/blenderbim/bim/module/csv/operator.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@ def execute(self, context):
7676
tool.Search.import_filter_query(data["query"], props.filter_groups)
7777

7878
props.csv_attributes.clear()
79-
for i, attribute in enumerate(data["attributes"]):
79+
for attribute in data["attributes"]:
8080
new = props.csv_attributes.add()
81-
new.name = attribute
82-
new.header = data["headers"][i]
81+
new.name = attribute["name"]
82+
new.header = attribute["header"]
83+
new.sort = attribute["sort"]
8384
return {"FINISHED"}
8485

8586
def invoke(self, context, event):
@@ -101,8 +102,7 @@ def execute(self, context):
101102

102103
data = {
103104
"query": tool.Search.export_filter_query(props.filter_groups),
104-
"attributes": [a.name for a in props.csv_attributes],
105-
"headers": [a.header for a in props.csv_attributes],
105+
"attributes": [{"name": a.name, "header": a.header, "sort": a.sort} for a in props.csv_attributes],
106106
}
107107

108108
with open(self.filepath, "w") as outfile:
@@ -146,6 +146,12 @@ def execute(self, context):
146146
ifc_csv = ifccsv.IfcCsv()
147147
attributes = [a.name for a in props.csv_attributes]
148148
headers = [a.header for a in props.csv_attributes]
149+
150+
sort = []
151+
for attribute in props.csv_attributes:
152+
if attribute.sort != "NONE":
153+
sort.append({"name": attribute.name, "order": attribute.sort})
154+
149155
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
150156
ifc_csv.export(
151157
ifc_file,
@@ -160,6 +166,7 @@ def execute(self, context):
160166
null=props.null_value,
161167
bool_true=props.true_value,
162168
bool_false=props.false_value,
169+
sort=sort,
163170
)
164171
return {"FINISHED"}
165172

src/blenderbim/blenderbim/bim/module/csv/prop.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
class CsvAttribute(PropertyGroup):
3636
name: StringProperty(name="Query", default="class")
3737
header: StringProperty(name="Header Value", default="IFC Class")
38+
sort: EnumProperty(items=[("NONE", "None", ""), ("ASC", "Ascending", ""), ("DESC", "Descending", "")])
3839

3940

4041
class CsvProperties(PropertyGroup):
@@ -80,4 +81,5 @@ class CsvProperties(PropertyGroup):
8081
)
8182
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
8283
should_show_settings: BoolProperty(default=False, name="Show Settings")
84+
should_show_sort: BoolProperty(default=False, name="Show Sort")
8385
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")

src/blenderbim/blenderbim/bim/module/csv/ui.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ def draw(self, context):
4242
row.prop(props, "should_load_from_memory")
4343
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
4444
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
45-
row.prop(props, "should_show_settings", icon="PREFERENCES", text="")
4645
else:
4746
row = layout.row(align=True)
4847
row.alignment = "RIGHT"
4948
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
5049
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
51-
row.prop(props, "should_show_settings", icon="PREFERENCES", text="")
50+
row.prop(props, "should_show_sort", icon="SORTSIZE", text="")
51+
row.prop(props, "should_show_settings", icon="PREFERENCES", text="")
5252

5353
if not IfcStore.get_file() or not props.should_load_from_memory:
5454
row = layout.row(align=True)
@@ -88,7 +88,10 @@ def draw(self, context):
8888
for index, attribute in enumerate(props.csv_attributes):
8989
row = layout.row(align=True)
9090
row.prop(attribute, "name", text="")
91-
row.prop(attribute, "header", text="")
91+
if props.should_show_sort:
92+
row.prop(attribute, "sort", text="")
93+
else:
94+
row.prop(attribute, "header", text="")
9295
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
9396

9497
row = layout.row(align=True)

src/ifccsv/ifccsv.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
# This can be packaged with `pyinstaller --onefile --clean --icon=icon.ico ifccsv.py`
2222

2323
import os
24+
import re
2425
import csv
2526
import argparse
2627
import 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

Comments
 (0)