From 4a6ec11f6f84a3d1fed2ee9b2f85d783ac3daa9d Mon Sep 17 00:00:00 2001 From: htlcnn Date: Sun, 1 Nov 2020 19:23:50 +0700 Subject: [PATCH] black ifccsv, ifcdiff --- src/ifccsv/ifccsv.py | 111 +++++++++++---------------- src/ifcdiff/ifcdiff.py | 170 ++++++++++++++++++++++++----------------- 2 files changed, 148 insertions(+), 133 deletions(-) diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index 16351def04..68bb7bcd21 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -8,23 +8,24 @@ import csv import lark import argparse -class IfcAttributeExtractor(): + +class IfcAttributeExtractor: @staticmethod def set_element_key(ifc_file, element, key, value): - if key == 'type' and element.is_a() != value: + if key == "type" and element.is_a() != value: return IfcAttributeExtractor.change_ifc_class(ifc_file, element, value) if hasattr(element, key): setattr(element, key, value) return element - if '.' not in key: + if "." not in key: return element - if key[0:3] == 'Qto': - qto, prop = key.split('.', 1) + if key[0:3] == "Qto": + qto, prop = key.split(".", 1) qto = IfcAttributeExtractor.get_element_qto(element, qto_name) if qto: IfcAttributeExtractor.set_qto_property(qto, prop, value) return element - pset_name, prop = key.split('.', 1) + pset_name, prop = key.split(".", 1) pset = IfcAttributeExtractor.get_element_pset(element, pset_name) if pset: IfcAttributeExtractor.set_pset_property(pset, prop, value) @@ -51,9 +52,11 @@ class IfcAttributeExtractor(): @staticmethod def get_element_qto(element, name): for relationship in element.IsDefinedBy: - if relationship.is_a('IfcRelDefinesByProperties') \ - and relationship.RelatingPropertyDefinition.is_a('IfcElementQuantity') \ - and relationship.RelatingPropertyDefinition.Name == name: + if ( + relationship.is_a("IfcRelDefinesByProperties") + and relationship.RelatingPropertyDefinition.is_a("IfcElementQuantity") + and relationship.RelatingPropertyDefinition.Name == name + ): return relationship.RelatingPropertyDefinition @staticmethod @@ -61,21 +64,22 @@ class IfcAttributeExtractor(): for prop in qto.Quantities: if prop.Name != name: continue - setattr(prop, prop.is_a()[len('IfcQuantity'):] + 'Value', value) + setattr(prop, prop.is_a()[len("IfcQuantity") :] + "Value", value) @staticmethod def get_element_pset(element, name): - if element.is_a('IfcTypeObject'): + if element.is_a("IfcTypeObject"): if element.HasPropertySets: for pset in element.HasPropertySets: - if pset.is_a('IfcPropertySet') \ - and pset.Name == name: + if pset.is_a("IfcPropertySet") and pset.Name == name: return pset else: for relationship in element.IsDefinedBy: - if relationship.is_a('IfcRelDefinesByProperties') \ - and relationship.RelatingPropertyDefinition.is_a('IfcPropertySet') \ - and relationship.RelatingPropertyDefinition.Name == name: + if ( + relationship.is_a("IfcRelDefinesByProperties") + and relationship.RelatingPropertyDefinition.is_a("IfcPropertySet") + and relationship.RelatingPropertyDefinition.Name == name + ): return relationship.RelatingPropertyDefinition @staticmethod @@ -93,37 +97,39 @@ class IfcAttributeExtractor(): try: property.NominalValue.wrappedValue = int(value) except: - property.NominalValue.wrappedValue = True if value.lower() in ['1', 't', 'true', 'yes', 'y', 'uh-huh'] else False + property.NominalValue.wrappedValue = ( + True if value.lower() in ["1", "t", "true", "yes", "y", "uh-huh"] else False + ) -class IfcCsv(): +class IfcCsv: def __init__(self): self.results = [] self.attributes = [] - self.output = '' + self.output = "" self.selector = None def export(self, ifc_file, elements): self.ifc_file = ifc_file for element in elements: result = [] - if hasattr(element, 'GlobalId'): + if hasattr(element, "GlobalId"): result.append(element.GlobalId) else: result.append(None) for index, attribute in enumerate(self.attributes): - if '*' in attribute: + if "*" in attribute: self.attributes.extend(self.get_wildcard_attributes(attribute)) - del(self.attributes[index]) + del self.attributes[index] for attribute in self.attributes: result.append(self.selector.get_element_value(element, attribute)) self.results.append(result) - with open(self.output, 'w', newline='', encoding='utf-8') as f: + with open(self.output, "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) - header = ['GlobalId'] + header = ["GlobalId"] header.extend(self.attributes) writer.writerow(header) for row in self.results: @@ -131,19 +137,19 @@ class IfcCsv(): def get_wildcard_attributes(self, attribute): results = set() - pset_qto_name = attribute.split('.', 1)[0] - for element in self.ifc_file.by_type('IfcPropertySet') + self.ifc_file.by_type('IfcElementQuantity'): + pset_qto_name = attribute.split(".", 1)[0] + for element in self.ifc_file.by_type("IfcPropertySet") + self.ifc_file.by_type("IfcElementQuantity"): if element.Name != pset_qto_name: continue - if element.is_a('IfcPropertySet'): + if element.is_a("IfcPropertySet"): results.update([p.Name for p in element.HasProperties]) else: results.update([p.Name for p in element.Quantities]) - return ['{}.{}'.format(pset_qto_name, n) for n in results] + return ["{}.{}".format(pset_qto_name, n) for n in results] def Import(self, ifc): ifc_file = ifcopenshell.open(ifc) - with open(self.output, newline='', encoding='utf-8') as f: + with open(self.output, newline="", encoding="utf-8") as f: reader = csv.reader(f) headers = [] for row in reader: @@ -155,44 +161,19 @@ class IfcCsv(): continue for i, value in enumerate(row): if i == 0: - continue # Skip GlobalId + continue # Skip GlobalId element = IfcAttributeExtractor.set_element_key(ifc_file, element, headers[i], value) ifc_file.write(ifc) -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description='Exports IFC data to and from CSV') - parser.add_argument( - '-i', - '--ifc', - type=str, - required=True, - help='The IFC file') - parser.add_argument( - '-c', - '--csv', - type=str, - default='data.csv', - help='The CSV file to import from or export to') - parser.add_argument( - '-q', - '--query', - type=str, - default='', - help='Specify a IFC query selector, such as ".IfcWall"') - parser.add_argument( - '-a', - '--arguments', - nargs='+', - help='Specify attributes that are part of the extract') - parser.add_argument( - '--export', - action='store_true', - help='Export from IFC to CSV') - parser.add_argument( - '--import', - action='store_true', - help='Import from CSV to IFC') + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Exports IFC data to and from CSV") + parser.add_argument("-i", "--ifc", type=str, required=True, help="The IFC file") + parser.add_argument("-c", "--csv", type=str, default="data.csv", help="The CSV file to import from or export to") + parser.add_argument("-q", "--query", type=str, default="", help='Specify a IFC query selector, such as ".IfcWall"') + parser.add_argument("-a", "--arguments", nargs="+", help="Specify attributes that are part of the extract") + parser.add_argument("--export", action="store_true", help="Export from IFC to CSV") + parser.add_argument("--import", action="store_true", help="Import from CSV to IFC") args = parser.parse_args() if args.export: @@ -204,7 +185,7 @@ if __name__ == '__main__': ifc_csv.attributes = args.arguments if args.arguments else [] ifc_csv.selector = selector ifc_csv.export(ifc_file, results) - elif getattr(args, 'import'): + elif getattr(args, "import"): ifc_csv = IfcCsv() ifc_csv.output = args.csv ifc_csv.Import(args.ifc) diff --git a/src/ifcdiff/ifcdiff.py b/src/ifcdiff/ifcdiff.py index 193c72782d..d6af516af2 100755 --- a/src/ifcdiff/ifcdiff.py +++ b/src/ifcdiff/ifcdiff.py @@ -9,7 +9,7 @@ import argparse import decimal -class IfcDiff(): +class IfcDiff: def __init__(self, old_file, new_file, output_file, inverse_classes=None): self.old_file = old_file self.new_file = new_file @@ -20,29 +20,29 @@ class IfcDiff(): self.precision = 2 def diff(self): - print('# IFC Diff') + print("# IFC Diff") self.load() self.precision = self.get_precision() - old_elements = set(e.GlobalId for e in self.old.by_type('IfcProduct')) - new_elements = set(e.GlobalId for e in self.new.by_type('IfcProduct')) + old_elements = set(e.GlobalId for e in self.old.by_type("IfcProduct")) + new_elements = set(e.GlobalId for e in self.new.by_type("IfcProduct")) self.deleted_elements = old_elements - new_elements self.added_elements = new_elements - old_elements same_elements = new_elements - self.added_elements total_same_elements = len(same_elements) - print(' - {} item(s) were deleted'.format(len(self.deleted_elements))) - print(' - {} item(s) were added'.format(len(self.added_elements))) - print(' - {} item(s) were retained between the old and new IFC file'.format(total_same_elements)) + print(" - {} item(s) were deleted".format(len(self.deleted_elements))) + print(" - {} item(s) were added".format(len(self.added_elements))) + print(" - {} item(s) were retained between the old and new IFC file".format(total_same_elements)) start = time.time() total_diffed = 0 for global_id in same_elements: total_diffed += 1 - print('{}/{} diffed ...'.format(total_diffed, total_same_elements), end='\r', flush=True) + print("{}/{} diffed ...".format(total_diffed, total_same_elements), end="\r", flush=True) old_element = self.old.by_id(global_id) new_element = self.new.by_id(global_id) self.diff_element(old_element, new_element) @@ -54,30 +54,33 @@ class IfcDiff(): self.representation_ids.append(representation_id) self.diff_element_geometry(old_element, new_element) - print(' - {} item(s) were changed either geometrically or with data'.format( - len(self.change_register.keys()))) - print('# Diff finished in {:.2f} seconds'.format(time.time() - start)) + print(" - {} item(s) were changed either geometrically or with data".format(len(self.change_register.keys()))) + print("# Diff finished in {:.2f} seconds".format(time.time() - start)) def export(self): - with open(self.output_file, 'w', encoding='utf-8') as diff_file: - json.dump({ - 'added': list(self.added_elements), - 'deleted': list(self.deleted_elements), - 'changed': self.change_register, + with open(self.output_file, "w", encoding="utf-8") as diff_file: + json.dump( + { + "added": list(self.added_elements), + "deleted": list(self.deleted_elements), + "changed": self.change_register, }, diff_file, indent=4, - cls=DiffEncoder) + cls=DiffEncoder, + ) def load(self): - print('Loading old file ...') + print("Loading old file ...") self.old = ifcopenshell.open(self.old_file) - print('Loading new file ...') + print("Loading new file ...") self.new = ifcopenshell.open(self.new_file) def get_precision(self): try: - precision = [c for c in self.new.by_type('IfcGeometricRepresentationContext') if c.ContextType == 'Model'][0].Precision + precision = [c for c in self.new.by_type("IfcGeometricRepresentationContext") if c.ContextType == "Model"][ + 0 + ].Precision exponent = decimal.Decimal(str(precision)).as_tuple().exponent if exponent < 0: return abs(exponent) @@ -86,14 +89,19 @@ class IfcDiff(): return 2 def diff_element(self, old_element, new_element): - diff = DeepDiff(old_element, new_element, - significant_digits=self.precision, ignore_string_type_changes=True, ignore_numeric_type_changes=True, + diff = DeepDiff( + old_element, + new_element, + significant_digits=self.precision, + ignore_string_type_changes=True, + ignore_numeric_type_changes=True, exclude_regex_paths=[ - r'root.*id$', - r'.*Representation.*', - r'.*OwnerHistory.*', - r'.*ObjectPlacement.*', - ]) + r"root.*id$", + r".*Representation.*", + r".*OwnerHistory.*", + r".*ObjectPlacement.*", + ], + ) if diff and new_element.GlobalId: self.change_register.setdefault(new_element.GlobalId, {}).update(diff) @@ -102,60 +110,87 @@ class IfcDiff(): return old_relationships_all = self.old.get_inverse(old_element) new_relationships_all = self.new.get_inverse(new_element) - if self.inverse_classes[0] == 'all': + if self.inverse_classes[0] == "all": old_relationships = old_relationships_all new_relationships = new_relationships_all else: old_relationships = [x for x in old_relationships_all if x.is_a() in self.inverse_classes] new_relationships = [x for x in new_relationships_all if x.is_a() in self.inverse_classes] - diff = DeepDiff(old_relationships, new_relationships, - significant_digits=self.precision, ignore_string_type_changes=True, ignore_numeric_type_changes=True, + diff = DeepDiff( + old_relationships, + new_relationships, + significant_digits=self.precision, + ignore_string_type_changes=True, + ignore_numeric_type_changes=True, exclude_regex_paths=[ - r'root.*id$', - r'.*GlobalId.*', - r'.*OwnerHistory.*', - r'.*RelatedObjects.*', - r'.*RelatingObject.*', - r'.*RelatingDefinitions.*', - r'.*RelatedObjectsType.*', # Deprecated in IFC4 anyway - ]) + r"root.*id$", + r".*GlobalId.*", + r".*OwnerHistory.*", + r".*RelatedObjects.*", + r".*RelatingObject.*", + r".*RelatingDefinitions.*", + r".*RelatedObjectsType.*", # Deprecated in IFC4 anyway + ], + ) if diff and new_element.GlobalId: self.change_register.setdefault(new_element.GlobalId, {}).update(diff) def diff_element_geometry(self, old_element, new_element): try: - DeepDiff(old_element.ObjectPlacement, new_element.ObjectPlacement, + DeepDiff( + old_element.ObjectPlacement, + new_element.ObjectPlacement, terminate_on_first=True, - significant_digits=self.precision, ignore_string_type_changes=True, ignore_numeric_type_changes=True, - exclude_regex_paths=r'root.*id$') - DeepDiff(old_element.Representation, new_element.Representation, + significant_digits=self.precision, + ignore_string_type_changes=True, + ignore_numeric_type_changes=True, + exclude_regex_paths=r"root.*id$", + ) + DeepDiff( + old_element.Representation, + new_element.Representation, terminate_on_first=True, - skip_after_n=1000, # Arbitrary value to "skim" check - significant_digits=self.precision, ignore_string_type_changes=True, ignore_numeric_type_changes=True, - exclude_regex_paths=r'root.*id$') - DeepDiff(old_element.HasOpenings, new_element.HasOpenings, + skip_after_n=1000, # Arbitrary value to "skim" check + significant_digits=self.precision, + ignore_string_type_changes=True, + ignore_numeric_type_changes=True, + exclude_regex_paths=r"root.*id$", + ) + DeepDiff( + old_element.HasOpenings, + new_element.HasOpenings, terminate_on_first=True, - significant_digits=self.precision, ignore_string_type_changes=True, ignore_numeric_type_changes=True, - exclude_regex_paths=r'root.*id$') - DeepDiff(old_element.HasProjections, new_element.HasProjections, + significant_digits=self.precision, + ignore_string_type_changes=True, + ignore_numeric_type_changes=True, + exclude_regex_paths=r"root.*id$", + ) + DeepDiff( + old_element.HasProjections, + new_element.HasProjections, terminate_on_first=True, - significant_digits=self.precision, ignore_string_type_changes=True, ignore_numeric_type_changes=True, - exclude_regex_paths=r'root.*id$') + significant_digits=self.precision, + ignore_string_type_changes=True, + ignore_numeric_type_changes=True, + exclude_regex_paths=r"root.*id$", + ) except: if new_element.GlobalId: - return self.change_register.setdefault(new_element.GlobalId, {}).update({'has_geometry_change': True}) + return self.change_register.setdefault(new_element.GlobalId, {}).update({"has_geometry_change": True}) def get_representation_id(self, element): if not element.Representation: return None for representation in element.Representation.Representations: - if not representation.is_a('IfcShapeRepresentation'): + if not representation.is_a("IfcShapeRepresentation"): continue - if representation.RepresentationIdentifier == 'Body' \ - and representation.RepresentationType != 'MappedRepresentation': + if ( + representation.RepresentationIdentifier == "Body" + and representation.RepresentationType != "MappedRepresentation" + ): return representation.id() - elif representation.RepresentationIdentifier == 'Body': + elif representation.RepresentationIdentifier == "Body": return representation.Items[0].MappingSource.MappedRepresentation.id() @@ -166,22 +201,21 @@ class DiffEncoder(json.JSONEncoder): except: return str(obj) -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Show the difference between two IFC files') - parser.add_argument('old', type=str, help='The old IFC file') - parser.add_argument('new', type=str, help='The new IFC file') + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Show the difference between two IFC files") + parser.add_argument("old", type=str, help="The old IFC file") + parser.add_argument("new", type=str, help="The new IFC file") parser.add_argument( - '-o', - '--output', - type=str, - help='The JSON diff file to output. Defaults to diff.json', - default='diff.json') + "-o", "--output", type=str, help="The JSON diff file to output. Defaults to diff.json", default="diff.json" + ) parser.add_argument( - '-r', - '--relationships', + "-r", + "--relationships", type=str, help='A list of IFC classes to check in inverse relationships, like "IfcRelDefinesByProperties", or "all".', - default='') + default="", + ) args = parser.parse_args() ifc_diff = IfcDiff(args.old, args.new, args.output, args.relationships.split())