Update IfcFM to use actors, not contacts, support federated datasets, and be more true to spec

This commit is contained in:
Dion Moult
2022-09-05 12:41:06 +10:00
parent 5d9fc44a69
commit d1baaa21e7
2 changed files with 158 additions and 197 deletions
+151 -189
View File
@@ -22,6 +22,7 @@ import ifcopenshell.util.fm
import ifcopenshell.util.selector import ifcopenshell.util.selector
import ifcopenshell.util.date import ifcopenshell.util.date
import ifcopenshell.util.schema import ifcopenshell.util.schema
import ifcopenshell.util.classification
class Parser: class Parser:
@@ -29,7 +30,7 @@ class Parser:
self.logger = logger self.logger = logger
self.file = None self.file = None
self.sheets = [ self.sheets = [
"contacts", "actors",
"facilities", "facilities",
"floors", "floors",
"spaces", "spaces",
@@ -69,11 +70,8 @@ class Parser:
def parse(self, files): def parse(self, files):
self.files = files self.files = files
# self.file = ifcopenshell.open(file)
# self.type_assets = self.selector.parse(self.file, type_query)
# self.component_assets = self.selector.parse(self.file, component_query)
self.get_contacts() self.get_actors()
self.get_facilities() self.get_facilities()
self.get_floors() self.get_floors()
self.get_spaces() self.get_spaces()
@@ -92,36 +90,30 @@ class Parser:
# self.get_coordinates() # self.get_coordinates()
# self.get_issues() # self.get_issues()
def get_contacts(self): def get_actors(self):
for element in self.files["arch"].by_type("IfcOrganization"): for ifc in self.files.values():
if "IfcApplication" in [e.is_a() for e in self.files["arch"].get_inverse(element)]: for element in ifc.by_type("IfcActor"):
continue name = element.TheActor.Name
name = element.Name self.actors[name] = self.get_actor(element)
self.contacts[name] = self.get_organisation(element)
def get_organisation(self, element): def get_actor(self, element):
psets = ifcopenshell.util.element.get_psets(element)
return { return {
"Name": element.Name, "Name": element.TheActor.Name,
"Category": self.get_organisation_category(element), "Category": self.get_classification(element),
"Email": self.get_organisation_address(element, "ElectronicMailAddresses"), "Email": self.get_actor_address(element, "ElectronicMailAddresses"),
"Phone": self.get_organisation_address(element, "TelephoneNumbers"), "Phone": self.get_actor_address(element, "TelephoneNumbers"),
"Department": self.get_organisation_address(element, "InternalLocation"), "CompanyURL": self.get_actor_address(element, "WWWHomePageURL"),
"Street": self.get_organisation_address(element, "AddressLines"), "Department": self.get_actor_address(element, "InternalLocation"),
"PostalBox": self.get_organisation_address(element, "PostalBox"), "Address1": self.get_actor_address(element, "AddressLines"),
"Town": self.get_organisation_address(element, "Town"), "Address2": self.get_actor_address(element, "Town"),
"StateRegion": self.get_organisation_address(element, "Region"), "StateRegion": self.get_actor_address(element, "Region"),
"PostalCode": self.get_organisation_address(element, "PostalCode"), "PostalCode": self.get_actor_address(element, "PostalCode"),
"Country": self.get_organisation_address(element, "Country"), "Country": self.get_actor_address(element, "Country"),
"CompanyURL": self.get_organisation_address(element, "WWWHomePageURL"),
} }
def get_organisation_category(self, element): def get_actor_address(self, element, name):
for role in element.Roles or []: for address in element.TheActor.Addresses or []:
if role.UserDefinedRole:
return role.UserDefinedRole
def get_organisation_address(self, element, name):
for address in element.Addresses or []:
if hasattr(address, name) and getattr(address, name, None): if hasattr(address, name) and getattr(address, name, None):
result = getattr(address, name) result = getattr(address, name)
if isinstance(result, tuple): if isinstance(result, tuple):
@@ -129,49 +121,43 @@ class Parser:
return result return result
def get_facilities(self): def get_facilities(self):
element = self.files["arch"].by_type("IfcBuilding")[0] for key, ifc in self.files.items():
self.facilities[element.Name] = { if "arch" not in key:
"Name": element.Name, continue
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name, element = ifc.by_type("IfcBuilding")[0]
"AuthorDate": ifcopenshell.util.date.ifc2datetime( self.facilities[element.Name] = {
self.files["arch"].by_type("IfcProject")[0].OwnerHistory.CreationDate "Name": element.Name,
).isoformat(), "AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"Category": self.get_classification(element), "AuthorDate": ifcopenshell.util.date.ifc2datetime(
"ProjectName": element.Decomposes[0].RelatingObject.Decomposes[0].RelatingObject.Name, ifc.by_type("IfcProject")[0].OwnerHistory.CreationDate
"SiteName": element.Decomposes[0].RelatingObject.Name, ).isoformat(),
"LinearUnits": "millimeters", "Category": self.get_classification(element),
"AreaUnits": "square meters", "ProjectName": element.Decomposes[0].RelatingObject.Decomposes[0].RelatingObject.Name,
"AreaMeasurement": "TODO", "SiteName": element.Decomposes[0].RelatingObject.Name,
"Phase": element.Decomposes[0].RelatingObject.Decomposes[0].RelatingObject.Phase, "LinearUnits": "millimeters",
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName, "AreaUnits": "square meters",
"ModelProjectID": self.files["arch"].by_type("IfcProject")[0].GlobalId, "AreaMeasurement": "Revit",
"ModelSiteID": element.Decomposes[0].RelatingObject.GlobalId, "Phase": element.Decomposes[0].RelatingObject.Decomposes[0].RelatingObject.Phase,
"ModelBuildingID": element.GlobalId, "ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
} "ModelProjectID": ifc.by_type("IfcProject")[0].GlobalId,
"ModelSiteID": element.Decomposes[0].RelatingObject.GlobalId,
"ModelBuildingID": element.GlobalId,
}
def get_classification(self, element): def get_classification(self, element):
rel = [r for r in element.HasAssociations or [] if r.is_a("IfcRelAssociatesClassification")] references = list(ifcopenshell.util.classification.get_references(element))
if rel: if references:
classification = rel[0].RelatingClassification if hasattr(references[0], "Identification"):
if getattr(classification, "Identification", None) and getattr(classification, "Name", None): return "{}:{}".format(references[0].Identification, references[0].Name)
return "{}:{}".format(classification.Identification, classification.Name) return "{}:{}".format(references[0].ItemReference, references[0].Name)
elif getattr(classification, "ItemReference", None) and getattr(classification, "Name", None):
return "{}:{}".format(classification.ItemReference, classification.Name)
def get_floors(self): def get_floors(self):
storeys = self.files["arch"].by_type("IfcBuildingStorey") for key, ifc in self.files.items():
self.floors["Site"] = { if "arch" not in key:
"Name": "Site", continue
"AuthorOrganizationName": storeys[0].OwnerHistory.OwningUser.TheOrganization.Name, storeys = ifc.by_type("IfcBuildingStorey")
"AuthorDate": datetime.datetime.now().replace(microsecond=0).isoformat(), for element in storeys:
"Category": "Site", self.get_floor(element)
"ModelSoftware": "IfcFM",
"ModelObject": "IfcExternalSpatialElement",
"ModelID": ifcopenshell.guid.new(), # TODO
"Elevation": None,
}
for element in storeys:
self.get_floor(element)
def get_floor(self, element): def get_floor(self, element):
name = element.Name name = element.Name
@@ -187,68 +173,61 @@ class Parser:
"Elevation": elevation, "Elevation": elevation,
} }
def get_property(self, psets, pset_name, prop_name, decimals=None):
if pset_name in psets:
result = psets[pset_name].get(prop_name, None)
if decimals is None or result is None:
return result
return round(result, decimals)
def get_spaces(self): def get_spaces(self):
primary_keys = [] for key, ifc in self.files.items():
for element in self.files["arch"].by_type("IfcSpace"): if "arch" not in key:
name = element.Name continue
primary_keys.append(name) primary_keys = []
# TODO: not correct mapping for element in ifc.by_type("IfcSpace"):
psets = ifcopenshell.util.element.get_psets(element) name = element.Name
primary_keys.append(name)
# TODO: not correct mapping
psets = ifcopenshell.util.element.get_psets(element)
category = None self.spaces[name] = {
if "Data" in psets and "COBie.Space.Category" in psets["Data"]: "Name": name,
category = psets["Data"]["COBie.Space.Category"].replace(" : ", ":") "AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
usable_height = None "Category": self.get_classification(element),
if "Data" in psets and "COBie.Space.Category" in psets["Data"]: "LevelName": element.Decomposes[0].RelatingObject.Name,
usable_height = round(psets["Data"]["COBie.Space.UsableHeight"], 2) or None "Description": element.LongName,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
area_gross = None "ModelID": element.GlobalId,
if "Data" in psets and "COBie.Space.GrossArea" in psets["Data"]: "BuildingRoomNumber": None,
area_gross = round(psets["Data"]["COBie.Space.GrossArea"], 2) or None "UsableHeight": self.get_property(psets, "Data", "COBie.Space.UsableHeight", decimals=0),
"AreaGross": self.get_property(psets, "Qto_SpaceBaseQuantities", "GrossFloorArea", decimals=2),
area_net = None "AreaNet": self.get_property(psets, "Qto_SpaceBaseQuantities", "NetFloorArea", decimals=2),
if "Data" in psets and "COBie.Space.NetArea" in psets["Data"]: }
area_net = round(psets["Data"]["COBie.Space.NetArea"], 2) or None
self.spaces[name] = {
"Name": name,
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime(element.OwnerHistory.CreationDate).isoformat(),
"Category": category,
"LevelName": element.Decomposes[0].RelatingObject.Name,
"Description": element.LongName,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelID": element.GlobalId,
"BuildingRoomNumber": None,
"UsableHeight": usable_height,
"AreaGross": area_gross,
"AreaNet": area_net,
}
def get_zones(self): def get_zones(self):
for element in self.files["arch"].by_type("IfcZone"): for ifc in self.files.values():
for rel in element.IsGroupedBy: for element in ifc.by_type("IfcZone"):
for space in rel.RelatedObjects: for rel in element.IsGroupedBy:
if not space.is_a("IfcSpace"): for space in rel.RelatedObjects:
continue if not space.is_a("IfcSpace"):
self.zones[element.Name + space.Name] = { continue
"Name": element.Name, self.zones[element.Name + space.Name] = {
"AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name, "Name": element.Name,
"AuthorDate": ifcopenshell.util.date.ifc2datetime( "AuthorOrganizationName": element.OwnerHistory.OwningUser.TheOrganization.Name,
element.OwnerHistory.CreationDate "AuthorDate": ifcopenshell.util.date.ifc2datetime(
).isoformat(), element.OwnerHistory.CreationDate
"Category": "Occupancy", ).isoformat(),
"SpaceName": space.Name, "Category": "Occupancy",
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName, "SpaceName": space.Name,
"ModelID": element.GlobalId, "ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ParentZoneName": None, "ModelID": element.GlobalId,
} "ParentZoneName": None,
}
def get_systems(self): def get_systems(self):
for discipline, ifc in self.files.items(): for discipline, ifc in self.files.items():
# if discipline == "arch":
# continue
for element in ifc.by_type("IfcSystem"): for element in ifc.by_type("IfcSystem"):
name = element.Name name = element.Name
self.systems[name] = { self.systems[name] = {
@@ -270,20 +249,7 @@ class Parser:
for element in ifcopenshell.util.fm.get_fmhem_types(self.files[ifc_file]): for element in ifcopenshell.util.fm.get_fmhem_types(self.files[ifc_file]):
name = element.Name name = element.Name
primary_keys.append(name) primary_keys.append(name)
category = self.get_classification(element)
psets = ifcopenshell.util.element.get_psets(element)
# Hack
category = None
if ifc_file == "arch":
# if "Data" in psets and "COBie.Type.Category" in psets["Data"]:
# if ":" in psets["Data"]["COBie.Type.Category"]:
# category = psets["Data"]["COBie.Type.Category"].replace(" : ", ":")
if "Data" in psets and "Classification.Uniclass.Pr.Number" in psets["Data"]:
category = f"{psets['Data']['Classification.Uniclass.Pr.Number']}:{psets['Data']['Classification.Uniclass.Pr.Description']}"
elif ifc_file == "hyd":
if "Data" in psets and "Classification.Uniclass.Pr.Number" in psets["Data"]:
category = f"{psets['Data']['Classification.Uniclass.Pr.Number']}:{psets['Data']['Classification.Uniclass.Pr.Description']}"
self.types[name] = { self.types[name] = {
"Name": name, "Name": name,
@@ -298,7 +264,7 @@ class Parser:
"WarrantyOrganizationName": None, "WarrantyOrganizationName": None,
"WarrantyDuration": None, "WarrantyDuration": None,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName, "ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelObject": element.is_a(), "ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
"ModelID": element.GlobalId, "ModelID": element.GlobalId,
"SpecificationSection": None, "SpecificationSection": None,
"SubmittalID": None, "SubmittalID": None,
@@ -311,7 +277,11 @@ class Parser:
def get_components_from_file(self, ifc_file): def get_components_from_file(self, ifc_file):
for element_type in ifcopenshell.util.fm.get_fmhem_types(self.files[ifc_file]): for element_type in ifcopenshell.util.fm.get_fmhem_types(self.files[ifc_file]):
for element in element_type.ObjectTypeOf[0].RelatedObjects: elements = ifcopenshell.util.element.get_types(element_type)
if not elements:
self.logger.warning("The type has no occurrences %s", element_type)
continue
for element in elements:
name = element.Name name = element.Name
system = None system = None
@@ -319,19 +289,8 @@ class Parser:
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.is_a("IfcSystem"): if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.is_a("IfcSystem"):
system = rel.RelatingGroup.Name system = rel.RelatingGroup.Name
space_name = None space = ifcopenshell.util.element.get_container(element)
space_name = space.Name if space.is_a("IfcSpace") else None
# Nasty hack
if ifc_file == "arch":
psets = ifcopenshell.util.element.get_psets(element)
if "Data" in psets and "COBie.Component.Space" in psets["Data"]:
space_name = psets["Data"]["COBie.Component.Space"]
if space_name not in self.spaces:
space_name = None
else:
space = element.ContainedInStructure[0].RelatingStructure
if space.is_a("IfcSpace"):
space_name = space.Name
self.components[name] = { self.components[name] = {
"Name": name, "Name": name,
@@ -342,7 +301,7 @@ class Parser:
"InstallationDate": None, "InstallationDate": None,
"WarrantyStartDate": None, "WarrantyStartDate": None,
"ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName, "ModelSoftware": element.OwnerHistory.OwningApplication.ApplicationFullName,
"ModelObject": element.is_a(), "ModelObject": "{}[{}]".format(element.is_a(), ifcopenshell.util.element.get_predefined_type(element)),
"ModelID": element.GlobalId, "ModelID": element.GlobalId,
"InstalledModelNumber": None, "InstalledModelNumber": None,
"SerialNumber": None, "SerialNumber": None,
@@ -360,39 +319,42 @@ class Parser:
} }
def get_documents(self): def get_documents(self):
for rel in self.files["arch"].by_type("IfcRelAssociatesDocument"): for ifc in self.files.values():
element = rel.RelatingDocument for rel in ifc.by_type("IfcRelAssociatesDocument"):
for related_object in rel.RelatedObjects: element = rel.RelatingDocument
name = element.Name if element.is_a("IfcDocumentInformation"):
worksheet_row = related_object.Name continue
if self.files["arch"].schema == "IFC2X3": for related_object in rel.RelatedObjects:
submittal_id = element.ItemReference name = element.Name
referenced_document = element.ReferenceToDocument[0] worksheet_row = related_object.Name
author_date = None if ifc.schema == "IFC2X3":
if referenced_document.CreationTime: submittal_id = element.ItemReference
author_date = ifcopenshell.util.date.ifc2datetime(referenced_document.CreationTime).isoformat() referenced_document = element.ReferenceToDocument[0]
else: author_date = None
submittal_id = element.Identification if referenced_document.CreationTime:
referenced_document = element.ReferencedDocument author_date = ifcopenshell.util.date.ifc2datetime(referenced_document.CreationTime).isoformat()
author_date = referenced_document.CreationTime else:
submittal_id = element.Identification
referenced_document = element.ReferencedDocument
author_date = referenced_document.CreationTime
worksheet_name = None worksheet_name = None
if related_object.is_a("IfcSpace"): if related_object.is_a("IfcSpace"):
worksheet_name = "Space" worksheet_name = "Space"
elif related_object.is_a("IfcTypeObject"): elif related_object.is_a("IfcTypeObject"):
worksheet_name = "Type" worksheet_name = "Type"
self.documents[element.Name + worksheet_name + worksheet_row] = { self.documents[element.Name + worksheet_name + worksheet_row] = {
"Name": name, "Name": name,
"AuthorOrganizationName": referenced_document.DocumentOwner.Name, "AuthorOrganizationName": referenced_document.DocumentOwner.Name,
"AuthorDate": author_date, "AuthorDate": author_date,
"Category": referenced_document.Purpose, "Category": referenced_document.Purpose,
"WorksheetName": worksheet_name, "WorksheetName": worksheet_name,
"WorksheetRow": worksheet_row, "WorksheetRow": worksheet_row,
"Revision": referenced_document.Revision, "Revision": referenced_document.Revision,
"Location": referenced_document.Name, "Location": referenced_document.Name,
"Description": referenced_document.Description, "Description": referenced_document.Description,
"SpecificationSection": None, "SpecificationSection": None,
"SubmittalID": submittal_id, "SubmittalID": submittal_id,
"SourceURL": None, "SourceURL": None,
} }
+7 -8
View File
@@ -93,7 +93,7 @@ class Writer:
def write(self): def write(self):
self.sheets = [ self.sheets = [
"Contact", "Actor",
"Facility", "Facility",
"Floor", "Floor",
"Space", "Space",
@@ -113,23 +113,22 @@ class Writer:
# "Issue", # "Issue",
] ]
self.write_data( self.write_data(
"Contact", "Actor",
self.parser.contacts, self.parser.actors,
[ [
"Name", "Name",
"Category", "Category",
"Email", "Email",
"Phone", "Phone",
"CompanyURL",
"Department", "Department",
"Street", "Address1",
"PostalBox", "Address2",
"Town",
"StateRegion", "StateRegion",
"PostalCode", "PostalCode",
"Country", "Country",
"CompanyURL",
], ],
"rirrrrrrrrrr", "rirrrrrrrrr",
["Name"], ["Name"],
) )
self.write_data( self.write_data(