From 852df6e2ce02b86dcd3955ccf15573656cdadd4c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 25 Oct 2019 08:22:16 +1100 Subject: [PATCH] Some bugfixes and package IFC COBie for alpha release --- src/{ifcblenderexport => ifccobie}/cobie.py | 70 ++++++++++++++------ src/ifccobie/icon.ico | Bin 0 -> 104035 bytes 2 files changed, 50 insertions(+), 20 deletions(-) rename src/{ifcblenderexport => ifccobie}/cobie.py (96%) create mode 100644 src/ifccobie/icon.ico diff --git a/src/ifcblenderexport/cobie.py b/src/ifccobie/cobie.py similarity index 96% rename from src/ifcblenderexport/cobie.py rename to src/ifccobie/cobie.py index 109b9d0f60..fc0feba527 100644 --- a/src/ifcblenderexport/cobie.py +++ b/src/ifccobie/cobie.py @@ -1,11 +1,30 @@ +# This can be packaged with `pyinstaller --onefile --clean --icon=icon.ico bimtester.py` + +print('# IFC COBie') +print(''' +IFC COBie is free software developed under the IfcOpenShell and BlenderBIM +projects. IFC COBie will convert IFC files, ideally produced with the COBie MVD +in mind, into a spreadsheet format for inspection. To learn more, visit: + + - http://ifcopenshell.org + - https://blenderbim.org + +To run, a `input.ifc` file is required in the current folder. +''') + +value = input('Would you like to run IFC COBie? [Y/n] ') +if value == 'n': + quit() + +print('Loading libraries ...') + import time import datetime import ifcopenshell import logging -import pprint import csv -from openpyxl import Workbook -from openpyxl.styles import PatternFill +from xlsxwriter import Workbook + class IfcCobieParser(): def __init__(self, logger): @@ -101,7 +120,7 @@ class IfcCobieParser(): self.default_date = (datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds = -2177452801)).isoformat() def parse(self): - self.file = ifcopenshell.open('C:/cygwin64/home/moud308/Projects/ifc2cobie/arch.ifc') + self.file = ifcopenshell.open('input.ifc') self.get_contacts() self.get_facilities() self.get_floors() @@ -492,7 +511,7 @@ class IfcCobieParser(): 'SheetName': 'Attributes', 'RowName': 'n/a', # I am not sure what this mapping is meant to be 'Value': self.get_object_attribute(property, 'NominalValue', default='n/a') if property.is_a('IfcPropertySingleValue') else 'n/a', - 'Unit': '{}{}'.format(property.Unit.Prefix, property.Unit.Name) if hasattr(property, 'Unit') and property.Unit else 'n/a', + 'Unit': '{}{}'.format(property.Unit.Prefix if hasattr(property.Unit, 'Prefix') else '', property.Unit.Name if hasattr(property.Unit, 'Name') else 'n/a') if hasattr(property, 'Unit') and property.Unit else 'n/a', 'ExtSystem': self.get_ext_system_from_history(attribute.OwnerHistory), 'ExtObject': self.get_ext_object(attribute), 'ExtIdentifier': attribute.GlobalId, @@ -595,7 +614,7 @@ class IfcCobieParser(): if not connection.is_a('IfcRelConnectsPorts'): return None object = getattr(connection, key) - if self.is_object_a_component_asset(object): + if object and self.is_object_a_component_asset(object): return object.Name self.logger.error('The connected object relationship {} is not a component asset for {}'.format(key, connection)) @@ -679,7 +698,8 @@ class IfcCobieParser(): def get_qto_from_object(self, object, name): for relationship in object.IsDefinedBy: - if relationship.RelatingPropertyDefinition.is_a('IfcQuantitySet') \ + if relationship.is_a('IfcRelDefinesByProperties') \ + and relationship.RelatingPropertyDefinition.is_a('IfcQuantitySet') \ and relationship.RelatingPropertyDefinition.Name == name: return relationship.RelatingPropertyDefinition self.logger.warning('The qto {} was not found for {}'.format(name, object)) @@ -838,7 +858,7 @@ class IfcCobieParser(): def get_name_from_person(self, person, attribute): name = getattr(person, attribute) - if not name.isalpha(): + if not name or not name.isalpha(): self.logger.warning('The person\'s {} seems to be badly formatted ("{}") for {}'.format(attribute, name, person)) return name if name else 'n/a' @@ -1042,7 +1062,7 @@ class CobieCsvWriter(): 'ExtIdentifier',]) def write_file(self, csv_name, data, primary_key, fieldnames): - with open('C:/cygwin64/home/moud308/Projects/ifc2cobie/{}.csv'.format(csv_name), 'w', newline='') as file: + with open('{}.csv'.format(csv_name), 'w', newline='', encoding='utf-8') as file: writer = csv.DictWriter(file, fieldnames=fieldnames) writer.writeheader() for name, row in data.items(): @@ -1061,8 +1081,13 @@ class CobieXlsWriter(CobieCsvWriter): 'n': '000000' # Not used } super().write() - self.workbook = Workbook() - self.workbook.remove(self.workbook.active) + self.workbook = Workbook('output.xlsx') + + self.cell_formats = {} + for key, value in self.colours.items(): + self.cell_formats[key] = self.workbook.add_format() + self.cell_formats[key].set_bg_color(value) + self.write_worksheet('Contact', self.colours['r'], 'ririrreeeoooooooooo') self.write_worksheet('Facility', self.colours['r'], 'ririrriiiireeeeeeeoooo') self.write_worksheet('Floor', self.colours['r'], 'ririeeeooo') @@ -1081,34 +1106,39 @@ class CobieXlsWriter(CobieCsvWriter): self.write_worksheet('Attribute', self.colours['r'], 'ririiirreeeoo') self.write_worksheet('Coordinate', self.colours['r'], 'ririiooooeeeooo') self.write_worksheet('Issue', self.colours['r'], 'ririooooooooooeee') - self.workbook.save(filename='C:/cygwin64/home/moud308/Projects/ifc2cobie/out.xlsx') self.workbook.close() def write_worksheet(self, name, tab_colour, fill_map): - worksheet = self.workbook.create_sheet(name) - worksheet.sheet_properties.tabColor = tab_colour + worksheet = self.workbook.add_worksheet(name) - with open('C:/cygwin64/home/moud308/Projects/ifc2cobie/{}.csv'.format(name), - 'r') as f: + with open('{}.csv'.format(name), 'r') as f: reader = csv.reader(f) for r, row in enumerate(reader): for c, col in enumerate(row): - cell = worksheet.cell(column=c+1, row=r+1, value=col) if r == 0: - cell.fill = PatternFill(fill_type='solid', fgColor=self.colours['s']) + cell = worksheet.write(r, c, col, self.cell_formats['s']) else: - cell.fill = PatternFill(fill_type='solid', fgColor=self.colours[fill_map[c]]) + cell = worksheet.write(r, c, col, self.cell_formats[fill_map[c]]) + +print('Processing IFC file ...') +print('Note: view issues with your file in `process.log` ...') start = time.time() logging.basicConfig( - filename='C:/cygwin64/home/moud308/Projects/ifc2cobie/process.log', + filename='process.log', filemode='a', level=logging.DEBUG) logger = logging.getLogger('IFCtoCOBie') logger.info('Starting conversion') parser = IfcCobieParser(logger) parser.parse() + +print('Generating reports ...') + #writer = CobieCsvWriter(parser) #writer.write() writer = CobieXlsWriter(parser) writer.write() logger.info('Finished conversion in {}s'.format(time.time() - start)) + +print('# All reports are complete :-)') +input('Press to quit.') diff --git a/src/ifccobie/icon.ico b/src/ifccobie/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b9eee9acb9dc5d899b66ab21088981ec5bbc2f7d GIT binary patch literal 104035 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 ZQ9L2_>W{|do_Lq~ki&3w(&j+l{r^sW^C