mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Update IfcFM to use actors, not contacts, support federated datasets, and be more true to spec
This commit is contained in:
+151
-189
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user