mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-30 16:43:00 +00:00
New support for sorting IfcCSV outputs
This commit is contained in:
@@ -76,10 +76,11 @@ class ImportCsvAttributes(bpy.types.Operator):
|
|||||||
tool.Search.import_filter_query(data["query"], props.filter_groups)
|
tool.Search.import_filter_query(data["query"], props.filter_groups)
|
||||||
|
|
||||||
props.csv_attributes.clear()
|
props.csv_attributes.clear()
|
||||||
for i, attribute in enumerate(data["attributes"]):
|
for attribute in data["attributes"]:
|
||||||
new = props.csv_attributes.add()
|
new = props.csv_attributes.add()
|
||||||
new.name = attribute
|
new.name = attribute["name"]
|
||||||
new.header = data["headers"][i]
|
new.header = attribute["header"]
|
||||||
|
new.sort = attribute["sort"]
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
@@ -101,8 +102,7 @@ class ExportCsvAttributes(bpy.types.Operator):
|
|||||||
|
|
||||||
data = {
|
data = {
|
||||||
"query": tool.Search.export_filter_query(props.filter_groups),
|
"query": tool.Search.export_filter_query(props.filter_groups),
|
||||||
"attributes": [a.name for a in props.csv_attributes],
|
"attributes": [{"name": a.name, "header": a.header, "sort": a.sort} for a in props.csv_attributes],
|
||||||
"headers": [a.header for a in props.csv_attributes],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
with open(self.filepath, "w") as outfile:
|
with open(self.filepath, "w") as outfile:
|
||||||
@@ -146,6 +146,12 @@ class ExportIfcCsv(bpy.types.Operator):
|
|||||||
ifc_csv = ifccsv.IfcCsv()
|
ifc_csv = ifccsv.IfcCsv()
|
||||||
attributes = [a.name for a in props.csv_attributes]
|
attributes = [a.name for a in props.csv_attributes]
|
||||||
headers = [a.header for a in props.csv_attributes]
|
headers = [a.header for a in props.csv_attributes]
|
||||||
|
|
||||||
|
sort = []
|
||||||
|
for attribute in props.csv_attributes:
|
||||||
|
if attribute.sort != "NONE":
|
||||||
|
sort.append({"name": attribute.name, "order": attribute.sort})
|
||||||
|
|
||||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||||
ifc_csv.export(
|
ifc_csv.export(
|
||||||
ifc_file,
|
ifc_file,
|
||||||
@@ -160,6 +166,7 @@ class ExportIfcCsv(bpy.types.Operator):
|
|||||||
null=props.null_value,
|
null=props.null_value,
|
||||||
bool_true=props.true_value,
|
bool_true=props.true_value,
|
||||||
bool_false=props.false_value,
|
bool_false=props.false_value,
|
||||||
|
sort=sort,
|
||||||
)
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ from bpy.props import (
|
|||||||
class CsvAttribute(PropertyGroup):
|
class CsvAttribute(PropertyGroup):
|
||||||
name: StringProperty(name="Query", default="class")
|
name: StringProperty(name="Query", default="class")
|
||||||
header: StringProperty(name="Header Value", default="IFC Class")
|
header: StringProperty(name="Header Value", default="IFC Class")
|
||||||
|
sort: EnumProperty(items=[("NONE", "None", ""), ("ASC", "Ascending", ""), ("DESC", "Descending", "")])
|
||||||
|
|
||||||
|
|
||||||
class CsvProperties(PropertyGroup):
|
class CsvProperties(PropertyGroup):
|
||||||
@@ -80,4 +81,5 @@ class CsvProperties(PropertyGroup):
|
|||||||
)
|
)
|
||||||
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
|
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
|
||||||
should_show_settings: BoolProperty(default=False, name="Show Settings")
|
should_show_settings: BoolProperty(default=False, name="Show Settings")
|
||||||
|
should_show_sort: BoolProperty(default=False, name="Show Sort")
|
||||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||||
|
|||||||
@@ -42,13 +42,13 @@ class BIM_PT_ifccsv(Panel):
|
|||||||
row.prop(props, "should_load_from_memory")
|
row.prop(props, "should_load_from_memory")
|
||||||
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
|
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
|
||||||
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
|
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
|
||||||
row.prop(props, "should_show_settings", icon="PREFERENCES", text="")
|
|
||||||
else:
|
else:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.alignment = "RIGHT"
|
row.alignment = "RIGHT"
|
||||||
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
|
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
|
||||||
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
|
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
|
||||||
row.prop(props, "should_show_settings", icon="PREFERENCES", text="")
|
row.prop(props, "should_show_sort", icon="SORTSIZE", text="")
|
||||||
|
row.prop(props, "should_show_settings", icon="PREFERENCES", text="")
|
||||||
|
|
||||||
if not IfcStore.get_file() or not props.should_load_from_memory:
|
if not IfcStore.get_file() or not props.should_load_from_memory:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
@@ -88,7 +88,10 @@ class BIM_PT_ifccsv(Panel):
|
|||||||
for index, attribute in enumerate(props.csv_attributes):
|
for index, attribute in enumerate(props.csv_attributes):
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.prop(attribute, "name", text="")
|
row.prop(attribute, "name", text="")
|
||||||
row.prop(attribute, "header", text="")
|
if props.should_show_sort:
|
||||||
|
row.prop(attribute, "sort", text="")
|
||||||
|
else:
|
||||||
|
row.prop(attribute, "header", text="")
|
||||||
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
|
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
|
|||||||
+38
-15
@@ -21,6 +21,7 @@
|
|||||||
# This can be packaged with `pyinstaller --onefile --clean --icon=icon.ico ifccsv.py`
|
# This can be packaged with `pyinstaller --onefile --clean --icon=icon.ico ifccsv.py`
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import csv
|
import csv
|
||||||
import argparse
|
import argparse
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
@@ -70,20 +71,23 @@ class IfcCsv:
|
|||||||
null="-",
|
null="-",
|
||||||
bool_true="YES",
|
bool_true="YES",
|
||||||
bool_false="NO",
|
bool_false="NO",
|
||||||
|
sort=None,
|
||||||
):
|
):
|
||||||
self.ifc_file = ifc_file
|
self.ifc_file = ifc_file
|
||||||
self.results = []
|
self.results = []
|
||||||
|
self.headers = []
|
||||||
|
attributes = attributes or []
|
||||||
|
|
||||||
if not headers:
|
if not headers:
|
||||||
headers = [None] * len(attributes)
|
headers = [None] * len(attributes)
|
||||||
|
|
||||||
|
if include_global_id:
|
||||||
|
attributes.insert(0, "GlobalId")
|
||||||
|
headers.insert(0, "GlobalId")
|
||||||
|
|
||||||
for element in elements:
|
for element in elements:
|
||||||
result = []
|
result = []
|
||||||
if include_global_id:
|
for index, attribute in enumerate(attributes):
|
||||||
if hasattr(element, "GlobalId"):
|
|
||||||
result.append(element.GlobalId)
|
|
||||||
else:
|
|
||||||
result.append(None)
|
|
||||||
|
|
||||||
for index, attribute in enumerate(attributes or []):
|
|
||||||
if "*" in attribute:
|
if "*" in attribute:
|
||||||
attributes.extend(self.get_wildcard_attributes(attribute))
|
attributes.extend(self.get_wildcard_attributes(attribute))
|
||||||
del attributes[index]
|
del attributes[index]
|
||||||
@@ -99,13 +103,29 @@ class IfcCsv:
|
|||||||
result.append(value)
|
result.append(value)
|
||||||
self.results.append(result)
|
self.results.append(result)
|
||||||
|
|
||||||
self.headers = ["GlobalId"] if include_global_id else []
|
self.headers = []
|
||||||
for i, attribute in enumerate(attributes or []):
|
for i, attribute in enumerate(attributes):
|
||||||
if headers[i]:
|
if headers[i]:
|
||||||
self.headers.append(headers[i])
|
self.headers.append(headers[i])
|
||||||
else:
|
else:
|
||||||
self.headers.append(attribute)
|
self.headers.append(attribute)
|
||||||
|
|
||||||
|
if sort:
|
||||||
|
def natural_sort(value):
|
||||||
|
if isinstance(value, str):
|
||||||
|
convert = lambda text: int(text) if text.isdigit() else text.lower()
|
||||||
|
return [convert(c) for c in re.split('([0-9]+)', value)]
|
||||||
|
return value
|
||||||
|
|
||||||
|
# Sort least important keys first, then more important keys.
|
||||||
|
# https://stackoverflow.com/questions/11476371/sort-by-multiple-keys-using-different-orderings
|
||||||
|
for sort_data in reversed(sort):
|
||||||
|
i = attributes.index(sort_data["name"])
|
||||||
|
reverse = sort_data["order"] == "DESC"
|
||||||
|
self.results = sorted(self.results, key=lambda x: natural_sort(x[i]), reverse=reverse)
|
||||||
|
else:
|
||||||
|
self.results = sorted(self.results, key=lambda x: x[1 if include_global_id else 0])
|
||||||
|
|
||||||
if format == "csv":
|
if format == "csv":
|
||||||
self.export_csv(output, delimiter=delimiter)
|
self.export_csv(output, delimiter=delimiter)
|
||||||
elif format == "ods":
|
elif format == "ods":
|
||||||
@@ -319,18 +339,20 @@ if __name__ == "__main__":
|
|||||||
help="Specify attributes that are part of the extract, using the IfcQuery syntax such as 'class', 'Name' or 'Pset_Foo.Bar'",
|
help="Specify attributes that are part of the extract, using the IfcQuery syntax such as 'class', 'Name' or 'Pset_Foo.Bar'",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-h",
|
"-h", "--headers", nargs="+", help="Specify human readable headers that correlate to each attribute."
|
||||||
"--headers",
|
|
||||||
nargs="+",
|
|
||||||
help="Specify human readable headers that correlate to each attribute.",
|
|
||||||
)
|
)
|
||||||
parser.add_argument("--export", action="store_true", help="Export from IFC to CSV")
|
parser.add_argument("--sort", nargs="+", help="Specify one or more attributes to sort by.")
|
||||||
parser.add_argument("--import", action="store_true", help="Import from CSV to IFC")
|
parser.add_argument("--order", nargs="+", help="Choose the sort order from ASC or DESC for each sorted attribute.")
|
||||||
|
parser.add_argument("--export", action="store_true", help="Export from IFC to the desired format.")
|
||||||
|
parser.add_argument("--import", action="store_true", help="Import from the autodetected format to IFC.")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.export:
|
if args.export:
|
||||||
ifc_file = ifcopenshell.open(args.ifc)
|
ifc_file = ifcopenshell.open(args.ifc)
|
||||||
results = ifcopenshell.util.selector.filter_elements(ifc_file, args.query)
|
results = ifcopenshell.util.selector.filter_elements(ifc_file, args.query)
|
||||||
|
sort = None
|
||||||
|
if args.sort and len(args.sort) == len(args.order):
|
||||||
|
sort = [{"name": s, "order": args.order[i]} for i, s in enumerate(args.sort)]
|
||||||
ifc_csv = IfcCsv()
|
ifc_csv = IfcCsv()
|
||||||
ifc_csv.export(
|
ifc_csv.export(
|
||||||
ifc_file,
|
ifc_file,
|
||||||
@@ -343,6 +365,7 @@ if __name__ == "__main__":
|
|||||||
null=args.null,
|
null=args.null,
|
||||||
bool_true=args.bool_true,
|
bool_true=args.bool_true,
|
||||||
bool_false=args.bool_false,
|
bool_false=args.bool_false,
|
||||||
|
sort=sort,
|
||||||
)
|
)
|
||||||
elif getattr(args, "import"):
|
elif getattr(args, "import"):
|
||||||
ifc_csv = IfcCsv()
|
ifc_csv = IfcCsv()
|
||||||
|
|||||||
Reference in New Issue
Block a user