IfcFM parser and writer is now easier to override for custom FM requirements

This commit is contained in:
Dion Moult
2023-02-02 14:29:48 +11:00
parent d69aa20802
commit b984a77862
2 changed files with 263 additions and 486 deletions
+105 -75
View File
@@ -29,28 +29,83 @@ class Parser:
def __init__(self, logger):
self.logger = logger
self.file = None
self.sheets = [
"actors",
"facilities",
"floors",
"spaces",
"zones",
"types",
"components",
"systems",
"assemblies",
"connections",
"spares",
"resources",
"jobs",
"impacts",
"documents",
"attributes",
"coordinates",
"issues",
]
for sheet in self.sheets:
setattr(self, sheet, {})
self.categories = {
"actors": self.get_actors,
"facilities": self.get_facilities,
"floors": self.get_floors,
"spaces": self.get_spaces,
"zones": self.get_zones,
"types": self.get_types,
"components": self.get_components,
"systems": self.get_systems,
# "assemblies",
# "connections",
# "spares",
# "resources",
# "jobs",
# "impacts",
"documents": self.get_documents,
# "attributes",
# "coordinates",
# "issues",
}
# COBie
self.custom_parameters = {
"types": {
"AssetType": lambda e, p: None,
"ManufacturerOrganizationName": lambda e, p: None,
"ModelNumber": lambda e, p: None,
"WarrantyGuarantorParts": lambda e, p: None,
"WarrantyDurationParts": lambda e, p: None,
"WarrantyGuarantorLabour": lambda e, p: None,
"WarrantyDurationLabour": lambda e, p: None,
"DurationUnit": lambda e, p: "months",
"WarrantyDescription": lambda e, p: None,
"ReplacementCost": lambda e, p: None,
"ExpectedLife": lambda e, p: None,
"NominalLength": lambda e, p: None,
"NominalWidth": lambda e, p: None,
"NominalHeight": lambda e, p: None,
},
"components": {
"SerialNumber": lambda e, p: None,
"InstallationDate": lambda e, p: None,
"WarrantyStartDate": lambda e, p: None,
"TagNumber": lambda e, p: None,
"BarCode": lambda e, p: None,
"AssetIdentifier": lambda e, p: None,
}
}
# AOH-BEM
self.custom_parameters = {
"types": {
"ProcurementMethod": lambda e, p: None,
"ManufacturerOrganizationName": lambda e, p: None,
"SupplierOrganizationName": lambda e, p: None,
"ModelNumber": lambda e, p: None,
"WarrantyOrganizationName": lambda e, p: None,
"WarrantyDuration": lambda e, p: None,
"SpecificationSection": lambda e, p: None,
"SubmittalID": lambda e, p: None,
"ProductURL": lambda e, p: None,
},
"components": {
"InstallationDate": lambda e, p: None,
"WarrantyStartDate": lambda e, p: None,
"InstalledModelNumber": lambda e, p: None,
"SerialNumber": lambda e, p: None,
"BarCode": lambda e, p: None,
"TagNumber": lambda e, p: None,
"OwnerAssetID": lambda e, p: None,
"FluidHotFeedName": lambda e, p: None,
"FluidColdFeedName": lambda e, p: None,
"ElectricPanelName": lambda e, p: None,
"ElectricCircuitName": lambda e, p: None,
"ControlledByName": lambda e, p: None,
"InterlockedWithName": lambda e, p: None,
"PartOfAssemblyName": lambda e, p: None,
}
}
self.picklists = {
"Category-Role": [],
"Category-Facility": [],
@@ -70,25 +125,9 @@ class Parser:
def parse(self, files):
self.files = files
self.get_actors()
self.get_facilities()
self.get_floors()
self.get_spaces()
self.get_zones()
self.get_types()
self.get_components()
self.get_systems()
# self.get_assemblies()
# self.get_connections()
# self.get_spares()
# self.get_resources()
# self.get_jobs()
# self.get_impacts()
self.get_documents()
# self.get_attributes()
# self.get_coordinates()
# self.get_issues()
for category, get_category in self.categories.items():
setattr(self, category, {})
get_category()
def get_actors(self):
for ifc in self.files.values():
@@ -180,6 +219,10 @@ class Parser:
return result
return round(result, decimals)
def get_custom_parameters(self, category, data, element, psets):
for parameter, get_parameter in self.custom_parameters.get(category, {}).items():
data[parameter] = get_parameter(element, psets)
def get_spaces(self):
for key, ifc in self.files.items():
if "arch" not in key:
@@ -190,8 +233,7 @@ class Parser:
primary_keys.append(name)
# TODO: not correct mapping
psets = ifcopenshell.util.element.get_psets(element)
self.spaces[name] = {
data = {
"Name": name,
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
@@ -205,6 +247,8 @@ class Parser:
"AreaGross": self.get_property(psets, "Qto_SpaceBaseQuantities", "GrossFloorArea", decimals=2),
"AreaNet": self.get_property(psets, "Qto_SpaceBaseQuantities", "NetFloorArea", decimals=2),
}
self.get_custom_parameters("spaces", data, element, psets)
self.spaces[name] = data
def get_zones(self):
for ifc in self.files.values():
@@ -249,27 +293,19 @@ class Parser:
for element in ifcopenshell.util.fm.get_fmhem_types(self.files[ifc_file]):
name = element.Name
primary_keys.append(name)
category = self.get_classification(element)
self.types[name] = {
psets = ifcopenshell.util.element.get_psets(element)
data = {
"Name": name,
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
"Category": category,
"ProcurementMethod": None,
"Category": self.get_classification(element),
"Description": element.Description,
"ManufacturerOrganizationName": None,
"SupplierOrganizationName": None,
"ModelNumber": None,
"WarrantyOrganizationName": None,
"WarrantyDuration": None,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
"ModelID": element.GlobalId,
"SpecificationSection": None,
"SubmittalID": None,
"ProductURL": None,
}
self.get_custom_parameters("types", data, element, psets)
self.types[name] = data
def get_components(self):
for discipline, ifc in self.files.items():
@@ -292,31 +328,23 @@ class Parser:
space = ifcopenshell.util.element.get_container(element)
space_name = space.Name if space.is_a("IfcSpace") else None
self.components[name] = {
psets = ifcopenshell.util.element.get_psets(element)
data = {
"Name": name,
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
"TypeName": element_type.Name,
"SpaceName": space_name,
"InstallationDate": None,
"WarrantyStartDate": None,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
"ModelID": element.GlobalId,
"InstalledModelNumber": None,
"SerialNumber": None,
"BarCode": None,
"TagNumber": None,
"OwnerAssetID": None,
"SystemName": system,
"FluidHotFeedName": None,
"FluidColdFeedName": None,
"ElectricPanelName": None,
"ElectricCircuitName": None,
"ControlledByName": None,
"InterlockedWithName": None,
"PartOfAssemblyName": None,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelObject": "{}[{}]".format(
element.is_a(), ifcopenshell.util.element.get_predefined_type(element)
),
"ModelID": element.GlobalId,
}
self.get_custom_parameters("components", data, element, psets)
self.components[name] = data
def get_documents(self):
for ifc in self.files.values():
@@ -331,7 +359,9 @@ class Parser:
identification = referenced_document.ItemReference
author_date = None
if referenced_document.CreationTime:
author_date = ifcopenshell.util.date.ifc2datetime(referenced_document.CreationTime).isoformat()
author_date = ifcopenshell.util.date.ifc2datetime(
referenced_document.CreationTime
).isoformat()
else:
referenced_document = element.ReferencedDocument
identification = referenced_document.Identification
+158 -411
View File
@@ -1,4 +1,3 @@
# IfcFM - IFC for facility management
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
@@ -70,7 +69,6 @@ class Writer:
def __init__(self, parser, filename=None):
self.filename = filename
self.parser = parser
self.sheets = []
self.sheet_data = {}
self.colours = {
"r": "fdff8e", # Required
@@ -90,415 +88,164 @@ class Writer:
"p": "b8dd73", # Project specific
"n": "000000", # Not used
}
self.sheets = {
"Actor": {
"data": self.parser.actors,
"fields": [
"Name",
"Category",
"Email",
"Phone",
"CompanyURL",
"Department",
"Address1",
"Address2",
"StateRegion",
"PostalCode",
"Country",
],
"colours": "rirrrrrrrrr",
"order": ["Name"],
},
"Facility": {
"data": self.parser.facilities,
"fields": [
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"ProjectName",
"SiteName",
"LinearUnits",
"AreaUnits",
"AreaMeasurement",
"Phase",
"ModelSoftware",
"ModelProjectID",
"ModelSiteID",
"ModelBuildingID",
],
"colours": "ririrrrrrreeee",
"order": ["Name"],
},
"Floor": {
"data": self.parser.floors,
"fields": [
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"ModelSoftware",
"ModelObject",
"ModelID",
"Elevation",
],
"colours": "ririeeer",
"order": ["Elevation"],
},
"Space": {
"data": self.parser.spaces,
"fields": [
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"LevelName",
"Description",
"ModelSoftware",
"ModelID",
"BuildingRoomNumber",
"UsableHeight",
"AreaGross",
"AreaNet",
],
"colours": "ririireerrrr",
"order": ["LevelName", "Name"],
},
"Zone": {
"data": self.parser.zones,
"fields": [
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"SpaceName",
"ModelSoftware",
"ModelID",
"ParentZoneName",
],
"colours": "ririieei",
"order": ["Name", "SpaceName"],
},
"Type": {
"data": self.parser.types,
"fields": [
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"Description",
"ModelSoftware",
"ModelObject",
"ModelID",
],
"colours": "ririreee",
"order": ["ModelObject", "Name"],
},
"Component": {
"data": self.parser.components,
"fields": [
"Name",
"AuthorOrganizationName",
"AuthorDate",
"TypeName",
"SpaceName",
"SystemName",
"ModelSoftware",
"ModelObject",
"ModelID",
],
"colours": "ririiieee",
"order": ["ModelObject", "Name"],
},
"System": {
"data": self.parser.systems,
"fields": [
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"ModelSoftware",
"ModelID",
"ParentSystemName",
],
"colours": "ririeei",
"order": ["Name"],
},
"Document": {
"data": self.parser.documents,
"fields": [
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"WorksheetName",
"WorksheetRow",
"Revision",
"Location",
"Description",
"SpecificationSection",
"SubmittalID",
"SourceURL",
],
"colours": "ririiirrrrer",
"order": ["Name"],
},
}
def write(self):
self.sheets = [
"Actor",
"Facility",
"Floor",
"Space",
"Zone",
"Type",
"Component",
"System",
# "Assembly",
# "Connection",
# "Spare",
# "Resource",
# "Job",
# "Impact",
"Document",
# "Attribute",
# "Coordinate",
# "Issue",
]
self.write_data(
"Actor",
self.parser.actors,
[
"Name",
"Category",
"Email",
"Phone",
"CompanyURL",
"Department",
"Address1",
"Address2",
"StateRegion",
"PostalCode",
"Country",
],
"rirrrrrrrrr",
["Name"],
)
self.write_data(
"Facility",
self.parser.facilities,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"ProjectName",
"SiteName",
"LinearUnits",
"AreaUnits",
"AreaMeasurement",
"Phase",
"ModelSoftware",
"ModelProjectID",
"ModelSiteID",
"ModelBuildingID",
],
"ririrrrrrreeee",
["Name"],
)
self.write_data(
"Floor",
self.parser.floors,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"ModelSoftware",
"ModelObject",
"ModelID",
"Elevation",
],
"ririeeer",
["Elevation"],
)
self.write_data(
"Space",
self.parser.spaces,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"LevelName",
"Description",
"ModelSoftware",
"ModelID",
"BuildingRoomNumber",
"UsableHeight",
"AreaGross",
"AreaNet",
],
"ririireerrrr",
["LevelName", "Name"],
)
self.write_data(
"Zone",
self.parser.zones,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"SpaceName",
"ModelSoftware",
"ModelID",
"ParentZoneName",
],
"ririieei",
["Name", "SpaceName"],
)
self.write_data(
"Type",
self.parser.types,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"ProcurementMethod",
"Description",
"ManufacturerOrganizationName",
"SupplierOrganizationName",
"ModelNumber",
"WarrantyOrganizationName",
"WarrantyDuration",
"ModelSoftware",
"ModelObject",
"ModelID",
"SpecificationSection",
"SubmittalID",
"ProductURL",
],
"ririiriirireeerrr",
["ModelObject", "Name"],
)
self.write_data(
"Component",
self.parser.components,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"TypeName",
"SpaceName",
"InstallationDate",
"WarrantyStartDate",
"ModelSoftware",
"ModelObject",
"ModelID",
"InstalledModelNumber",
"SerialNumber",
"BarCode",
"TagNumber",
"OwnerAssetID",
"SystemName",
"FluidHotFeedName",
"FluidColdFeedName",
"ElectricPanelName",
"ElectricCircuitName",
"ControlledByName",
"InterlockedWithName",
"PartOfAssemblyName",
],
"ririirreeerrrrriiiiiiii",
["ModelObject", "Name"],
)
self.write_data(
"System",
self.parser.systems,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"ModelSoftware",
"ModelID",
"ParentSystemName",
],
"ririeei",
["Name"],
)
# self.write_data(
# "Assembly",
# self.parser.assemblies,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "SheetName",
# "ParentName",
# "ChildNames",
# "AssemblyType",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "Description",
# ],
# "rirrrrreeeo",
# self.parser.custom_data["assemblies"],
# )
# self.write_data(
# "Connection",
# self.parser.connections,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "ConnectionType",
# "SheetName",
# "RowName1",
# "RowName2",
# "RealizingElement",
# "PortName1",
# "PortName2",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "Description",
# ],
# "ririiiiiiieeeo",
# self.parser.custom_data["connections"],
# )
# self.write_data(
# "Spare",
# self.parser.spares,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "Category",
# "TypeName",
# "Suppliers",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "Description",
# "SetNumber",
# "PartNumber",
# ],
# "ririiieeeooo",
# self.parser.custom_data["spares"],
# )
# self.write_data(
# "Resource",
# self.parser.resources,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "Category",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "Description",
# ],
# "ririeeeo",
# self.parser.custom_data["resources"],
# )
# self.write_data(
# "Job",
# self.parser.jobs,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "Category",
# "Status",
# "TypeName",
# "Description",
# "Duration",
# "DurationUnit",
# "Start",
# "TaskStartUnit",
# "Frequency",
# "FrequencyUnit",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "TaskNumber",
# "Priors",
# "ResourceNames",
# ],
# "ririiirriririeeeoii",
# self.parser.custom_data["jobs"],
# )
# self.write_data(
# "Impact",
# self.parser.impacts,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "ImpactType",
# "ImpactStage",
# "SheetName",
# "RowName",
# "Value",
# "Unit",
# "LeadInTime",
# "Duration",
# "LeadOutTime",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "Description",
# ],
# "ririiiirioooeeeo",
# self.parser.custom_data["impacts"],
# )
self.write_data(
"Document",
self.parser.documents,
[
"Name",
"AuthorOrganizationName",
"AuthorDate",
"Category",
"WorksheetName",
"WorksheetRow",
"Revision",
"Location",
"Description",
"SpecificationSection",
"SubmittalID",
"SourceURL",
],
"ririiirrrrer",
["Name"],
)
# self.write_data(
# "Attribute",
# self.parser.attributes,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "Category",
# "SheetName",
# "RowName",
# "Value",
# "Unit",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "Description",
# "AllowedValues",
# ],
# "ririiirreeeoo",
# )
# self.write_data(
# "Coordinate",
# self.parser.coordinates,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "Category",
# "SheetName",
# "RowName",
# "CoordinateXAxis",
# "CoordinateYAxis",
# "CoordinateZAxis",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# "ClockwiseRotation",
# "ElevationalRotation",
# "YawRotation",
# ],
# "ririiooooeeeooo",
# )
# self.write_data(
# "Issue",
# self.parser.issues,
# "Name",
# [
# "Name",
# "CreatedBy",
# "CreatedOn",
# "Type",
# "Risk",
# "Chance",
# "Impact",
# "SheetName1",
# "RowName1",
# "SheetName2",
# "RowName2",
# "Description",
# "Owner",
# "Mitigation",
# "ExtSystem",
# "ExtObject",
# "ExtIdentifier",
# ],
# "ririooooooooooeee",
# )
for category, spec in self.sheets.items():
self.write_data(category, spec["data"], spec["fields"], spec["colours"], spec["order"])
def write_data(self, sheet, data, fieldnames, colours, sort_fields):
self.sheet_data[sheet] = {"headers": fieldnames, "colours": colours, "rows": []}
@@ -530,7 +277,7 @@ class XlsWriter(Writer):
self.cell_formats[key] = self.workbook.add_format()
self.cell_formats[key].set_bg_color(value)
for sheet in self.sheets:
for sheet in self.sheets.keys():
self.write_worksheet(sheet)
self.workbook.close()
@@ -567,7 +314,7 @@ class OdsWriter(Writer):
self.doc.automaticstyles.addElement(style)
self.cell_formats[key] = style
for sheet in self.sheets:
for sheet in self.sheets.keys():
self.write_table(sheet)
self.doc.save(self.filename, True)