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